【代码随想录】第5章:哈希表

简介: 【代码随想录】第5章:哈希表

哈希表


1. 哈希表理论基础


哈希表有一个现象:哈希冲突


解决哈希冲突的方法:拉链法,线性探测法


常见的三种哈希结构:


  1. 数组


  1. set


  1. map


哈希查找:O(1)


枚举查找:O(n)



红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。



2. 有效的字母异位词


242. 有效的字母异位词【简单】



思路一:sort()函数先对字符串进行排序,在判断两个字符串相等。时间复杂度高


思路二:桶排序;对字符串1进行++计数;对字符串2进行–计数;判断最后桶中是否都为0


思路三:哈希表计数;思路还是桶排序的思路,只是用哈希表实现


代码只记录思路三的,其他两个不写。


时间复杂度:O(n) n为字符串长度


空间复杂度:O(S) S为字符集大小


class Solution {
public:
  bool isAnagram(string s, string t) {
    if (s.size() != t.size())  return false;  //字符串长度不相等  false
    unordered_map<char, int> map;
    for (int i = 0; i < s.size(); i++) {   //计数;一个++;一个--
      map[s[i]]++;
      map[t[i]]--;
    }
    for (auto p : map) {
      if (p.second != 0) return false;  //最后计数必须为0
    }
    return true;
  }
};


383. 赎金信【简单】



题目意思:判断字符串a中的字符能不能构成字符串b


思路一:sort()排序+双for暴力枚举


思路二:暴力枚举+erase()函数


// 时间复杂度: O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        for (int i = 0; i < magazine.length(); i++) {
            for (int j = 0; j < ransomNote.length(); j++) {
                // 在ransomNote中找到和magazine相同的字符
                if (magazine[i] == ransomNote[j]) {
                    ransomNote.erase(ransomNote.begin() + j); // ransomNote删除这个字符
                    break;
                }
            }
        }
        // 如果ransomNote为空,则说明magazine的字符可以组成ransomNote
        if (ransomNote.length() == 0) {
            return true;
        }
        return false;
    }
};


思路三:桶排序;26个桶统计字符个数


思路四:哈希表计数;主串计数++;子串计数–;判断计数后是否有<0的。


下面记录思路二的代码


class Solution {
public:
  bool canConstruct(string ransomNote, string magazine) {
    if (magazine.size() < ransomNote.size())  return false;   //排除子串长度大于主串的情况
    unordered_map<char, int> map;
    for (auto i : magazine) {
      map[i]++;
    }
    for (auto i : ransomNote) {
      map[i]--;
    }
    for (auto p : map) {
      if (p.second < 0)  return false;
    }
    return true;
  }
};


49. 字母异位词分组【中等】



思路:哈希表


这题有点难度;前序后的作为模板,即作为键;排序前的作为能匹配到模板的值


class Solution {
public:
  vector<vector<string>> groupAnagrams(vector<string>& strs) {
    unordered_map<string, vector<string>> map;
    for (auto str : strs) {
      string key = str;
      sort(key.begin(), key.end());    //排序后的作为键     //这句代码可以换成其他的实现排序
      map[key].emplace_back(str);       //排序前的作为值
    }
    vector<vector<string>> res;
    for (auto p : map) {
      res.push_back(p.second);   //一下子插入一个vector<string>
    }
    return res;
  }
};


438. 找到字符串中所有字母异位词【中等】



思路一:用一个桶计数并滑动窗口


//滑动窗口:一边延伸,一边缩进
//用一个桶计数
class Solution {
public:
  bool equal(int* tong) {
    for (int i = 0; i < 26; i++) {
      if (tong[i] != 0) return false;
    }
    return true;
  }
  vector<int> findAnagrams(string s, string p) {
    if (p.size() > s.size())  return vector<int>();   //排除子串比主串长的情况
    vector<int> ans;  //用于存储索引
    int tong[26] = { 0 };
    for (int i = 0; i < p.size(); i++) {
      tong[p[i] - 'a']--;
      tong[s[i] - 'a']++;
    }
    for (int i = p.size(); i <= s.size(); i++) {
      if (equal(tong)) {
        ans.push_back(i - p.size());
      }
      if (i != s.size()) {    //防止索引越界
        tong[s[i] - 'a']++;               //实现滑动
        tong[s[i - p.size()] - 'a']--;
      }
    }
    return ans;
  }
};


//滑动窗口:一边延伸,一边缩进
//用一个桶计数
class Solution {
public:
  bool equal(int* tong) {
    for (int i = 0; i < 26; i++) {
      if (tong[i] != 0) return false;
    }
    return true;
  }
  vector<int> findAnagrams(string s, string p) {
    if (p.size() > s.size())  return vector<int>();   //排除子串比主串长的情况
    vector<int> ans;  //用于存储索引
    int tong[26] = { 0 };
    for (int i = 0; i < p.size(); i++) {
      tong[p[i] - 'a']--;
      tong[s[i] - 'a']++;
    }
    if (equal(tong)) ans.push_back(0);
    for (int i = 0; i < s.size() - p.size(); i++) {
      tong[s[i] - 'a']--;               //实现滑动
      tong[s[i + p.size()] - 'a']++;
      if (equal(tong))   ans.push_back(i + 1);
    }
    return ans;
  }
};


