Studying-代码随想录训练营day7| 454.四数相加II、383.赎金信、15.三数之和、18.四数之和、总结

第七天✊ε=( o`ω′)ノ✊,编程语言:c++

目录

454.四数相加

383.赎金信

15.三数之和

 18.四数之和

哈希表总结


454.四数相加

文档讲解:代码随想录四数相加

视频讲解:手撕四数相加

题目:

初看:第一想法,四个整数数组,可以通过四个循环遍历所有的情况。显然这种方法的时间复杂度为O(n^4),时间复杂度过高。第二个想法是先将两个数组加和,并存储到一个新的数组,然后再加入一个数组进行新的遍历,最后再遍历最后一个数组,总时间复杂度还是O(n^4),且空间复杂度过大,不可取。

学习:可以采取将其中两个数组先进行遍历,并把所有加和情况存储到一个哈希表内,之后再通过两个循环遍历剩下两个数组的所有情况,以此查找可能的组合。能够将时间复杂度降低为O(n^2)。其中由于本题是找到所有的可能性,不用去重,因此哈希表可以采取unordered_map结构,key为加和值,value为key出现的次数。之后在每次找到合适的值时,count += value,即可得到所有的可能组合数。

代码:

//时间复杂度O(n^2)
//空间复杂度O(n^2)
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> sum; //key表示nums1和nums2的元素之和,value表示相同的和出现的次数
        for (int a : nums1) {
            for (int b : nums2) {
                sum[a + b]++; //unoredered_map重载了[]运算符,即使没有以key为键的键值对,也会使用该键向当前容器中插入一个新键值对
            }
        }
        int count = 0; //统计最终的个数
        for (int c : nums3) {
            for (int d : nums4) {
                auto it = sum.find(0-(c + d)); //返回一个指向目标值的迭代器
                if(it != sum.end()) {
                    count += it->second;
                }
            }
        }
        return count;
    }
};

收获:

  1. 本题是四个独立的数组,因此可以采用哈希表的方式先存储两个数组的和。但如果是一个数组里面找四个元素,可能就不适合使用哈希表来进行求解。
  2. 本题使用哈希表的方式降低时间复杂度,属于是空间换时间的方式,这种思想很重要,需要多学习。

383.赎金信

文档讲解: 代码随想录赎金信

初看:本题和242.有效的字母异位词有所相同,后者是找到字母种类个数完全一样的,本题则是magazine包含ransomNote中的所有字母,但本质都是查找自身的元素在不在对方那。出现查找元素的情况就可以尝试使用哈希法来进行求解。

代码:

1.先创建一个容量为26的数组,作为一个哈希表。(因为字符串都由小写字母组成,连续且数量一定,可通过与'a'相减,映射到数组下标内)

2.分别将两个字符串加入到数组内,其中因为是求magazine是否包含ransomNote的全部字母,所以对magazine循环,数组内元素++,对ransomNote循环,数组内元素--,最后如果数组内存在负数,则说明magazine内缺少足够的字母,返回false。

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        //由小写字母构成,则可以通过设计一个26容量的数组构造哈希表
        int sum[26] {0};
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        for (char it : magazine) {
            sum[it - 'a']++;
        }
        for (char it : ransomNote) {
            sum[it - 'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if(sum[i] < 0) return false;
        }
        return true;
    }
};

15.三数之和

文档讲解:代码随想录三数之和

视频讲解:手撕三数之和

题目:

思路:

  1. 哈希法:使用两层for循环确定a和b的数值,之后使用哈希法来确定0-(a+b)是否在数组里出现过。但要注意本题不可以包含重复的三元组,因此还需要进行去重。
  2. 双指针法:本题使用哈希法并不方便进行去重,同时还需要创建一个哈希表。采用双指针法能够更高效的一些。

 

双指针法代码:

//时间复杂度:O(n^2)
//空间复杂度:O(1)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ans;
        //先对数组进行排序,使用双针法,也便于对a进行去重
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for(int i = 0; i < n - 2; i++) {
            if (nums[i] > 0) return ans; //排序后如果最小值都大于0,则一定不能组成和为0的三元组
            if (i > 0 && nums[i] == nums[i-1]) { //对a进行去重,这里也可以使用while,对i进行++,但要主要防止i出界
                continue;
            }
            //创建左右指针
            int left = i + 1;
            int right = nums.size() - 1;
            while(left < right) {
                if (nums[i] + nums[left] + nums[right] > 0) { //和大于0,值应该减小
                    right--;
                }
                else if (nums[i] + nums[left] + nums[right] < 0) { //和小于0,值应该增大
                    left++;
                }
                else {
                    ans.push_back({nums[i], nums[left], nums[right]});
                    while(left < right && nums[left] == nums[left+1]) left++;  //对j进行去重
                    while(left < right && nums[right] == nums[right-1]) right--; //对k进行去重

                    left++;  //进行新一轮循环
                    right--;
                }
            }
        } 
        return ans;
    }
};

哈希法代码:

//时间复杂度O(n^2)
//空间复杂度O(n)
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[j], c = -(a + b)
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
                continue;
            }
            unordered_set<int> set;
            for (int j = i + 1; j < nums.size(); j++) {
                if (j > i + 2
                        && nums[j] == nums[j-1]
                        && nums[j-1] == nums[j-2]) { // 三元组元素b去重
                    continue;
                }
                int c = 0 - (nums[i] + nums[j]);
                if (set.find(c) != set.end()) {
                    result.push_back({nums[i], nums[j], c});
                    set.erase(c);// 三元组元素c去重
                } else {
                    set.insert(nums[j]);
                }
            }
        }
        return result;
    }
};

 18.四数之和

文档讲解:代码随想录四数之和

视频讲解:手撕四数之和

题目:

思路:和三数之和基本是一个思路,多增加一层循环,总体的时间复杂度为O(n^3)。

代码:

//时间复杂度O(n^3)
//空间复杂度O(1)
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/4) 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[i] > 0) break;
                if (i > k + 1 && nums[i] == nums[i-1]) continue; //是大于k+1不是大于1
                
                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;
    }
};

哈希表总结

文档讲解: 代码随想录哈希表总结

 哈希表常见的三种哈希结构:数组、set(集合)、map(映射)。

1.数组作为哈希表:当所需要开辟的空间大小较小,且一定,则可以采用数组作为哈希表,例如242.有效的字母异位词和383.赎金信,都是小写字母,开辟长度为26的数组即可。

2.set作为哈希表: 当没有限制数值的大小,就无法创建一个数组来做哈希表。此时就需要使用set来作为哈希表。c++中提供了如下三种可用的数据结构:set、multiset、unordered_set。其中set和multiset底层实现是红黑树,unordered_set的底层实现是哈希。

3.map作为哈希表:

set和数组来做哈希法还是存在一些局限:数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费;set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x和y的下标。所以set也不能用。

此时可能就需要使用map来作为哈希表,map是一种<key,value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值