首页 > 其他分享 >随想录Day7|454. 四数相加Ⅱ、383. 赎金信、15. 三数之和、18. 四数之和

随想录Day7|454. 四数相加Ⅱ、383. 赎金信、15. 三数之和、18. 四数之和

时间:2023-09-26 12:11:38浏览次数:35  
标签:四数 15 nums int 复杂度 随想录 ++ vector sum

随想录Day7|454. 四数相加Ⅱ、383. 赎金信、15. 三数之和、18. 四数之和

 

454. 四数相加Ⅱ

文章&视频讲解

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

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

提示:

  • 1 <= n <= 200
  • -228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228

 

第一想法

如果用map存“值:下标”,可是总共有四个数,不能和昨天一样target - num这样找到两个数中的另一个了。如果暴力三重循环而用0减去前三个数得到最后一个数的值,时间复杂度就有\(O(n^3)\),显然也不对。而且要下标有什么用呢?显然也不太合理。昨天那道“两数之和”是要求下标的,但是这里只要求计数。

四个数组长度都一样且不超过200。长度一样,一定是一个会用到的性质……

 

看了随想录题解的想法

化归的思想,最终简化成类似两数之和!把四个数组分两组处理,遍历用map的key记录a+b的值和出现的次数,然后遍历后两个数组,看看0 - (c + d)是否在map中出现过,如果是,则对应的次数叠加到count中。之前两数之和是要下标,所以map的second记录下标,这里要次数,所以记录次数,非常合理。而且记录次数的思想又在“有效字母的异位词”中用到过。

 

复杂度分析

时间复杂度:\(O(n^2)\),因为两层循环是\(O(n^2)\)。

空间复杂度:\(O(n^2)\),因为最坏情况下每个a + b都不相同,需要记录\(n^2\)个key。

 

用时24min~

int fourSumCount(vector<int> &nums1, vector<int> &nums2, vector<int> &nums3, vector<int> &nums4)
{
    int count = 0;
    unordered_map<int, int> map;
    for (int a : nums1)
    {
        for (int b : nums2)
            map[a + b]++;
    }
    for (int c : nums3)
    {
        for (int d : nums4)
        {
            if (map.find(-c - d) != map.end())
                count += map[-c - d];
        }
    }
    return count;
}

 

383. 赎金信

文章讲解

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

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

magazine 中的每个字符只能在 ransomNote 中使用一次。

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNotemagazine 由小写英文字母组成

 

第一想法

先扫描magazine,只有小写英文字母所以用数组即可,先记录每个字母出现次数,然后扫描ransomNote时次数自减(用掉一个),最后如果数组每个数都非负就是true,一旦出现负数就返回false

 

复杂度分析

时间复杂度:\(O(n)\)

空间复杂度:\(O(1)\)

 

用时6min~

bool canConstruct(string ransomNote, string magazine)
{
    int freq[26] = {0};
    for (int i = 0; i < magazine.length(); i++)
        freq[magazine[i] - 'a']++;
    for (int i = 0; i < ransomNote.length(); i++)
        freq[ransomNote[i] - 'a']--;
    for (int i = 0; i < 26; i++)
    {
        if (freq[i] < 0)
            return false;
    }
    return true;
}

 

15. 三数之和

文章&视频讲解

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

 

第一想法

和四数相加不一样,这里需要考虑去重的因素。而且这里要的是元组本身,需要返回的每个三元组加起来都是0。则直接用之前一样存和的方法就会丢失信息。而且卡尔在提示里面也说哈希法会很麻烦,正解是双指针法。

也就是这两个指针以\(O(n^2)\)的方式遍历,每次找是否有一个不一样的值使得三数之和为0?找一个元素是否存在,应该联想到哈希,但是如果找到重复的?因为find有结果,那个结果也可能就是指针已经指着的两个数之一。

 

看了随想录题解后的想法

首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i + 1的位置上,定义下标right 在数组结尾的位置上。

依然还是在数组中找到 a b c 使得a + b + c =0,我们这里相当于 a = nums[i]b = nums[left]c = nums[right]

接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明此时三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

已经说得很清楚了!而且其实这么说应该是三指针了。

