代码随想录算法训练营第6天

代码随想录算法训练营第6天 | 哈希表理论基础,242.有效的字母异位词,349. 两个数组的交集,第202题. 快乐数,1. 两数之和

一、刷题部分

1.1 哈希表理论基础

  • 原文链接:代码随想录

  • 题目链接:🈚️

  • 首先 hash 表是根据元素的值来访问元素的。

  • hash 表可以快速地判断元素是否在其中,是一种用空间换时间的数据结构。

  • hash 函数结果可能相同,就是 hash 碰撞,可以用拉链法 或 线性探测法 来解决。

  • 哈希相关算法可以使用以下 3 种结构来实现:

    • 数组
    • 集合 set
    • 映射 map
集合 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率
std::set 红黑树 有序 O(log n) O(log n)
std::multiset 红黑树 有序 O(logn) O(logn)
std::unordered_set 哈希表 无序 O(1) O(1)
映射 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率
std::map 红黑树 key有序 key不可重复 key不可修改 O(logn) O(logn)
std::multimap 红黑树 key有序 key可重复 key不可修改 O(log n) O(log n)
std::unordered_map 哈希表 key无序 key不可重复 key不可修改 O(1) O(1)
  • 哈希算法优先使用下图左边的4种结构:

1.2 242.有效的字母异位词

1.2.1 题目描述

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

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

示例 1:

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

示例 2:

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

提示:

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

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

1.2.2 初见想法

尽管哈希表的功能函数怎么写的还不会,但是不妨碍我来分析一下这题怎么结合哈希表来解。如果 s 用一个哈希表存,那么遍历一遍 t,让 t 里每个元素都判断是否在 s 中。这样的想法是否可行呢?看例子中会出现一个字串含相同字母的情况,那么这个算法就不太合适。

或者我可以做一个映射,维护一个新的哈希表,将 s 的每个元素依次填入,s 每次填入的时候既填字母本身又填出现的次数,如 abca 就填 a1 b1 c1 a2。同理遍历 t 的时候也用类似的方式做验证,这样就可以解决字母重复的问题了。

接下来就是学习一下哈希表的具体写法就可以开始实现我的想法了。

class Solution {
public:
    bool isAnagram(string s, string t) {
        if (s.size() != t.size()) {
            return false;
        }
        //创建一个空表
        unordered_set<string> mySet;
        //创建一个26长度的集合,表示字母出现的次数
        int letterCounts[26] = {0};
        //边添加到集合边维护数组
        for (int i = 0; i < s.size(); i++) {
            int index = int(s[i] - 'a');
            letterCounts[index]++;
            string info = s[i] + to_string(letterCounts[index]);
            mySet.insert(info);
            //cout << "插入了"  << info << endl;
        }
        //遍历并验证
        bool result = true;
        for (int i = 0; i < t.size(); i++) {
            int index = int(t[i] - 'a');
            string info = t[i] + to_string(letterCounts[index]);
            //cout << "检索了" << info << endl;
            if (mySet.find(info) == mySet.end()) {
                //查不到直接返回 false
                return false;
            }
            letterCounts[index]--;
        }
        return true;
    }
};

试了一下使用 unordered_set 的方法,这个类有 insert(XXX) 和 find(XXX) 的方法,还是挺方便的。

1.2.3 看录后想法

大受震撼,为什么我没想到可以用这么简单的方法就做好,还是局限在哈希表里了。或者说这个题解用的方法就是一种广义的哈希表呢,字母与下标一一映射。

class Solution {
public:
    bool isAnagram(string s, string t) {
        if (s.size() != t.size()) {
            return false;
        }
        //创建一个26长度的集合,表示字母出现的次数
        int letterCounts[26] = {0};
        //遍历s,统计各字母出现次数
        for (int i = 0; i < s.size(); i++) {
            int index = int(s[i] - 'a');
            letterCounts[index]++;
        }
        //遍历t,将各字母的次数减掉
        for (int i = 0; i < t.size(); i++) {
            int index = int(t[i] - 'a');
            letterCounts[index]--;
        }
        //遍历letterCounts,全0则匹配上了
        bool result = true;
        for (int i = 0; i < 26; i++) {
            if (letterCounts[i] != 0) {
                return false;
            }
        }
        return true;
    }
};

