【LeetCode】剑指 Offer Ⅱ 第8章:树(12道题) -- Java Version

题库链接:https://leetcode.cn/problem-list/e8X3pBZi/

在这里插入图片描述

类型题目解决方案
二叉树的深搜剑指 Offer II 047. 二叉树剪枝递归(深搜):二叉树的后序遍历 (⭐)
剑指 Offer II 048. 序列化和反序列化二叉树递归(深搜):二叉树的前序遍历(⭐)
剑指 Offer II 049. 从根节点到叶节点的路径数字之和递归(深搜):二叉树的前序遍历(⭐)
剑指 Offer II 050. 向下的路径节点值之和递归(深搜):二叉树的前序遍历(⭐)
剑指 Offer II 051. 节点值之和最大的路径递归(深搜):二叉树的后序遍历(⭐)
二叉搜索树剑指 Offer II 052. 展平二叉搜索树栈 + 二叉树中序遍历(⭐)
剑指 Offer II 053. 二叉搜索树的下一个节点中序遍历:二叉搜索树的特性(⭐)
剑指 Offer II 054. 所有大于或等于节点的值之和倒序的中序遍历(⭐)
剑指 Offer II 055. 二叉搜索树迭代器二叉搜索树中序遍历:过程拆分(⭐)
剑指 Offer II 056. 二叉搜索树中两个节点的值之和中序遍历 + HashSet(⭐)
TreeSet 和 TreeMap 的应用剑指 Offer II 057. 值和下标之差都在给定的范围内平衡二叉搜索树:TreeSet (⭐)
剑指 Offer II 058. 日程表平衡二叉搜索树:TreeMap(⭐)

二叉树是一种典型的具有递归性质的数据结构,二叉树的广度优先搜索通常是通过队列来实现,而二叉树的深度优先搜索可以分为中序遍历、前序遍历和后序遍历。二叉树 DFS 的递归写法非常简单,按照遍历的顺序编写递归顺序即可;而将递归代码改写成迭代的形式则通常需要 来辅助,栈主要用于二叉树左侧节点的存储。
……
而二叉搜索树则是一种特殊的二叉树,在二叉搜索树中进行搜索、添加和删除操作的平均时间复杂度均为 O(logn),如果按照中序遍历的顺序遍历一棵二叉搜索树,那么则会按照从小到大的顺序依次遍历每个节点。由于这个特性,与二叉搜索树相关的很多面试题都适合使用中序遍历解决。
……
在 Java 中提供了 TreeSetTreeMap 这两种实现了平衡二叉搜索树的数据结构,如果需要动态地在一个排序的数据集合中添加元素,或者需要根据数据的大小查找,那么可以使用 TreeSet 和 TreeMap 解决。
常用方法:

  • ceiling():返回键大于或等于给定值的最小键;如果没有则返回 null
  • floor():返回键小于或等于给定值的最大键;如果没有则返回 null
  • higher():返回键大于给定值的最小键;如果没有则返回 null
  • lower():返回键小于给定值的最大键;如果没有则返回 null

1. 剑指 Offer II 047. 二叉树剪枝 – P132

给定一个二叉树 根节点 root ,树的每个节点的值要么是 0,要么是 1。请剪除该二叉树中所有节点的值为 0 的子树。
节点 node 的子树为 node 本身,以及所有 node 的后代。

在这里插入图片描述

1.1 递归(深搜):二叉树的后序遍历 – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( h ) O(h) O(h)

Key:二叉树的后序遍历(左、右、根),一般我们可以用递归(深搜)来实现二叉树的遍历,适用于小树深的情况;而如果是大树深,则更推荐采用迭代的方式(用栈来保存路径)。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // Solution1:二叉树后序遍历
    public TreeNode pruneTree(TreeNode root) {
        if (root == null) return null;
        
        root.left = pruneTree(root.left);
        root.right = pruneTree(root.right);
        
        if (root.left == null && root.right == null && root.val == 0) {
            return null;  // 将左、右子节点为null, 且自身值为0的节点剪枝
        }
        return root;
    }
}

在这里插入图片描述

2. 剑指 Offer II 048. 序列化和反序列化二叉树 – P134

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
……
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

在这里插入图片描述

