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;
}
};