1.最简单的TwoSum问题
问题描述:
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
这个问题比较简单,第一想法是两次遍历,时间复杂度为O(n^2),当然这种方法毫无思考
然后我们要思考如何将时间复杂度降低,可以对数组进行排序O(nlogn),然后对排序数组进行查找相加和等于目标target的时间复杂度是O(n),
所以时间复杂度为O(nlogn + n) ---> O(nlogn)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
sort(nums.begin(),nums.end());//由小到大进行排序
int lo = 0;
int hi = nums.size()-1;
vector<int> res;
while(lo < hi)
{
if(nums[lo] + nums[hi] == target)
{
res.push_back(lo+1);
res.push_back(hi+1);
return res;
}
else if(nums[lo] + nums[hi] > target)//如果相加和大于目标,说明要减小被加的数
{
hi--;
}
else//反之要增大被加的数
{
lo++;
}
}
return res;
}
};
思考一下能否继续降低,继续降低只能考虑O(n)了,O(n)一应该就是遍历一次,我们通过哈希表来实现
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
vector<int> res;
unordered_map<int,int> mymap;
for(int i = 0;i < nums.size();i++)
{
int complement = target - nums[i];
if(mymap.find(complement) != mymap.end())//每进行一次遍历,就在哈希表中找其互补元素,如果没有就将自身插入哈希表,等待后序被遍历的元素来寻找互补元素
{
res.push_back(mymap[complement]);
res.push_back(i);
return res;
}
mymap[nums[i]] = i;
}
return res;
}
};
2.扩展到3Sum问题
Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
The solution set must not contain duplicate triplets.
Example:
Given array nums = [-1, 0, 1, 2,2, -1, -4,-4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]
暴力方法破解,那就是a,b,c进行三次嵌套循环遍历,并排除相同的结果,时间复杂度为O(n^3)
为了降低复杂度,以2Sum的方式思考,可以将其转化为2Sum问题,对数组进行排序,固定其中一个,然后就变为了已排好序的2Sum问题,不同之处在于上面的2Sum问题中没有相同元素,而这里有且要求不能由重复结果,所以要重点处理这一问题:
对于固定其中一个,肯定要遍历进行尝试固定哪一个,对于[-1,0,1,2,2,-1,-4,-4]升序排列之后为[-4,-4,-1,-1,0,1,2,2]开始遍历固定,固定首元素-4开始,然后就转化为[-4,-1,-1,0,1,2,2]中的2Sum问题,然后固定下一个元素,此时我们发现下一个元素还是-4,那么如果我们继续固定它,那么肯定会找到之前答案的子集,这不是我们想要的,所以我们再固定元素之前要进行检查,我们是否在之前已经固定过该元素,(是检查之前,而不是看之后是否有相同元素)
for(int i = 0;i <= nums.size()-3;i++)
{
//判断之前是否已经固定过该元素
if(i - 1 >= 0 && nums[i] == nums[i-1])
{
continue;
}
}
然后问他就来到了转换为的2Sum问题上,因为转化为的2Sum问题中含有重复元素,所以为了不产生重复结果,要对其进行特殊处理
固定-4已经转化为[-4,-1,-1,0,1,2,2]问题,我们利用上面的2Sum问题的前后夹逼来寻找元素,假设我们要求其中找到目标和为1的元素,那么很容易夹逼出-1+2 = 1;-1+2 = 1;0 +1 = 1;
其中有结果是重复的,而这正是我们要处理的问题,问题发生在元素和等于目标时(废话,否则怎么会产生重复结果),因为已经排好序,所以当发生-1+2 = 1时,如果-1的下一个元素还是-1,那么肯定会产生相同元素,同理如果最后一个元素的前一个元素还是2,那么也会产生相同的结果,所以需要将这些元素进行剔除
所以完整程序如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> res;
int len = nums.size();
if(len < 3)
{
return res;
}
//本题的思路与已排好序的数组来找两个和等于目标值的方法类似,前后两个指针相互移动
sort(nums.begin(),nums.end());
for(int i = 0;i <= nums.size()-3;i++)
{
/*这一段程序下面解释
if(nums[i] + nums[i+1] + nums[i+2] > 0)
{
break;
}//因为是已经排好序的数组,所以如果前三者大于目标,说明后面的任意三个均大于目标,不用比了
if(nums[i] + nums[len-2] + nums[len-1] < 0)
{
continue;
}
*/
//因为不能有重复,所以要跳过排好序的重复元素
if(i - 1 >= 0 && nums[i] == nums[i-1])
{
continue;
}
int left = i+1;
int right = nums.size()-1;
//转化为2Sum问题
while(left < right)
{
int sum = nums[i] + nums[left] + nums[right];//转化为两个之和等于目标值的问题
if(sum == 0)
{
//将符合条件的元素记录下来
res.push_back(vector<int> {nums[i],nums[left],nums[right]});
//然后判断符合条件元素前后是否由相同值
while(left+1 < right && nums[left] == nums[left+1])
{
left++;
}
while(right -1 > left && nums[right] == nums[right-1])
{
right--;
}
//没有重复之后,因为left,right处的元素已经判断完毕,所以继续向下判断
left++;
right--;
}
else if(sum > 0)
{
right--;
}
else
{
left++;
}
}
}
return res;
}
};
到这里我们要注意,能否进一步优化程序呢?
想一想,因为我们已将数组按升序排列,如果前三个元素之和已经大于目标值,那么肯定不会找到其他元素之和等于目标值,循环也就没了意义,直接跳出循环即可
而当我们的判断的固定第一个元素与最后两个最大元素之和小于目标值时,说明固定的元素太小了,不可能是符合的元素,一个固定下一个元素,所以需要跳出本次循环,继续下一次固定元素循环continue
3.扩展到4Sum
与3Sum类似,我们固定两个元素,进一步转化为2Sum问题
思想与3Sum完全一样,只不过是多嵌套了一层循环
完整程序:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target)
{
//先用和3sum 类似的方法来尝试一下
sort(nums.begin(),nums.end());
vector<vector<int>> res;
int len = nums.size();
if(len < 4)
{
return vector<vector<int>>();
}
for(int first = 0;first <= nums.size()-4;first++)//循环固定第一个元素
{
if(nums[first] + nums[first+1] + nums[first+2] + nums[first+3]> target)
{
break;
}
if(nums[first] + nums[len - 3] + nums[len - 2] + nums[len - 1]< target)
{
continue;
}
if(first - 1 >= 0 && nums[first] == nums[first-1])
{
continue;
}
for(int second = first+1;second <= nums.size()-3;second++)//循环固定第二个元素
{
if(nums[first] + nums[second] +nums[second + 1] + nums[second+2]> target)
{
break;
}
if(nums[first] + nums[second] + nums[len - 2] + nums[len - 1] < target)
{
continue;
}
if(second - 1 > first && nums[second] == nums[second-1])//要判断是前面是否已经出现过相同元素,而不是和后面比较是否有相同元素
{
continue;
}
int third = second + 1;
int forth = nums.size()-1;
while(third < forth)//转化为2Sum问题
{
int sum = nums[first] + nums[second] + nums[third] + nums[forth];
if(sum == target)
{
res.push_back(vector<int>{nums[first],nums[second],nums[third],nums[forth]});
while(third + 1 < forth && nums[third] == nums[third+1])
{
third++;
}
while(forth -1 > third && nums[forth] == nums[forth-1])
{
forth--;
}
third++;
forth--;
}
else if(sum > target)
{
forth--;
}
else
{
third++;
}
}
}
}
return res;
}
};