2.1 递归(深搜):二叉树的前序遍历 – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( h ) O(h) O(h)

如何将二叉树序列化成一个字符串:

  1. 前序遍历每一个节点;
  2. 用分隔符对不同的节点进行分隔;
  3. 把 null 节点序列化成一个特殊的字符串,如 ”#“;

……
反序列化则是:

  1. 将序列化形成的字符串按分隔符进行分割;
  2. ”#“ 与 null 对应;
  3. 递归还原节点;

……
细节:方法传参时,传递的是一个只有一个元素的数组,来作为字符串遍历的下标。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) return "#";

        String left =  serialize(root.left);
        String right = serialize(root.right);

        return String.valueOf(root.val)+ "," + left + "," + right;
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] strs = data.split(",");
        int[] i = {0};  // 细节,变量传参不会改变值,因此要用数组传参
        return dfs(strs, i);
    }

    public TreeNode dfs(String[] strs, int[] i) {
        String str = strs[i[0]];
        i[0]++;
        if (str.equals("#")) return null;

        TreeNode node = new TreeNode(Integer.parseInt(str));
        node.left = dfs(strs, i);
        node.right = dfs(strs, i);
        return node;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));

在这里插入图片描述

3. 剑指 Offer II 049. 从根节点到叶节点的路径数字之和 – P136

给定一个二叉树的根节点 root ,树中每个节点都存放有一个 09 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:

  • 例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123

计算从根节点到叶节点生成的 所有数字之和
叶节点 是指没有子节点的节点。

在这里插入图片描述

3.1 递归(深搜):二叉树的前序遍历 – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( h ) O(h) O(h)

Key:二叉树中路径相关的面试题,通常都可以使用深度优先搜索的方法解决,这是因为路径通常顺着指向子节点的指针的方向,也就是纵向方法。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // Solution1:二叉树前序遍历记录路径
    public int sumNumbers(TreeNode root) {
        return dfs(root, 0);
    }

    public int dfs(TreeNode root, int path) {
        if (root == null) return 0;

        path = path * 10 + root.val;  // 记录路径
        if (root.left == null && root.right == null) return path;

        return dfs(root.left, path) + dfs(root.right, path);
    }
}

在这里插入图片描述

4. 剑指 Offer II 050. 向下的路径节点值之和 – P136

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

在这里插入图片描述

4.1 递归(深搜):二叉树的前序遍历 – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( h ) O(h) O(h)

Key:path 用于记录从根节点出发所经过的路径,map 用于记录累加路径和出现的次数,遍历所有的节点,在这个过程中 path - target 在map 中出现的次数即为二叉树中节点值之和等于 target 的路径的数目。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        Map<Long, Integer> map = new HashMap<>();  // Node.val <= 109, 相加有可能数值溢出
        map.put(0L, 1);  // 赋初值,path - target 
        return dfs(root, targetSum, map, 0);
    }

    public int dfs(TreeNode root, int target, Map<Long, Integer> map, long path) {
        if (root == null) return 0;
        path += root.val;

        int count = map.getOrDefault(path-target, 0);  // 查询当前路径下是否存在符合条件的子路径
        map.put(path, map.getOrDefault(path,0)+1);  // 记录当前路径

        count += dfs(root.left, target, map, path);  // 向左遍历
        count += dfs(root.right, target, map, path);  // 向右遍历

        map.put(path, map.get(path)-1);  // 删除当前路径节点,回到上一路径状态
        return count;
    }
}

在这里插入图片描述

5. 剑指 Offer II 051. 节点值之和最大的路径 – P139

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给定一个二叉树的根节点 root ,返回其 最大路径和,即所有路径上节点值之和的最大值。

在这里插入图片描述

5.1 递归(深搜):二叉树的后序遍历 – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( h ) O(h) O(h)

Key:原书题解太过麻烦,推荐 【灵茶山艾府】彻底掌握直径 DP!从二叉树到一般树!(Python/Java/C++/Go),重点是要理解递归思想,递归就像是一个后入先出的栈,一层层递归返回。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int res = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        dfs(root);
        return res;
    }

    public int dfs(TreeNode root) {
        if (root == null) return 0;

        int left = dfs(root.left);  // 左子树最大链和
        int right = dfs(root.right);  // 右子树最大链和
        res = Math.max(res, left + right + root.val);  // 两条链拼成路径
        return Math.max(Math.max(left, right) + root.val, 0);  // 路径和为负数,则不选
    }
}

