算法-回溯法/双指针-三数之和
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)