LeetCode 热题 HOT 100 Java 题解 -- Part 3

练习地址

Part 1 : https://blog.csdn.net/qq_41080854/article/details/128829494

Part 2 : https://blog.csdn.net/qq_41080854/article/details/129278336

78. 最佳买卖股票时机含冷冻期

给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

解析

dp[i][0] 买入股票的利益 dp[i][1] 卖出股票的利益
必须要先卖再买
今天卖的股票 = 今天不卖 + 今天卖(买出的收益 + 今天价格)
今天买的股票 = 今天不买 + 今天买的(卖出的价格 + 冷冻期 - 今天价格)

代码

class Solution {
    public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][2];
        //dp[i][0] 买入股票的利益 dp[i][1] 卖出股票的利益
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        //必须要先卖再买
        for(int i = 1; i < prices.length; i++){
            //今天卖的股票 = 今天不卖 + 今天卖(买出的收益 + 今天价格)
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
            //今天买的股票 = 今天不买 + 今天买的(卖出的价格 + 冷冻期 - 今天价格)
            dp[i][0] = Math.max(dp[i - 1][0], (i >= 2 ? dp[i - 2][1] : 0) - prices[i]);
        }
        return Math.max(dp[prices.length - 1][0], dp[prices.length - 1][1]);
    }
}

79. 戳气球

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

示例 1:
输入:nums = [3,1,5,8]
输出:167
解释:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 315 + 358 + 138 + 181 = 167

解析

固定DP区间,而不固定爆破的气球,dp[i][j] 表示 i 到 j 之间能获得最大金币,不包括i,j边界(戳爆中间,两段不戳爆)

0<= i < k < j <=n + 1 符合这样的位置关系,只管最后一个戳爆的气球!k是最后一个戳爆的气球,分治思想dp[i][k]前半部分,dp[k][j]后半部分

class Solution {
    public int maxCoins(int[] nums) {
        int n = nums.length;
        int[] helper = new int[n + 2];
        // 固定区间,而不固定爆破的气球
        // dp[i][j] 表示 i 到 j 之间能获得最大金币,不包括i,j边界,戳爆中间,两段不戳爆
        int[][] dp = new int[n + 2][n + 2];
        helper[0] = helper[n + 1] = 1;
        for(int i = 1; i < n + 1; i++){
            helper[i] = nums[i - 1];
        }
        // 0<= i < k < j <=n + 1 符合这样的位置关系,从后往前戳破气球!
        for(int i = n - 1; i >= 0; i--){
            for(int j = i + 2; j < n + 2; j++){
                for(int k = i + 1; k < j; k++){
                    //k是最后一个戳爆的气球
                    int sum = helper[i] * helper[k] * helper[j];
                    sum += dp[i][k] + dp[k][j];
                    dp[i][j] = Math.max(dp[i][j], sum);
                }
            }
        }
        return dp[0][n + 1];
    }
}

80. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

解析

完全背包

代码

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];// dp[i] 金额 i所需的最少硬币
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;//0 元 0 种
        for(int i = 0; i < coins.length; i++){//先遍历硬币,物品
            for(int j = coins[i]; j < amount + 1; j++){//遍历金额,容量
                dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1); 
            }
        }
        return dp[amount] == amount + 1 ? -1 : dp[amount];
    }
}

81. 打家劫舍 III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

输入: root = [3,2,3,null,3,null,1]
输出: 7 
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

解析

使用后序遍历,对于根的计算分为打劫和不打劫两种

class Solution {
    public int rob(TreeNode root) {
        if(root == null) return 0;
        int[] res = dfs(root);
        //int[0] 表示不打劫,int[1] 表示打劫
        return Math.max(res[0], res[1]);
    }

    private int[] dfs(TreeNode node){
        if(node == null) return new int[]{0, 0};
        //后序遍历
        int[] left = dfs(node.left);
        int[] right = dfs(node.right);

        int[] res = new int[2];
        //不打劫当前节点
        res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        //打劫当前节点
        res[1] = node.val + left[0] + right[0];
        return res;
    }
}

82. 比特位计数

给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

示例 1:

输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
示例 2:

输入:n = 5
输出:[0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

解析

对于任何整数都有, x = x &(x - 1),最后一位的1变为0,然后计数

代码

class Solution {
    public int[] countBits(int n) {
        //res[i] 第i个有多少个1
        int[] res = new int[n + 1];
        for(int i = 0; i < n + 1; i++){
            int count = 0;
            int temp = i;
            while(temp != 0){
                count++;
                temp = temp & (temp - 1);//将最后一个位从1变为0
            }
            res[i] = count;
        }

        return res;
    }
}

83. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:

输入: nums = [1], k = 1
输出: [1]

解析

在这里,我们可以利用堆的思想:建立一个大顶堆,然后遍历「出现次数数组」

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        int[] res = new int[k];
        Map<Integer, Integer> map = new HashMap<>();
        for(int num : nums){
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        //大根堆
        PriorityQueue<Integer> pq = new PriorityQueue<>((x, y) -> map.get(y) - map.get(x));
        for(Integer key : map.keySet()){
            pq.offer(key);
        }
        for(int i = 0; i < k; i++){
            res[i] = (int)pq.poll();
        }

        return res;

    }
}

84. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例 1:

输入:s = “3[a]2[bc]”
输出:“aaabcbc”

解析

双栈

class Solution {
    public String decodeString(String s) {
        // 栈
        Stack<Integer> stack = new Stack<>();
        // 临时结果栈
        Stack<StringBuilder> temp = new Stack<>();
        temp.push(new StringBuilder());
        // 字符串长度、当前的乘数
        int len = s.length(), mul = 0;
        for (int i=0; i<len; i++) {
            char c = s.charAt(i);
            if (c >= '0' && c <= '9') {
                // 是数字,则累计
                mul = mul * 10 + (c - '0');
            } else if (c == '[') {
                // 遇到左边框,新增一个临时过程
                stack.push(mul);
                temp.push(new StringBuilder());
                mul = 0;
            } else if (c == ']') {
                // 遇到右边框,处理临时过程到上一个临时过程
                StringBuilder t = temp.pop();
                temp.peek().append(t.toString().repeat(stack.pop()));
            } else {
                // 遇到字符串,拼接到当前的临时过程
                temp.peek().append(c);
            }
        }
        return temp.pop().toString();
    }
}

85. 除法求值

给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。

另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。

返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案。

注意:输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。

示例 1:

输入:equations = [[“a”,“b”],[“b”,“c”]], values = [2.0,3.0], queries = [[“a”,“c”],[“b”,“a”],[“a”,“e”],[“a”,“a”],[“x”,“x”]]
输出:[6.00000,0.50000,-1.00000,1.00000,-1.00000]
解释:
条件:a / b = 2.0, b / c = 3.0
问题:a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
结果:[6.0, 0.5, -1.0, 1.0, -1.0 ]

解析

将其转换为图,并使用带权重的并查集来模拟

在这里插入图片描述
在这里插入图片描述

代码

class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        UnionFind union = new UnionFind();
        int i = 0;
        for(List<String> eq: equations){
            String a = eq.get(0);
            String b = eq.get(1);
            double value = values[i++];
            union.union(a, b, value);
        }
        double[] res = new double[queries.size()];
        i = 0;
        for(List<String> query: queries){
            String x = query.get(0);
            String y = query.get(1);
            res[i++] = union.query(x, y);
        }
        return res;

    }

    //带权重的并查集
    private class UnionFind{
        private Map<String, String> parent;
        private Map<String, Double> weight;
        
        public UnionFind(){
            parent = new HashMap<>();
            weight = new HashMap<>();
        }

        public void union(String x, String y, double value){
            parent.putIfAbsent(x, x);
            parent.putIfAbsent(y, y);
            weight.putIfAbsent(x, 1.0d);
            weight.putIfAbsent(y, 1.0d);
            String xRoot = find(x);
            String yRoot = find(y);
            if (!xRoot.equals(yRoot)) {
                parent.put(xRoot, yRoot);
                weight.put(xRoot, weight.get(y) * value / weight.get(x));
            }
        }

        //找到根节点
        public String find(String x){
            if(!parent.get(x).equals(x)){
                String oldPatient = parent.get(x);
                parent.put(x, find(parent.get(x)));
                weight.put(x, weight.get(oldPatient) * weight.get(x));
             }
             return parent.get(x);
        }

        public double query(String x, String y) {
            if (!parent.containsKey(x) || !parent.containsKey(y)) {
                return -1.0d;
            }
            String xRoot = find(x);
            String yRoot = find(y);
            if (xRoot.equals(yRoot)) {
                return weight.get(x) / weight.get(y);
            } else {
                return -1.0d;
            }
        }
    }

}

86. 根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

解析