在这里插入图片描述

6. 剑指 Offer II 052. 展平二叉搜索树 – P142

给你一棵二叉搜索树,请 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。

在这里插入图片描述

6.1 栈 + 二叉树中序遍历 – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)

以中序遍历的顺序,递增式的遍历每个节点,并在遍历的过程中改变节点的指向。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // Solution1:二叉树中序遍历
    public TreeNode increasingBST(TreeNode root) {
        Stack<TreeNode> sk = new Stack<>();
        TreeNode cur = root;  // 当前节点
        TreeNode prev = null;  // 前一节点
        TreeNode head = null;  // 展开后的首节点

        while (cur != null || !sk.isEmpty()) {
            while (cur != null) {  // 将以当前节点为首的左侧节点全部入栈
                sk.push(cur);
                cur = cur.left;
            }

            cur = sk.pop();  // 左侧节点以此出栈
            if (prev != null) { 
                prev.right = cur;
            } else {
                head = cur;  // 如果prev为null,说明此时出栈的节点是展开后的头节点
            }

            prev = cur;
            cur.left = null;
            cur = cur.right;  // 继续遍历
        }
        return head;
    }
}

在这里插入图片描述

7. 剑指 Offer II 053. 二叉搜索树的下一个节点 – P144

给定一棵二叉搜索树和其中的一个节点 p ,找到该节点在树中的中序后继。如果节点没有中序后继,请返回 null
节点 p 的后继是值比 p.val 大的节点中键值最小的节点,即按中序遍历的顺序节点 p 的下一个节点。

在这里插入图片描述

7.1 中序遍历:二叉搜索树的特性 – O(h)(⭐)

时间复杂度 O ( h ) O(h) O(h),空间复杂度 O ( 1 ) O(1) O(1)

该题最直观的思路就是采用二叉树的中序遍历,逐一遍历二叉树的每个节点。但由于本题是二叉搜索树,因此我们可以利用二叉搜索树的特性,每当到达一个节点就比较根节点的值和节点 p 的值,如果当前节点的值比节点 p 的值大,我们就暂存其节点,并向其左孩子遍历;如果当前节点的值比节点 p 的值要小,那么我们就向其右孩子遍历,直至找到所有比 p 节点值大的节点中值最小的那一个。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        TreeNode cur = root;
        TreeNode res = null;  // 返回结果
        while (cur != null) {
            if (cur.val > p.val) {
                res = cur;
                cur = cur.left;  // 当前节点值大于p节点值,暂存当前节点,并寻找大于p节点值中最小的节点
            } else {
                cur = cur.right;
            }
        }
        return res;        
    }
}

在这里插入图片描述

8. 剑指 Offer II 054. 所有大于或等于节点的值之和 – P146

给定一个二叉搜索树,请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。

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

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

在这里插入图片描述

8.1 倒序的中序遍历 – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( h ) O(h) O(h)

Key:二叉搜索树的中序遍历按照节点值从小到大按顺序遍历,也就是当遍历到某个节点时比该节点小的节点都已经遍历过了,而本题要求的是将每个节点的值替换成树中大于或者等于该节点值的所有节点值之和,因此可以倒序中序遍历,从右 根 左的顺序,从大到小进行遍历,这样就保证了只需一次遍历即可求出节点值的和。(当然了我们也可以通过两次遍历,第一次先求出所有节点值的和,第二次依次遍历用 total - sum 得到需要的值)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // Soultion1:倒序的中序遍历(右 根 左)
    public TreeNode convertBST(TreeNode root) {
        Stack<TreeNode> sk = new Stack<>();
        TreeNode cur = root;
        int sum = 0;
        while (cur != null || !sk.isEmpty()) {
            while (cur != null) {
                sk.push(cur);
                cur = cur.right;
            }

            cur = sk.pop();
            sum += cur.val;
            cur.val = sum;
            cur = cur.left;
        }
        return root;
    }
}

在这里插入图片描述