之所以要跳过和上一轮同样的leftright,是因为只有三个数,因此在内层循环中,这两个数只要有一个和上一轮一样,另一个也一定和上一轮一样,则造成了重复的三元组。

 

遇到的问题

这是第一次用LeetCode插件debug,主要还是在去重这一步,这三个数中但凡遇到了和上一轮循环一样的数字,都要一直向后/向前跳过,直到达到新的值。其中lr的移动应该让r先左移,因为一定有一个l > 0给它兜底,但是如果l往右,可能会超出范围。

以输入[0,0,0,0]为例,如果l = 1r = 3,则l的右移会一路移到3,r不动,然后在l++这一步直接超出数组范围。所以需要先移动r,以此约束l

 

复杂度分析

时间复杂度:\(O(n^2)\)

空间复杂度:\(O(1)\)

 

用时40min~

vector<vector<int>> threeSum(vector<int> &nums)
{
    vector<vector<int>> result;
    sort(nums.begin(), nums.end());
    int len = nums.size();
    // 先排除特殊情况,即全正或全负
    if (nums[0] > 0 || nums[len - 1] < 0)
        return result;
    for (int i = 0; i < len - 2; i++)
    {
        // 我们要在nums[i]右边找两个数,如果已经>0,则不可能三数和为0
        if (nums[i] > 0)
            return result;
        // 对nums[i]也要注意不要和上一轮走到同一个值
        if (i > 0 && nums[i] == nums[i - 1])
            continue;

        int l = i + 1;
        int r = len - 1;
        int sum = nums[i] + nums[l] + nums[r];
        while (l < r)
        {
            if (sum == 0)
            {
                result.push_back(vector<int>{nums[i], nums[l], nums[r]});
                // 让left和right同时向内,走到与刚刚不同的值
                while (l < r && nums[r] == nums[r - 1])
                    r--; // 此处必须r先走,否则l可能走到超出上限
                while (l < r && nums[l] == nums[l + 1])
                    l++;
                l++;
                r--; // 此时它们都抵达了下一个不一样的值的下标
            }
            else if (sum > 0)
                r--;
            else
                l++;
            sum = nums[i] + nums[l] + nums[r];
        }
    }
    return result;
}

 

18. 四数之和

文章&视频讲解

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

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

你可以按 任意顺序 返回答案 。

提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

 

第一想法

比起四数相加,这里多了去重,也许这里需要四个指针?先排序,再遍历,里层的循环只看外层指针右侧的元素即可,就是比三数之和多了一层循环。

 

遇到的问题

wrong answer,大部分都过了,不过对于这个样例:

[-2,-1,-1,1,1,2,2]
0
Answer			Expected Answer
[[-2,-1,1,2]]	[[-2,-1,1,2],[-1,-1,1,1]]

我没有统计上后一个,但是在我完善了剪枝判断之后,这个错误就解决了。在第二层循环关于j的判断中,我错误地写成了j > 1,但是应该是j > i + 1。因为是允许第二个数字和第一个数字值重复的,所以第二个数字的去重只在自己可以变换的范围比较,即第一个数字的右侧。

还有就是四个数相加可能溢出int的范围,因此改用long long

 

复杂度分析

时间复杂度:\(O(n^3)\)

空间复杂度:\(O(1)\)

 

用时37min~

vector<vector<int>> fourSum(vector<int> &nums, int target)
{
    vector<vector<int>> result;
    sort(nums.begin(), nums.end());
    int len = nums.size();

    for (int i = 0; i < len - 3; i++)
    {
        if (nums[i] >= 0 && nums[i] > target)
            break;

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

        for (int j = i + 1; j < len - 2; j++)
        {
            long long sum_ij = nums[i] + nums[j];
            if (sum_ij >= 0 && sum_ij > target)
                break;

            if (j > i + 1 && nums[j] == nums[j - 1])
                continue;

            int l = j + 1;
            int r = len - 1;
            long long sum_all = sum_ij + nums[l] + nums[r];
            while (l < r)
            {
                if (sum_all == target)
                {
                    result.push_back(vector<int>{nums[i], nums[j], nums[l], nums[r]});
                    while (l < r && nums[r] == nums[r - 1])
                        r--;
                    while (l < r && nums[l] == nums[l + 1])
                        l++;
                    l++;
                    r--;
                }
                else if (sum_all < target)
                    l++;
                else
                    r--;
                sum_all = sum_ij + nums[l] + nums[r];
            }
        }
    }
    return result;
}

 