(套路):一般这种数对,还涉及排序的,根据第一个元素正向排序,根据第二个元素反向排序,或者根据第一个元素反向排序,根据第二个元素正向排序,往往能够简化解题过程。
本题先第一个元素逆序,然后第二个元素升序,接着插队

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //数组的第一个元素进行逆序,数组第二个元素正序
        Arrays.sort(people, (x, y) -> {
        if(x[0] == y[0]){
            return x[1] - y[1];//升序
        }else{
            return y[0] - x[0];//降序
        }
        });
        
        //再插队,根据下标k进行插队
        List<int[]> res = new ArrayList<>();
        for(int[] p : people){
            res.add(p[1], p);
        }
        return res.toArray(new int[res.size()][0]);
    }
}

87. 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

解析

0,1 背包问题

代码

class Solution {
    public boolean canPartition(int[] nums) {
        // 0 1背包问题
        int n = nums.length;
        int sum = 0; //统计nums的总数
        for(int num : nums){
            sum += num;
        }
        if(sum % 2 != 0) return false;
        int target = sum / 2;
        int[] dp = new int[target + 1];//dp[i] 表示第 i 个正整数的元素和

        // 0 1 背包模板
        for(int i = 0; i < n; i++) {
            for(int j = target; j >= nums[i]; j--) {
                // 重量和价值都为nums[i]
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }

        return dp[target] == target;
    }
}


86. 路径总和 III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
在这里插入图片描述
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示

解析

先序递归遍历每个节点,再以每个节点作为起始点递归寻找满足条件的路径,双重递归

代码

class Solution {
    long path = 0;
    public int pathSum(TreeNode root, int targetSum) {
        //先序遍历每个结点,然后每个结点作为起始点递归查找
        if(root == null) return 0;
        sum(root, targetSum);
        pathSum(root.left, targetSum);
        pathSum(root.right, targetSum);
        return (int)path;
    }

    private void sum(TreeNode root, long targetSum){
        if(root == null) return;
        targetSum -= root.val;
        if(targetSum == 0) path++;
        sum(root.left, targetSum);
        sum(root.right, targetSum);
    }
}

87. 找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。

输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。

解析

滑动窗口

代码

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int[] map = new int[128];
        for(char c : p.toCharArray()) map[c]++;
        List<Integer> res = new ArrayList<>();
        for(int left = 0, right = 0; right < s.length(); right++){
            map[s.charAt(right)]--;
            while(map[s.charAt(right)] < 0){
                map[s.charAt(left++)]++;
            }
            if(right - left + 1 == p.length()){
                res.add(left);
            }
        }
        return res;
    }
}

88. 找到所有数组中消失的数字

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
示例 1:

输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]

解析

原地哈希,将num - 1 + n 作为判断的依据

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        int n = nums.length;
        for(int num : nums){
            int x = (num - 1) % n;
            nums[x] += n;
        }

        List<Integer> res = new ArrayList<>();
        for(int i = 0; i < n; i++){
            if(nums[i] <= n){
                res.add(i + 1);
            }
        }
        return res;
    }
}

89. 汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

给你两个整数 x 和 y,计算并返回它们之间的汉明距离。

示例 1:

输入:x = 1, y = 4
输出:2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
示例 2:

输入:x = 3, y = 1
输出:1

解析

位操作

代码

class Solution {
    public int hammingDistance(int x, int y) {
        int s = x ^ y;
        int count = 0;
        while(s != 0){
            count += s & 1;//count + 该位是否不同
            s = s >> 1;
        }
        return count;
    }
}

90. 目标和

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
示例 2:

输入:nums = [1], target = 1
输出:1

解析

sum(P) 前面符号为+的集合;sum(N) 前面符号为减号的集合,所以题目可以转化为
sum(P) - sum(N) = target 
=> sum(nums) + sum(P) - sum(N) = target + sum(nums)
=> 2 * sum(P) = target + sum(nums) 
=> sum(P) = (target + sum(nums)) / 2
因此题目转化为01背包,装满背包,也就是能组合成容量为sum(P)的方式有多少种

代码

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for(int num : nums){
            sum += num;
        }

        if(sum < target || (sum + target) % 2 == 1) return 0;
        //数学推导
        int w = (sum + target) / 2;
        if(w < 0) return 0;
        //dp[i] 表示第i个目标和由多少个正数方案组成
        int[] dp = new int[w + 1];
        dp[0] = 1;
        //0 1 背包模板
        for(int i = 0; i < nums.length; i++){
            for(int j = w; j >= nums[i]; j--){
                //装满背包
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[w];
    }
}

91. 把二叉搜索树转换为累加树

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。

解析

中序遍历二叉搜素数是递增的,反序遍历是递减的,这是就可以按照题目累加值并赋值给val

