美文网首页
LeetCode热门100题算法和思路(day10)

LeetCode热门100题算法和思路(day10)

作者: 码农朱同学 | 来源:发表于2022-08-24 09:59 被阅读0次

    LeetCode538 把二叉搜索树转换为累加树

    题目详情

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

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

    注意:本题和 1038: https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/ 相同

    示例 1:

    输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
    输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
    示例 2:

    输入:root = [0,null,1]
    输出:[1,null,1]
    示例 3:

    输入:root = [1,0,2]
    输出:[3,3,2]
    示例 4:

    输入:root = [3,2,4,1]
    输出:[7,9,4,10]

    提示:

    树中的节点数介于 0 和 104 之间。
    每个节点的值介于 -104 和 104 之间。
    树中的所有值 互不相同 。
    给定的树为二叉搜索树。

    代码
    public class LeetCode538 {
        public static void main(String[] args) {
            TreeNode.prettyPrintTree(new Solution().convertBST(TreeNode.buildBinaryTree(new Integer[]{4, 1, 6, 0, 2, 5, 7, null, null, null, 3, null, null, null, 8})));
        }
    
        static class Solution {
            private int sum = 0;
    
            public TreeNode convertBST(TreeNode root) {
                if (root != null) {
                    convertBST(root.right);
                    sum += root.val;
                    root.val = sum;
                    convertBST(root.left);
                }
                return root;
            }
        }
    }
    

    LeetCode543二叉树的直径

    题目详情

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

    示例 :
    给定二叉树

          1
         / \
        2   3
       / \     
      4   5    
    

    返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
    注意:两结点之间的路径长度是以它们之间边的数目表示。

    代码
    public class LeetCode543 {
        public static void main(String[] args) {
            System.out.println(new Solution().diameterOfBinaryTree(TreeNode.buildBinaryTree(new Integer[]{1, 2, 2, null, 3, null, 3})));
        }
    
        /*
        深度优先搜索
         */
        static class Solution {
            int ans;
    
            public int diameterOfBinaryTree(TreeNode root) {
                ans = 1;
                depth(root);
                return ans - 1;
            }
    
            public int depth(TreeNode node) {
                if (node == null) {
                    return 0; // 访问到空节点了,返回0
                }
                int L = depth(node.left); // 左儿子为根的子树的深度
                int R = depth(node.right); // 右儿子为根的子树的深度
                ans = Math.max(ans, L + R + 1); // 计算d_node即L+R+1 并更新ans
                return Math.max(L, R) + 1; // 返回该节点为根的子树的深度
            }
        }
    }
    

    LeetCode560 和为K的子数组

    题目详情

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

    示例 1:

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

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

    提示:

    1 <= nums.length <= 2 * 104
    -1000 <= nums[i] <= 1000
    -107 <= k <= 107

    代码
    class LeetCode560 {
        public static void main(String[] args) {
            System.out.println(new Solution().subarraySum(new int[]{1, 2, 3, 0, 2, 1, 3, 2, 4}, 3));
            System.out.println(new Solution().subarraySum1(new int[]{1, 2, 3, 0, 2, 1, 3, 2, 4}, 3));
            System.out.println(new Solution().subarraySum2(new int[]{1, 2, 3, 0, 2, 1, 3, 2, 4}, 3));
            System.out.println(new Solution().subarraySum3(new int[]{1, 2, 3, 0, 2, 1, 3, 2, 4}, 3));
        }
    
        static class Solution {
            /*
            暴力法
             */
            public int subarraySum(int[] nums, int k) {
                // 初始化,使用count储存结果
                int count = 0;
                //遍历nums考虑每个位置start作为子数组结尾的情况
                for (int start = 0; start < nums.length; ++start) {
                    //记录当前子数组的和
                    int sum = 0;
                    for (int end = start; end >= 0; --end) {
                        sum += nums[end];
                        if (sum == k) {
                            count++;
                        }
                    }
                }
                return count;
            }
    
            /*
            暴力法
             */
            public int subarraySum1(int[] nums, int k) {
                int len = nums.length;
                int sum = 0;
                int count = 0;
                //双重循环
                for (int i = 0; i < len; ++i) {
                    for (int j = i; j < len; ++j) {
                        sum += nums[j];
                        //发现符合条件的区间
                        if (sum == k) {
                            count++;
                        }
                    }
                    //记得归零,重新遍历
                    sum = 0;
                }
                return count;
            }
    
            public int subarraySum2(int[] nums, int k) {
                //前缀和数组
                int[] presum = new int[nums.length + 1];
                for (int i = 0; i < nums.length; i++) {
                    //这里需要注意,我们的前缀和是presum[1]开始填充的
                    presum[i + 1] = nums[i] + presum[i];
                }
                //统计个数
                int count = 0;
                for (int i = 0; i < nums.length; ++i) {
                    for (int j = i; j < nums.length; ++j) {
                        //注意偏移,因为我们的nums[2]到nums[4]等于presum[5]-presum[2]
                        //所以这样就可以得到nums[i,j]区间内的和
                        if (presum[j + 1] - presum[i] == k) {
                            count++;
                        }
                    }
                }
                return count;
            }
    
            public int subarraySum3(int[] nums, int k) {
                if (nums.length == 0) {
                    return 0;
                }
                // hash
                // 记录合适的连续字符串数量
                int count = 0;
                // 记录前面数字相加之和
                int pre = 0;
                // map记录前几个数字之和为K出现相同和的次数为V
                HashMap<Integer, Integer> map = new HashMap<>();
                // 初始化
                map.put(0, 1);
                for (int i = 0; i < nums.length; i++) {
                    pre += nums[i];
                    // 前缀和
                    // 设
                    // pre[i]=pre[i−1]+nums[i]
                    // 由于补上了0,1这个情况 问题由多少个连续数字之和等于k 转为
                    // pre[i]−pre[j−1]==k (前缀和之差为k,代表这两个前缀和中间的数字相加就是K)
                    // 如果前面某些数字之和加上这个数字正好等于K(存在一个数字加上nums[i]结果为K
                    // 说明找到了
                    if (map.containsKey(pre - k)) {
                        // 累计
                        count += map.get(pre - k);
                    }
                    // 计算新的和放入map
                    map.put(pre, map.getOrDefault(pre, 0) + 1);
                }
                return count;
            }
        }
    }
    

    LeetCode581 最短无序连续子数组

    题目详情

    给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
    请你找出符合题意的 最短 子数组,并输出它的长度。

    示例 1:

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

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

    输入:nums = [1]
    输出:0

    提示:

    1 <= nums.length <= 104
    -105 <= nums[i] <= 105

    进阶:你可以设计一个时间复杂度为 O(n) 的解决方案吗?

    代码
    public class LeetCode581 {
        public static void main(String[] args) {
            System.out.println(new Solution().findUnsortedSubarray(new int[]{2, 6, 4, 8, 10, 9, 15}));
            System.out.println(new Solution2().findUnsortedSubarray(new int[]{2, 6, 4, 8, 10, 9, 15}));
        }
    
        static class Solution {
            public int findUnsortedSubarray(int[] nums) {
                int l = nums.length, r = 0;
                for (int i = 0; i < nums.length - 1; i++) {
                    for (int j = i + 1; j < nums.length; j++) {
                        if (nums[j] < nums[i]) {
                            r = Math.max(r, j);
                            l = Math.min(l, i);
                        }
                    }
                }
                return r - l < 0 ? 0 : r - l + 1;
            }
        }
    
        static class Solution2 {
            public int findUnsortedSubarray(int[] nums) {
                Stack<Integer> stack = new Stack<Integer>();
                int l = nums.length, r = 0;
                for (int i = 0; i < nums.length; i++) {
                    while (!stack.isEmpty() && nums[stack.peek()] > nums[i])
                        l = Math.min(l, stack.pop());
                    stack.push(i);
                }
                stack.clear();
                for (int i = nums.length - 1; i >= 0; i--) {
                    while (!stack.isEmpty() && nums[stack.peek()] < nums[i])
                        r = Math.max(r, stack.pop());
                    stack.push(i);
                }
                return r - l > 0 ? r - l + 1 : 0;
            }
        }
    }
    

    LeetCode617 合并二叉树

    题目详情

    给你两棵二叉树: root1 和 root2 。
    想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
    返回合并后的二叉树。
    注意: 合并过程必须从两个树的根节点开始。

    示例 1:

    输入:root1 = [1,3,2,5], root2 = [2,1,3,null,4,null,7]
    输出:[3,4,5,5,4,null,7]
    示例 2:

    输入:root1 = [1], root2 = [1,2]
    输出:[2,2]

    提示:

    两棵树中的节点数目在范围 [0, 2000] 内
    -104 <= Node.val <= 104

    代码
    public class LeetCode617 {
        public static void main(String[] args) {
            TreeNode.prettyPrintTree(new Solution().mergeTrees(
                    TreeNode.buildBinaryTree(new Integer[]{1, 3, 2, 5}),
                    TreeNode.buildBinaryTree(new Integer[]{2, 1, 3, null, 4, null, 7})));
        }
    
        /*
        递归
         */
        static class Solution {
            public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
                if (t1 == null)
                    return t2;
                if (t2 == null)
                    return t1;
                t1.val += t2.val;
                t1.left = mergeTrees(t1.left, t2.left);
                t1.right = mergeTrees(t1.right, t2.right);
                return t1;
            }
        }
    }
    

    LeetCode621 任务调度器

    题目详情

    给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
    然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
    你需要计算完成所有任务所需要的 最短时间 。

    示例 1:

    输入:tasks = ["A","A","A","B","B","B"], n = 2
    输出:8
    解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
    在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。
    示例 2:

    输入:tasks = ["A","A","A","B","B","B"], n = 0
    输出:6
    解释:在这种情况下,任何大小为 6 的排列都可以满足要求,因为 n = 0
    ["A","A","A","B","B","B"]
    ["A","B","A","B","A","B"]
    ["B","B","B","A","A","A"]
    ...
    诸如此类
    示例 3:

    输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
    输出:16
    解释:一种可能的解决方案是:
    A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

    提示:

    1 <= task.length <= 104
    tasks[i] 是大写英文字母
    n 的取值范围为 [0, 100]

    代码
    public class LeetCode621 {
        public static void main(String[] args) {
            System.out.println(new Solution().leastInterval(new char[]{'A', 'A', 'A', 'B', 'B', 'B'}, 3));
            System.out.println(new Solution2().leastInterval(new char[]{'A', 'A', 'A', 'B', 'B', 'B'}, 3));
            System.out.println(new Solution3().leastInterval(new char[]{'A', 'A', 'A', 'B', 'B', 'B'}, 3));
        }
    
        /*
        1、手下把最大的任务量排好,并列的最大按照最小间隔排好;
        2、剩下的任务(出来最大的任务以及与最大并列的最后一个任务),需要插前边的空(不需要插最后一组后边的);
        3、假如空不够插,直接返回task长度,否则就是此时最后一个最大量任务的结束时间
         */
        static class Solution {
            public int leastInterval(char[] tasks, int n) {
                int count[] = new int[26];
                for (int i = 0; i < tasks.length; i++) {
                    count[tasks[i] - 'A']++;
                }
                Arrays.sort(count);
                int max = count[25];
                int numMax = 0;//与最大数并列的任务数
                for (int i = 25; i >= 0; i--) {
                    if (max == count[i]) {
                        numMax++;
                    } else {
                        break;
                    }
                }
                //最大任务产生空隙:n*(max-1)
                //需要插空的其他任务的数量task.length-max-(numMax-1)
                return n * (max - 1) <= tasks.length - max - (numMax - 1) ? tasks.length : (n + 1) * (max - 1) + numMax;
            }
        }
    
        /*
        模拟
        一种容易想到的方法是,我们按照时间顺序,依次给每一个时间单位分配任务。
        那么如果当前有多种任务不在冷却中,那么我们应该如何挑选执行的任务呢?
        直觉上,我们应当选择剩余执行次数最多的那个任务,将每种任务的剩余执行次数尽可能平均,
        使得 CPU 处于待命状态的时间尽可能少。当然这也是可以证明的,详细证明见下一个小标题。
         */
        static class Solution2 {
            public int leastInterval(char[] tasks, int n) {
                Map<Character, Integer> freq = new HashMap<Character, Integer>();
                for (char ch : tasks) {
                    freq.put(ch, freq.getOrDefault(ch, 0) + 1);
                }
                // 任务总数
                int m = freq.size();
                List<Integer> nextValid = new ArrayList<Integer>();
                List<Integer> rest = new ArrayList<Integer>();
                Set<Map.Entry<Character, Integer>> entrySet = freq.entrySet();
                for (Map.Entry<Character, Integer> entry : entrySet) {
                    int value = entry.getValue();
                    nextValid.add(1);
                    rest.add(value);
                }
    
                int time = 0;
                for (int i = 0; i < tasks.length; ++i) {
                    ++time;
                    int minNextValid = Integer.MAX_VALUE;
                    for (int j = 0; j < m; ++j) {
                        if (rest.get(j) != 0) {
                            minNextValid = Math.min(minNextValid, nextValid.get(j));
                        }
                    }
                    time = Math.max(time, minNextValid);
                    int best = -1;
                    for (int j = 0; j < m; ++j) {
                        if (rest.get(j) != 0 && nextValid.get(j) <= time) {
                            if (best == -1 || rest.get(j) > rest.get(best)) {
                                best = j;
                            }
                        }
                    }
                    nextValid.set(best, time + n + 1);
                    rest.set(best, rest.get(best) - 1);
                }
                return time;
            }
        }
    
        /*
        构造
        我们首先考虑所有任务种类中执行次数最多的那一种,记它为 A,的执行次数maxExeC
         */
        static class Solution3 {
            public int leastInterval(char[] tasks, int n) {
                Map<Character, Integer> freq = new HashMap<Character, Integer>();
                // 最多的执行次数
                int maxExec = 0;
                for (char ch : tasks) {
                    int exec = freq.getOrDefault(ch, 0) + 1;
                    freq.put(ch, exec);
                    maxExec = Math.max(maxExec, exec);
                }
    
                // 具有最多执行次数的任务数量
                int maxCount = 0;
                Set<Map.Entry<Character, Integer>> entrySet = freq.entrySet();
                for (Map.Entry<Character, Integer> entry : entrySet) {
                    int value = entry.getValue();
                    if (value == maxExec) {
                        ++maxCount;
                    }
                }
                return Math.max((maxExec - 1) * (n + 1) + maxCount, tasks.length);
            }
        }
    }
    

    LeetCode647 回文子串

    题目详情

    给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
    回文字符串 是正着读和倒过来读一样的字符串。
    子字符串 是字符串中的由连续字符组成的一个序列。
    具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

    示例 1:

    输入:s = "abc"
    输出:3
    解释:三个回文子串: "a", "b", "c"
    示例 2:

    输入:s = "aaa"
    输出:6
    解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

    提示:

    1 <= s.length <= 1000
    s 由小写英文字母组成

    代码
    public class LeetCode647 {
        public static void main(String[] args) {
            System.out.println(new Solution().countSubstrings("aaa"));
            System.out.println(new Solution2().countSubstrings("aaa"));
        }
    
        /*
        中心拓展
         */
        static class Solution {
            public int countSubstrings(String S) {
                int N = S.length(), ans = 0;
                for (int center = 0; center <= 2 * N - 1; ++center) {
                    int left = center / 2;
                    int right = left + center % 2;
                    while (left >= 0 && right < N && S.charAt(left) == S.charAt(right)) {
                        ans++;
                        left--;
                        right++;
                    }
                }
                return ans;
            }
        }
    
        /*
        Manacher 算法
         */
        static class Solution2 {
            public int countSubstrings(String s) {
                int n = s.length();
                StringBuffer t = new StringBuffer("$#");
                for (int i = 0; i < n; ++i) {
                    t.append(s.charAt(i));
                    t.append('#');
                }
                n = t.length();
                t.append('!');
    
                int[] f = new int[n];
                int iMax = 0, rMax = 0, ans = 0;
                for (int i = 1; i < n; ++i) {
                    // 初始化 f[i]
                    f[i] = i <= rMax ? Math.min(rMax - i + 1, f[2 * iMax - i]) : 1;
                    // 中心拓展
                    while (t.charAt(i + f[i]) == t.charAt(i - f[i])) {
                        ++f[i];
                    }
                    // 动态维护 iMax 和 rMax
                    if (i + f[i] - 1 > rMax) {
                        iMax = i;
                        rMax = i + f[i] - 1;
                    }
                    // 统计答案, 当前贡献为 (f[i] - 1) / 2 上取整
                    ans += f[i] / 2;
                }
    
                return ans;
            }
        }
    }
    

    LeetCode739 每日温度

    题目详情

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

    示例 1:

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

    输入: temperatures = [30,40,50,60]
    输出: [1,1,1,0]
    示例 3:

    输入: temperatures = [30,60,90]
    输出: [1,1,0]

    提示:

    1 <= temperatures.length <= 105
    30 <= temperatures[i] <= 100

    代码
    class LeetCode739 {
        public static void main(String[] args) {
            System.out.println(Arrays.toString(new Solution().dailyTemperatures(new int[]{73, 74, 75, 71, 69, 72, 76, 73})));
            System.out.println(Arrays.toString(new Solution2().dailyTemperatures(new int[]{73, 74, 75, 71, 69, 72, 76, 73})));
            System.out.println(Arrays.toString(new Solution3().dailyTemperatures(new int[]{73, 74, 75, 71, 69, 72, 76, 73})));
        }
    
        /*
        暴力
         */
        static class Solution {
            public int[] dailyTemperatures(int[] temperatures) {
                int length = temperatures.length;
                int[] ans = new int[length];
                int[] next = new int[101];
                Arrays.fill(next, Integer.MAX_VALUE);
                for (int i = length - 1; i >= 0; --i) {
                    int warmerIndex = Integer.MAX_VALUE;
                    for (int t = temperatures[i] + 1; t <= 100; ++t) {
                        if (next[t] < warmerIndex) {
                            warmerIndex = next[t];
                        }
                    }
                    if (warmerIndex < Integer.MAX_VALUE) {
                        ans[i] = warmerIndex - i;
                    }
                    next[temperatures[i]] = i;
                }
                return ans;
            }
        }
        /*
        输入: temperatures = [73,74,75,71,69,72,76,73]
        单调栈
        可以维护一个存储下标的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减。
        如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。
         */
    
        static class Solution2 {
            public int[] dailyTemperatures(int[] temperatures) {
                int length = temperatures.length;
                //存储结果
                int[] ans = new int[length];
                //双端队列,作为队列时,add和remove方法,作为栈时,push和pop
                Deque<Integer> stack = new LinkedList<Integer>();
                for (int i = 0; i < length; i++) {
                    int temperature = temperatures[i];
                    //当栈不为空,且 当前温度大于栈顶温度
                    while (!stack.isEmpty() && temperature > temperatures[stack.peek()]) {
                        //匹配成功,出栈并赋值
                        int prevIndex = stack.pop();
                        ans[prevIndex] = i - prevIndex;
                    }
                    //因为判断需要以后的元素,所以最后入栈
                    stack.push(i);
                }
                return ans;
            }
        }
    
        static class Solution3 {
            public int[] dailyTemperatures(int[] T) {
                int[] ans = new int[T.length];
                Stack<Integer> stack = new Stack();
                for (int i = T.length - 1; i >= 0; --i) {
                    while (!stack.isEmpty() && T[i] >= T[stack.peek()]) stack.pop();
                    ans[i] = stack.isEmpty() ? 0 : stack.peek() - i;
                    stack.push(i);
                }
                return ans;
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:LeetCode热门100题算法和思路(day10)

          本文链接:https://www.haomeiwen.com/subject/szgigrtx.html