9. 剑指 Offer II 055. 二叉搜索树迭代器 – P148

实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器:

  • BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指针应初始化为一个不存在于 BST 中的数字,且该数字小于 BST 中的任何元素。
  • boolean hasNext() 如果向指针右侧遍历存在数字,则返回 true ;否则返回 false
  • int next()将指针向右移动,然后返回指针处的数字。

……
注意,指针初始化为一个不存在于 BST 中的数字,所以对 next() 的首次调用将返回 BST 中的最小元素。

9.1 二叉搜索树中序遍历:过程拆分 – O(1)(⭐)

时间复杂度 O ( 1 ) O(1) O(1),空间复杂度 O ( h ) O(h) O(h)

该题根据不同的限制条件有不同的解法,如:

  1. 可以改变输入的二叉树结构:那么我们就可以将二叉搜索树展平,相当于对链表的遍历;
  2. 如果不可以改变输入的二叉树结构:那么我们可以在二叉搜索树遍历的时候用链表将节点记录下来,这样的缺点就是需要使用额外的存储结构;
  3. 如果不可以改变输入的二叉树结构,且不能使用额外的存储空间:拆分二叉搜素树中序遍历的过程也是可以的,我们需要理解 二叉搜索树中序遍历 本身的外循环判断条件就可以作为 hasNext 的判断,然后通过 next ,每调用一次,进行一次循环遍历即可。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class BSTIterator {
    TreeNode cur;
    Stack<TreeNode> sk;

    public BSTIterator(TreeNode root) {  // 构造函数:输入二叉搜索树的根节点初始化该迭代器
        cur = root;
        sk = new Stack<>();
    }
    
    public int next() {  // 返回二叉搜索树中下一个最小的节点的值
        while (cur != null) {
            sk.push(cur);
            cur = cur.left;
        }

        cur = sk.pop();
        int val = cur.val;
        cur = cur.right;
        return val;
    }
    
    public boolean hasNext() {  // 返回二叉搜索树是否还有下一个节点
        return cur != null || !sk.isEmpty();
    }
}

/**
 * Your BSTIterator object will be instantiated and called as such:
 * BSTIterator obj = new BSTIterator(root);
 * int param_1 = obj.next();
 * boolean param_2 = obj.hasNext();
 */

在这里插入图片描述

10. 剑指 Offer II 056. 二叉搜索树中两个节点的值之和 – P150

给定一个二叉搜索树的 根节点 root 和一个整数 k , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 k 。假设二叉搜索树中节点的值均唯一。

在这里插入图片描述

10.1 中序遍历 + HashSet – O(n)(⭐)

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)

Key:该题可以看成二叉树版的两数之和,常规做法就是通过顺序遍历,找出该数据结构中的数据是否存在 target - cur.val,如果存在,则说明该数据结构中存在两数之和等于 target,后序进阶可能会要求输出符合条件的两数的下标值,改用 HashMap 在记录值的同时存储下标即可解决。
……
Ps:当然以上做法并未考虑二叉搜索树这一要素,如想进一步优化,我们可以利用二叉搜素树的特点,将时间复杂度降低至 O(h),通过数组双指针的策略来进行数据的遍历,但需要编写专门的二叉搜索树迭代器(正序和倒序都需要)。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean findTarget(TreeNode root, int k) {
        Set<Integer> st = new HashSet<>();
        Deque<TreeNode> sk = new ArrayDeque<>();  // stack换成Deque可以提高速度
        TreeNode cur = root;

        while (cur != null || !sk.isEmpty()) {  // 中序遍历
            while (cur != null) {
                sk.push(cur);
                cur = cur.left;
            }
            cur = sk.pop();
            if (st.contains(k-cur.val)) {  // 常规套路(两数之和)
                return true;
            }

            st.add(cur.val);  // 记录每一次遍历到的值
            cur = cur.right;
        } 
        return false;
    }
}

在这里插入图片描述

11. 剑指 Offer II 057. 值和下标之差都在给定的范围内 – P155

给你一个整数数组 nums 和两个整数 kt 。请你判断是否存在 两个不同下标 ij,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k
如果存在则返回 true,不存在返回 false

在这里插入图片描述

