算法训练 day07 | 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和

454.四数相加II

题目链接:四数相加

视频讲解:学透hash表,map使用有技巧!

        为了减少时间复杂度,四个数组可以分两部分遍历。

方法

1、先定义一个unordered_map

2、在A、B、C、D四个数组中先遍历A、B数组,求A、B两个中元素的和a + b。用map的key存和a + b,value存两数之和出现的次数;

3、定义一个count,记录满足a + b + c + d =0的次数;

4、一起遍历C、D数组,求和c + d,从map中找到满足0 - (c + d)的key,读取key对应的value即是满足条件的个数,把它加到count中;

5、最后返回统计值count即可;

// 时间复杂度: O(n^2)
// 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> m;
        for (auto n1 : nums1)
        {
            for (auto n2 : nums2)
            {
                m[n1 + n2]++; // 求和的值存入key中
            }
        }
        int count = 0; // 统计a+b+c+d=0出现的次数
        for (auto n3 : nums3)
        {
            for (auto n4 : nums4)
            {
                int t = 0 - (n3 + n4);
                if (m.find(t) != m.end()) // 找到map中满足0-(c+d)的key所对应的value
                {
                    count += m[t];
                }
            }
        }
        return count;
    }
};

383. 赎金信

题目链接:赎金信

        这题与242有效字母异位词很像,可以采用数组的方法。但用数组保存哪个字符串的内容需要确定好。

方法

1、先判断两个字符串的长度,如果被表示的更长,直接返回false;

2、用数组r[] 存储26个字符,先把magazine中的字符存入数组中,用下标表示,重复的字符下标所对应的值++;

3、遍历ransomNote,若在r[] 中找到ransomNote中的字符,该字符下标所对应的值--;

4、判断r[] 中每个字符数如果小于0,表示ransomNote中有字符而在magazine中没有,返回false,否则返回true;

// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int r[26] = {0};
        if (ransomNote.size() > magazine.size())
        {
            return false;
        }
        for (int i = 0; i < magazine.size(); ++i)
        {
            r[magazine[i] - 'a']++; // 记录字母在magazine中出现的次数
        }
        for (int i = 0; i < ransomNote.size(); ++i)
        {
            r[ransomNote[i] - 'a']--;
            if (r[ransomNote[i]- 'a'] < 0)
            {
                return false;
            }
        }
        return true;
    }
};

15. 三数之和

题目链接:三数之和

视频讲解:梦破碎的地方!

        此题不适合用哈希法,采用双指针法会更高效些,更容易去重。

方法

1、定义一个二元数组结果;

2、先将数组有小到大排序,然后用for循环i从0遍历排序后的数组。同时定义一个left表示数组下标为i+1的位置,right表示数组下标最后一个位置;

3、接下来移动left和right。用a,b,c表示数组下标为i,left,right所对应的值,如果a+b+c>0则表示三数之和大了,需要减小。此时排序的作用就体现出来了,只需将right左做移动,三数之和就会变小。同理,若a+b+c<0则表示三数之和小了,就让left向右移动。当left与right相遇结束循环。

去重

主要是对a,b,c去重。首先a是遍历数组得到的,先对a去重。如果a重复了,应该直接跳过去,但有两种办法判断两个相邻的元素是否相等:nums[i] = nums[i + 1] 和 nums[i] = nums[i -1],那么这两个是否能达到同样的效果,我们用哪个好呢?

如果用第一种判断

if (nums[i] == nums[i + 1]) { // 去重操作
    continue;
}

那么就会把三元组中出现重复元素的情况忽略掉。如{-1, -1, 2},当遍历到第一个-1时,第二个也是-1,那么这组数组就会pass,进行下一轮循环。

本题需要的是不重复的三元组,不是三元组内不能有重复的元素

所以应该用第二种判断

if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}

这样,当我们遍历到nums[i]时,判断的是nums[i]前面的元素。如{-1, -1, 2},当遍历到第一个-1时,比较的是-1前面,第二个-1就不会忽略了。

对于b,c的去重,是要在找到满条件的三元组后。

// 时间复杂度: O(n^2)
// 空间复杂度: O(1)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res; // 二元数组,存储三元组
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); ++i) // 遍历数组,找到满足条件的三元组
        {
            if (nums[i] > 0) // 排序后,第一个元素大于0,不可能满足三个元素之和为0
            {
                return res;
            }
            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
                {
                    res.push_back(vector<int>{nums[i], nums[left], nums[right]}); // 满足条件的放入res中
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++; // 对第二、三个元素去重,一定要满足三数之和为0才进行去重

                    right--;
                    left++; // 找到答案后,两个指针同时收缩
                }
            }
        }
        return res;
    }
};

18. 四数之和

题目链接:四数之和

视频讲解:难在去重和剪枝

        跟三数之和类似,也需要用到双指针法,不过四数之和比三数之和多一个循环。

方法

在三数之和的基础上外面再加一层for循环,对于双指针left和right不变,两层for循环nums[i] + nums[j] 是确定值 ,然后移动left和right找到nums[i] + nums[j] + nums[left] +nums[right] = target。

同理,五数之和、六数之和等都用这种方法。

// 时间复杂度: O(n^3)
// 空间复杂度: O(1)
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++)
        {
            if (nums[i] > target && nums[i] >= 0) // 剪枝,不满足条件的
            {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) // 第一个元素去重
            {
                continue;
            }
            for (int j = i + 1; j < nums.size(); j++)
            {
                if (nums[j] + nums[i] >= 0 && nums[j] + nums[i] > target) // 第二重剪枝
                {
                    break;
                }
                if (j > i + 1 && nums[j] == nums[j - 1]) // 第二个元素出重
                {
                    continue;
                }
                
                int left = j + 1;
                int right = nums.size() - 1;
                while (right > left)
                {
                    if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target)
                    {
                        right--;
                    }
                    else if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target)
                    {
                        left++;
                    }
                    else
                    {
                        res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
                        while (right > left && nums[left] == nums[left + 1]) left++;
                        while (right > left && nums[right] == nums[right - 1]) right--; // 第二、三个元素去重

                        left++;
                        right--;
                    }
                }
            }
        }
        return res;
    }
};
  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值