代码随想录算法训练营第七天|454.四数相加II 383. 赎金信 15. 三数之和18. 四数之和 总结

454.四数相加II

题目链接 LC.454

给你四个整数数组 nums1、nums2、nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

在这里插入图片描述

思路

  1. 观察题目要求,要我们从四个数组中各取一个元素,使得相加为0 ,当然通过暴力法也可以解出来,但是时间复杂度会到4次方的级别。
  2. 我们可以两两遍历,通过unordered_map来存放两个数组元素相加的值以及出现的次数。选择unordered_map是因为他的key无序,底层实现是哈希表,查询和修改效率最高。
  3. 在统计输出的时候,我们不能只是单纯的加1,而是把map中对应值出现的次数加到count上。(这点很重要)
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        // 查找元素在某个集合里是否出现,选择使用哈希法
        // 考虑到记录的不仅有值,还有出现过的次数,所以选map
        // 之所以两两遍历,是因为要使得时间复杂度在大On平方
        unordered_map<int, int> k;
        for (int a :nums1) {
            for (int b : nums2) {
                k[a+b]++;
            }
        }
        int count = 0;
        for (int c : nums3) {
            for (int d : nums4) {
                if (k.find(0-(c+d)) != k.end()) {
                    //这一步很重要,不是单纯的+1,而是要统计a+b出现的次数
                    count += k[0-(c+d)];
                }
            }
        }
        return count;
    }
};

总结

  1. 而这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况
  2. count += k[0-(c+d)]; 这一步是关键 ,不能单纯的加1,而是要加上出现的次数。
  3. 之所以选择两两循环遍历,是为了节省时间,让时间复杂度降到平方级别。

383. 赎金信

题目链接 LC.383
给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能ransomNote使用一次
在这里插入图片描述

思路1(暴力解法)

  1. 通过题目,是要ransomNote在magazine里面查找字符,并且每个字符只能出现一次。
  2. 我们可以在magazine里面遍历ransomNode,当他们字符一样的时候,删除ransomNote的该元素,最后判断长度是否为0,是返回true,否则false
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 (ransomNote[j] == magazine[i]) {
                    ransomNote.erase(ransomNote.begin() + j);
                    break; // 保证每个字母只能用一次
                }
            }
        }
        if (ransomNote.length() == 0) {
            return true;
        }
        return false;
    }
};

总结1

  1. 不能使用size()进行获取长度,因为当我们下面使用erase的时候,会出现问题。
  2. 因为每个字符只能使用一次,在删除后需要break出循环,从下一个字符开始。

思路2:哈希解法

  1. 根据思路1的过程,我们可以使用哈希法来解决
  2. 首先在哈希map或者哈希数组中,记录magazine出现的字符和出现的次数
  3. 再遍历ransom中出现的字符,并使得哈希对应的字符value减一,当出现负数的value值的时候,return false
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int hash[26] = {0};

        for (int i = 0; i < magazine.size(); i++) {
            hash[magazine[i] - 'a']++;
        }

        for (int j = 0; j < ransomNote.size(); j++) {
            hash[ransomNote[j] - 'a']--;
            if (hash[ransomNote[j] - 'a'] < 0) {
                return false;
            }
        }
        return true;
    }
};
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> hash;

        for (int i = 0; i < magazine.size(); i++) {
            hash[magazine[i]]++;
        }

        for (int j = 0; j < ransomNote.size(); j++) {
            hash[ransomNote[j]]--;
            if (hash[ransomNote[j]] < 0) {
                return false;
            }
        }
        return true;
    }
};

总结2

  1. 这道题用数组和map都可以解决,如果只考虑出现小写字母,那么可以用数组来解决节约空间。
    使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
  2. 要灵活使用返回的判断条件,本题和力扣242有效字母异位词很相似,但是返回条件不同,不能按照哪个思路来做判断。

15. 三数之和

题目链接 LC.15

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
在这里插入图片描述

