問題描述
給你一個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有和為 0 且不重復的三元組,
注意:答案中不可以包含重復的三元組,
示例 1:
輸入:nums = [-1,0,1,2,-1,-4]
輸出:[[-1,-1,2],[-1,0,1]]
示例 2:
輸入:nums = []
輸出:[]
示例 3:
輸入:nums = [0]
輸出:[]
提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105
解題思路(排序+雙指標)
如果使用三重回圈來列舉每種情況,則時間復雜度為O(N3),
另外題中還要求答案中不可以包含重復的三元組,雖然可以使用哈希表來進行去重操作,但是這樣又大大消耗了空間,而且操作起來相當的不方便,我們可以想到先將陣列排好序,這樣相同的元素就可以保持相鄰,另外,在我們列舉的三元組 (a, b, c)滿足 a≤b≤c,保證了只有 (a,b,c) 這個順序會被列舉到,而(b,a,c)、(c,b,a) 等等這些不會,這樣就減少了重復,要實作這一點,我們可以將陣列中的元素從小到大進行排序,隨后使用普通的三重回圈就可以滿足上面的要求,
Arrays.sort(nums);
for(int i=0;i<len;i++){
for(int j=i+1;j<len;j++){
for(int k=j+1;k<len;k++){
if(nums[i]+nums[j]+nums[k]==0){
//add結果集
...
}
}
}
}
對于每一重回圈而言,相鄰兩次列舉的元素不能相同,否則也會造成重復,
而對于三重回圈,我們可以固定一個指標a,然后從a的后面兩端來列舉b和c,即從小到大列舉b,從大到小列舉c,這樣就可以保持第二重回圈不變,第三重回圈變成一個從陣列最右端開始向左移動的指標
當我們需要列舉陣列中的兩個元素時,如果我們發現隨著第一個元素的遞增,第二個元素是遞減的,那么就可以使用雙指標的方法,將列舉的時間復雜度從 O(N^2) 減少至 O(N),為什么是 O(N)呢?這是因為在列舉的程序每一步中,「左指標」會向右移動一個位置(也就是題目中的 bb),而「右指標」會向左移動若干個位置,這個與陣列的元素有關,但我們知道它一共會移動的位置數為 O(N),均攤下來,每次也向左移動一個位置,因此時間復雜度為 O(N),
實作代碼:
class Solution {
private List<List<Integer>> lists;
public List<List<Integer>> threeSum(int[] nums) {
lists=new ArrayList<List<Integer>>();
//如果陣列長度小于3,無法滿足有三個數,直接回傳空集
if(nums.length<3){
return lists;
}
Arrays.sort(nums);
int len=nums.length;
for(int i=0;i<len-2;i++){
//如果最左邊的數都大于0,那么后面的數肯定無法滿足3數之和為0,直接退出回圈
if(nums[i]>0){
break;
}
//避免重復
if(i!=0 && nums[i]==nums[i-1]){
continue;
}
//定義雙指標
int j=i+1;
int k=len-1;
int prej=-1; //prej為了去除j指標所指的元素的重復情況
while(j<k){ //當j==k時退出回圈
//如果三數之和大于0,那么k--
if((nums[j]+nums[k]+nums[i])>0){
k--;
//如果三數之和小于0,那么j++
}else if((nums[j]+nums[k]+nums[i])<0){
j++;
//如果三數之和等于0
}else if((nums[j]+nums[k]+nums[i])==0){
//當該數在之前已經被列舉到了,僅移動指標
if(prej!=-1 && nums[j]==nums[prej]){
k--;
j++;
continue;
}else{
//如果沒有,就添加到集合中,并標記這一次成功列舉時的指標j的位置
List<Integer> list=new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
lists.add(list);
prej=j;
}
k--;
j++;
}
}
}
return lists;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/259771.html
標籤:其他
