一.和为s的两个数字:
1.题目描述:
这个题目就是找出两个数,这两个数的和是目标值,找到其中一对就可以返回了。
2.算法原理:
方法一:
暴力枚举的策略:
就是两层for循环,固定一个数,然后另一个数去遍历整个数组,去加加然后找出是否有值加
起来两数之和是目标值的。
伪代码:
for(i=0;i<n;i++){
for(j=i+1;j<n;j++){
check(nums[i]+nums[j]==t);
}
}
这个暴力枚举的方法的时间复杂度就是O(n^2).所以去编写代码这个时间复杂度肯定是过不了的。
方法二:
任何题目第一个想到的方法就是暴力枚举的方法,但是这个题目还有一个注意的点是,这个数
组是递增的顺序,所以我们还可以优化这个暴力枚举。在昨天我们利用了数组的单调性解决了一个
问题,这个题目也可以利用这个单调性来解决。
这里我们来举个例子:
就利用这两个双指针,然后分为三种情况,和大于目标值,和小于目标值,和等于目标值。
看图中这种情况left所指的数加上right所指的数,加起来小于目标值,而中间这些数都是比right所
指的数小的,所以2不需要和这些数相加,因为肯定会小于目标数的,所以此时left此时指的数,不
会是最终结果,那么left++就行,再换一个区域去找即可:
此时left加加,最后到7,加起来和还是小于目标值,所以left继续移动:
此时加起来和就超过了目标值,那么right所指的数就不对了,那么就right--,往前找小的数,让和
变小:
此时我们发现加起来的和正好为目标值,那么此时就找到了,就可以直接返回了。
这种算法原理,我们判断一次就可以干掉好多种情况,少掉好多次判断,时间复杂度就大大缩减,
这种时间复杂度就变为O(n)。
3.代码展示:
class Solution {
public:
vectot<int>towSum(vector<int>& nums,int target){
int left=0,right=nums.size()-1;
while(left<right){
int sum=nums[left]+nums[right];
if(sum>t){
right--;
}else if(sum<t){
left++;
}
else{
return {nums[left],nums[right]};
}
}
//如果编译器此时没有找到就也需要一个返回路径,所以随便返回两个数即可
return {-1,-1};
}
};
二.15. 三数之和 - 力扣(LeetCode)
1.题目描述:
这里题目的意思就是三个数的下标不一样,并且加起来要等于0那么这一组就叫三元组。
这里我们通过题目给的例子来分析一下题目的意思:
在题目给的例子,我们自己列出来了三种情况,但题目只返回了两种,因为我们列的第一种和第三
种虽然顺序不一样,但是因为里面的内容一样的,所以最后要舍去,所以接下来写代码也要考虑
到这一点。
2.算法原理:
方法一:暴力解法
就是用三层循环来找加起来的和为0的数。
但是找很简单,我们还有一个重要的步骤就是去重,那么如何去去重呢,一开始想的是,把
和加起来为0的三元组里面的元素都排个序,然后再去看看,哪个重了,但是这样就更加麻烦了,
所以这里我们想到直接上来就给这个无序的数组先排序,然后再来暴力枚举可能会好一点。
例如:
这样我们看,这样排完序后再去找,直接就是一模一样的,不需要再去每一个三元组都去排序那么
麻烦,我们在C++学过set容器可以去重,直接放进去就可以。这样时间复杂度就是O(n^3),因为
只有这三重循环有时间复杂度,排序和去重都有相对于的函数和容器去弄,就相当于是一个常数的
时间复杂度。
方法二:
方法二也就是在暴力枚举的基础上去进行优化,减少时间复杂度。这里我们在暴力解法中,
已经讲数组排成有序的,那么既然是有序的,我们就想到了利用双指针算法,就去利用单调性去
来找。在和为s这道题我们是利用双指针解决的,这么是三个数相加和为0,那么我们也可以利用双
指针算法去解决这个问题。我们来看一个例子:
我们在暴力枚举里面是先固定一个数然后去找,那么在这里我们也先固定一个数,去找:
那么既然这样我们就固定第一个数去找,那么问题就转变成了在我画的这个区间去找两个数和为4
的数,就转变成了和为s的两个数字的那个题目的解法。正好-4+4=0.那么此时再利用双指针去找,
这里去找还有个可以优化,如果固定的数大于0,那么找的两数之和肯定不会是负数,因为是从小
到大的顺序排列的数组
在处理细节上不漏掉能构成的数组的情况比较好解决,那么就是在和为s的两个数字的题目中,我
们是找到了就返回,那么现在我们找到了还不能返回,要让left和right同时减减,这样才能做到情
况不漏。所以找到一种结果不能立马返回,不能停,两边都缩小区间去继续找。
既然不漏掉情况这个细节已经处理完毕了,去重怎么办呢,因为我们已经排好序了:
比如我已经在里面找到了0,4两个数据,因为已经排好序了,所以相等的值就在后面,如果后面的
值相等,我们就++,--过去,不考虑重复的数。既然里面去重了,外面固定的数也要去重。这样才
能完美去重。例如第一个-4枚举完了,完美就不枚举下一个-4,就直接跳过去。
当我们处理完这两个细节,我们还要避免越界,因为如果所有元素都相等,一直跳,跳出界就也会
报错
当这两个细节处理好,那么这个题目就可以完美得到解决,最重要的就是这两个细节的处理。
3.代码展示:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ret;
//排序:
sort(nums.begin(),nums.end());
//利用双指针解决问题
int n=nums.size();
for(int i=0;i<n;)//固定一个数
{
//如果固定的数大于0,那么找的两数之和肯定不会是负数,因为是从小到大的顺序排列的数组
if(nums[i]>0){
break;
}
int left=i+1,right=n-1,target=-nums[i];
while(left<right){
if(nums[left]+nums[right]>target){
right--;
}else if(nums[left]+nums[right]<target){
left++;
}
else{
ret.push_back({nums[i],nums[left],nums[right]});
left++,right--;//不漏的细节处理
//区间里的去重操作 left<right是防止越界访问
while(left<right&&nums[left]==nums[left-1]) left++;
while(left<right&&nums[right]==nums[right+1]) right--;
}
}
//去重 固定的那个数
i++;
while(i<n&&nums[i]==nums[i-1]) i++;
}
return ret;
}
};
这个代码有非常多的细节需要去注意,所有建议多看这个解题过程。