11.1 平衡二叉搜索树:TreeSet – O(nlogn)(⭐)

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)

Key:该题最常规的做法就是遍历所有数字,然后每遍历到一个数字就判断该数字的前 k 个数字与其差的绝对值是否小于等于 t,这种方法的时间复杂度为 O(nk)。
……
除以上方法外,还有一种巧妙的方法可以时间复杂度降低至 O(n),此时我们需要借助 HashMap,并通过给遍历的每个数字划分桶号(这里我们可以理解为数据范围,0-t 范围入 0 桶,t+1 - 2t+1范围入 1 桶,以此类推),这样我们只要找到一个桶中有两个数即可判断是否有数字满足条件;如果没有,我们还可以查看当前数字所在桶的前后两桶中是否有数字可以和当前数字组合满足条件,循环遍历一遍即可解决问题。
……
本题下方给出的代码属于另一种方法,属于对 TreeSet 性质的运用,通过 TreeSet 我们可以以 O(logn) 的时间复杂度得出一个数字对应集合中 ≥的最小值,以及 ≤的最大值,对这两个数字进行判断,即可得出当前数字与其前 k 个数字之间的一个数据范围,然后得出答案。

class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        TreeSet<Long> set = new TreeSet<>();  // 注意包装类应选择Long,int有溢出风险
        
        for (int i = 0; i < nums.length; i++) {

            Long lower = set.floor((long)nums[i]);  // 下取值,找小最大
            if (lower != null && nums[i] - lower <= t) return true;

            Long higher = set.ceiling((long)nums[i]);  // 上取值,找大最小
            if (higher != null && higher - nums[i] <= t) return true;

            set.add((long) nums[i]);

            if (i >= k) {  // 移除多余数值,set只保留当前数字的前k个数值
                set.remove((long)nums[i-k]);
            }
        }
        return false;
    }
}

在这里插入图片描述

12. 剑指 Offer II 058. 日程表 – P157

请实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内没有其他安排,则可以存储这个新的日程安排。
MyCalendar 有一个 book(int start, int end)方法。它意味着在 startend 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end
当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订。
每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。

12.1 平衡二叉搜索树:TreeMap – O(nlogn)(⭐)

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)

常规方法:直接遍历,通过 ArrayList<int[]> 对数据进行存储,每次 book(start, end) 操作都会对 ArrayList 进行一次遍历,比较当前事件是否与已经存在的事件之间存在冲突,如果冲突返回 false,无冲突则将当前事件加入到集合中,并返回 true。
……
利用二叉搜索树的特性:使用 TreeSet 或 TreeMap 对数据进行存储,利用其特有的 API — floorEntry() / ceilEntry() 找出小最大和大最小,然后与当前事件的时间范围进行比较即可,节省了比较次数。
……
还可以使用线段树的方法,详细内容可参考:方法三:线段树

TreeMap 实现的接口、继承的类,如下图所示:
在这里插入图片描述

class MyCalendar {
    TreeMap<Integer,Integer> map;  // 写成 Map 不行,会出现 error: cannot find symbol [in MyCalendar.java] 错误,这是因为其中的floorEntry()以及ceilingEntry()方法是源自于public interface NavigableMap<K,V>接口的。

    public MyCalendar() {
        map = new TreeMap<>();
    }
    
    public boolean book(int start, int end) {
        Map.Entry<Integer,Integer> event = map.floorEntry(start);  // 下取值,找到比当前开始时间低的最近的开始事件
        if (event != null && event.getValue() > start) return false;  // 判断找到的事件的结束时间是否超过了当前的开始时间

        event = map.ceilingEntry(start);  // 上取值,找到比当前开始时间高的最近开始事件
        if (event != null && event.getKey() < end) return false;  // 判断找到的事件的开始时间是否早于当前的结束时间

        map.put(start, end);  // 都没有,则说明当前事件符合要求,可以放入集合
        return true;
    }
}
/**
 * Your MyCalendar object will be instantiated and called as such:
 * MyCalendar obj = new MyCalendar();
 * boolean param_1 = obj.book(start,end);
 */

在这里插入图片描述

13. 继续提升:加练题目

