首页 > 编程语言 >代码随想录算法day5 - 哈希表1

代码随想录算法day5 - 哈希表1

时间:2024-08-31 21:36:49浏览次数:12  
标签:hash nums int day5 随想录 result 哈希 return size

题目1 242. 有效的字母异位词

给定两个字符串 *s**t* ,编写一个函数来判断 *t* 是否是 *s* 的字母异位词。

字母异位词 是通过重新排列不同单词或短语的字母而形成的单词或短语,通常只使用所有原始字母一次。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母

进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

思路

哈希表方法

这道题是经典的用空间换时间,只需要用一个额外的数组来统计两个字符串的长度就行了,时间复杂度从\(O(n*m)\)变为\(O(n+m)\)。

代码

class Solution {
public:
    bool isAnagram(string s, string t) {
        if(s.size() != t.size())
        {
            return false;
        }
        int hash[26];
        for(int i = 0; i < 26; i++)
            hash[i] = -1;
        vector<int> result;
        for(auto c : s)
        {
            if(hash[c - 'a'] == -1)
            {
                result.push_back(1);
                hash[c - 'a'] = result.size() - 1;
            }
            else
            {
                result[hash[c - 'a']]++;
            }
        }
        for(auto c : t)
        {
            //如果没有初始化这个字母则说明字符串s中没有这个字符
            if(hash[c - 'a'] == -1)
            {
                return false;
            }
            result[hash[c - 'a']]--;
        }
        for(auto &num : result)
        {
            if(num != 0)
                return false;
        }
        return true;
    }
};

暴力解法1(超时)

我试了嵌套循环,第35个测试用例直接超时,如果能在每次遍历t时缩小遍历范围也许能过。用暴力解法的时间复杂度为\(O(n*m)\)其中n为s的长度,m为t的长度。

代码

class Solution {
public:
    bool isAnagram(string s, string t) {
        if(s.size() != t.size())
        {
            return false;
        }
        int tLength = t.size();
        for(auto &c1 : s)
        {
            int length = 0;
            for(auto &c2 : t)
            {
                if(c1 == c2)
                {
                    c2 = 'a' - 1;
                    break;
                }
                length++;
            }
            if(length == tLength)
                return false;
        }
        return true;
    }
};

暴力解法2

这种方法通过调用algorithm头文件里的sort对字符串s和t进行排序,之后再进行一一比较得出结果。其复杂度为:

\[O(nlogn+nlogn+n) = O(nlogn) \]

class Solution {
public:
    bool isAnagram(string s, string t) {
        if(s.size() != s.size())
        {
            return false;
        }
        sort(s.begin(), s.end());
        sort(t.begin(), t.end());
        return s == t;
    }
};

题目2 349. 两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的

交集

。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

思路

这道题也是用空间换时间,暴力解法就嵌套循环遍历找交集就行了时间复杂度为\(O(n*m)\),n为nums1的长度,m为nums2的长度。使用额外数组空间来记录可以将时间复杂度减少为\(O(n+m)\)。

当然使用随想录的模板类unordered_set的做法也是类似的思路。

代码

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int> result;
        result.reserve(1001);
        int hash[1001];
        memset(hash,0,sizeof(hash));
        for(auto &num : nums1)
        {
            hash[num]++;
        }
        for(auto &num : nums2)
        {
            //如果有交集则hash中的数值置0,且将该数放入result中
            if(hash[num] > 0)
            {
                hash[num] = 0;
                result.push_back(num);
            }
        }
        return result;
    }
};

题目3 202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

提示:

  • 1 <= n <= 231 - 1

思路

这道题的解题关键在题干里的无限循环,既然无限循环的话就表明可以用set模板类来记录不重复的计算结果,并通过结果数量来判断是否循环。

代码

class Solution {
public:
    bool isHappy(int n) {
        int index   = 0,
            lst[32] = {0};
        set<int> uniqueNum;
        int num;
        while(1)
        {
            index = 0;
            num = uniqueNum.size();
            uniqueNum.insert(n);
            //如果出现无限循环则不是快乐数
            if(num == uniqueNum.size())
            {
                return false;
            }
            while(n != 0)
            {
                lst[index] = n % 10;
                n /= 10;
                index++;
            }
            for(int i = 0; i < index; i++)
            {
                n += lst[i] * lst[i];
            }
            if(n == 1)
            {
                return true;
            }
        }
    }
};

题目4 1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

思路

这道题的思路比较唯一就是通过target-nums[i]获取另一个元素的值,保存到hash表中并继续进行数组遍历找到目标值。比较需要注意的就是hash模板类的选择,这道题如果用unordered_map的效率是最高的,因为能够同时保存目标元素值与当前元素的下标,避免了另一轮的循环。我使用的是unordered_set模板类,代价就是额外需要循环判断下标。(当然如果能将元素值与下标进行编码和解码并保存到unordered_set中,效率会更高,因为节省了空间。)我也尝试了一下编解码和unoredered_set模板类,但在处理result-nums[i] < 0的情况时出了点问题。

