算法力扣刷题记录 十九【15. 三数之和】

前言

哈希篇,继续。

记录 十九【15. 三数之和】


一、题目阅读

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

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。	//这两行虽然i,j,k不一样,但是3个元素是:-1,-1,0,如果都输出,认为[-1,-1,0]重复
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

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

二、尝试实现

思路

(陷入哈希法 使用,方法单一)

(1)从提示:数组长度不算小,每个元素最大为105,求和之后,值也不小。从两数之和那道题思路,第一步,确定使用哈希法。(这是一种思路陷井,没有反应出来别的方法(参考所学的双指针法))

  • 确定哈希法,接下来选择哈希结构。

(2)刚刚完成"四数相加"的题目,所以考虑求a+b作为key,对应元素对作为value,使用map结构。然后遍历nums,找到c的相反数。

  • 因为不是找下标索引,也不是计数,题目还不让重复。value的结构用二维数组?但是push_back还要判断重复。很复杂,value是二维数组行不通
  • 不想让元素对重复,用一个大的unordered_map,map中每个类型是vector< int >,可是[1,0]和[0,1]算不同的vector,都能放到unordered_map中,也没有做到不重复。行不通
  • 换一种key-value思路,看(3)。

(3)先把整个nums重复的元素去掉,但是重复元素可以求和,所以得记录重复次数。所以用一个map结构(还没有说顺序),key是nums元素,value是元素出现次数。key不能重复,所以排除multimap。那么用unordered_map还是map?继续(4)分析。

(4)怎么在一个map中找到 三个元素和为0?肯定得遍历。

  • 如果用unorder_map无序,无法知道下一个元素是什么?下一个元素是变大?还是变小?很杂乱。所以确定结构是map:有序(元素从小到大排过,能知道下一个元素一定是增大的),不重复。定义:map<int,int> nums_nondup;
  • 解决遍历:
    • 最外层是i,在nums_nondup中元素从小到大依次往后;
    • 第二层是j:j从i的下一个开始吗?(不完全是)
      • 当元素只出现一次,j从i的下一个开始。
      • 当元素只出现多次,j从i开始。因为重复元素得有个和。示例一中:-1重复,和为-2时有[-1,-1]。
      • 所以j指向的nums一定 大于/等于 i指向的nums
    • 第三层是it指针,也就是第三个元素。
      • 得找it = nums_nondup.find(0- i->first - j->first);

      • 如果it == nums.end(),肯定下一轮;

      • 当it != nums.end(),找到元素(还要去重):

          1.如果it指向的元素 > j指向的元素,肯定没重复,因为没遍历到那里。可以push到返回值中;
          2.在it指向的元素 <= j指向的元素下:
          		A.如果(it指向的元素 = j指向的元素)&& (i指向的nums != j指向的nums):得看j元素有没有多出现?至少出现2次,才能nums[j] = nums[k];可以push到返回值中;
          		B.如果(it指向的元素 = j指向的元素)&& (i指向的nums == j指向的nums):如示例3,得看元素是不是至少出现3次。才能nums[i]=nums[j] = nums[k];可以push到返回值中;
          		C.剩下情况:不可以push因为已经遍历过。
        

所以如何不重复是一个难点。判断可不可以push到返回值中,也有情况讨论。

代码实现(测试通过)

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        map<int,int> nums_nondup;//元素值作为key,出现次数作为value,按顺序
        map<int,int>::iterator j;
        //去重,记录出现的次数。
        for(int x: nums){
            nums_nondup[x]++;
        }
        
        //开始遍历,i指针是最外层循环
        for(auto i = nums_nondup.begin();i != nums_nondup.end();++i){
            if(i->second > 1){ //出现多次
                j = i;
            }else{
                j = next(i,1);
            }
			//第二层,nums[j]
            for(j;j != nums_nondup.end();++j){  
                auto it = nums_nondup.find(0- i->first - j->first);	//找nums[k]
                if(it != nums_nondup.end()){	//能不能push_back,也要分情况判断
                    if((it->first > j->first)){
                        result.push_back({i->first,j->first,it->first});
                    }else if(it == j && j != i && it->second >= 2){
                        result.push_back({i->first,j->first,it->first});
                    }else if(j == i && it == j && it->second >= 3){
                        result.push_back({i->first,j->first,it->first});
                    }
                }
            }
            
        }
        return result;
    }

};

       

弯路

