哈希表理论+异位词

哈希表(Hash Table)也叫散列表,通过关键码的值直接访问的数据结构,查询效率高;

index = hashFunction(name)

hashFunction = hashCode(name) % tableSize

需要做映射的数据大于哈希表,会出现哈希碰撞,解决方法:1.发生冲突元素存在链表2.线性探测:既表的长度大于数据长度,利用空位解决碰撞问题。

常见三种哈希结构:

数组,集合,映射

1.异位词

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        slist = list(s)
        tlist = list(t)
        nums = [0]*26
        for i in slist:
            nums[ord(i)-97] += 1
        for j in tlist:
            nums[ord(j)-97] -= 1
        nums = [abs(x) for x in nums]
        return False if (sum(nums))!=0 else True

C++版本代码同理,但是需要注意的是直接通过字符相减即可 ,这是因为在 C++ 中,字符类型本质上是一个整数类型,字符的 ASCII 值可以用来计算索引。

class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[26] = {0};
        for (int i = 0; i < s.size(); i++) {
            // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            record[s[i] - 'a']++;
        }
        for (int i = 0; i < t.size(); i++) {
            record[t[i] - 'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if (record[i] != 0) {
                // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
                return false;
            }
        }
        // record数组所有元素都为零0,说明字符串s和t是字母异位词
        return true;
    }
};

2.两个数组交集

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums1 = set(nums1)
        nums2 = set(nums2)
        result = list(nums1 & nums2)
        return result

C++的话,要考虑的是使用哪一种表,std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set;
        unordered_set<int> nums_set(nums1.begin(),nums1.end());
        for(int num:nums2){
            if(nums_set.find(num)!=nums_set.end()){
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(),result_set.end());
    }
};

nums_set.find(num)

  • findunordered_set 类的一个成员函数,用于查找集合中是否存在某个特定的元素。
  • 如果集合中包含 num,则 find 函数会返回一个指向 num 的迭代器。
  • 如果集合中不包含 num,则 find 函数会返回一个指向集合末尾(即 nums_set.end())的迭代器。

3.快乐数

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

        }
    }
};

思路一样,但是注意C++中/可以判断两边类型,都是整形最后得到的也是

python中用//表示整除,/表示除 

class Solution:
    def isHappy(self, n: int) -> bool:
        record = set()
        while(1):
            n = self.getSum1(n)
            if(n==1):
                return True
            if n in record:
                return False
            else:
                record.add(n)
    def getSum1(self, n:int)->int:
        sum1 = 0
        while(n):
            sum1 += (n%10)**2
            n = n // 10
        return sum1

4.两数之和

这到底也是查询一个元素是否出现,可以使用hash,由于需要返回出现的元素值和索引,也就是需要元素值和索引对应起来,根据三种类型的hash,map是最符合的,将元素值作为key,然后将索引作为value。遍历数组中的值,通过target-nums[i],然后再去map中找,如果存在就返回索引。

值得注意的一点是,不要在一开始就把所有的数据和对应索引值放到字典中去,否则会造成相同的数据,但是最后索引只有一个。每次都去比较之前的数据,如果是两个重复的值相加而得到的数据的话,不会造成覆盖。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        map1 = {}
        for id,i in enumerate(nums):
            if(target-i in map1):
                return [id, map1[target-i]]
            map1[i] = id
        return []
    
sol = Solution()
nums = [3,3]
target =6
print(sol.twoSum(nums,target))
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map<int,int> map;
        for(int i =0;i<nums.size();i++){
            auto iter = map.find(target-nums[i]);
            if(iter!=map.end()){
                return {iter->second, i};
            }
            map.insert(pair<int,int>(nums[i],i));
        }
        return {};
    }
};

auto iter = map.find(target - nums[i]);:

  • auto 是一种类型推导,iter 作为一个迭代器,它的具体类型由 map.find() 返回的类型决定。对于 unordered_map<int, int>iter 的类型是 std::unordered_map<int, int>::iterator

return {iter->second, i};:

  • iter->second 表示找到的目标元素在 map 中对应的值,即数组中之前的元素的索引。
  • iter->second 通过箭头运算符 -> 访问 unordered_map 元素的 second 成员。unordered_map 中的元素是键值对(pair),其中 first 是键,second 是值。
  • 该行代码返回的是一个包含两个整数的 vector,这两个整数分别是两个符合条件的数组元素的索引。

map.insert(pair<int, int>(nums[i], i));:

  • 这行代码将当前数组元素及其索引作为键值对插入到 map 中。

5.四数相加

