LeetCode 前300题里面的hard题目

注意总结题目的考察点,技巧。

297. 二叉树的序列化与反序列化 - 力扣(LeetCode)

理解题意:

  • 为什么需要序列化和反序列化? 为了在网络上传输对象,对象在内存中是二叉树结构,数据+左右孩子引用,在不同机器上,二叉树在内存中的位置不一样,所以不能直接传输整个结构。而是要序列化成一个与内存地址无关的表示。
  • 怎么做?要保证唯一,序列化后,能反序列化。一种方法,先序遍历,然后使用*代替null,得到一个字符串。

解法1:使用先序+null占位符(*),然后序列化成String,用逗号分隔每个val和null。

点击显示答案
public class Codec { // time: O(n), space: O(n)

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        List<String> preOrders = new ArrayList<>();
        TreeNode cur = root;
        Deque<TreeNode> stack = new ArrayDeque<>();
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                preOrders.add(String.valueOf(cur.val));
                stack.push(cur);
                cur = cur.left;
            }
            preOrders.add("*");
            cur = stack.pop().right;            
        }
        String ans = String.join(",", preOrders);
        // System.out.println("encode: " + ans);
        return ans;
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] preOrders = data.split(",");
        if (preOrders.length == 0) return null;
        preOrderSeq = 0;
        return buildTree(preOrders);
    }

    TreeNode buildTree(String[] preOrders) {
        if (preOrderSeq == preOrders.length) return null;
        String val = preOrders[preOrderSeq++];
        if (val.isEmpty() || val.equals("*")) return null;
        TreeNode node = new TreeNode(Integer.valueOf(val));
        node.left = buildTree(preOrders);
        node.right = buildTree(preOrders);
        return node;
    }

    private int preOrderSeq;
}

解法2: 使用LC官方的序列化方式

序列化方案,层次遍历,但是对于最下一层的null,全部删除。中间节点的null要加上。
实现细节:先按正常的层次遍历,把null加上,然后从后往前,删除末尾的连续的null。
反序列化时,也是使用队列,进行层次遍历来构建。

296. 最佳的碰头地点 - 力扣(LeetCode)

理解:

提示:考虑一维的情况,坐标轴上两个点,取中间任意一个点到两者的距离都是一样的。如果是奇数个点,取排序后最中间的那个就可以了。

暴力做法:枚举grid中的每个点,将它作为碰头点,然后第二步,找所有人到这个点的距离之和,从这个点采用BFS遍历整个grid即可。时间:O(mn*mn). 空间:O(mn), 需要标记每个grid是否访问过。 提交后会超时。

点击显示答案
class Solution {
    public int minTotalDistance(int[][] A) {
        int ans = Integer.MAX_VALUE;
        // 枚举每个grid, 将它作为碰头点        
        for (int i = 0; i < A.length; i++) {
            for (int j = 0; j < A[i].length; j++) {
                int dist = helper(A, i, j);
                ans = Math.min(ans, dist);
            }
        }
        return ans;
    }

    int helper(int[][] A, int row, int col) {
        // 从A[row][col] 处碰头,采用BFS遍历整个grid
        // BFS: queue + visit数组标记是否访问过. queue要记录坐标&距离  (r, c, dist)
        int ans = 0, dist = 0;
        int m = A.length, n = A[0].length;
        boolean[][] visited = new boolean[m][n];
        Queue<int[]> que = new LinkedList<>();
        que.offer(new int[] {row, col, dist});
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上下左右

        while (!que.isEmpty()) {
            // 取出当前点
            int[] cur = que.poll();
            int r = cur[0], c = cur[1], d = cur[2];            
            if (r < 0 || r >= m || c < 0 || c >= n || visited[r][c]) {
                continue;
            }
            visited[r][c] = true;
            if (A[r][c] == 1) {
                ans += d;
            }
            // 遍历当前点的周围4个点
            for (int[] dir: dirs) {
                int nr = dir[0] + r;
                int nc = dir[1] + c;
                que.offer(new int[] {nr, nc, d + 1});
            }
        }
        return ans;
    }
}

最佳解法

思路:

  • 将原问题转化成两个一维问题:曼哈顿距离可以看成x,y两个方向上两个独立的变量的距离之和。因此,求最短距离,可以考虑两个一维的,可以通过找grid中两个点的最近的碰头点,来理解这一点。
  • 解决一维问题:对于一维,找最近的碰头点,怎么做? 在坐标轴上,两个点的情况,碰头点是在两点之间任意一个位置即可。 如果是3个点呢? 则只能取中间那个点。因此,可以总结过滤,排序之后,最中间的那个点(不论奇数,还是偶数都覆盖到了)。
  • 具体做法:收集两个方向的坐标点,然后排序,取中点,计算距离之和。时间:O(mn log mn)
