2024-03-30 代码随想录

代码随想录算法训练营day25 | 题目34、题目2476、题目216、题目17


题目一描述

34. 在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:
输入:nums = [], target = 0
输出:[-1,-1]

提示:

0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
nums 是一个非递减数组
-10^9 <= target <= 10^9

解题思路

寻找左右边界的一个较为通用的解法,找到目标值后继续移动指针。

代码实现

方法一:

class Solution {
    int[] searchRange(int[] nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        return new int[] { leftBorder, rightBorder };
    }

    int getLeftBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int leftBorder = -1; // 记录一下leftBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] == target) { // 寻找左边界,nums[middle] == target的时候更新right
                leftBorder = middle;
                right = middle - 1;
            } else if (nums[middle] > target) {
                if (leftBorder != -1) {
                    leftBorder = middle;
                }
                right = middle - 1;
            } else {
                left = middle + 1;
            }
        }
        return leftBorder;
    }

    int getRightBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int rightBorder = -1; // 记录一下rightBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] == target) { // 寻找右边界,nums[middle] == target的时候更新left
                rightBorder = middle;
                left = middle + 1;
            } else if (nums[middle] < target) {
                if (rightBorder != -1) {
                    rightBorder = middle;
                }
                left = middle + 1;
            } else if (nums[middle] > target) {
                right = middle - 1;
            }
        }
        return rightBorder;
    }
}

题目二描述

2476. 二叉搜索树最近节点查询

给你一个 二叉搜索树 的根节点 root ,和一个由正整数组成、长度为 n 的数组 queries 。

请你找出一个长度为 n 的 二维 答案数组 answer ,其中 answer[i] = [mini, maxi] :

mini 是树中小于等于 queries[i] 的 最大值 。如果不存在这样的值,则使用 -1 代替。
maxi 是树中大于等于 queries[i] 的 最小值 。如果不存在这样的值,则使用 -1 代替。
返回数组 answer 。

示例 1 :


示例1

输入:root = [6,2,13,1,4,9,15,null,null,null,null,null,null,14], queries = [2,5,16]
输出:[[2,2],[4,6],[15,-1]]
解释:按下面的描述找出并返回查询的答案:

  • 树中小于等于 2 的最大值是 2 ,且大于等于 2 的最小值也是 2 。所以第一个查询的答案是 [2,2] 。
  • 树中小于等于 5 的最大值是 4 ,且大于等于 5 的最小值是 6 。所以第二个查询的答案是 [4,6] 。
  • 树中小于等于 16 的最大值是 15 ,且大于等于 16 的最小值不存在。所以第三个查询的答案是 [15,-1] 。

示例 2 :


示例2

输入:root = [4,null,9], queries = [3]
输出:[[-1,4]]
解释:树中不存在小于等于 3 的最大值,且大于等于 3 的最小值是 4 。所以查询的答案是 [-1,4] 。

提示:

树中节点的数目在范围 [2, 10^5] 内
1 <= Node.val <= 10^6
n == queries.length
1 <= n <= 10^5
1 <= queries[i] <= 10^6

解题思路

直接在树上搜索可能会超时,因为没说平衡,最坏的情况,树退化成链表。
所以先把搜索二叉树中序遍历变成数组,再在数组上用二分查找来使用类似找左右边界的方法找到结果。

代码实现

方法一:

class Solution {
    public List<List<Integer>> closestNodes(TreeNode root, List<Integer> queries) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> nodeList = new ArrayList<>();
        dfsToArray(root, nodeList);
        int[] nodes = new int[nodeList.size()];
        for (int i = 0; i < nodes.length; i++) {
            nodes[i] = nodeList.get(i);
        }
        for (int target : queries) {
            res.add(Arrays.asList(findMax(nodes, target), findMin(nodes, target)));
        }
        return res;
    }

    private void dfsToArray(TreeNode root, List<Integer> nodeList) {
        if (root == null)
            return;
        dfsToArray(root.left, nodeList);
        nodeList.add(root.val);
        dfsToArray(root.right, nodeList);
    }

    private int findMax(int[] nodes, int target) {
        int left = 0;
        int right = nodes.length - 1;
        int res = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nodes[mid] == target) {
                return nodes[mid];
            } else if (nodes[mid] > target) {
                right = mid - 1;
            } else if (nodes[mid] < target) {
                res = nodes[mid];
                left = mid + 1;
            }
        }
        return res;
    }

    private int findMin(int[] nodes, int target) {
        int left = 0;
        int right = nodes.length - 1;
        int res = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nodes[mid] == target) {
                return nodes[mid];
            } else if (nodes[mid] > target) {
                res = nodes[mid];
                right = mid - 1;
            } else if (nodes[mid] < target) {
                left = mid + 1;
            }
        }
        return res;
    }
}

题目三描述

216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:
输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

提示:

2 <= k <= 9
1 <= n <= 60

解题思路

也是回溯,注意搜索树的最大宽度就是k。
注意剪枝的操作。

代码实现

方法一:

class Solution {
    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        backtracking(k, n, 1, path, res, 0);
        return res;
    }

    private void backtracking(int k, int n, int startIndex, List<Integer> path, List<List<Integer>> res, int sum) {
        if (path.size() == k) {
            if (sum == n)
                res.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝,限制startIndex取到的范围
            sum += i;
            if (sum > n) { // 第二处剪枝,当前和大于目标值就没必要再加新的路径
                return;
            }
            path.add(i);
            backtracking(k, n, i + 1, path, res, sum);
            sum -= i;
            path.removeLast();
        }
    }
}

技巧总结

removeLast的运用,这里其实是错的,
只有 LinkedList<Integer> path = new LinkedList<>(); 才能用
List是不行的,统一用path.remove(path.size() - 1)比较好。


题目四描述

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。


示例

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:

输入:digits = ""
输出:[]
示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

0 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字。

解题思路

回溯算法,注意for循环范围是从0开始,与startIndex无关。

代码实现

方法一:

class Solution {
    List<String> map = Arrays.asList("", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz");

    public List<String> letterCombinations(String digits) {
        List<String> res = new ArrayList<>();
        if (digits.length() == 0)
            return res;
        StringBuilder path = new StringBuilder();
        backtracking(digits, 0, res, path);
        return res;
    }

    private void backtracking(String digits, int startIndex, List<String> res, StringBuilder path) {
        if (path.length() == digits.length()) {
            res.add(path.toString());
            return;
        }
        int curKey = digits.charAt(startIndex) - '0'; // char类型转整型,用这种方法
        String letters = map.get(curKey);
        for (int i = 0; i < letters.length(); i++) { // 注意从0开始
            path.append(letters.charAt(i));
            backtracking(digits, startIndex + 1, res, path);
            path.deleteCharAt(path.length() - 1); // 删除最后一个字符
        }
    }
}

技巧总结

学会char类型转整型
学会stringBuilder删除最后一个字符


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容