🎈 可参考:

  • 树 · SharingSource/LogicStack-LeetCode Wiki · GitHub
  • 二叉树 · SharingSource/LogicStack-LeetCode Wiki · GitHub
  • 线段树 · SharingSource/LogicStack-LeetCode Wiki · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/114800.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

吴恩达《机器学习》4-1->4-5:多变量线性回归

一、引入多维特征 在多维特征中&#xff0c;我们考虑的不再是单一的特征&#xff0c;而是一组特征&#xff0c;例如房价模型中可能包括房间数、楼层等多个特征。这些特征将组成一个向量&#xff0c;表示为(&#x1d465;₁, &#x1d465;₂, . . . , &#x1d465;ₙ)&#x…

【腾讯云HAI域探秘】速通腾讯云HAI

速览HAI 产品简介 腾讯云高性能应用服务(Hyper Application lnventor&#xff0c;HA)&#xff0c;是一款面向 Al、科学计算的 GPU 应用服务产品&#xff0c;为开发者量身打造的澎湃算力平台。无需复杂配置&#xff0c;便可享受即开即用的GPU云服务体验。在 HA] 中&#xff0c;…

配置git并把本地项目连接github

一.配置git 1.下载git&#xff08;Git&#xff09;&#xff0c;但推荐使用国内镜像下载&#xff08;CNPM Binaries Mirror&#xff09; 选好64和版本号下载&#xff0c;全部点下一步 下载完成后打开终端&#xff0c;输入 git --version 出现版本号则说明安装成功 然后继续…

Redis统计大法:挖掘数据的四重宝藏【redis第五部分】

Redis统计大法&#xff1a;挖掘数据的四重宝藏 前言第一&#xff1a;redis集合统计简介第二&#xff1a;聚合统计->数据的综合分析总和&#xff08;Sum&#xff09;&#xff1a;平均值&#xff08;Average&#xff09;中位数&#xff08;Median&#xff09; 第三&#xff1a…

【C++】多态 ⑪ ( 纯虚函数和抽象类 | 纯虚函数语法 | 抽象类和实现 | 代码示例 )

文章目录 一、纯虚函数和抽象类1、纯虚函数2、纯虚函数语法3、抽象类和实现 二、完整代码示例 一、纯虚函数和抽象类 1、纯虚函数 纯虚函数 : 在 C 语言中 , " 纯虚函数 " 是 特殊类型的 虚函数 , " 纯虚函数 " 在 父类 中 声明 , 但是没有实现 ; 抽象类 …

从瀑布模式到水母模式:ChatGPT引领软件研发的革新之路

ChatGPT引领软件研发的革新之路 概述操作建议本书优势 内容简介作者简介专家推荐读者对象目录直播预告写在末尾&#xff1a; 主页传送门&#xff1a;&#x1f4c0; 传送 概述 计算机技术的发展和互联网的普及&#xff0c;使信息处理和传输变得更加高效&#xff0c;极大地改变了…

Azure 机器学习 - 使用 AutoML 和 Python 训练物体检测模型

目录 一、Azure环境准备二、计算目标设置三、试验设置四、直观呈现输入数据五、上传数据并创建 MLTable六、配置物体检测试验适用于图像任务的自动超参数扫描 (AutoMode)适用于图像任务的手动超参数扫描作业限制 七、注册和部署模型获取最佳试用版注册模型配置联机终结点创建终…

JUL 日志

JUL日志级别 日志分为7个级别&#xff0c;详细信息我们可以在Level类中查看&#xff1a; SEVERE&#xff08;最高值&#xff09;- 一般用于代表严重错误WARNING - 一般用于表示某些警告&#xff0c;但是不足以判断为错误INFO &#xff08;默认级别&#xff09; - 常规消息CON…

Hadoop HDFS(分布式文件系统)

一、Hadoop HDFS(分布式文件系统) 为什么要分布式存储数据 假设一个文件有100tb&#xff0c;我们就把文件划分为多个部分&#xff0c;放入到多个服务器 靠数量取胜&#xff0c;多台服务器组合&#xff0c;才能Hold住 数据量太大&#xff0c;单机存储能力有上限&#xff0c;需要…

QT在线安装5.15之前的版本(下载速度飞快)

使用最新的QT在线安装器&#xff0c;安装QT版本时只能安装5.15以及之后的版本&#xff0c;安装QT5.15之前的版本只能通过离线安装的方式&#xff0c;离线安装后还要自己去配置QT&#xff0c;离线安装还有个问题的&#xff0c;后续维护比较麻烦&#xff0c;QT的维护工具还要自己…

springboot2.x使用@RestControllerAdvice实现通用异常捕获

文章目录 demo地址实现效果引入基础类准备1.通用枚举与错误状态枚举2.定义通用返回结果3.自定义业务异常 统一异常捕获测试 demo地址 demo工程地址 实现效果 当我们输入1时&#xff0c;正常的返回通用的响应结果当我们输入2时&#xff0c;抛出异常&#xff0c;被捕获然后返回…

macOS 安装brew

参考链接&#xff1a; https://mirrors4.tuna.tsinghua.edu.cn/help/homebrew/ https://www.yii666.com/blog/429332.html 安装中科大源的&#xff1a; https://zhuanlan.zhihu.com/p/470873649

《TCP/IP详解 卷一:协议》第5章的IPv4数据报的Checksum(校验和)字段的计算(这里才能解开你的困惑)

首先&#xff0c;我当你看过书&#xff0c;但是比较懵。 1&#xff0c;实例说明Checksum(校验和)的计算步骤 直奔主题&#xff0c;分析一下这个Checksum&#xff08;校验和&#xff09;怎么算出来的。 先用Wireshark随便抓一个UDP或TCP包分析一下。 如上面&#xff0c;我们得…

Python数据分析:在职场中的竞争优势

前言 在职场中&#xff0c;技能的重要性是不言而喻的。越来越多的职位要求员工具备数据分析能力&#xff0c;而Python作为一种强大的数据分析工具&#xff0c;正在成为职场中的“利器”。然而&#xff0c;尽管Python数据分析提供了巨大的优势&#xff0c;许多人依然未能掌握这…

团队表 -多级团队设计

团队表 -多级团队设计 user_team团队表 &#xff0c;如果存在子团队 1.我们可以通过每一个团队字段加一个parentid &#xff08;相当于一对多的关系&#xff09; 2.还可以设置一个字段CodingNum,比如这样: //系统为了管理查询团队自动生成的有序编号 可以使用3位数代表一个…

06.Oracle数据备份与恢复

Oracle数据备份与恢复 一、通过RMAN方式备份二、使用emp/imp和expdb/impdb工具进行备份和恢复三、使用Data guard进行备份与恢复 一、通过RMAN方式备份 通过 RMAN&#xff08;Oracle 数据库备份和恢复管理器&#xff09;方式备份 Oracle 数据库&#xff0c;可以使用以下步骤&a…

【漏洞复现】74cms任意文件读取

漏洞描述 74CMS 是一款国内用的比较多招聘网站管理系统&#xff08;Job Board CMS&#xff09;&#xff0c;专注于招聘和人力资源领域的网站建设&#xff0c;存在任意文件读取漏洞 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&#xff0c…

Temp directory ‘C:\WINDOWS\TEMP‘ does not exist

问题描述 解决方法 管理员权限问题&#xff0c;进入temp文件夹更改访问权限即可。 点击 temp文件夹 属性 -> 安全 -> 高级 -> 更改主体Users权限 给读取和写入权限 参考博客 开发springboot项目时无法启动Temp directory ‘C: \WINDOWS\TEMP‘ does not exist

K-edge 和逃逸问题

一 k-eage基本概念 1 k-edge概念 K-edge称为K边, 其物理意义是高原子序数物质原子内部K层自由电子, 易与特定能量下X射线光子发生光电吸收作用, 导致对该能量的X射线光子吸收特别大。 而K-edge特性表现为X射线与物质发生相互作用时, 其衰减系数随着能量的增加而逐渐减小, 但在…

Postgresql批量按照顺序更新某一个字段

如批量更新采购订单行sequence字段&#xff0c;按照订单行id的顺序赋值1&#xff0c;2&#xff0c;3&#xff0c;4...&#xff1a; UPDATE purchase_order_line_copy1 SET sequence subquery.new_sequence FROM (SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS new_sequence…