题目位置:https://leetcode-cn.com/problems/two-sum/
刚刚接触算法的人一般看到这个题目就想到可以二次循环来莽穿,这样其实可以,我试了试居然也可以过。
这个算法的时间复杂度是O(n),分析一下计算步骤,我们会发现一个很大的问题:假设输入数据是
nums=[100,5,4,3,2,1,6] ,target=106,
循环开始时,nums[i]先取100,nums[j]取5,发现100+5小于106
再取nums[j]=4,这时我们发现问题,既然100+5就小于目标了,100+4以及后面的100+3,100+2就没有了判断的必要。
算法都是这样,针对具体问题,必须具体分析,才能找到优解。
既然找答案与数字大小有关,可以考虑先进行排序
然后使用双指针法进行解题。简单介绍双指针方法,此算法在很多题目中均有广泛应用。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> cpnums=nums;
vector<int> res;
sort(cpnums.begin(), cpnums.end());
int i=0;
int j=nums.size()-1;
while(i<j){
if(cpnums[i]+cpnums[j]>target){
j--;
}
else if(cpnums[i]+cpnums[j]<target){
i++;
}else{
return res;
}
}
return res;
}
};
双指针法,记住左指针和右指针都是 只能往一个方向移动的 指针。这里左指针只能右移动,而右指针只能左移动。而且,两个指针移动 对于答案的影响是相反的 类似二分查找。(我感觉我应该单独再写一个blog说这个问题)
排序,一般都是使用快速排序或者归并排序,时间复杂度为O(ln n ),而双指针查找最多只遍历一遍数组,所以时间复杂度为O(n),合起来时间复杂度为O(ln n )
差不多就是这样,但是这个代码寻找的是所有的二元组,而不是i,j,因为在排序的过程中,数据的原始位置被打乱了,不能用排序来解,所以我没有在leetcode上提交。
最后,有没有可以在O(n)时间复杂度内实现的解法?我们再来看题目,两个数,确定一个数之后找另一个相加=0的数,可以想到查找算法。而两个数对应键值对也让我们往那里想。此题使用散列表字典进行求解,就可以在O(n)的时间复杂度内实现。
散列表中,元素没有顺序,所以为解答此题,需要在字典结构(元素的“值”)中记录该元素原本在数组中的位置。元素的键则是该数的大小,因为同样的数字不会出现两次,而且找数的时候是按键来找的。
循环时,将元素加入到散列表中,同时算出和它相加=结果的数,再查找散列表中是否有这个数。因为散列表的查找可以在常数时间内完成,所以这三步都可以在常数时间内完成。 程序仅对数组进行一次遍历,时间复杂度为O(n)。
可以看到,虽然此算法时间上远超上一个,但是空间复杂度仅有12.18%,这是因为时间与空间不可兼得,一般采用像散列表这样用空间换时间的方式获得更好的算法。