給出一個(gè)有n個(gè)整數(shù)的數(shù)組S,在S中找到三個(gè)整數(shù)a, b, c,找到所有使得a + b + c = 0的三元組。
注意事項(xiàng)
在三元組(a, b, c),要求a <= b <= c。
結(jié)果不能包含重復(fù)的三元組。
樣例
如S = {-1 0 1 2 -1 -4}
, 你需要返回的三元組集合的是:
(-1, 0, 1),(-1, -1, 2)
雙指針加set暴力去重
三數(shù)之和相比于兩數(shù)之和要稍微復(fù)雜一些,如果不加任何思考直接遍歷所有可能的組合,這當(dāng)然是一種方法,但是時(shí)間復(fù)雜度可能就會(huì)變得不能接受,所以一種比較好的方法是排序之后采用雙指針的方法。具體算法如下:
- 排序。排序可以讓數(shù)據(jù)變得有序,在許多算法中都有使用。
- 定義三個(gè)指針
i,left,right
. - i用來遍歷數(shù)據(jù),從下下標(biāo)為0一直到sz-3。
- 對(duì)于每一個(gè)i,令
left=i+1,right=sz-1
,然后檢查三個(gè)指針?biāo)笖?shù)據(jù)的和,
如果為0,說明找到一個(gè)符合條件的組合,把三個(gè)數(shù)放入vector中然后再放入set中,接著把left++,right--。
如果>0,則說明當(dāng)前的right偏大,則把right--,因?yàn)槭桥判虻模哉w和會(huì)變小。
如果<0,說明當(dāng)前的left偏小,則把left++,因?yàn)槭桥判虻模哉w的和會(huì)變大。(這里變大變小不是一定的,因?yàn)榭赡艽嬖谥貜?fù)的數(shù)據(jù),不影響)
這里主要要理解的是如果為0的話為什么left和right同時(shí)可以變化,這是因?yàn)橐笕ブ兀绻桓淖円粋€(gè)還符合條件的話一定是重復(fù)的,即使兩個(gè)都改變還是可能會(huì)出現(xiàn)重復(fù)的組合,所以放在set中進(jìn)行暴力去重。還有一種寫法不用set去重,增加了去重的判斷條件,我認(rèn)為這種寫法增加了算法的復(fù)雜度,所以沒有用。有興趣可以看這里.
這樣對(duì)于每個(gè)i來說,只遍歷其后的數(shù)據(jù),而且利用雙指針有效避免了一些重復(fù)搜索。代碼寫起來也比較簡(jiǎn)潔:
vector<vector<int>> threeSum(vector<int> &numbers) {
set<vector<int>> sres;
vector<vector<int>> res;
int sz=numbers.size();
if(sz<3) return res;
sort(numbers.begin(),numbers.end()); //先排序
vector<int> tmp;
for(int i=0;i<=sz-3;i++)
{
int left=i+1;
int right=sz-1;
while(left<right)
{
if(numbers[i]+numbers[left]+numbers[right]==0) //符合條件
{
tmp.push_back(numbers[i]);
tmp.push_back(numbers[left]);
tmp.push_back(numbers[right]); //放入vector
sres.insert(tmp); //插入set,去重
tmp.clear(); //清空tmp
left++;
right--; //這兩個(gè)指針都移動(dòng),因?yàn)樵}是要求去重的,如果只移動(dòng)一個(gè)還滿足的話肯定是重復(fù)的。
}
else if(numbers[i]+numbers[left]+numbers[right]<0)
{
left++; //如果小,就把左邊的指針右移
}
else
right--; //大的話,就把右邊的指針左移
}
}
for(auto s:sres) //去重之后的結(jié)果放入vector
{
res.push_back(s);
}
return res;
// write your code here
}
順便復(fù)習(xí)一下set,剛才寫的時(shí)候竟然用了push(),一段時(shí)間不寫果然是忘記很快。
set的資料見set
列出一些基本的成員函數(shù)以供參考,掌握一些常用的就可以了:
set