思路:(双指针法 + 去重)

  1. 题目要求从数组中取三个元素,使得相加后等于0,我们可以先对数组进行排序,在循环的时候,固定一个位置,移动两个指针,当两个指针和固定点加起来为0的时候满足条件。
  2. 双指针的设置,分别是固定点后面一个和数组最后一个。
  3. 去重操作很关键,要对 i, j, k分别进行去重。
  4. i 去重时,判断num[i]nums[i-1]的值
  5. j, k去重时,需要在获取一个结果后再进行判断,同时有也要满足 j < k这一条件。

具体代码如下

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ans;
        //1.排序
        sort(nums.begin(), nums.end());
        //剪枝 (为了代码节约时间空间)
        if (nums[0] > 0) return {};

        for (int i = 0; i < nums.size(); i++) {
            // 对 i 这一指针进行去重
            if (i > 0 && nums[i] == nums[i-1]) {
                continue;
            }
            int j = i + 1;
            int k = nums.size() - 1;

            // j 和 k 不能相等,如果相同会指向同一个数值
            while (j < k) {
                if (nums[i] + nums[j] + nums[k] > 0) {
                    k--;
                }
                else if (nums[i] + nums[j] + nums[k] < 0) {
                    j++;
                }
                else {
                    ans.push_back({nums[i], nums[j], nums[k]});
                    // 对 j 和 k进行去重, 要保证 j < k 的前提
                    while (j < k && nums[j] == nums[j + 1]) j++;
                    while (j < k && nums[k] == nums[k - 1]) k--;
                    j++;
                    k--;
                }

            }
        }
        return ans;
    }
};

总结: 如何进行去重操作是本题的关键,要牢记

  1. i 去重的时候为什么是和 i - 1,而不是i + 1,当进入循环后,如果和i+1进行比较了,那么就会导致和left进行了比较,造成错误。
  2. 去重的时候多考虑条件,有可能是好几个都满足才判断。

18. 四数之和

题目链接 lc,18四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。
在这里插入图片描述

思路

  1. 在三数之和的基础上进行添加一层循环,同时做剪枝去重操作
  2. 剪枝的时候不能仅判断nums[k] > target,因为target是输入的,有可能为负数。比如:数组是[-4, -3, -2, -1],target是-10,不能因为-4 > -10而跳过。
  3. 在二级剪枝的时候,要把前两个数的和看作一个整体进行判断。
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ans;
        //排序
        sort(nums.begin(), nums.end());

        //遍历
        for (int a = 0; a < nums.size(); a++) {
            //一级剪枝
            if (nums[a] > target && nums[a] >= 0) {
                break;
            }
            // 一级去重
            if (a > 0 && nums[a] == nums[a - 1]) {
                continue;
            }

            //第二个指针
            for (int b = a + 1; b < nums.size(); b++) {
                //二级剪枝  把 nums[a] + nums[b]看作一个整体
                if (nums[a] + nums[b] > target && nums[a] + nums[b] >= 0) {
                    break;
                } 
                //二级去重
                if (b > a + 1 && nums[b] == nums[b-1]) {
                    continue;
                }

                int c = b + 1; // 第三个指针
                int d = nums.size() - 1;

                while (c < d) {
                    if ((long) nums[a] + nums[b] + nums[c] + nums[d] < target) {
                        c++;
                    }
                    else if ((long) nums[a] + nums[b] + nums[c] + nums[d] > target) {
                        d--;
                    }
                    else {
                        ans.push_back(vector<int>{nums[a], nums[b], nums[c], nums[d]});
                        while (c < d && nums[c] == nums[c+1]) c++;
                        while (c < d && nums[d] == nums[d-1]) d--;
                        c++;
                        d--;
                    }
                }
            }
        }
        return ans;
    }
};

总结

  1. nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出,所以使用
    (long) nums[a] + nums[b] + nums[c] + nums[d] > target来扩大。
  2. 四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。那么一样的道理,五数之和、六数之和等等都采用这种解法。
  3. 核心就是要学会三数之和的方法。

哈希表总结

  1. 一般来说哈希表都是用来快速判断一个元素是否出现集合里。对于哈希表,要知道哈希函数和哈希碰撞在哈希表中的作用.
  2. 哈希函数是把传入的key映射到符号表的索引上。哈希碰撞处理有多个key映射到相同索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。
  3. 接下来是常见的三种哈希结构:数组 set(集合) map(映射)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值