LeetCode 三数之和

本文深入探讨了解决三数之和问题的高效算法,通过排序和双指针技巧实现,避免重复解的同时,分享了代码优化的心得,如动态数组的合理使用,以及从该问题延伸出的两数之和的改进思路。

题目链接 三数之和
这次这个程序给自己更重要的体会是后面的那些心得,各位大佬也可以看看。

题目描述

找出三个数,要求这三个数的的和为0,并且输出所有的答案。

解题思路

利用两数之和的思想(但是我上次并没用想到这种方法,待会儿会补充),我们先将整个数组排序,排序后的数组为从小到大。
然后我们用一个for循环来挑选nums[i],现在我们就只要在nums[i]的右端找出剩下两个数。
这个时候我们对nums[i]的有段区间用双指针,指针l在区间左边,指针r在区间右边。
现在问题来了,我们要如何去移动 l 和 r 这两个指针,因为这个数组是有序性的,如果三个数的和sum大于0,说明挑选的nums[r]偏大,要挑一个小一点的数,所以r–,如果三个数的和sum小于0,说明nums[l偏小,要挑一个偏大一点的数,所以l++。
之后我们会想到,这样的话有重复的该怎么办。当nums[i]==nums[i+1]或者nums[l]==nums[l+1]或者nums[r]==nums[r-1],说明会重复,这时候我们i++,l++,r–。这样就解决了重复的问题。

程序代码

c++
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int> >ans;//用于存储答案
        int n=nums.size();
        if (n <= 2) return ans;//如果长度小于等于2,直接退出
	    sort(nums.begin(),nums.end());
	    //排序,排序后数组中为从小到大
	    for(int i=0;i<n-2;++i) {//i为开始确定的第一个数的指针
            if(nums[i] > 0) break;
            //如果nums[i]都大于0了,那就直接退出,因为后面的都大于0
		    int l=i+1;//挑选第二个数的指针
			int r=n-1;//挑选第三个数的指针
		    while(l<r) {
		    	int sum=nums[i]+nums[l]+nums[r];//计算三数之和
                int a=nums[i],b=nums[l],c=nums[r];		    
			    if(sum==0) {
			    	vector<int> temp{b,a,c};//将b,a,c放入temp中
			    	ans.push_back(temp);//再将temp放入ans中
			    	while( l<r && nums[l]==nums[l+1] )l++;
				    while( l<r && nums[r]==nums[r-1] )r--;
				    //上面两个语句是处理答案重复的情况
				    l++;
				    r--;
			    }
			    else if(sum<0)l++;
			    //如果sun小于0,说明目前挑选的第二个数小了,得偏大一点,于是l++
			    else if(sum>0)r--;
			    //同理,sum>0说明目前挑选的第三个数大了,要找个小一些的,于是r--
		    }
		    while (i + 1 < n-2 && nums[i] == nums[i + 1])i++;
		    //也是一个去重
	    }
        return ans;
    }
};
java

后续补上

心得

第一个心得:
vector temp{b,a,c};这句话因为是定义,所以放在if外面和if里面好像没啥区别,但是在实际运算中时间和空间会有很大的差别。我猜想的是因为vetocr是个动态数组,是c++中的关联容器,有可能多次定义,在运行时的内存没有及时消掉,就占用了大量的内存,自然时间就被拉长了。但为什么放在if里面就减少了呢,因为满足sum==0的可能不是很多,所以vector定义的次数就减少了很多很多,时间也降下来了。
修改前的版本

while(l<r) {
	int sum=nums[i]+nums[l]+nums[r];//计算三数之和
    int a=nums[i],b=nums[l],c=nums[r];	
    vector<int> temp{b,a,c};//将b,a,c放入temp中
	if(sum==0) {
		ans.push_back(temp);//再将temp放入ans中
		while( l<r && nums[l]==nums[l+1] )l++;
		while( l<r && nums[r]==nums[r-1] )r--;
		//上面两个语句是处理答案重复的情况
		l++;
		r--;
}

修改后的版本

while(l<r) {
	int sum=nums[i]+nums[l]+nums[r];//计算三数之和
    int a=nums[i],b=nums[l],c=nums[r];		    
	if(sum==0) {
		vector<int> temp{b,a,c};//将b,a,c放入temp中
		ans.push_back(temp);//再将temp放入ans中
		while( l<r && nums[l]==nums[l+1] )l++;
		while( l<r && nums[r]==nums[r-1] )r--;
		//上面两个语句是处理答案重复的情况
		l++;
		r--;
}

第二个心得

vector temp{b,a,c};这里我本来不是这样写的,是这样写的vector temp{nums[l],nums[i],nums[r]};因为在LeetCode中提交时nums是动态数组,不是普通的数组,所以我想应该不能直接那样写,得转换一下再写。

第三个心得

此题还有另一个想法

第四个心得

这道题温习到的知识点有vetcor一维的定义 二维的定义
一维动态数组 vector nums;
二维动态数组 vector<vector >ans;
还复习了c++中排序的写法
sort(nums+1,nums+n+1,cmp);
cmp为自己写的一个比较的函数
int cmp(int a,int b) {return a<b;}
sort(nums+1,nums+n+1);//以上两个当nums为数组时
sort(nums.begin(),nums.end());当nums为动态数组时

第五个心得

吸取到了两数之和更为简单的写法,到时候有空再去写一下两数之和。自己一开始的两数之和做法是nlogn的,现在可以像
那个做法一样,用两重循环来找第一个数和第二个数,然后用map来找第三个数。
至于去重的问题一开始我想的是把三个数按照字符串存在map中,然后每得到一个新的我们就判断一下,要是之前存在了就不放入ans中。
后面想到一个做法,就是按照上面的有序性,相同的就跳过,然后在第二个数的右端区间查找,这里就不能用map了,但是自己可以写一个二分查找树,然后去做查找,也是一个logn的查找时间。(有空了再写吧)
时间方面,理论上是nnlogn,鉴于上面的算法用时几十毫秒,所以这中算法应该能过。

第六个心得

有这个题,我想到了可以有一个新的两数之和的方法,复杂度nlogn(主要是因为排序要nlogn),后面的做法还是o(n)。我们排序的时候用结构体,一个存储数值,一个存储数据本身的位置。然后再利用上面三数之和的思路,去找所有的答案。(原问题两数之和只要找一个,现在可以找很多个,还可输出位置)
在c++整个程序中我知道如何写,因为可以自己写函数,但是在LeetCode上不知道如何在函数中调用自己写的另一个函数。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值