点击显示折叠内容
class Solution {
    public int minTotalDistance(int[][] A) {
        // 将原问题拆解成两个一维的问题,先收集两个一维上的点
        List<Integer> rows = new ArrayList<>();
        List<Integer> cols = new ArrayList<>();
        for (int i = 0; i < A.length; i++) {
            for (int j = 0; j < A[i].length; j++) {
                if (A[i][j] == 1) {
                    rows.add(i);
                    cols.add(j);
                }
            }
        }
        int ans = helper(rows, rows.size() / 2);
        // 排序列这一维
        Collections.sort(cols);
        ans += helper(cols, cols.size() / 2);
        return ans;
    }

    int helper(List<Integer> points, int selected) {
        // 计算一维上的坐标点,每个点到碰头的点的曼哈顿距离
        int ans = 0;
        for(int x : points) {
            ans += Math.abs(x - points.get(selected));
        }
        return ans;
    }
}

收获:

  • 在一维坐标轴上计算一堆点的距离之和,分奇数和偶数两种情况分析,之前在某个校招面试中遇到过
    • 问题描述:给定一维空间上的N个点的坐标,找到一个点,使得该点到所有其他点的距离之和最小。
    • 解决思路:可以使用中位数的概念来解决这个问题。中位数是将一组数据划分为相等的两部分的值。在一维空间上,中位数即为所有点的中点。
    • 这个思路可以应用于一维空间中的其他类似问题,例如找到一维空间上的最佳停车位、最佳放置广告牌的位置等。
  • 对于曼哈顿距离,可以看成两个一维的独立的变量的和

扩展问题1:找到一维空间上的最佳停车位

在一维空间上找到最佳停车位的问题可以定义为:给定一条道路上的N个停车位和M个车辆,找到一个停车位,使得所有车辆到该停车位的距离之和最小。

解决这个问题的一种常用方法是使用中位数。以下是解决该问题的具体步骤:

  1. 对给定的停车位坐标进行排序。
  2. 如果停车位的数量N为奇数,那么中位数就是最佳停车位的位置。 如果停车位的数量N为偶数,那么有两个中位数,可以选择任意一个作为最佳停车位的位置。一种常用的方法是选择中间两个停车位之间的任意位置作为最佳停车位。

通过选择中位数作为最佳停车位的位置,可以确保所有车辆到最佳停车位的距离之和最小。这是因为中位数具有使得距离之和最小的特性。

需要注意的是,这种方法假设车辆可以停在停车位上,而且停车位之间的距离是相等的。如果存在其他限制条件或约束,可能需要考虑其他方法来解决问题。

这个问题可以进一步扩展到多维空间中的最佳停车位选择问题,但在多维空间中,可能需要使用其他算法或技术来找到最佳停车位的位置。

扩展问题2:最佳放置广告牌的位置

在一维空间中找到最佳放置广告牌的位置的问题可以定义为:给定一条道路的长度和N个位置候选点,找到一个位置,使得所有位置候选点到该位置的距离之和最小,从而最大化广告牌的曝光效果。

解决这个问题的一种常用方法是使用中位数。以下是解决该问题的具体步骤:

  1. 对给定的位置候选点进行排序。
  2. 如果位置候选点的数量N为奇数,那么中位数就是最佳放置广告牌的位置。 如果位置候选点的数量N为偶数,那么有两个中位数,可以选择任意一个作为最佳放置广告牌的位置。一种常用的方法是选择中间两个位置候选点之间的任意位置作为最佳放置广告牌。

通过选择中位数作为最佳放置广告牌的位置,可以确保所有位置候选点到最佳位置的距离之和最小,从而最大化广告牌的曝光效果。

需要注意的是,这种方法假设广告牌可以放置在位置候选点上,并且位置候选点之间的距离是相等的。如果存在其他限制条件或约束,例如广告牌的尺寸、位置候选点的可用性等,可能需要考虑其他方法或算法来解决问题。

类似地,如果问题是在二维或多维空间中放置广告牌,可能需要使用其他技术或算法来找到最佳位置,例如最大覆盖范围、最大覆盖人群等。问题的具体定义和约束将会影响选择合适的方法。

posted @ 2023-07-15 07:46  编程爱好者-java  阅读(102)  评论(0编辑  收藏  举报