LeetCode No.1 两数之和
方法一:暴力解决法
遍历每一个元素,查找出两个数之和能够等于target
的。
并且用vector
存储下来,用于返回对应的下标。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> vec;
int n = nums.size();
for(int i =0;i<n;i++){
for(int j = i;j<n;j++){
if(nums[i]+nums[j] == target && i!=j ){
vec.push_back(i);
vec.push_back(j);
}
}
}return vec;
}
};
时间复杂度O(n²);
空间复杂度O(1);
暴力法虽然思路快,但是也仅仅能应用于两数之和上,若要是三数之和、四数、五数的话,时间复杂度会很大。
LeetCode No.1 暴力解决法用时 556ms。
方法二:哈希表法
为了降低时间复杂度,我们可以利用map
来记录对应数值的数标,并且在进行时回头查找是否有符合条件的数出现。
targer - nums[i]
寻找的就是第二个可能符合条件的数。
(所有事先存储到map
里的数都可能成为第一个符合条件的数)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> map;
vector<int> vec(2);
for(int i = 0;i<nums.size();i++){
if(map.count(target - nums[i])){//这里也可以用map.find,但是我喜欢count
vec[0] = map[target - nums[i]];
vec[1] = i;
}map[nums[i]] = i;
}return vec;
}
};
时间复杂度O(n);
空间复杂度O(n);
所需要的空间大小为map
的元素个数,此处为nums.size()
LeetCode No.1 哈希表法用时 20ms。
优化
在上述哈希表法中利用了vector
进行存储和最后的返回,但是返回的仅仅是两个数而已,所以我们尽量争取把这点空间节省掉。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> map;
for(int i=0;i<nums.size();i++){
if(map.count(target-nums[i]))
return {map[target-nums[i]],i};
map[nums[i]]=i;
}return {};
}
};
时间复杂度O(n);
空间复杂度O(n);
LeetCode No.1 哈希表法2.0版本用时 8ms。
LeetCode No.15 三数之和
方法:双指针法
当做完了“两数之和”后,第一眼看到“三数之和”后的第一反应就是“这不都是一个题么?!”
没错!“两数之和”、“三数之和”就是一个题,只不过后者的target
变成了可变的。vector
从一维变成了二维的。
并且第一反应就是双指针法(一头一尾)来解决的。
那么延续上一题的思路写出下面的代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int> > vec;
sort(nums.begin(),nums.end());
int n = nums.size();
for (int a = 0; a < n-2; ++a) {//a<n、a<n-2都可以
if (nums[a] > 0) break;
if (a>0 && nums[a] == nums[a-1]) continue;
int target = 0 - nums[a];
int i = a+1;
int j = n-1;
while(i<j){
if(nums[i]>target) break;
if(nums[i]+nums[j] == target){
vector<int> vv;
vv.push_back(nums[i]);
vv.push_back(nums[j]);
vv.push_back(-target);
vec.push_back(vv);
i++;
}
else if (j<n-1 && nums[j] == nums[j+1] || nums[i]+nums[j]>target) --j;
else if (i>a+1 && nums[i] == nums[i-1] || nums[i]+nums[j]<target) ++i;
}
}
vec.erase(unique(vec.begin(),vec.end()),vec.end());//[0,0,0,0]
return vec;
}
};
但是做完之后发现,其实相对“两数之和”来说,“三数之和”还是多了挺多细节的。
- 要用双指针法的话就一定要先排好序,
sort(nums.begin(),nums.end());
是必要的!
但是注意sort
后的第一位要小于0;并且数组里面会有重复的数字,当target
选则了相同的数字后会出现多个相同的解,所以直接跳过continue
即可。 - 然后就是双指针法(一头一尾)的模板了:
if(头 + 尾 == 目标) 记录;
else if(头 + 尾 > 目标) 尾--;
//(j<n-1 && nums[j] == nums[j+1] || nums[i]+nums[j]>target) --j;
else if(头 + 尾 < 目标) 头++;
//(i>a+1 && nums[i] == nums[i-1] || nums[i]+nums[j]<target) ++i;
逻辑运算符的顺序不要写错,否则会越界!
- 最后的
ve.erase
去重是因为发现[0,0,0,0]的结果有重复。
LeetCode Node No.15 用时 116ms。
优化:
仔细想一想,其实target
是可以省略的,因为绝大部分的判断好像都和target
有关。
并且vector<int>vv
也是可以省略的。(真想不明白当时为什么要这个vv)
while循环处参考了LeetCode No.15 pinku-2的题解。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int> > vec;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int a = 0; a < n-2; ++a) {
if (a>0 && nums[a] == nums[a-1]) continue;
int i = a+1;
int j = n-1;
while(i<j) {
int sum = nums[a]+nums[i]+nums[j];
if (sum == 0){
vec.push_back({nums[a],nums[i],nums[j]});
while(i>j && nums[i] == nums[++i]);
while(i<j && nums[j] == nums[--j]);
}else if (sum>0) {
--j;
}else if (sum<0) {
++i;
}
}
}
return vec;
}
};
LeetCode Node No.15 用时 48ms(在所有 C++ 提交中击败了97.17%的用户)
LeetCode No.18 四数之和
在“三数之和”上再加一层循环即可。
class Solution{
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int> > res;
sort(nums.begin(),nums.end());
int n=nums.size();
for(int a=0;a<n-3;a++){
if(a>0 && nums[a]==nums[a-1]) continue;
for(int b=a+1;b<n-2;b++){
if(b>a+1 && nums[b]==nums[b-1])continue;
int i=b+1;
int j=n-1;
while(i<j){
if(nums[a]+nums[b]+nums[i]+nums[j] == target){
res.push_back({nums[a],nums[b],nums[i],nums[j]});
while(i>j && nums[i] == nums[++i]);
while(i<j && nums[j] == nums[--j]);
}
else if(nums[a]+nums[b]+nums[i]+nums[j]<target)
i++;
else if(nums[a]+nums[b]+nums[i]+nums[j]>target)
j--;
}
}
}return res;
}
};
LeetCode Node No.18 用时 40ms(在所有 C++ 提交中击败了72.17%的用户)