5.哈希表(1) | 有效的字母异位词、两个数组的交集、快乐数、两数之和

        这4道题的前3道主要让人了解了set和map的各项基本用法,题目本身的方法方面没有难度。第4题低于O(n²)的方法还是不容易想到的,这次难得自己想到,不过具体实现上相比题解还要很大优化空间。


        第1题(242.有效的字母异位词)是典型的哈希表问题,但又不完全是。做题过程主要学习了解了unordered_map的初始化、增改元素、遍历的方法,顺利通过了。

#include<unordered_map>

class Solution {
public:
    bool isAnagram(string s, string t) {
        if (s.size() != t.size()) {
            return false;
        }
        unordered_map<char, int> nums;
        for (int i = 0; i < s.size(); ++i) {
            if (nums.find(s[i]) == nums.end()) {
                nums[s[i]] = 1;
            }
            else {
                nums[s[i]]++;
            }
        }
        for (int i = 0; i < t.size(); ++i) {
            if (nums.find(t[i]) == nums.end()) {
                return false;
            }
            else {
                nums[t[i]]--;
                if(nums[t[i]] < 0) {
                    return false;
                }
            }
        }
        for (auto it = nums.begin(); it != nums.end(); it++) {
            if (it->second > 0) {
                return false;
            }
        }
        return true;
    }
};

        然而,说是通过了却又没真正地通过。看了题解代码才意识到为什么时间、空间占用排名如此靠后,原来这道题目并不需要unordered_map这种数据结构。因为key只可能26个字母,且他们有顺序,那么只需要长度为26的int的数组即可。

public:
    bool isAnagram(string s, string t) {
        if (s.size() != t.size()) {
            return false;
        }
        int nums[26] = {0}; // 只有在初始化为0时可以用这种写法
        for (int i = 0; i < s.size(); ++i) {
            nums[s[i] - 'a']++;
        }
        for (int i = 0; i < t.size(); ++i) {
            nums[t[i] - 'a']--;
        }
        for (int i = 0; i < 26; ++i) {
            if (nums[i] != 0) {
                return false;
            }
        }
        return true;
    }
};

在实现过程中也学习到int数组的初始化只有当初始化为0时才可以写为int a[n] = {0},初始化目标值为其他时都不能用这种写法,否则只会使a[0]初始化成功。一般可以使用memset(a, x, sizeof(a)),其中x为目标初始化值。memset()的头文件为<cstring>。

        二刷:忘记map的遍历方式。


        第2题(349. 两个数组的交集)比较简单,过程中主要熟悉了unordered_set的基本用法遍历方法。然而这个简单题还是没有一次性AC,因为起初在遍历nums2时直接将在集合中存在的数字添加进了返回值vector,导致vector存放了重复元素。更改为将交集结果存入新的unordered_set后在对其遍历并取值存入vector后正确。

#include<unordered_set>

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> nums, ansSet;
        for (int i = 0; i < nums1.size(); ++i) {
            nums.insert(nums1[i]);
        }
        vector<int> ansVec;
        for (int i = 0; i < nums2.size(); ++i) {
            if (nums.find(nums2[i]) != nums.end()) {
                ansSet.insert(nums2[i]);
            }
        }
        for (auto it = ansSet.begin(); it != ansSet.end(); ++it) {
            ansVec.push_back(*it);
        }
        return ansVec;
    }
};

        而在看题解时,学到了unordered_set与vector相互转化的方法,参数只需填入各自的.begin()和.end()即可,使用后代码量减少很多。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> nums(nums1.begin(), nums1.end()), ans;
        for (int i = 0; i < nums2.size(); ++i) {
            if (nums.find(nums2[i]) != nums.end()) {
                ans.insert(nums2[i]);
            }
        }
        return vector<int>(ans.begin(), ans.end());
    }
};

        二刷:结果也要求不重复,忘记用set来保存结果,最后再转vector。


                第3题(202. 快乐数)也比较简单,不断判断新的结果是否在集合中,不存在就放入到集合中继续循环即可。

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

然而再次没有一次性AC,因为忘记了在循环中将n加入到集合中和初始化nNext为0,以及弄错循环条件,循环条件应当是未找到,即.find() == .end()。其中将n加入到循环中的时机很重要,应该放在循环的最开始处,否则原始n不会被添加。且不能放在循环开始前,因为会导致循环不能开始。

        二刷:忘记用set来判断是否出现过的方法。


        第4题(1. 两数之和)如果只是O(n²)就很简单,但低于O(n²)的方法想了半天才想到可以用target减掉所有数,得到每个数期望的另一个数,用它们构成集合。再重新遍历数组,在集合中查找当前数,如果存在,即找到。但又没这么简单,因为还面临2个问题:

  1. 题目要求返回下标,在集合中虽找到了,但下标却无法得到。于是改集合为map,用value来存储下标。
  2. 有时会遇到数本身与期望数相同的情况,如[3, 3]和target = 6,这种情况如何避免“错误地查找成功”?有了上面的下标后这个问题也就解决了,即判断找到数字的下标与当前下标不等时才认为是找到了。
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        multimap<int, int> minus;
        for (int i = 0; i < nums.size(); ++i) {
            minus.insert({target - nums[i], i});
        }
        vector<int> ans;
        for (int i = 0; i < nums.size(); ++i) {
            auto it = minus.find(nums[i]);
            if (it != minus.end() && it->second != i) {
                ans.push_back(i);
                ans.push_back(it->second);
                break;
            }
        }
        return ans;
    }
};

此外,还学习到multimap在插入元素时不能像map一样用下标方式插入,只能用multimap.insert({key, valus})。另外需要注意multimap容易丢掉i,错写为multmap。

        看了题解发现其基本思路跟自己的一致,但更简洁高效,主要是只对数组遍历了1次,每次首先从map中查找目标,找到即返回,没找到则将当前数字和下标添加进map。这样做相比自己方法的好处主要有3点:

  1. 可以做到用map就解决问题。查找当前数字的期望数字时,当前数字还未加进map,所以不会有map中不会有重复元素,所以就用不到multimap;
  2. 查找效率更高。map是不断扩大的,所以查找规模是逐渐增大,而不像自己的方法一样每次查找都是最大规模;
  3. 流程更简单,代码更简洁。
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        map<int, int> minus;
        for (int i = 0; i < nums.size(); ++i) {
            auto it = minus.find(nums[i]);
            if (it != minus.end()) {
                return {it->second, i};
            }
            minus.insert({target - nums[i], i});
        }
        return {};
    }
};

另外,从题解代码中也学会了2点:

  • vector的赋值或返回可以直接写为{v1, v2, ···};
  • map.insert()中除可以写作{key, value}外也可以写为pair<typeKey, typeValue>(key, value)。

        二刷:只想到第一种方法,没想到更优的第二种。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值