碎碎念

今天也是上午写完,难熬的数据库有了完美的度过方法,真好。今天学到了哈希法和双指针法,真的很巧妙。

标签:四数,15,nums,int,复杂度,随想录,++,vector,sum
From: https://www.cnblogs.com/alouette/p/17729801.html

相关文章

  • Luogu9157「GLR-R4」夏至
    抢到最优解了,UOJ校验码上80pts过不去。/kk这里是官方题解的简化。首先考虑\(n=1\)怎么做,相当于对\(m\le10^{10}\)筛出\(f\)的前缀和。由于\(f(p)=p\),直接构造函数\(g(n)=n\)然后PN筛\(O(\sqrtm)\)求即可。然后考虑\(n>1\),由于\(n\)比较小,考虑对每一个\(i......
  • 5157
    https://www.acwing.com/problem/content/5157/利用贡献思想入门的一道题,对于看起来复杂的问题,我们去考虑每一个元素在每一轮中的贡献,如果这道题不理解了可以去看视频讲解,里面说的非常明晰。在本题实现过程中需要找到找到数组中的最大数,并且统计有几个同时最大的数,我的实现非常......
  • Educational Codeforces Round 155 D (CF1879_D)
    题目大意给一个长度为\(n\)的数组,求\(\Sigma_{i=1}^{n}\Sigma_{j=i}^{n}区间异或和\times(j-i+1)\)其中\(n\leq3e5,~a[i]\leq1e9\)分析首先注意到由\(l\)到\(r\)的区间异或和可以转化为\(sum_{l-1}~XOR~sum_r\)因此,对于每一个点\(x\),无论它作为上述的\(sum......
  • 随想录Day5|242. 有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和
    随想录Day5|242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和 242.有效的字母异位词文章&视频讲解给定两个字符串s和t,编写一个函数来判断t是否是s的字母异位词。注意:若s和t中每个字符出现的次数都相同,则称s和t互为字母异位词。1......
  • echarts 150种图标path
    echarts150种图标pathleticonArray=['circle',//实心圆'rect',//矩形'roundRect',//圆角矩形'triangle',//三角形'diamond',//菱形'pin',//'arrow',//箭头'path......
  • P3128 [USACO15DEC] Max Flow P
    P3128[USACO15DEC]MaxFlowP有好几种解决方法,这里讲第一种树状数组主要是线段树没调好区间修改,单点查询,很明显我们可以用树状数组,简单又方便树状数组#include<bits/stdc++.h>usingnamespacestd;constintN=5e4+10;intn;intread(){//快读 charc=getcha......
  • Educational Codeforces Round 155 (Rated for Div. 2)
    EducationalCodeforcesRound155(RatedforDiv.2)A.Rigged!解题思路:若存在\(s[i]>=s[1]\)并且\(e[i]>=e[i]\),那么答案为\(-1\).否则,答案为\(s[1]\).代码:#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;constintN=1e6+10;con......
  • 15,面向对象原型继承
    子类对象名.__proto__=父类对象名;varuse={name:'岳不群',age:123,ff:function(){console.log(this.name+'在跑步');}};varniao={fei:function(){console.log(this.name+'在飞');}};varliyu......
  • CF_EduRound155小丑寄
    一句话总结:A题理解错了,数据又水,所以寄了。过程:22:35开题。22:40怎么还没加载出来??急急急22:42哦,严格大于,但是主宾对调了,乐乐乐乐乐乐乐,cout<<ans;\(\rightarrow\)cout<<ans-1;22:45一发过。。。22:45-23:38啥事没有。23:38开E题23:50左右好像有点思路......
  • 《架构即未来》中最常用的15个架构原则
    《架构即未来》这本书的第12章简单阐述了架构设计的一些常用的原则(后面章节会详细阐述)。这些原则中很多都是在架构一开始的设计中就要考虑进去的,这样在出现任何问题时,我们都能够及时的处理,和把问题影响的范围有效的缩小。否则就像我现在的项目,一开始设计时,考虑的很少,出问题时,没有做......