【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)

文章目录

  • 449. 序列化和反序列化二叉搜索树⭐⭐⭐⭐⭐(二叉树的重建)
    • 解法
    • 相关题目——297. 二叉树的序列化与反序列化⭐⭐⭐⭐⭐
      • 解法——深度优先搜索
  • 2605. 从两个数字数组里生成最小数字
    • 哈希表分情况讨论
    • 位运算表示集合,分情况讨论(用位运算表示集合)
  • 1123. 最深叶节点的最近公共祖先
    • 解法1——dfs找深度,再往上找公共祖先
    • 解法2——一次dfs(⭐要学习这个思路)
  • 2594. 修车的最少时间
    • 解法——二分查找答案(⭐)
    • 二分查找优化——使用哈希表统计各个能力人数
  • 2651. 计算列车到站时间(超简单取模题目)
  • 207. 课程表(拓扑排序)
    • 解法1——bfs拓扑排序
    • 解法2——dfs拓扑排序
  • 210. 课程表 II(求拓扑序列)

449. 序列化和反序列化二叉搜索树⭐⭐⭐⭐⭐(二叉树的重建)

https://leetcode.cn/problems/serialize-and-deserialize-bst/?envType=daily-question&envId=2023-09-04

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第1张图片
提示:
树中节点数范围是 [0, 10^4]
0 <= Node.val <= 10^4
题目数据 保证 输入的树是一棵二叉搜索树。

解法

  • 给定一颗二叉树的先序遍历和中序遍历可以恢复这颗二叉树
  • 给定一颗二叉树的后序遍历和中序遍历可以恢复这棵二叉树
  • 由于二叉搜索树的中序遍历是有序的,因此给定一颗二叉树的先序遍历(或后续遍历),相当于有了中序遍历和先序遍历(或后续遍历),因此只要有先序遍历(或后序遍历)就可以恢复二叉搜索树

思路总结:
编码——将二叉搜索树的后序遍历结果保存在列表中,将列表转成字符串。
解码——使用split将列表转成字符串数组,将字符串数组解析到栈中,使用栈重建后序遍历二叉树。

