算法-回溯法/雙指針-三數之和
1 題目概述
1.1 題目出處
https://leetcode-cn.com/problems/3sum
1.2 題目描述
給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重複的三元組。
注意:答案中不可以包含重複的三元組。
示例:
給定數組 nums = [-1, 0, 1, 2, -1, -4],
滿足要求的三元組集合爲:
[
[-1, 0, 1],
[-1, -1, 2]
]
2 回溯法
2.1 思路
3個level,層層遞進回溯,並進行相應剪枝。
可惜的是,就算如此仍有超長用例超時。
2.2 代碼
class Solution {
private List<List<Integer>> resultList = new ArrayList<>();
public List<List<Integer>> threeSum(int[] nums) {
if(nums == null || nums.length < 3){
return resultList;
}
// 必須先排序,避免重複組合
Arrays.sort(nums);
backtrack(nums, 0, new ArrayList<Integer>(), 0);
return resultList;
}
// 返回是否應剪枝
private void backtrack(int[] nums, int level, List<Integer> tmpList, int index){
if(level == 2){
// 結束條件
List<Integer> result = new ArrayList(tmpList);
int sum = result.get(0) + result.get(1);
for(int i = index; i < nums.length; i++){
int tmp = sum + nums[i];
if(tmp == 0){
// 找到一個滿足和爲0的就返回了
result.add(nums[i]);
resultList.add(result);
return;
}
if(tmp > 0){
// 剪枝1
// 因爲已經排序,所以sum+nums[i+1]肯定也 > 0
return;
}
}
// 最終沒找到和爲0的也返回
return;
}
// 記錄本level已經選過的,選過的不再重複選
// 也就是說3個level,每個level選的數字不會重複,避免重複組合
Set<Integer> selected = new HashSet<>();
for(int i = index; i < nums.length - 1; i++){
if(selected.contains(nums[i])){
continue;
}
tmpList.add(nums[i]);
int tmp = 0;
for(int num : tmpList){
tmp += num;
}
tmp += nums[i + 1];
if(tmp > 0){
// 剪枝2
// 當level小於2時,當前數字和下一個數字之和大於0,則直接剪枝.
tmpList.remove(level);
return;
}
selected.add(nums[i]);
backtrack(nums, level + 1, tmpList, i + 1);
tmpList.remove(level);
}
}
}
2.3 時間複雜度
3 雙指針
3.1 思路
利用棧,如果棧爲空或者當前元素比棧頂元素大,就將棧頂元素出棧並計算相應結果,然後繼續比較下一個棧頂元素,直到當前元素不大於棧頂元素或棧爲空。
最後將元素直接放入棧頂。
遍歷完成後,還需要處理棧中元素,剩餘的元素代表右側沒有比他們更高的氣溫了,所以升高氣溫天數都爲0。
3.2 代碼
class Solution {
private List<List<Integer>> resultList = new ArrayList<>();
public List<List<Integer>> threeSum(int[] nums) {
if(nums == null || nums.length < 3){
return resultList;
}
// 必須先排序,避免重複組合
Arrays.sort(nums);
for(int i = 0; i < nums.length - 2; i++){
if(nums[i] > 0){
break;
}
int p = i + 1;
int q = nums.length - 1;
if(i > 0 && nums[i] == nums[i-1]){
// 避免重複組合
continue;
}
while(p < q){
int sum = nums[i] + nums[p] + nums[q];
if(sum == 0) {
List<Integer> result = new ArrayList<>();
result.add(nums[i]);
result.add(nums[p]);
result.add(nums[q]);
resultList.add(result);
while(++p < q && nums[p] == nums[p - 1]);
while(p < --q && nums[q] == nums[q + 1]);
} else if(sum > 0){
q--;
} else {
p++;
}
}
}
return resultList;
}
}
3.3 時間複雜度
O(N^2)
3.4 空間複雜度
O(1)
4 數組模擬棧
4.1 思路
思路和前面棧方法相同,只不過不用Stack類而是自己用數組模擬棧,減少了很多不必要的複雜操作開銷。
4.2 代碼
class Solution {
public int[] dailyTemperatures(int[] T) {
int[] result = new int[T.length];
// 該int數字0位表示在原氣溫數組中位置,1位表示氣溫值
int[][] tmp = new int[T.length][2];
int[] top;
// 標記tmp中當前應處理的行下標
int offset = -1;
for(int i = 0; i < T.length; i++){
while(offset > -1){
top = tmp[offset];
if(T[i] <= top[1]){
break;
}
offset--;
// 升高所需天數
result[top[0]] = i - top[0];
}
// 最後將當前數字入棧
tmp[++offset] = new int[]{i, T[i]};
}
return result;
}
}
4.3 時間複雜度
O(N)
4.4 空間複雜度
O(N)
5 倒序查找
5.1 思路
要求右側第一個溫度跟高的元素,所以其實從右往左找更方便。
而且,我們還加入一個快速判定方法,這裏假設初始時j=i+1:
- T[i] < T[j]
顯然result[i] = j - I; - T[i] >= T[j]且result[j] == 0,即i元素溫度不低於j元素,但j元素右側沒有更高的溫度,則i元素顯然也不能在右側升高溫度:
result[i] = 0; - T[i] >= T[j]且result[j] > 0,即i元素溫度不低於j元素,且j元素右側有更高的溫度,此時可通過
j = result[j] + j
快速找到第一個比j高的溫度的下標,然後讓i和這個新下表元素比較
5.2 代碼
class Solution {
public int[] dailyTemperatures(int[] T) {
int[] result = new int[T.length];
result[T.length - 2] = 0;
for(int i = T.length - 2; i >= 0; i--){
int j = i + 1;
// 往右找第一個比i還大的
while(true){
if(T[i] < T[j]){
result[i] = j - i;
break;
}else if(result[j] == 0){
// 注意此時T[i] >= T[j]
result[i] = 0;
break;
} else{
// 注意此時T[i] >= T[j]且result[j] > 0
// 找到第一個比j高的溫度的下標
j = result[j] + j;
}
}
}
return result;
}
}
5.3 時間複雜度
O(N)
5.4 空間複雜度
O(1)