算法-回溯法/双指针-三数之和

算法-回溯法/双指针-三数之和

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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章