或者用两个容器,其实都一样,只是判断相等的语句换了


class Solution {
public:
  vector<int> findAnagrams(string s, string p) {
    if (s.size() < p.size())   return vector<int>();
    vector<int> ans;
    vector<int> sCount(26);
    vector<int> pCount(26);
    for (int i = 0; i < p.size(); ++i) {
      sCount[s[i] - 'a']++;
      pCount[p[i] - 'a']++;
    }
    if (sCount == pCount)   ans.emplace_back(0);
    for (int i = 0; i < s.size() - p.size(); i++) {    //需要滑动的次数
      sCount[s[i] - 'a']--;
      sCount[s[i + p.size()] - 'a']++;
      if (sCount == pCount)   ans.emplace_back(i + 1);
    }
    return ans;
  }
};


思路:滑动窗口哈希表


时间复杂度和空间复杂度都很高;这里不推荐此方法


class Solution {
public:
  bool equal(unordered_map<char, int> map) {
    for (auto p : map) {
      if (p.second != 0) return false;
    }
    return true;
  }
  vector<int> findAnagrams(string s, string p) {
    if (p.size() > s.size())  return vector<int>();
    unordered_map<char, int> map;
    for (int i = 0; i < p.size(); i++) {
      map[p[i]]--;
      map[s[i]]++;
    }
    vector<int> res;
    if (equal(map))  res.push_back(0);
    for (int i = p.size(); i < s.size(); i++) {
      map[s[i]]++;
      map[s[i - p.size()]]--;
      if (equal(map))  res.push_back(i - p.size() + 1);
    }
    return res;
  }
};


滑动窗口优化


都是在优化判断相等的方法:统计不为0的个数来判断相等


class Solution {
public:
  vector<int> findAnagrams(string s, string p) {
    int sLen = s.size(), pLen = p.size();
    if (sLen < pLen) {
      return vector<int>();
    }
    vector<int> ans;
    vector<int> count(26);
    for (int i = 0; i < pLen; ++i) {
      ++count[s[i] - 'a'];
      --count[p[i] - 'a'];
    }
    int differ = 0;
    for (int j = 0; j < 26; ++j) {
      if (count[j] != 0) {
        ++differ;
      }
    }
    if (differ == 0) {
      ans.emplace_back(0);
    }
    for (int i = 0; i < sLen - pLen; ++i) {
      if (count[s[i] - 'a'] == 1) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
        --differ;
      }
      else if (count[s[i] - 'a'] == 0) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
        ++differ;
      }
      --count[s[i] - 'a'];
      if (count[s[i + pLen] - 'a'] == -1) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
        --differ;
      }
      else if (count[s[i + pLen] - 'a'] == 0) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
        ++differ;
      }
      ++count[s[i + pLen] - 'a'];
      if (differ == 0) {
        ans.emplace_back(i + 1);
      }
    }
    return ans;
  }
};


3. 两个数组的交集


349. 两个数组的交集【简单】



思路一:枚举暴力破解;时间复杂度O(mn)


思路二:sort函数排序+双指针指向两个数组开始遍历;


时间复杂度O(nlogn+mlogm) ;


空间复杂度:O(logm+logn); 主要取决于排序使用的额外空间


思路三:哈希表可以实现去重;


时间复杂度O(n+m)


空间复杂度O(n+m)


class Solution {
public:
  //哈希表存数 unordered_set无序,每种元素只能存储一个;   set是有序的
  vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    unordered_set<int> s1, s2;
    for (auto& i : nums1) s1.insert(i);   //存元素且去重,保证唯一
    for (auto& i : nums2) s2.insert(i);
    vector<int>v;
    for (auto& i : s2) {  //遍历s2中的每一个元素  在s1中进行查找
      if (s1.count(i))   v.push_back(i);
    }
    return v;
  }
};


或者以下:


空间复杂度O(m)


class Solution {
public:
  vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    unordered_set<int> set;
    for (auto i : nums1) {     //只存num1的
      set.insert(i);
    }
    vector<int> res;
    for (auto i : nums2) {   //遍历num2中的每一个元素,在set中进行查找  找到了要在哈希表中删除该元素 保证唯一
      if (set.count(i)) {       //查询的时间复杂度也为O(1)
        res.push_back(i);
        set.erase(i);        //删除的时间复杂度为O(1)
      }
    }
        return res;
  }
};


350. 两个数组的交集 II【简单】



思路一:排序+双指针指向两个数组;该方法比349的还要简单了


思路二:哈希表计数。因同种元素要计算次数了,由unordered_set改成unordered_map


class Solution {
public:
  vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
    unordered_map<int, int> map;   //第二个int为元素的次数
    for (auto i : nums1) {
      map[i]++;
    }
    vector<int> res;
    for (auto i : nums2) {
      if (map.count(i) && map[i] > 0) {   //找到了该元素  且个数>0才行
        res.push_back(i);
        map[i]--;                       //消消乐抵消一个
      }
    }
    return res;
  }
};


