Leetcode之15. 3Sum (medium)
15. 3Sum (medium)
描述
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, -1, -4], A solution set is: [ [-1, 0, 1], [-1, -1, 2] ]
分析
首先是最容易想到的暴力破解,通過三重遍歷數組nums
,依次確定nums[i]
,nums[j]
,nums[k]
並計算三元素之和是否為0。這是最粗暴的解法,但是存在去重的問題,如[-1, 0, 1]和[0, 1, -1]這種情況。
其次,這個題目作為2Sum的進階題目,很容易聯想到將3Sum轉化為求target值為0 - nums[i]
,並在數組剩余元素中找出兩個元素之和為target的2Sum問題。但是同樣存在去重問題。
關於去重,由於是一個List
最後,以上兩種思路都存在去重問題,問題需要的是找出數組中三個元素之和為0的所有組合。去重過程很明顯是和結果無關,但是卻非常麻煩,因此要優化算法就要著眼於移除去重這個步驟。
在暴力破解的時候就該意識到邊界問題。
在做第一層循環時可以這樣寫:for(int i = 0; i < nums.length - 2; i++)
。即第一層循環的結束條件是nums.length - 2
,並不需要到nums.length
。
同樣第二層循環時:for(int j = i + 1; j < nums.length - 1; j++)
。開始索引不需要從0開始,可以直接從i + 1
開始,而結束為nums.length - 1
。
第三層:for(int k = j + 1; k < nums.length; k++)
。
可以看到在暴力破解的時候,我們已經有意識地通過邊界條件過濾掉一些情況,進行了初步優化。註意到數組本身是無序的,所以在確定元素的時候難以界定當前遍歷元素是否已經被選中過。如果數組是有序的,那麽三重遍歷的時候就可以有意識地跳過重復元素。到這裏已經對暴力破解的解題思路進行了優化,但是三重遍歷無疑是導致時間復雜度為O(N^3),這麽高的時間復雜度肯定是要被拋棄的。那麽該如何繼續優化呢?
嘗試優化思路二。首先使用排序解決去重問題。遍歷排序後的數組,固定第一個元素為nums[i]
,接下來在索引位i + 1
至nums.length
之間找出兩個元素nums[j]
和nums[k]
,二者之和為0 - nums[i]
。固然這可以做遍歷兩次達到目的,相信基本上2Sum都是這樣完成的。但是針對一個有序數組,夾逼法可以將這個過程的時間復雜度降為O(N)。因此,使用夾逼法找出剩余兩個元素。ps,別忘了同時對2Sum使用夾逼法進行優化。
代碼
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new LinkedList<List<Integer>>();
if (nums == null || nums.length < 3) {
return result;
}
// 對數組排序
Arrays.sort(nums);
//固定第一個元素nums[i]
for (int i = 0; i < nums.length - 2; i++) {
//默認是從小到大的排序,所以當nums[i]大於0的時候,就可以結束
if (nums[i] > 0) {
break;
}
//nums[i - 1] != nums[i]執行了去重,註意這裏在理解的時候要意識到此時操作的數組已經是有序數組
if (i == 0 || nums[i - 1] != nums[i]) {
//使用加逼法
int j = i + 1;
int k = nums.length - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[j], nums[k]));
}
if (sum <= 0) {
while (j < k && nums[j] == nums[++j]);
}
if(sum >= 0){
while (j < k && nums[k] == nums[--k]);
}
}
}
}
return result;
}
上面的代碼是優化之後的代碼,對於理解加逼的過程有點不便,下面是加逼的原始寫法:
while (j < k) {
int target = 0 - nums[i];
if(target == (nums[j] + nums[k])){
result.add(Arrays.asList(nums[i], nums[j], nums[k]));
j++;
while(nums[j] == nums[j - 1] && j < k){ //去重,註意這是一個有序數組
j++;
}
k--;
while(nums[k] == nums[k + 1] && j < k){ //去重,註意這是一個有序數組
k--;
}
}else if(target < (nums[j] + nums[k])){
k--;
while(nums[k] == nums[k + 1] && j < k){
k--;
}
}else if(target > (nums[j] + nums[k])){
j++;
while(nums[j] == nums[j - 1] && j < k){
j++;
}
}
}
Leetcode之15. 3Sum (medium)