作者: CheungQ

LeetCode刷题【1339】分裂二叉树的最大乘积

给你一棵二叉树,它的根为 root 。请你删除 1 条边,使二叉树分裂成两棵子树,且它们子树和的乘积尽可能大。

由于答案可能会很大,请你将结果对 10^9 + 7 取模后再返回。

 

示例 1:

输入:root = [1,2,3,4,5,6]
输出:110
解释:删除红色的边,得到 2 棵子树,和分别为 11 和 10 。它们的乘积是 110 (11*10)

示例 2:

输入:root = [1,null,2,3,4,null,null,5,6]
输出:90
解释:移除红色的边,得到 2 棵子树,和分别是 15 和 6 。它们的乘积为 90 (15*6)

示例 3:

输入:root = [2,3,9,10,7,8,6,5,4,11,1]
输出:1025

示例 4:

输入:root = [1,1]
输出:1

 

提示:

  • 每棵树最多有 50000 个节点,且至少有 2 个节点。
  • 每个节点的值在 [1, 10000] 之间。
Related Topics
  • 深度优先搜索
  • 二叉树
  • \n
  • 👍 59
  • 👎 0
  • 题解

    1.求出二叉树总和

    2.找到一个节点上的总和,最接近于树上总和的一半

    3.求出面积即是结果

    4.为啥是一半呢,看代码中间的注释

    
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    
    /**
     * 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 {
        double sum = 0;
        double halfSum = 0;
        double nearHalf = 0;
        public int maxProduct(TreeNode root) {
            //两数之和固定,两数需要乘积最大化,则两个数应当无限接近,最优解为两数之和的一半
            //类似于一根固定长的绳子摆成矩形的两个相邻边,要求使矩形面积最大化,这是一个高中还是初中数学题来着
            //假设这条绳子长为 2x,分成两段
            // 一段长度为 x - t , 另一段的长度就为 x + t
            //( x - t )* ( x + t ) <= x^2
            // x^2 + x*t - x*t - t^2  <= x^2
            // x^2 - t^2 <= x^2
            // 有且只有当t = 0 的时候面积最大
            sum = dfs(root);
            halfSum = sum/2;
            dfsNearHalf(root);
            return (int)((sum-nearHalf)*nearHalf%1000000007);
    
        }
    
    
        private double dfsNearHalf(TreeNode node){
            if(node==null){
                return 0;
            }
            double tempSum = node.val + dfsNearHalf(node.left) + dfsNearHalf(node.right);
            nearHalf = Math.abs(nearHalf - halfSum) > Math.abs(tempSum-halfSum)?tempSum:nearHalf;
            return tempSum;
        }
    
    
        private double dfs(TreeNode node){
            if(node==null){
                return 0;
            }
            return node.val + dfs(node.left) + dfs(node.right);
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    其实也可以第一DFS之后把结果缓存起来,在然后遍历缓存起来的结果,找到最接近的那个,或者在第二次dfs的之后从缓存里取结果,而不是再算一次,但是这两种我都试了下。效率还没有再算一次的效率高。

    LeetCode刷题【551】学生出勤记录 I

    给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

    • 'A':Absent,缺勤
    • 'L':Late,迟到
    • 'P':Present,到场

    如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

    • 总出勤 计,学生缺勤('A'严格 少于两天。
    • 学生 不会 存在 连续 3 天或 3 天以上的迟到('L')记录。

    如果学生可以获得出勤奖励,返回 true ;否则,返回 false

     

    示例 1:

    输入:s = "PPALLP"
    输出:true
    解释:学生缺勤次数少于 2 次,且不存在 3 天或以上的连续迟到记录。
    

    示例 2:

    输入:s = "PPALLL"
    输出:false
    解释:学生最后三天连续迟到,所以不满足出勤奖励的条件。
    

     

    提示:

    • 1 <= s.length <= 1000
    • s[i]'A''L''P'
    Related Topics
  • 字符串
  • \n
  • 👍 89
  • 👎 0
  • 题解

    8月17日每日一题

    一次遍历。两个变量统计判断,没啥复杂的

    public boolean checkRecord(String s) {
            if (s == null || s.length() <=1){
                return true;
            }
            // 'A':Absent,缺勤
            // 'L':Late,迟到
            // 'P':Present,到场
            if (s.length()==2){
                return !(s.charAt(0)=='A'&& s.charAt(1)=='A');
            }
    
            int aCount = 0;
            int lCount = 0;
            //先把前两位的算下
            for (int i = 0; i < 2; i++) {
                if (s.charAt(i)=='A'){
                    aCount++;
                }
                if (s.charAt(i)=='L'){
                    lCount++;
                }else{
                    lCount=0;
                }
            }
            //从第三天开始需要判断
            for (int i = 2; i < s.length(); i++) {
                if (s.charAt(i)=='A'){
                    aCount++;
                    //只要总缺勤天数达到了2,就说明不符合
                    if (aCount >= 2){
                        return false;
                    }
                    //额。。。。这个,。。没上课的时候连续迟到也归零,
                    //所以 LLA 的可以获得出勤奖励
                    //而 LLL 的得不到出勤奖励
                    lCount=0;
                }else if (s.charAt(i)=='L'){
                    //如果当天迟到了,连续迟到天数加1,
                    lCount++;
                    if (lCount == 3){
                        //当连续迟到了3天,则不符合
                        return false;
                    }
                }else{
                    //如果当天没迟到,则连续迟到的记录归零
                    lCount=0;
                }
            }
            return true;
        }

    不过有点点点小问题。。我注解里写的

    LLA 的可以获得出勤奖励,而 LLL 的得不到出勤奖励,第三天假如迟到的话不如直接缺勤…….emmmm

    LeetCode刷题【971】反转二叉树以匹配先序遍历

    给你一棵二叉树的根节点 root ,树中有 n 个节点,每个节点都有一个不同于其他节点且处于 1n 之间的值。

    另给你一个由 n 个值组成的行程序列 voyage ,表示 预期 的二叉树 先序遍历 结果。

    通过交换节点的左右子树,可以 翻转 该二叉树中的任意节点。例,翻转节点 1 的效果如下:

    请翻转 最少 的树中节点,使二叉树的 先序遍历 与预期的遍历行程 voyage 相匹配 。 

    如果可以,则返回 翻转的 所有节点的值的列表。你可以按任何顺序返回答案。如果不能,则返回列表 [-1]

     

    示例 1:

    输入:root = [1,2], voyage = [2,1]
    输出:[-1]
    解释:翻转节点无法令先序遍历匹配预期行程。
    

    示例 2:

    输入:root = [1,2,3], voyage = [1,3,2]
    输出:[1]
    解释:交换节点 2 和 3 来翻转节点 1 ,先序遍历可以匹配预期行程。

    示例 3:

    输入:root = [1,2,3], voyage = [1,2,3]
    输出:[]
    解释:先序遍历已经匹配预期行程,所以不需要翻转节点。
    

     

    提示:

    • 树中的节点数目为 n
    • n == voyage.length
    • 1 <= n <= 100
    • 1 <= Node.val, voyage[i] <= n
    • 树中的所有值 互不相同
    • voyage 中的所有值 互不相同
    Related Topics
  • 深度优先搜索
  • 二叉树
  • \n
  • 👍 70
  • 👎 0
  • 题解

    还是考验的对先序遍历的理解,如果同时有左右子节点,左子树的遍历节点在右子树的遍历节点之前。

    所以只要判断右子节点的是否出现在当前节点的下一个(有左子节点的情况下),这种情况是需要翻转当前节点的左右子节点的。

    倘若只有一个子节点,这个子节点放在左边或者右边都不影响最终的遍历顺序。

    
    
    //leetcode submit region begin(Prohibit modification and deletion)
    
    import java.util.ArrayList;
    import java.util.Arrays;
    
    /**
     * 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 {
    
        List<Integer> res = new ArrayList<>();
        boolean breaked = false;
        int[] voyage;
        int index = 0;
        public List<Integer> flipMatchVoyage(TreeNode root, int[] voyage) {
            this.voyage = voyage;
            dfs(root);
            if (breaked){
                return Arrays.asList(-1);
            }
            return res;
        }
    
    
        private void dfs(TreeNode root){
            if (breaked){
                return;
            }
            if (root==null){
                return;
            }
    
            //中序遍历的当前节点判断
            if (root.val != voyage[index++]){
                breaked = true;
                return;
            }
            //左右对比
            if (root.left!=null && root.right !=null
                    && root.right.val == voyage[index]
            ){
                //左右调换,先右再左
                res.add(root.val);
                dfs(root.right);
                dfs(root.left);
            }else{
                dfs(root.left);
                dfs(root.right);
            }
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题【981】基于时间的键值存储

    设计一个基于时间的键值数据结构,该结构可以在不同时间戳存储对应同一个键的多个值,并针对特定时间戳检索键对应的值。

    实现 TimeMap 类:

    • TimeMap() 初始化数据结构对象
    • void set(String key, String value, int timestamp) 存储键 key、值 value,以及给定的时间戳 timestamp
    • String get(String key, int timestamp)
      • 返回先前调用 set(key, value, timestamp_prev) 所存储的值,其中 timestamp_prev <= timestamp
      • 如果有多个这样的值,则返回对应最大的  timestamp_prev 的那个值。
      • 如果没有值,则返回空字符串("")。
     

    示例:

    输入:
    ["TimeMap", "set", "get", "get", "set", "get", "get"]
    [[], ["foo", "bar", 1], ["foo", 1], ["foo", 3], ["foo", "bar2", 4], ["foo", 4], ["foo", 5]]
    输出:
    [null, null, "bar", "bar", null, "bar2", "bar2"]
    
    解释:
    TimeMap timeMap = new TimeMap();
    timeMap.set("foo", "bar", 1);  // 存储键 "foo" 和值 "bar" ,时间戳 timestamp = 1   
    timeMap.get("foo", 1);         // 返回 "bar"
    timeMap.get("foo", 3);         // 返回 "bar", 因为在时间戳 3 和时间戳 2 处没有对应 "foo" 的值,所以唯一的值位于时间戳 1 处(即 "bar") 。
    timeMap.set("foo", "bar2", 4); // 存储键 "foo" 和值 "bar2" ,时间戳 timestamp = 4  
    timeMap.get("foo", 4);         // 返回 "bar2"
    timeMap.get("foo", 5);         // 返回 "bar2"
    

     

    提示:

    • 1 <= key.length, value.length <= 100
    • keyvalue 由小写英文字母和数字组成
    • 1 <= timestamp <= 107
    • set 操作中的时间戳 timestamp 都是严格递增的
    • 最多调用 setget 操作 2 * 105
    Related Topics
  • 设计
  • 哈希表
  • 字符串
  • 二分查找
  • \n
  • 👍 135
  • 👎 0
  • 题解

    HashMap配合List的二分查找来找到指定的值。当然也可以用HashMap配合BST二叉搜索树来实现,或者更进一步AVL树,树的搜索表现应当比二分法来得更好些。

    不过其实我对这题有点点疑问

    1. 看了下timestamp一定是递增的嘛?感觉不应该是恒定递增的,常见的在业务中,并发条件下时间12345和时间12346发起的两个请求,因为网络或者硬件等因素可能是12346的请求先到服务器的,并不能预先假设这个时间一定是递增的。
    2. 题目中要求“如果有多个这样的值,则返回对应最大的 timestamp_prev 的那个值”,题目中感觉说的模棱两可,并没有明确说明是对应当前key下的前一个时间的值,还是不限定当前key下的前一个时间的值

    先放个二分法查找的在这,配合二叉树的写法回头加上

    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class TimeMap {
    
        class Node {
            String val;
            int timestamp;
    
            public Node(String val, int timestamp) {
                this.val = val;
                this.timestamp = timestamp;
            }
        }
    
        HashMap<String, List<Node>> map;
    
        /** Initialize your data structure here. */
        public TimeMap() {
            map = new HashMap<>();
        }
    
        public void set(String key, String value, int timestamp) {
            Node node = new Node(value,timestamp);
            if (map.containsKey(key)){
                map.get(key).add(node);
            }else{
                List<Node> list = new ArrayList<>();
                list.add(node);
                map.put(key,list);
            }
        }
    
        public String get(String key, int timestamp) {
            if (!map.containsKey(key)){
                return "";
            }
            String val = "";
    //            for (int i = 0; i < map.get(key).size(); i++) {
    //                if (map.get(key).get(i).timestamp <= timestamp){
    //                    val = map.get(key).get(i).val;
    //                }
    //                if (map.get(key).get(i).timestamp > timestamp){
    //                    break;
    //                }
    //            }//直接遍历查找超时了,放弃
            int left = 0, right = map.get(key).size()-1;
            while (left <= right){
                int mid =  (left + right) >> 1;
                if (map.get(key).get(mid).timestamp <= timestamp){
                    left = mid + 1;
                    val = map.get(key).get(mid).val;
                }else{
                    right = mid-1;
                }
            }
            return val;
        }
    }
    

    LeetCode刷题【526】优美的排列

    假设有从 1 到 N 的 个整数,如果从这 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:

    1. 第 位的数字能被 整除
    2. i 能被第 i 位上的数字整除

    现在给定一个整数 N,请问可以构造多少个优美的排列?

    示例1:

    输入: 2
    输出: 2
    解释: 
    
    第 1 个优美的排列是 [1, 2]:
      第 1 个位置(i=1)上的数字是1,1能被 i(i=1)整除
      第 2 个位置(i=2)上的数字是2,2能被 i(i=2)整除
    
    第 2 个优美的排列是 [2, 1]:
      第 1 个位置(i=1)上的数字是2,2能被 i(i=1)整除
      第 2 个位置(i=2)上的数字是1,i(i=2)能被 1 整除
    

    说明:

    1. N 是一个正整数,并且不会超过15。
    Related Topics
  • 位运算
  • 数组
  • 动态规划
  • 回溯
  • 状态压缩
  • \n
  • 👍 172
  • 👎 0
  • 题解

    8月16日的每日一题,一开始拿到题目有点不理解,反复看了几遍,大意就是,1-15的数字,任意排序,索引0的位置忽略(这样实际需要的是一个长度为16的数组),保证索引i的位置上的数字,要么能整除i,要么能被i整除。

    明白了题意就好办了,和之前的拨轮盘一样,用回溯来处理。

    
    import java.util.*;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
    
        Map<Integer, List<Integer>> map = new HashMap<>();
        Set<Integer> visited = new HashSet<>();
        int count = 0;
        int length = 0;
        public int countArrangement(int n) {
            length = n;
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    if (!map.containsKey(i)){
                        map.put(i,new ArrayList<>());
                    }
                    if (i%j==0 || j%i==0){
                        map.get(i).add(j);
                    }
                }
            }
            backtrack(1);
            return count;
        }
    
        void backtrack(int index) {
            if (visited.size() == length){
                count++;
                return;
            }
            for (Integer matchNum : map.get(index)) {
                if (!visited.contains(matchNum)){
                    visited.add(matchNum);
                    backtrack(index+1);
                    visited.remove(matchNum);
                }
            }
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题【1008】前序遍历构造二叉搜索树

    返回与给定前序遍历 preorder 相匹配的二叉搜索树(binary search tree)的根结点。

    (回想一下,二叉搜索树是二叉树的一种,其每个节点都满足以下规则,对于 node.left 的任何后代,值总 < node.val,而 node.right 的任何后代,值总 > node.val。此外,前序遍历首先显示节点 node 的值,然后遍历 node.left,接着遍历 node.right。)

    题目保证,对于给定的测试用例,总能找到满足要求的二叉搜索树。

     

    示例:

    输入:[8,5,1,7,10,12]
    输出:[8,5,10,1,7,null,12]
    
    

     

    提示:

    • 1 <= preorder.length <= 100
    • 1 <= preorder[i] <= 10^8
    • preorder 中的值互不相同
    Related Topics
  • 二叉搜索树
  • 数组
  • 二叉树
  • 单调栈
  • \n
  • 👍 161
  • 👎 0
  • 题解

    思路和LeetCode刷题【剑指 Offer 33】二叉搜索树的后续遍历序列一样了,根据前序遍历的特性,一样的第一位为根节点,后面的序列中,比根节点小的为左子树上的节点,比根节点大的为右子树上的节点

    
    
    //leetcode submit region begin(Prohibit modification and deletion)
    /**
     * 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 TreeNode bstFromPreorder(int[] preorder) {
            return buildTree(preorder,0,preorder.length-1);
        }
    
    
    
        private TreeNode buildTree(int[] preorder,int left ,int right){
            //必要判断条件,如果区间范围不成立,表名没有这个节点
            if (left > right || left < 0 || right < 0) return null;
            //根据前序遍历的特性,该连续数组的第一位为这个树的根节点
            TreeNode node = new TreeNode(preorder[left]);
            if (left==right){
                return node;
            }
            //首位为根节点,往后进一格,处理子节点
            left++;
            //预设一个-1标记
            int rightStart = -1;
            for (int i = left; i <= right ; i++) {
                if (preorder[i]>node.val){
                    //如果当前节点比根节点大
                    //那么从当前节点开始,往后的都是右子节点
                    rightStart = i;
                    break;
                }
            }
            //如果没有找到右子节点的话,因为之前预设了-1,需要额外判断下右子节点的开始位置
            //不然还是-1,
            //没有找到右子节点的区间的话,说明左子节点区间应该为从left到right都是
            if (rightStart == -1){
                rightStart = right+1;
            }
            //分别拿出左右子节点区间继续判断处理
            node.left = buildTree(preorder,left,rightStart-1);
            node.right = buildTree(preorder,rightStart,right);
            return node;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    LeetCode刷题【剑指 Offer 33】二叉搜索树的后续遍历序列

    输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

     

    参考以下这颗二叉搜索树:

         5
        / \
       2   6
      / \
     1   3

    示例 1:

    输入: [1,6,3,2,5]
    输出: false

    示例 2:

    输入: [1,3,2,6,5]
    输出: true

     

    提示:

    1. 数组长度 <= 1000
    Related Topics
  • 二叉搜索树
  • 递归
  • 二叉树
  • 单调栈
  • \n
  • 👍 321
  • 👎 0
  • 题解

    以一个正确的序列为例,根据后续遍历的性质

    1,3,2,6,5

    应该是根节点,从根节点往前。比他大的部分应该为根节点的右子树,比根节点的小的应该是左子树,这样找到了[1,3,2]和[6]两个区间。而[6]只有一个节点,可以不做更多判断了。左边的[1,3,2]区间中,2必然是根节点,按照之前的方法,[1]是2的左子树上的节点,[3]是2的右子树上的节点。

    在这样的分割的中还需要满足一个特征,就是比根节点小的部分和比根节点大的部分的应当是正好分割的,不能有重叠,也不能有中间空的部分。

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public boolean verifyPostorder(int[] postorder) {
            return validate(postorder,0,postorder.length-1);
        }
    
    
        private boolean validate(int[] postorder, int left ,int right){
            //如果左指针大于等于右指针了,说明当前节点以及没有子节点了,自然是符合条件的】
            //小于0的判断是从下面leftEnd rightStart 的初始赋值来的,说明没有这个子节点
            if (left>=right || left < 0 || right < 0) return true;
            //找到这段数组对应的根节点,根据后序遍历的特性,即为这段数组的最后一位
            int rootNum = postorder[right];
            //初始赋值
            int leftEnd = -1;
            int rightStart = -1;
            //开始遍历
            for (int i = left; i < right; i++) {
                if (postorder[i] < rootNum){
                    //如果这个值小于根节点的值,说明这个节点应该是在左子树中
                    leftEnd = i;
                }
                if (postorder[i] > rootNum && rightStart == -1){
                    //如果这个值大于根节点的值,说明这个节点应该是右子树上的
                    //且rightStart == -1 表示是第一次碰到的
                    rightStart = i;
                }
            }
            //此时如果符合条件的话,应该是 leftEnd 在 rightStart 的左边一位
            //或者  没有左子树:leftEnd == -1 且rightStart == left
            //或者  没有右子树:rightStart == -1 且leftEnd == right-1
            boolean validateResult = (leftEnd>-1 &&  rightStart> -1 && leftEnd+1== rightStart)
                    || ( leftEnd == -1 && rightStart == left )
                    || ( rightStart == -1 && leftEnd == right-1);
            //自身验证完了,还要对分割好了的子序列的有效性判断
            if (validateResult){
                return validate( postorder, left, leftEnd ) && validate( postorder, rightStart, right-1 );
            }
            return false;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)