class Solution {
    int sum = 0;
    public TreeNode convertBST(TreeNode root) {
        if(root == null) return null;
        convertBST(root.right);
        sum += root.val;
        root.val = sum;
        convertBST(root.left);
        return root;
    }
}

92. 二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

解析

dfs计算子树高度。左右子树高度之和和max的最大值即为直径

class Solution {
    int max = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        dfs(root);
        return max;
    }

    private int dfs(TreeNode node){
        if(node == null) return 0;
        int left = dfs(node.left);
        int right = dfs(node.right);
        max = Math.max(left + right, max);//左右子树高度之和和max的最大值即为直径
        return Math.max(left, right) + 1;//计算子树高度
    }
}

93. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数 。

示例 1:

输入:nums = [1,1,1], k = 2
输出:2

解析

前缀和 + 哈希表(存放前缀和–个数)

代码

class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0, pre = 0;//pre 为 前缀和
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);//前缀和为0, 个数为1
        for(int num : nums){
            pre += num;
            if(map.containsKey(pre - k)){
                count += map.get(pre - k);
            }
            map.put(pre, map.getOrDefault(pre, 0) + 1);
        }
        return count;
    }
}

96. 最短无序连续子数组

给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

解析

这个数组分成三段,左段和右段是标准的升序数组,中段数组虽是无序的,但满足最小值大于左段的最大值,最大值小于右段的最小值。

中段的左右边界,分别定义为begin 和 end;
分两头开始遍历:

从左到右维护一个最大值max,在进入右段之前,那么遍历到的nums[i]都是小于max的,我们要求的end就是遍历中最后一个小于max元素的位置;
同理,从右到左维护一个最小值min,在进入左段之前,那么遍历到的nums[i]也都是大于min的,要求的begin也就是最后一个大于min元素的位置。

代码

class Solution {
    public int findUnsortedSubarray(int[] nums) {
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        int left = 0, right = -1; //确保 right - left + 1为0
        for(int i = 0; i < nums.length; i++){
            if(nums[i] < max){//从左到右维护max,max的前一个就是right
                right = i;
            }else{
                max = nums[i];
            }
            if(nums[nums.length - 1 - i] > min){ //从右到做维护min,min的后一个就是left
                left = nums.length - 1 - i;
            }else{
                min = nums[nums.length - 1 - i];
            }
        }
        return right - left + 1;
    }
}

97. 合并二叉树

给你两棵二叉树: root1 和 root2 。

想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。

返回合并后的二叉树。

解析

先序遍历

代码

class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if(root1 == null) return root2;
        if(root2 == null) return root1;
        TreeNode node = new TreeNode(root1.val + root2.val);
        node.left = mergeTrees(root1.left, root2.left);
        node.right = mergeTrees(root1.right, root2.right);
        return node;
    }
}

98. 任务调度器

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。

然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的 最短时间 。

解析

解题思路:
1、将任务按类型分组,正好A-Z用一个int[26]保存任务类型个数

2、对数组进行排序,优先排列个数(count)最大的任务,如题得到的时间至少为
retCount =(count-1)* (n+1) + 1 ==> A->X->X->A->X->X->A(X为其他任务或者待命)

3、再排序下一个任务,如果下一个任务B个数和最大任务数一致,则retCount++ ==> A->B->X->A->B->X->A->B

4、如果空位都插满之后还有任务,那就随便在这些间隔里面插入就可以,因为间隔长度肯定会大于n,在这种情况下就是任务的总数是最小所需时间

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] map = new int[26];
        for(char task : tasks){
            map[task - 'A']++;
        }
        Arrays.sort(map);
        int res = (n + 1) * (map[25] - 1) + 1;
        for(int i = 0; i < 25; i++){
            if(map[i] == map[25]){
                res++;
            }
        }
        return Math.max(res, tasks.length);
    } 
}

99. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”

解析

中心扩散法,只有满足条件才计数

class Solution {
    int res = 0;
    public int countSubstrings(String s) {
        for(int i = 0; i < s.length(); i++){
            count(s, i, i);//奇数
            count(s, i, i + 1);//偶数
        }
        return res;
    }

    private void count(String s, int left, int right){
        while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
            res++;
            left--;
            right++;
        }
    }
}

100. 每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

解析

单调栈,栈内元素递减的

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int[] res = new int[temperatures.length];
        Deque<Integer> stack = new LinkedList<>();
        for(int i = 0; i < temperatures.length; i++){
            while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){
                int idx = stack.pop();
                res[idx] = i - idx;
            }
            stack.push(i);
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值