LeetCode之 15.三數之和 (3Sum)總結
生命不止,刷題不息~~~~~~
前兩天就一直在做15.三數之和,這個題在LeetCode和LeetCode中國上獲贊很多,絕對的好題啊!不過,我喜歡這個題僅僅是因為它採用了快速排序的思想啦。
從捋清思路到程式碼實現,突破重重Bugs大關,終於提交成功,對於小白而言,實屬不易,接下來就跟大家分享一下這個題的解題思路,並附上用C++,java,和python的程式碼實現,希望能給大家提供幫助!
1、題目:
給定一個包含 n 個整數的陣列 nums
,判斷 nums
中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重複的三元組。
注意:答案中不可以包含重複的三元組。
例如, 給定陣列 nums = [-1, 0, 1, 2, -1, -4],
滿足要求的三元組集合為:
[
[-1, 0, 1],
[-1, -1, 2]
]
2、思路分析:
剛剛我也提到了這個題採用快速排序的思想是很棒的解題思路,在這裡,我想提供給大家也是這個解題思路:
大家看到這個題肯定瞬間會想到用三層迴圈呀,豈不是很簡單?是的,但是這個思路想想就算了,就不勞您去實現了,在實際應用中,time complexity 為(n^3)的演算法,除非迫不得已,我們還是不建議使用的。
在次之間如果大家已經刷過LeetCode上 11.盛最多水的容器 並且用快排的思想實現的,那3sum這個題對你來說就沒有難度了。我們來分析一下這道題的特點,要我們找出三個數且和為0,那麼除了三個數全是0的情況之外,肯定會有負數和正數,我們還是要先fix一個數,然後去找另外兩個數,我們只要找到兩個數且和為第一個fix數的相反數就行了,如何能更有效的定位呢?我們肯定不希望遍歷所有兩個數的組合吧,所以如果陣列是有序的,那麼我們就可以用雙指標以線性時間複雜度來遍歷所有滿足題意的兩個數組合。這個題了,那恭喜你,你對這個演算法已經瞭解了一大半!
我們對原陣列進行排序,然後開始遍歷排序後的陣列,這裡注意不是遍歷到最後一個停止,而是到倒數第三個就可以了。這裡我們可以先做個剪枝優化,就是當遍歷到正數的時候就break,為啥呢,因為我們的陣列現在是有序的了,如果第一個要fix的數就是正數了,那麼後面的數字就都是正數,就永遠不會出現和為0的情況了。然後我們還要加上重複就跳過的處理,處理方法是從第二個數開始,如果和前面的數字相等,就跳過,因為我們不想把相同的數字fix兩次。對於遍歷到的數,用0減去這個fix的數得到一個target,然後只需要再之後找到兩個數之和等於target即可。我們用兩個指標分別指向fix數字之後開始的陣列首尾兩個數,如果兩個數和正好為target,則將這兩個數和fix的數一起存入結果中。然後就是跳過重複數字的步驟了,兩個指標都需要檢測重複數字。如果兩數之和小於target,則我們將左邊那個指標start右移一位,使得指向的數字增大一些。同理,如果兩數之和大於target,則我們將右邊那個指標end左移一位,使得指向的數字減小一些。
感謝Grandyang提供了清晰的演算法描述,感謝大神!
3、程式碼實現:
C++實現
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); ++k) {
if (nums[k] > 0) break;
if (k > 0 && nums[k] == nums[k - 1]) continue;
int target = 0 - nums[k];
int i = k + 1, j = nums.size() - 1;
while (i < j) {
if (nums[i] + nums[j] == target) {
res.push_back({nums[k], nums[i], nums[j]});
while (i < j && nums[i] == nums[i + 1]) ++i;
while (i < j && nums[j] == nums[j - 1]) --j;
++i; --j;
} else if (nums[i] + nums[j] < target) ++i;
else --j;
}
}
return res;
}
};
執行時間:104ms 戰勝 81.54%的提交使用者
Java實現:
class Solution {
public List<List<Integer>> threeSum(int[] num) {
Arrays.sort(num);
List<List<Integer>> res = new LinkedList<>();
for (int i = 0; i < num.length-2; i++) {
if (i == 0 || (i > 0 && num[i] != num[i-1])) {
int lo = i+1, hi = num.length-1, sum = 0 - num[i];
while (lo < hi) {
if (num[lo] + num[hi] == sum) {
res.add(Arrays.asList(num[i], num[lo], num[hi]));
while (lo < hi && num[lo] == num[lo+1]) lo++;
while (lo < hi && num[hi] == num[hi-1]) hi--;
lo++; hi--;
}
else if (num[lo] + num[hi] < sum) lo++;
else hi--;
}
}
}
return res;
}
}
執行時間:84ms 戰勝 56.92%的提交使用者
Python實現:
def threeSum(nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
the capacibility of python original sort method is better than quick_sort
"""
# new_nums = self.quick_sort(nums, 0, len(nums)-1)
nums.sort()
start, end ,sum = 0,0,0
result = []
for i in range(len(nums)-2):
if i==0 or (i>0 and nums[i]!=nums[i-1]): # ensure result has no repeat list
sum = 0 - nums[i]
start = i+1
end = len(nums)-1
while start < end:
if nums[start] + nums[end] == sum:
each_list = [nums[i], nums[start], nums[end]]
result.append(each_list)
while start < end and nums[start]==nums[start+1]:
start +=1
while start < end and nums[end]==nums[end-1]:
end -=1
start +=1
end -=1
elif nums[start] + nums[end] < sum:
start +=1
elif nums[start] + nums[end] > sum:
end -=1
return result
執行時間:1052ms 戰勝 38.11%的提交使用者
4、通過比較分析,可以看出java的執行效率是最高的,這也是我為什麼要執著於java的原因。
再次感謝給本題提供解題思路的博主們,狗子們一起加油~~~