/**
 * 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) {
        List<Integer> list = new ArrayList<>();
        postOrder(root, list);  // 后序遍历,结果放入list
        String str = list.toString();
        return str.substring(1, str.length() - 1);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data.isEmpty()) return null;
        // 这里用", "分割是因为Java的toString方法对列表会生成类似[1, 2, 3]的结果
        String[] arr = data.split(", ");
        // 将String转成int存入栈中
        Deque<Integer> stk = new ArrayDeque<>();
        int len = arr.length;
        for (int i = 0; i < len; ++i) {
            stk.push(Integer.parseInt(arr[i]));
        }
        return construct(Integer.MIN_VALUE, Integer.MAX_VALUE, stk);
    }

    // 后序遍历
    void postOrder(TreeNode root, List<Integer> ls) {
        if (root == null) return;
        postOrder(root.left, ls);
        postOrder(root.right, ls);
        ls.add(root.val);
    }

    TreeNode construct(int lower, int upper, Deque<Integer> stk) {
        if (stk.isEmpty() || stk.peek() < lower || stk.peek() > upper) return null;
        int val = stk.pop();        // 取出最后一个节点作为根节点
        TreeNode root = new TreeNode(val);
        root.right = construct(val, upper, stk);    // 重建右子树
        root.left = construct(lower, val, stk);     // 重建左子树
        return root;
    }
}

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

相关题目——297. 二叉树的序列化与反序列化⭐⭐⭐⭐⭐

https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第2张图片

提示:
树中结点数在范围 [0, 10^4] 内
-1000 <= Node.val <= 1000

解法——深度优先搜索

https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/solutions/290065/er-cha-shu-de-xu-lie-hua-yu-fan-xu-lie-hua-by-le-2/

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第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) {
        return enCode(root, "");        // 存储前序遍历的结果
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] arr = data.split(",");
        List<String> ls = new LinkedList<String>(Arrays.asList(arr));
        return deCode(ls);
    }

    // 
    public String enCode(TreeNode root, String str) {
        if (root == null) {
            str += "None,";         // 使用None
        } else {
            str += str.valueOf(root.val) + ",";
            str = enCode(root.left, str);
            str = enCode(root.right, str);
        }
        return str;
    }

    // 前序遍历重建
    public TreeNode deCode(List<String> ls) {
        if (ls.get(0).equals("None")) {
            ls.remove(0);
            return null;
        }

        TreeNode root = new TreeNode(Integer.valueOf(ls.get(0)));
        ls.remove(0);
        root.left = deCode(ls);
        root.right = deCode(ls);
        return root;
    }
}

// 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));

总结一下这种题目的套路——
把二叉树用前序遍历或者后序遍历存成字符串,(如果不是搜索树的话需要用 None 表示空节点)。
重建的时候通过 split 将各个节点变成 列表或者数组的形式存储下来,同样按照前序遍历或者后序遍历重建二叉树即可。

2605. 从两个数字数组里生成最小数字

https://leetcode.cn/problems/form-smallest-number-from-two-digit-arrays/description/?envType=daily-question&envId=2023-09-05

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第4张图片

提示:
1 <= nums1.length, nums2.length <= 9
1 <= nums1[i], nums2[i] <= 9
每个数组中,元素 互不相同 。

哈希表分情况讨论

class Solution {
    public int minNumber(int[] nums1, int[] nums2) {
        int ans = 99, a = 10, b = 10;
        Set<Integer> s = new HashSet<>();
        for (int num: nums1) {
            s.add(num);
            a = Math.min(a, num);
        }
        for (int num: nums2) {
            if (s.contains(num)) ans = Math.min(ans, num);
            b = Math.min(b, num);
        }
        // 如果有交集 直接返回
        if (ans != 99) return ans;
        // 没有交集,返回 a * 10 + b
        if (a > b) {
            int t = a;
            a = b;
            b = t;
        }
        return 10 * a + b;
    }
}

位运算表示集合,分情况讨论(用位运算表示集合)

class Solution {
    public int minNumber(int[] nums1, int[] nums2) {
        int mask1 = 0, mask2 = 0;
        for (int x: nums1) mask1 |= 1 << x;
        for (int x: nums2) mask2 |= 1 << x;
        // 检查是否有交集
        int m = mask1 & mask2;
        if (m > 0) return Integer.numberOfTrailingZeros(m);
        // 如果没有交集
        int x = Integer.numberOfTrailingZeros(mask1), y = Integer.numberOfTrailingZeros(mask2);
        return Math.min(x * 10 + y, y * 10 + x);
    }
}

1123. 最深叶节点的最近公共祖先

https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves/?envType=daily-question&envId=2023-09-06

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第5张图片

提示:
树中的节点数将在 [1, 1000] 的范围内。
0 <= Node.val <= 1000
每个节点的值都是 独一无二 的。

解法1——dfs找深度,再往上找公共祖先

/**
 * 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 {
    Queue<TreeNode> q = new LinkedList<>();
    Map<TreeNode, TreeNode> m = new HashMap<>();
    int d = 0;

    public TreeNode lcaDeepestLeaves(TreeNode root) {
        dfs(root, 0);
        while (q.size() != 1) {
            int sz = q.size();
            Set<TreeNode> s = new HashSet<>();
            for (int i = 0; i < sz; ++i) {
                s.add(m.get(q.poll()));
            }
            for (TreeNode t: s) {
                q.offer(t);
            }
        }
        return q.poll();
    }

    public void dfs(TreeNode root, int t) {
        if (root.left == null && root.right == null) {
            if (t > d) {
                q.clear();
                d = t;
            }
            if (t == d) q.offer(root);
        }
        else {
            if (root.left != null) {
                m.put(root.left, root);
                dfs(root.left, t + 1);
            }
            if (root.right != null) {
                m.put(root.right, root);
                dfs(root.right, t + 1);
            }
        }
    }
}

解法2——一次dfs(⭐要学习这个思路)

https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves/solutions/488055/liang-chong-si-lu-yi-chong-qian-xu-bian-li-yi-chon/?envType=daily-question&envId=2023-09-06【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第6张图片

class Solution {
    TreeNode res = null;	// 答案
    int pre = 0;		// 更新最大深度

    public TreeNode lcaDeepestLeaves(TreeNode root) {
        dfs(root, 1);
        return res;
    }

    int dfs(TreeNode node, int d) {
        if (node == null) return d;
        int l = dfs(node.left, d + 1), r = dfs(node.right, d + 1);
        if (l == r && l >= pre) {
            res = node;
            pre = l;
        }
        return Math.max(l, r);
    }
}

2594. 修车的最少时间

https://leetcode.cn/problems/minimum-time-to-repair-cars/?envType=daily-question&envId=2023-09-07

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第7张图片

提示:
1 <= ranks.length <= 10^5
1 <= ranks[i] <= 100
1 <= cars <= 10^6

解法——二分查找答案(⭐)

class Solution {
    public long repairCars(int[] ranks, int cars) {
        Arrays.sort(ranks);
        long l = ranks[0], r = (long)ranks[0] * cars * cars;
        while (l < r) {
            long mid = l + r >> 1;
            if (check(mid, ranks, cars)) r = mid;
            else l = mid + 1;
        }
        return l;
    }

    public boolean check(long k, int[] ranks, int cars) {
        for (int r: ranks) {
            cars -= Math.floor(Math.sqrt((double)(k / r)));
        }
        return cars <= 0;
    }
}

二分查找优化——使用哈希表统计各个能力人数

能力值相同的人,在 t 分钟内修好的车的个数是一样的。

根据数据范围,ranks 中至多有 100 个不同的数字,我们可以统计 ranks 中每个数字的出现次数,这样每次二分至多循环 100 次。

class Solution {
    public long repairCars(int[] ranks, int cars) {
        int[] cnt = new int[101];
        int minR = ranks[0];
        for (int r: ranks) {
            cnt[r]++;
            minR = Math.min(minR, r);
        }
        long l = 0, r = (long)minR * cars * cars;
        while (l < r) {
            long mid = l + r >> 1;
            if (check(mid, cars, cnt, minR)) r = mid;
            else l = mid + 1;
        }
        return l;
    }

    public boolean check(long k, int cars, int[] cnt, int minR) {
        for (int r = minR; r <= 100; ++r) {
            cars -= Math.floor(Math.sqrt(k / r)) * cnt[r];
        }
        return cars <= 0;
    }
}

2651. 计算列车到站时间(超简单取模题目)

https://leetcode.cn/problems/calculate-delayed-arrival-time/?envType=daily-question&envId=2023-09-08

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第8张图片

提示:
1 <= arrivaltime < 24
1 <= delayedTime <= 24

class Solution {
    public int findDelayedArrivalTime(int arrivalTime, int delayedTime) {
        return (arrivalTime + delayedTime) % 24;
    }
}

207. 课程表(拓扑排序)

https://leetcode.cn/problems/course-schedule/?envType=daily-question&envId=2023-09-09

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第9张图片

提示:
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同

解法1——bfs拓扑排序

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<Integer>[] g = new ArrayList[numCourses];
        Arrays.setAll(g, e -> new ArrayList<>());
        int[] in = new int[numCourses];
        for (int[] p: prerequisites) {
            g[p[0]].add(p[1]);
            in[p[1]]++;
        }
        int cnt = numCourses;
        Queue<Integer> q = new LinkedList<>();
        for (int i = 0; i < numCourses; ++i) {
            if (in[i] == 0) {
                q.offer(i);
                cnt--;
            }
        }

        while (!q.isEmpty()) {
            int cur = q.poll();
            for (int nt: g[cur]) {
                if (--in[nt] == 0) {
                    q.offer(nt);
                    cnt--;
                }
            }
        }
        return cnt == 0;
    }
}

解法2——dfs拓扑排序

class Solution {
    List<Integer>[] g;
    boolean[] st;
    int[] in;
    int sum = 0;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        g = new ArrayList[numCourses];
        Arrays.setAll(g, e -> new ArrayList<Integer>());
        in = new int[numCourses];
        st = new boolean[numCourses];
        for (int[] prerequisity: prerequisites) {
            g[prerequisity[0]].add(prerequisity[1]);
            in[prerequisity[1]]++;
        }
        
        for (int i = 0; i < numCourses; ++i) {
            if (in[i] == 0) {
                dfs(i);
            }
        }

        return sum == numCourses;
    }

    public void dfs(int x) {
        if (st[x]) return;
        ++sum;
        st[x] = true;
        for (int y: g[x]) {
            if (--in[y] == 0) {
                dfs(y);
            }
        }
    }
}

210. 课程表 II(求拓扑序列)

https://leetcode.cn/problems/course-schedule-ii/?envType=daily-question&envId=2023-09-10

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)_第10张图片
提示:
1 <= numCourses <= 2000
0 <= prerequisites.length <= numCourses * (numCourses - 1)
prerequisites[i].length == 2
0 <= ai, bi < numCourses
ai != bi
所有[ai, bi] 互不相同

class Solution {
    List<Integer>[] g;
    List<Integer> ans = new ArrayList();
    int[] in;

    public int[] findOrder(int numCourses, int[][] prerequisites) {
        g = new ArrayList[numCourses];
        Arrays.setAll(g, e -> new ArrayList<Integer>());
        in = new int[numCourses];
        for (int[] prerequisity: prerequisites) {
            g[prerequisity[1]].add(prerequisity[0]);
            in[prerequisity[0]]++;
        }
        
        Queue<Integer> q = new LinkedList<Integer>();
        for (int i = 0; i < numCourses; ++i) {
            if (in[i] == 0) {
                q.offer(i);
                ans.add(i);
            }
        }
        while (!q.isEmpty()) {
            int x = q.poll();
            for (int y: g[x]) {
                if (--in[y] == 0) {
                    ans.add(y);
                    q.offer(y);
                }
            } 
        }

        if (ans.size() == numCourses) return ans.stream().mapToInt(Integer::intValue).toArray();
        else return new int[]{};
    }
}

你可能感兴趣的:(算法刷题记录,leetcode,算法,二叉树,二分,拓扑排序,每日一题)