代码(unordered_set)

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_set<int> result;
        vector<int> ret(2, INT_MAX);
        for(int i = 0; i < nums.size(); i++)
        {
            if(find(result.begin(), result.end(), nums[i]) != result.end())
            {
                ret[1] = i;
                ret[0] = target - nums[i];
                break;
            }
            else
            {
                result.insert(target - nums[i]);
            }
        }
        for(int i = 0; i < nums.size(); i++)
        {
            if(nums[i] == ret[0])
            {
                ret[0] = i;
                break;
            }
        }
        return ret;
    }
};

代码(编解码unordered_set,未AC)

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_set<long long> result;
        vector<int> ret(2, INT_MAX);
        for(long long i = 0; i < nums.size(); i++)
        {
            unordered_set<long long>::iterator iter;
            iter = find_if(result.begin(), result.end(), [&](long long encoded){
                return (encoded & 0xFFFF) == nums[i];
            });
            if(iter != result.end())
            {
                ret[1] = i;
                ret[0] = *iter >> 32;
                return ret;
            }
            else
            {
                long long encodedValue = (i << 32) | ((target - nums[i]) & 0xFFFF);
                result.insert(encodedValue);
            }
        }
        return ret;
    }
};

标签:hash,nums,int,day5,随想录,result,哈希,return,size
From: https://www.cnblogs.com/code4log/p/18390806

相关文章

  • 哈希表hashtable课堂笔记
    /*哈希表,表示键/值对的集合,这些键/值对根据键的哈希代码进行组织。它的每个元素都是一个存储在DictionaryEntry对象中的键/值对。键不能为空引用,但值可以。哈希表的构造函数有多种,这里介绍两种最常用的。*///(1)使用默认的初始容量、加载因子、哈希代码提供程序和比......
  • 「代码随想录算法训练营」第五十天 | 图论 part8
    目录拓扑排序题目:117.软件构建dijkstra(朴素版)题目:47.参加科学大会dijkstra算法和prim算法的区别dijkstra(堆优化版)题目:47.参加科学大会拓扑排序拓扑排序概括来说就是给出一个有向无环图,把这个有向无环图转成线性的排序,就叫拓扑排序。使用广度优先搜索(BFS)即可。如上图,当我们......
  • 代码随想录算法训练营,8月31日 | 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点
    24.两两交换链表中的节点题目链接:24.两两交换链表中的节点文档讲解︰代码随想录(programmercarl.com)视频讲解︰两两交换链表中的节点日期:2024-08-31做前思路:用上虚拟头指针,从头开始,先指向2再到1,再到3,但要注意保留原本的结点。Java代码如下:classSolution{publicListN......
  • 代码随想录day46 || 647 回文子串, 516 最长回文子序列
    647回文字串funccountSubstrings(sstring)int{ //动规五部曲 //dp[i][j]表示s[i:j+1]区间是否是一个回文 //ifs[i]==s[j]{ifi-j<=1||dp[i+1][j-1]==true{dp[i][j]==true}} //初始化为false //从下往上,从左往右 //print varcountint var......
  • Hash哈希学习笔记
    概念:通过一个hash函数建立值与存储地址的关系原则:开小数组+冲突解决冲突越少,用时越少;可通过调整余数或优质的hash算法尽量使hash值分散,减少碰撞hash算法的构成:hash函数的初始化构造hash函数:典型的函数包括除余法H......
  • 代码随想录算法day4 - 链表2
    题目124.两两交换链表中的节点给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。示例1:输入:head=[1,2,3,4]输出:[2,1,4,3]示例2:输入:head=[]输出:[]示例3:输入:head=[1]输出:[1]......
  • 代码随想录算法训练营,8月30日 | 203.移除链表元素, 707.设计链表, 206.反转链表
    链表理论基础1.单链表:数据域和指针域(指向下一个结点的位置)组成,头结点,结尾为空指针;双链表:多了一个指向前一个结点的指针;循环链表:结尾指向头结点。2.链表在内存中的存储不是顺序的,跟数组不同,要找一个数据只能通过前一个数据来找,所有这就导致链表的查询比数组麻烦,但是插入删除数据......
  • 哈希
    树状数组是个好东西,写起来也相对好看。但是操作比较局限,区间修改就掉回\(O(nlogn)\),那还不如\(O(n)\)。线段树完美的解决问题。线段树,也可以理解的一堆线段组成的树。大致如上,有一线段\([l,r]\)当\(l\ner\)可化为\([l,mid]\),\([mid+1,r]\)PS:\(mid=(l+r......
  • 「代码随想录算法训练营」第四十九天 | 图论 part7
    目录最小生成树的解题prim算法举例说明(来自代码随想录)题目:53.寻宝Kruskal算法举例说明(来自代码随想录)题目:53.寻宝最小生成树的解题最小生成树类型的题目主要用于解决所有节点的最小连通子图的问题,即:以最小的成本(边的权值)将图中所有节点链接到一起。最小生成树可以使用prim算......
  • day59-graph theory-part09-8.30
    tasksfortoday:1.digkstra堆优化版47.参加科学大会2.bellman_ford算法94.城市间货物运输I---------------------------------------------------------------------------------1.dijkstra堆优化版Thisisanoptimizationforthevanilladijkstra,throughus......