1.2.4 遇到的困难

本题思路较为清晰,暂未遇到困难。

1.3 349. 两个数组的交集

1.3.1 题目描述

给定两个数组 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

1.3.2 初见想法

暴力就是双循环,一下子就能想到,现在想想有没有更好的方法。如何用哈希表来应用到这里呢?就利用一下 stl 的 unordered_set 来实现吧。不过说实话感觉效率跟暴力差不多。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //开一个 unordered_set 来把 nums1 的数据存进去
        unordered_set<int> mySet;
        //无重存储结果
        unordered_set<int> result;
        for (int i = 0; i < nums1.size(); i++) {
            mySet.insert(nums1[i]);
        }
        //遍历 nums2 看是否在 mySet 存在
        for (int i = 0; i < nums2.size(); i++) {
            if (mySet.find(nums2[i]) != mySet.end()) {
                result.insert(nums2[i]);
            }
        }
        vector<int> result_vec(result.begin(),result.end());
        return result_vec;
    }
};

1.3.3 看录后想法

对啊,为什么在定义 mySet 的时候没有想到直接赋值呢。STL库用的还是不熟练。遍历的写法也要学习一下。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //开一个 unordered_set 来把 nums1 的数据存进去
        unordered_set<int> mySet(nums1.begin(), nums1.end());
        //无重存储结果
        unordered_set<int> result;
        //遍历 nums2 看是否在 mySet 存在
        for (int num2 : nums2) {
            if (mySet.find(num2) != mySet.end()) {
                result.insert(num2);
            }
        }
        vector<int> result_vec(result.begin(),result.end());
        return result_vec;
    }
};

1.3.4 遇到的困难

主要还是STL的一些语法需要学习,这一块都没好好学过。

1.4 第202题. 快乐数

1.4.1 题目描述

编写一个算法来判断一个数 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

1.4.2 初见想法

这题还可以和哈希表联系上吗?仔细分析一下。一个数可能会无限循环下去,我怎么知道它到底会不会无限循环呢,只需要知道这个数字之前有没有出现过就好。那确实用一个set就比较方便地解决了。

class Solution {
public:
    bool isHappy(int n) {
        unordered_set<int> appearedNumbers;
        while (appearedNumbers.find(n) == appearedNumbers.end()) {
            if(n == 1) {
                return true;
            }
            appearedNumbers.insert(n);
            n = findHappy(n);
        }
        return false;
    }
    int findHappy (int num) {
        int ans = 0;
        while (num != 0) {
            ans += (num % 10) * (num % 10);
            num /= 10;
        }
        return ans;
    }
};

1.4.3 看录后想法

录的解法和我几乎一样。不多说了。

1.4.4 遇到的困难

🈚️

1.5 1. 两数之和

1.5.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
  • 只会存在一个有效答案

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

1.5.2 初见想法

这题怎么也能和哈希表联系上了???让我好好想想。

可以用一个 set 用来存(每个数与 target 的差值:下标)。对数组中的每一个数,先判断在不在 set 里,如果在则找到其下标然后和自己的下标组成答案;如果不在则将(与 target 的差值 : 下标)存进去。代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> myMap;
        for (int i = 0; i < nums.size(); i++) {
            //在map里说明找到答案了
            if (myMap.find(nums[i]) != myMap.end()) {
                return {myMap[nums[i]],i};
            }
            //差值添加到map里
            //cout << "添加{" << target - nums[i] << "," << i << "}到map里。" << endl;
            myMap.insert({target - nums[i], i});
        }
        return {-1};
    }
};

1.5.3 看录后想法

与我思路基本一样,就是map里直接存值,然后每次用差值去map里找,和我的方法是等价的。

1.5.4 遇到的困难

🈚️

二、总结与回顾

感觉开始用到C++的STL库里的东西了。这一块我想找个时间好好学一下。

posted @ 2025-01-13 11:03  xc0208  阅读(431)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示