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删除最后一个字符


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容