实际上就是先将两两记录成一个map,然后两两相加,将问题简化。

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> map1;
        for(int i=0;i<nums1.size();i++){
            for(int j=0;j<nums2.size();j++){
                map1[nums1[i]+nums2[j]]++;
            }
        }
        int count = 0;
        for(int i=0;i<nums3.size();i++){
            for(int j=0;j<nums4.size();j++){
                if(map1.find(0-(nums3[i]+nums4[j]))!=map1.end()){
                    count += map1[0-(nums3[i]+nums4[j])];
                }
            }
        }
        return count;
    }
};
class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        map1 = dict()
        for i in nums1:
            for j in nums2:
                if i+j in map1:
                    map1[i+j] += 1
                else:
                    map1[i+j] = 1
        count=0
        for i in nums3:
            for j in nums4:
                if (0-(i+j)) in map1:
                    count += map1[-(i+j)]
        return count

 5.赎金信

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        record = [0]*26
        ran = list(ransomNote)
        mag = list(magazine)
        for i in mag:
            record[ord(i)-ord('a')] += 1
        for j in ran:
            record[ord(j)-ord('a')] -= 1
        for i in record:
            if(i<0):
                return False
        return True
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};
        if(ransomNote.size()>magazine.size()){
            return false;
        }
        for(int i=0;i<magazine.length();i++){
            record[magazine[i]-'a']++;
        }
        for(int j=0;j<ransomNote.size();j++){
            record[ransomNote[j]-'a']--;
            if(record[ransomNote[j]-'a']<0){
                return false;
            }
        }
        return true;
    }

6.三数之和:开始梦碎

比较疑惑的一点就是在去重,对于a的去重,考虑的是i-1,对于-1-12,这种情况如果i对应第二个-1,那么left一定在i的右边,想要得到一个重复的元组,唯一的可能就是right--到第一个-1,但是后面给出的条件已经是right>left,所以不可能实现,那么也就不可能出现重复的情况.

仔细想想,他只是实现了对i去重的功能,这就是仅仅针对i不能重复。如果前面一个和当前相同,说明这个i值已经用过了,后面一旦能够出现使三数和为0的,那么这两个就是重复的,所以要判断前一个是否相同,不同则是i第一次使用这个值,否则就是第二次。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());
        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){
                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;
    }
};

今天清醒了一点,三数之和的主要做法用的是双指针,先固定i作为一个循环,然后left和right分别从i后面和数组末尾开始收缩。由于一开始已经做了排序操作,所以需要判断第一个数是否>0,然后开始接下来的操作,注意的是在结果进行去重,那么首先就是在里面对a,b,c这三个元素进行去重,先对a,昨天已经理解了,就是因为第一个使用这个a,那么后面的left和right指向的值的和就能够满足三者之和为0,此时如果下一个还是和a一样的值,很明显就会产生重复,那么现在就只需要取这个值第一次出现的即可。对于b,c既left和right 同理。 

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        result = []
        nums.sort()
        for i in range(len(nums)):
            if nums[i] > 0:
                return result
            
            if i > 0 and nums[i]==nums[i-1]:
                continue
            left = i+1
            right = len(nums) - 1

            while right > left:
                sum_ = nums[i] + nums[left] + nums[right]
            
                if sum_<0:
                    left+=1
                elif sum_>0:
                    right-=1
                else:
                    result.append([nums[i],nums[left],nums[right]])
                    while right > left and nums[right] == nums[right-1]:
                        right -= 1
                    while right > left and nums[left] == nums[left+1]:
                        left += 1
                    right -=1
                    left +=1
        return result

7.四数之和

相比于三数之和,差别在于外面多加了一层循环,这层循环里面是三数之和。但是对于剪枝还是有一点细微差别。由于此时目标值是target,不再是0.所以对排序后第一个值的判断不同了,不仅需要大于target还需要大于0,才能跳出,之后对于去重还是和三数之和一样判断这个数是不是第一次出现,是否和前面相同了

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(nums[k]>target && nums[k]>=0){
                break;
            }
            if(k>0 && nums[k]==nums[k-1]){
                continue;
            }
            for(int i=k+1;i<nums.size();i++){
                if(nums[k]+nums[i]>target && nums[k]+nums[i]>=0){
                    break;
                }
                if(i>k+1 && nums[i]==nums[i-1]){
                    continue;
                }
                int left = i + 1;
                int right = nums.size()-1;
                while(right>left){
                    if((long) nums[k] + nums[i] + nums[left] + nums[right] > target){
                        right--;
                    }
                    else if((long) nums[k] + nums[i] + nums[left] + nums[right] < target){
                        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;
    }
};
class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        result = []
        nums.sort()
        for i in range(len(nums)):
            if(nums[i]>0 and nums[i]>target):
                break
            if(i>0 and nums[i]==nums[i-1]):
                continue
            for j in range(i+1,len(nums)):
                if(nums[i]+nums[j]>0 and nums[i]+nums[j]>target):
                    break
                if(j>i+1 and nums[j]==nums[j-1]):
                    continue
                left = j+1
                right = len(nums)-1
                while(left<right):
                    if nums[i]+nums[j]+nums[left]+nums[right] < target: left += 1
                    elif nums[i]+nums[j]+nums[left]+nums[right] > target: right -= 1
                    else:
                        result.append([nums[i],nums[j],nums[left],nums[right]])
                        while left<right and nums[right]==nums[right-1]: right-=1
                        while left<right and nums[left]==nums[left+1]: left+=1
                        right-=1
                        left+=1
        return result

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值