月度归档: 2021年8月

LeetCode刷题【1110】删点成林

给出二叉树的根节点 root,树上每个节点都有一个不同的值。

如果节点值在 to_delete 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。

返回森林中的每棵树。你可以按任意顺序组织答案。

 

示例:

输入:root = [1,2,3,4,5,6,7], to_delete = [3,5]
输出:[[1,2,null,4],[6],[7]]

 

提示:

  • 树中的节点数最大为 1000
  • 每个节点都有一个介于 1 到 1000 之间的值,且各不相同。
  • to_delete.length <= 1000
  • to_delete 包含一些从 1 到 1000、各不相同的值。
Related Topics
  • 深度优先搜索
  • 二叉树

  • 👍 134
  • 👎 0
  • 题解

    二叉树搜索删除对应节点,看到题目第一反应应该是:因为提供的删除集合是个数组集合,所以先把to_delete转换成hashset,转换成hash查找提供的删除节点

    然后就是遍历各个节点找到要删除的节点。删除的节点需要把左右子树放入返回集合中,但是需要判断左右子树的根节点是不是需要删除的节点,如果是则不放入结果结合。

    另外还有一个要点,遍历最后需要把删除的节点删除掉

    class Solution {
    
        Set<Integer> deleteSet = new HashSet<>();
        List<TreeNode> list = new ArrayList<>();
        public List<TreeNode> delNodes(TreeNode root, int[] toDelete) {
            for (int i : toDelete) {
                deleteSet.add(i);
            }
            if (!ifDelete(root.val )){
                list.add(root);
            }
            collectDfs(root);
            return list;
        }
    
    
        public void collectDfs(TreeNode root){
            if (root == null){
                return;
            }
            TreeNode left = root.left;
            TreeNode right = root.right;
            if (ifDelete(root.val)){
                root = null;
                if (left !=null && !ifDelete(left.val)){
                    list.add(left);
                }
                if (right !=null && !ifDelete(right.val)){
                    list.add(right);
                }
            }
            collectDfs(left);
            collectDfs(right);
            if (root==null)return;
            if (root.left != null && ifDelete(root.left.val)){
                root.left = null;
            }
            if (root.right != null && ifDelete(root.right.val)){
                root.right = null;
            }
        }
    
    
        public boolean ifDelete(int val){
            return deleteSet.contains(val);
        }
    }

    其实本来一开始并不是这么写的,本来写了两次dfs,第一次会把删除的节点的值标记成-1,第二次dfs的时候根据节点值为-1来判断,但是实际跑出来效果速度不行。还是就整合起来一次dfs搞定

    LeetCode刷题【965】单值二叉树

    如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。

    只有给定的树是单值二叉树时,才返回 true;否则返回 false

     

    示例 1:

    输入:[1,1,1,1,1,null,1]
    输出:true
    

    示例 2:

    输入:[2,2,2,5,2]
    输出:false
    

     

    提示:

    1. 给定树的节点数范围是 [1, 100]
    2. 每个节点的值都是整数,范围为 [0, 99] 。
    Related Topics
  • 深度优先搜索
  • 广度优先搜索
  • 二叉树

  • 👍 86
  • 👎 0
  • 题解

    今天是继续练习二叉树相关的题目,深化学习掌握

    本题是直观的二叉树遍历要求的题目,可以深搜也可以广搜,深搜每递进一层都需要开启一层额外的栈空间,最终和树的高度是相关的,广搜需要新建额外的队列保存下一层需要遍历的结果集。个人还是偏向深搜来解题一点。

    多加个notSame值判断当前判断的情况,可以提前中断,如果有不同了,不用遍历完全树,后续其他节点也都不用再遍历了。当然如果是广搜的要实现提前中断就很容易了。直接终止while循环退出即可。

    class Solution {
        boolean notSame = false;
        public boolean isUnivalTree(TreeNode root) {
            return isSameTree(root);
        }
    
        public boolean isSameTree(TreeNode root){
            if (notSame){
                return false;
            }
            if (root==null){
                return true;
            }
            if (root.left != null && root.left.val != root.val){
                notSame = true;
                return false;
            }
            if (root.right != null && root.right.val != root.val){
                notSame = true;
                return false;
            }
            if (isSameTree(root.left) && isSameTree(root.right)){
                return true;
            }
            notSame = true;
            return false;
        }
    
    }

    LeetCode刷题【797】所有可能的路径

    给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序

    二维数组的第 i 个数组中的单元都表示有向图中 i 号节点所能到达的下一些节点,空就是没有下一个结点了。

    译者注:有向图是有方向的,即规定了 a→b 你就不能从 b→a 。

     

    示例 1:

    输入:graph = [[1,2],[3],[3],[]]
    输出:[[0,1,3],[0,2,3]]
    解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
    

    示例 2:

    输入:graph = [[4,3,1],[3,2,4],[3],[4],[]]
    输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]
    

    示例 3:

    输入:graph = [[1],[]]
    输出:[[0,1]]
    

    示例 4:

    输入:graph = [[1,2,3],[2],[3],[]]
    输出:[[0,1,2,3],[0,2,3],[0,3]]
    

    示例 5:

    输入:graph = [[1,3],[2],[3],[]]
    输出:[[0,1,2,3],[0,3]]
    

     

    提示:

    • n == graph.length
    • 2 <= n <= 15
    • 0 <= graph[i][j] < n
    • graph[i][j] != i(即,不存在自环)
    • graph[i] 中的所有元素 互不相同
    • 保证输入为 有向无环图(DAG)
    Related Topics
  • 深度优先搜索
  • 广度优先搜索
  • 回溯

  • 👍 161
  • 👎 0
  • 题解

    8月25日每日一题

    有向,无环这两个输入参数的特性使得这题的难度直线下降,不用考虑访问到已访问过的节点,不要考虑进入环的可能性

    直接就根据入参重新构建一个反向的关系图,如,

    原本入参告诉了从0节点可以前往4,3,1节点,而新的图上0的位置记录的是可以到达0节点的上一个节点有哪些,0节点是初始出发节点,显然没有,不如换个终点4节点。

    可知,能到达4节点的上一个节点可能为【0,1,3】,0节点为初始节点,可以结束,1节点的上一个节点可能为【0】结束,3节点的上一个节点可能为【0,2】,2节点的上一个节点可能为【1】,1节点的上一个节点可能为【0】结束。

    class Solution {
    
        boolean[][] pathMap;
        List<List<Integer>> list;
        public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
            int target = graph.length-1;
            list = new ArrayList<>();
            createMap(graph);
            List<Integer> ls = new ArrayList<>();
            ls.add(target);
            dfs(ls,target);
            return list;
        }
    
        public void dfs(List<Integer> visitList, int node){
            if (node == 0){
                list.add(new ArrayList<>(visitList));
                return;
            }
            if (pathMap[node].length > 0){
                for (int i = 0; i < pathMap[node].length; i++) {
                    if (!pathMap[node][i])continue;
                    visitList.add(0,i);
                    dfs(visitList,i);
                    visitList.remove(0);
                }
            }
        }
    
    
        public void createMap(int[][] graph){
            pathMap = new boolean[graph.length][graph.length];
            for (int i = 0; i < graph.length; i++) {
                for (int j = 0; j < graph[i].length; j++) {
                    pathMap[graph[i][j]][i] = true;
                }
            }
        }
    }

    后续

    从上面的例子可以看到第一开始从4节点找到1节点,1节点的上一个节点可能为【0】结束,而2节点后来又找到了1节点,这边可以优化下,额外记录下能到达0节点的所有点的可能路径,假设一个完整的能到达0路径是【8,6,5,1,0】

    那么需要记录下 8经过【6,5,1,0】,6经过【5,1,0】,5经过【1,0】能到达0节点。

    这样在后面再次絮叨6、5、1这些节点的时候无需再次尝试计算能否到达0节点

    补充,大意了,直接从0节点开始搜索也是可以的,之所以想要反向搜索是想要从基础数据的层面来规避最后进入死胡同的情况,比如这样的情况

    【【1,2】,【】,【】】最终结果应当是到达2节点,但是0节点也可以到达1节点,1节点最后却不能达到2节点,构建反向的路径的时候可以直接规避掉从0节点开始搜索进入1节点的情况

    LeetCode刷题【700】二叉搜索树中的搜索

    给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。

    例如,

    给定二叉搜索树:
    
            4
           / \
          2   7
         / \
        1   3
    
    和值: 2
    

    你应该返回如下子树:

          2     
         / \   
        1   3
    

    在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL

    Related Topics
  • 二叉搜索树
  • 二叉树

  • 👍 146
  • 👎 0
  • 题解

    直白简单,搜索树特性。左小又大,递归先写个,

    
    //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 searchBST(TreeNode root, int val) {
            if (root==null)return null;
            if (root.val == val)return root;
            if (root.val > val){
                return searchBST(root.left,val);
            }
            return searchBST(root.right,val);
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    结果

    解答成功:
    执行耗时:0 ms,击败了100.00% 的Java用户
    内存消耗:39.6 MB,击败了5.26% 的Java用户

    内存消耗应该是来自于递归栈空间消耗。再试着不递归写个吧

    class Solution {
        public TreeNode searchBST(TreeNode root, int val) {
            while (!(null==root || root.val==val)){
                root = root.val > val ? root.left : root.right;
            }
            return root;
        }
    }
    解答成功:
    执行耗时:0 ms,击败了100.00% 的Java用户
    内存消耗:39.2 MB,击败了62.94% 的Java用户

    勉强还行吧….

    LeetCode刷题【7】整数反转

    给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

    如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。

    假设环境不允许存储 64 位整数(有符号或无符号)。

     

    示例 1:

    输入:x = 123
    输出:321
    

    示例 2:

    输入:x = -123
    输出:-321
    

    示例 3:

    输入:x = 120
    输出:21
    

    示例 4:

    输入:x = 0
    输出:0
    

     

    提示:

    • -231 <= x <= 231 - 1
    Related Topics
  • 数学

  • 👍 3012
  • 👎 0
  • 题解

    简单题,最直接的思路就是翻转了,但是还有个符号位,所以符号位的逻辑要额外处理下。刷题刷得可嗨了……被同事看到了,然后他也想试试,emm过了半天跑来问我这题。顺手写下再给他讲下。

    /**
     * 字符串拼接转换,性能较低
     * 解答成功:
     * 执行耗时:3 ms,击败了16.46% 的Java用户
     * 内存消耗:35.7 MB,击败了19.93% 的Java用户
     * @param x
     * @return
     */
    public int reverse(int x) {
        String str = Integer.toString(x);
        char[] arr = str.toCharArray();
        int left = 0;
        int right = arr.length-1;
        if (arr[0]=='-'){
            left=1;
        }
        char tmp;
        while (left<right){
            tmp = arr[left];
            arr[left]=arr[right];
            arr[right]=tmp;
            left++;
            right--;
        }
        String newStr = new String(arr);
        int newInt;
        try {
            newInt = Integer.parseInt(newStr);
        }catch (NumberFormatException e){
            //用了异常来实现逻辑、这种方法在实际编码中绝对绝对不可取
            return 0;
        }
        return newInt;
    }

    但是这边用了个try/catch来处理异常来实现逻辑上的处理,这种处理办法应该是绝对禁止的,非常不符合代码规范。而且try/catch带来的性能损失应该也是不小的。

    所以,更好的办法如下:

    /**
     * 数学计算绝对不溢出方法
     * 解答成功:
     * 执行耗时:1 ms,击败了100.00% 的Java用户
     * 内存消耗:35.6 MB,击败了39.89% 的Java用户
     *
     * @param x
     * @return
     */
    public int reverse2(int x) {
        String str = Integer.toString(x);
        char[] arr = str.toCharArray();
        x = 1;
        int limit = 0;
        if (arr[0]=='-'){
            x = -1;
            limit = 1;
            arr[0] = '0';
        }
        int num = 0;
        for (int i = str.length()-1; i >= limit; i--) {
            num = num*10 + (arr[i]-'0');
            if (i==limit+1){
                if (Integer.MAX_VALUE/10 <num || (Integer.MAX_VALUE/10 == num*10 && Integer.MAX_VALUE%10 < (arr[limit]-'0'))){
                    return 0;
                }
            }
        }
        return x*num;
    }

    这边溢出判断用了Integer.MAX_VALUE/10来处理,这样能有效避免溢出的问题。另外也有想过溢出后正负数相乘应该为负数这种方法来判断,但是实际数据表明……..乘以10之后溢出的话,仍然可能还为正数,指多绕了几圈又绕回正数区间了,然后再相乘的话是一个更加更加大的数值,依然可能为正数,所以这种判断方式不可取。必须老老实实用Integer.MAX_VALUE/10和还没乘以10进位的数字大小做比较。

    顺便,评论区有个这么一个答题内容

        public int reverse(int x) {
            long n = 0;
            while(x != 0) {
                n = n*10 + x%10;
                x = x/10;
            }
            return (int)n==n? (int)n:0;
        }

    一眼看到好惊艳,但是再看下其实不对劲。关键在这里

    long n = 0;

    long类型变量的存在就忽略了题中要求的“假设环境不允许存储 64 位整数(有符号或无符号)。”

    LeetCode刷题【1487】保证文件名唯一

    给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。

    由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数

    返回长度为 n 的字符串数组,其中 ans[i] 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。

     

    示例 1:

    输入:names = ["pes","fifa","gta","pes(2019)"]
    输出:["pes","fifa","gta","pes(2019)"]
    解释:文件系统将会这样创建文件名:
    "pes" --> 之前未分配,仍为 "pes"
    "fifa" --> 之前未分配,仍为 "fifa"
    "gta" --> 之前未分配,仍为 "gta"
    "pes(2019)" --> 之前未分配,仍为 "pes(2019)"
    

    示例 2:

    输入:names = ["gta","gta(1)","gta","avalon"]
    输出:["gta","gta(1)","gta(2)","avalon"]
    解释:文件系统将会这样创建文件名:
    "gta" --> 之前未分配,仍为 "gta"
    "gta(1)" --> 之前未分配,仍为 "gta(1)"
    "gta" --> 文件名被占用,系统为该名称添加后缀 (k),由于 "gta(1)" 也被占用,所以 k = 2 。实际创建的文件名为 "gta(2)" 。
    "avalon" --> 之前未分配,仍为 "avalon"
    

    示例 3:

    输入:names = ["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece"]
    输出:["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece(4)"]
    解释:当创建最后一个文件夹时,最小的正有效 k 为 4 ,文件名变为 "onepiece(4)"。
    

    示例 4:

    输入:names = ["wano","wano","wano","wano"]
    输出:["wano","wano(1)","wano(2)","wano(3)"]
    解释:每次创建文件夹 "wano" 时,只需增加后缀中 k 的值即可。

    示例 5:

    输入:names = ["kaido","kaido(1)","kaido","kaido(1)"]
    输出:["kaido","kaido(1)","kaido(2)","kaido(1)(1)"]
    解释:注意,如果含后缀文件名被占用,那么系统也会按规则在名称后添加新的后缀 (k) 。
    

     

    提示:

    • 1 <= names.length <= 5 * 10^4
    • 1 <= names[i].length <= 20
    • names[i] 由小写英文字母、数字和/或圆括号组成。
    Related Topics
  • 数组
  • 哈希表
  • 字符串

  • 👍 31
  • 👎 0
  • 题解

    class Solution {
        HashMap<String,Integer> hashMap = new HashMap<>();
        public String[] getFolderNames(String[] names) {
            for (int i = 0; i < names.length; i++) {
                names[i] = putName(names[i], 0);
            }
            return names;
        }
    
        public String putName(String name, int num){
            String newName;
            while (true){
                newName= num==0?name:name+"("+num+")";
                if (!hashMap.containsKey(newName)){
                    hashMap.put(newName,0);
                    if (num>0){
                        hashMap.put(name,num);
                    }
                    break;
                }
                hashMap.put(name,hashMap.get(name)+1);
                num = hashMap.get(name);
            }
            return newName;
        }
    }

    原本在这个之前写过一版,超时,32 / 33 个通过测试用例,在最后一个超长的测试用力的时候超时了,所以再这个基础上再加改进,改原来的hashset为map,额外统计对应字符已经用过的次数,下次遇到冲突的时候根据之前的记忆来获取新的key。

    class Solution {
        HashSet<String> hashSet = new HashSet<>();
        public String[] getFolderNames(String[] names) {
            for (int i = 0; i < names.length; i++) {
                names[i] = putName(names[i], 0);
            }
            return names;
        }
    
        public String putName(String name, int num){
            String newName = num==0?name:name+"("+num+")";
            if (!hashSet.contains(newName)){
                hashSet.add(newName);
                return newName;
            }
            return putName(name,num+1);
        }
    }

    有一点就是 如果在已经输入了“abc”,“abc(1)”的情况下,再次输入“abc”,
    此时记录的数量应该为:
    “abc”1次
    “abc(1)”1次

    需要新增一个key=“abc”的记录,发现已有再次尝试“abc(1)”还是已有的情况下,需要对“abc”的数量修正加1
    此时变成:
    “abc”2次
    “abc(1)”1次

    这样能把之前的输入“abc(1)”的时候没有去给“abc”加1的误差修正回来,便于本次插入的统计,而最终结果只要是能插入就行,后续还有其他的中间状态的统计不正确并不需要去修正处理。

    当然这边你也可以选择对插入的文件名进行判断括号字符串,找到除去括号外的真实文件名,但是我觉得这种做法有点点点…..不大优雅….吧,或者说有点点傻….

    还有一点就是在getFolderNames中的for循环遍历中,直接修改当前插入的字符串的存到对应位置,最终仍旧是返回String[] names数组,无需额外声明数组开辟新的内存空间。

    LeetCode刷题 【1539】第k个缺失的正整数

    给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。

    请你找到这个数组里第 k 个缺失的正整数。

     

    示例 1:

    输入:arr = [2,3,4,7,11], k = 5
    输出:9
    解释:缺失的正整数包括 [1,5,6,8,9,10,12,13,...] 。第 5 个缺失的正整数为 9 。
    

    示例 2:

    输入:arr = [1,2,3,4], k = 2
    输出:6
    解释:缺失的正整数包括 [5,6,7,...] 。第 2 个缺失的正整数为 6 。
    

     

    提示:

    • 1 <= arr.length <= 1000
    • 1 <= arr[i] <= 1000
    • 1 <= k <= 1000
    • 对于所有 1 <= i < j <= arr.length 的 i 和 j 满足 arr[i] < arr[j] 
    Related Topics
  • 数组
  • 二分查找

  • 👍 52
  • 👎 0
  • 题解

    一次遍历记录丢失差值数量,如果有返回对应的位置的数字。如果遍历内没能找到,则表述数组是连续完整递增的,则返回数组长度加k位的

    class Solution {
        public int findKthPositive(int[] arr, int k) {
    
            int cur = 0;
            int missingCount = 0;
            while (cur < arr.length){
                while (cur+missingCount+1 < arr[cur]){
                    missingCount++;
                    if (missingCount==k){
                        return cur+missingCount;
                    }
                }
                cur++;
            }
            return arr.length+k;
        }
    }