4. 快乐数


202. 快乐数【简单】



思路:我们可以认为之前出现过的数之后不会出现,即不成环,则总有一天走到1


如果成环,则不是快乐数


主要是看懂题目,看懂了题目就很简单。


class Solution {
public:
  int getSum(int n) {   //获取下一个个数
    int res = 0;
    while (n) {
      res += (n % 10) * (n % 10);
      n = n / 10;
    }
    return res;
  }
  bool isHappy(int n) {
    unordered_set<int> set;
    while (true) {
      int next_sum = getSum(n);  //1的下一个数也是1  所以这句代码可以写前面那
      if (next_sum == 1) return true;
      if (set.count(next_sum))  return false;           //成环
      else                      set.insert(next_sum);   //没成环
      n = next_sum;
    }
  }
};


5. 两数之和


1. 两数之和【简单】



思路一:双for循环暴力枚举


思路二:哈希表+遍历查询,因为返回索引,索引用unordered_map


思路二代码如下:


class Solution {
public:
  vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> map;
    for (int i = 0; i < nums.size(); i++) {
      if (map.count(target - nums[i]))  return { i, map[target - nums[i]] };
      map[nums[i]] = i;   //map插入元素  因为答案唯一,不需要担心插入两个相同的元素
    }
        return {-1,-1};    //这句代码走不到
    }
};


454. 四数相加 II【中等】



思路一:4for暴力枚举;不用多想,肯定超超时


思路二:4for拆开成两个两个,第一个双for存入哈希表,第二个双for查找哈希表


class Solution {
public:
  int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
    unordered_map<int, int> map;
    for (auto num1 : nums1) {
      for (auto num2 : nums2) {
        map[num1 + num2]++;
      }
    }
    int res;
    for (auto num3 : nums3) {
      for (auto num4 : nums4) {
        if (map.count(-num3 - num4)) {
          res += map[-num3 - num4];    //有几个这样的就加几
        }
      }
    }
    return res;
  }
};


15. 三数之和【中等,双指针】




思路:排序+双指针


哈希表时间复杂度和空间复杂度都很高


难度主要在于降重


时间复杂度:O(n^2)


空间复杂度:O(1)


class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0     a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组
            if (nums[i] > 0) return result;
            // 正确去重方法
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                if (nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                }
                else if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                }
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }
        }
        return result;
    }
};


18. 四数之和【中等】



与上一题思路一样


class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int k = 0; k < nums.size(); k++) {
            // 去重
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for (int i = k + 1; i < nums.size(); i++) {
                // 正确去重方法
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    if (nums[k] + nums[i] > target - (nums[left] + nums[right])) {
                        right--;
                    }
                    else if (nums[k] + nums[i] < target - (nums[left] + nums[right])) {
                        left++;
                    }
                    else {
                        result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
                        // 去重逻辑应该放在找到一个四元组之后
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;
                        // 找到答案时,双指针同时收缩
                        right--;
                        left++;
                    }
                }
            }
        }
        return result;
    }
};


             else if (nums[k] + nums[i] < target - (nums[left] + nums[right])) {
                    left++;
                }
                else {
                    result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个四元组之后
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }
        }
    }
    return result;
}


目录
相关文章
VS Code 中文乱码及编码格式问题全解
VS Code 中文乱码及编码格式问题全解
8573 0
|
5天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1490 0
|
5天前
|
人工智能 自然语言处理 安全
阿里云AI数智鉴密:AI 生成内容如何拿到一张"防篡改的身份证"
隐形水印 + C2PA签名:让AI生成内容“持证上岗”。
1131 0
|
14天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
3781 4
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
5天前
|
人工智能 安全 前端开发
刚刚 GPT-6 Astra 发布,全球最强,AGI 时代到来!
OpenAI 正式推出 GPT-6 Astra 模型,带大家看看这次 GPT 有哪些提升,跟 Claude Fable 5.1 有什么差距?AI 编程能力如何?AGI 真的来了么?
633 0
|
2天前
|
SQL 人工智能 前端开发
QoderWake 1.0 正式发布:从桌面里的 Agent,到工作现场的数字员工
QoderWake v1.0正式发布:企业级数字员工团队平台。支持“一句话建岗”,预置10类特训岗位;Waker常驻钉钉/飞书群,@即响应、自动协作、跨任务记忆;具备定时/事件/API多触发方式与统一任务看板;已沉淀27.6万条记忆、12.3万项技能,助力组织实现人机协同增效。
627 0
|
6天前
|
网络协议 Linux iOS开发
【2026实测】Wireshark下载+安装+汉化+使用教程(图文版,巨详细)
Wireshark 是一款免费开源的网络协议分析工具,可实时捕获、解析并可视化数据包,助你诊断网络故障、分析通信协议(如HTTP、DNS、TCP等)。支持Windows/macOS/Linux,含中文界面,新手入门便捷。(239字)