(1)因为"四数相加"的题目,是把nums1和nums2的和作为key,对应下标对;在思路(3)没有nums去重之前的思路,在去重之后,还想尝试。可是仍然有重复。

  • 解释:以示例一nums = [-1,0,1,2,-1,-4],

      思路(3)之后得到nums_nondup内的元素:<-4,1>、<-1,2>、<0,1>、<1,1>、<2,1>。
      用另一个unordered_map<int,vector<vector<int>>>  sum;中key是不重复元素之和,value对应元素对。(每个key对应的元素对不可能重复,因为nums_nondup去重过)
      在sum结构中:
      	和为0:对应元素对有[-1,1];
      	和为1:对应元素对有[0,1];
      当在nums_nondup遍历nums[k]第三个元素时:遍历到0,找到和为0,0加入到[-1,1]中。遍历到-1,找到和为1,-1加入到[0,1]。如果push到返回数组中,仍然得到重复三元组。(因为输出顺序不重要,[-1,1,0]和[0,1,-1]认为是一样的。)
    

总结:这一步不成功,所以回归到思路(4)直接遍历。


三、代码随想录学习

学习内容

双指针法

回归题目,传的参数一个数组nums,三个元素来源于同一个数组。也就是在一个数组中找、进行操作;数组操作方法在数组篇学到双指针法

双指针法:组成一个区间,逐步移动区间大小或者位置。

双指针思路

  • 最外层肯定是nums遍历,从第一个到最后一个。需要排序:从小到大。有了顺序,才知道求和比0大,指针怎么移;比0小指针怎么移。
  • 内层j和k,分别是j = i+1;k = nums.size()-1;如果nums[i] + nums[j] + nums[k] > 0,k - -;如果nums[i] + nums[j] + nums[k] < 0,j++;
  • 如何去重?i,j,k都得去掉重复的值。
    • i去重:nums[i] == nums[i-1],当前i和前一个比。
    • j去重:num[j] == nums[j-1],当前j和前一个比。
    • k去重:num[k] == nums[k+1],当前k和后一个比。

代码实现

cpp 复制代码
//根据新思路再次实现
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> result;

        for(int i = 0;i < nums.size();i++){
            if(i > 0&& nums[i] == nums[i-1]){
                continue;
            }

            int j = i+1;
            int k = nums.size()-1;
            while(j < k){
                if(nums[i] +nums[j] +nums[k] > 0){
                    k--;
                }else if(nums[i] +nums[j] +nums[k] < 0){
                    j++;
                }else{
                    result.push_back({nums[i],nums[j],nums[k]});    //放的是元素值
                    j++;	//先移动,再去重。
                    k--;
                    while(j < k && nums[k] == nums[k+1]){
                        k--;
                    }
                    while(j < k && nums[j] == nums[j-1]){
                        j++;
                    }
                }
            }   
        }
        return result;
    }
};

实现区别:

else部分,是先移动还是先去重?
如果是先移动,k和后一个元素比;j和前一个元素比。(个人实现)
如果是先去重,k和前一个元素比,直到前一个和当前不一样,指向重复元素的第一个;j和后一个元素比,直到后一个和当前不一样,指向重复元素的最后一个。再同时k--;j++。(参考)

四、总结

(1)思路应该打开,只要是学过的方法,都可以尝试。可能相近题目"两数之和"、"四数之和"、"三叔之和"背后不是一种方法。

  • "两数之和":哈希法,返回下标索引;结构unordered_map.
  • "四数之和":哈希法,返回数量;结构unordered_map.
  • "三数之和":双指针法优先;哈希法也可以。

(欢迎指正,转载表明出处)

相关推荐
lxyzcm9 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿28 分钟前
C/C++基础错题归纳
c++
古希腊掌管学习的神30 分钟前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
qystca31 分钟前
洛谷 P1706 全排列问题 C语言
算法
古希腊掌管学习的神36 分钟前
[LeetCode-Python版]相向双指针——611. 有效三角形的个数
开发语言·python·leetcode
浊酒南街37 分钟前
决策树(理论知识1)
算法·决策树·机器学习
雨中rain43 分钟前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
就爱学编程1 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
ProcessOn官方账号1 小时前
如何绘制网络拓扑图?附详细分类解说和用户案例!
网络·职场和发展·流程图·拓扑学
学术头条1 小时前
清华、智谱团队:探索 RLHF 的 scaling laws
人工智能·深度学习·算法·机器学习·语言模型·计算语言学