LeetCode1461~1480题目解析

LeetCode 1461-1480. 题解合集

文章目录


1461. Check If a String Contains All Binary Codes of Size K(检查一个字符串是否包含所有长度为 K 的二进制子串)

题目描述

给你一个二进制字符串 s 和一个整数 k。如果所有长度为 k 的二进制字符串都是 s 的子串,

请返回 true,否则请返回 false。

示例 1

复制代码
s = "00110110", k = 2 -> true

示例 2

复制代码
s = "0110", k = 1 -> true

示例 3

复制代码
s = "0110", k = 2 -> false

约束:

  • 1 <= s.length <= 5 * 10^5;s[i] 为 '0' 或 '1';1 <= k <= 20。

解题思路

解法一:滑动窗口哈希收集(主提交版本)

全部 k 位二进制串共有 2^k 个。用长度为 k 的滑动窗口,把每个窗口的十进制值

放入 boolean 数组 seen(大小 2^k),最终统计不同值个数是否达到 2^k。

维护滚动值 val = ((val << 1) & mask) | bit,mask = (1<<k) - 1。

复杂度:O(n),空间 O(2^k)。

解法二:HashSet 收集(对照)

同样滑动窗口,但用 HashSet 存储窗口值,最后 size == 1<<k。

复杂度:O(n),空间 O(2^k)。

代码实现

java 复制代码
import java.util.HashSet;

public class Solution {

    public boolean hasAllCodes(String s, int k) {
        int n = s.length();
        if (n < k) {
            return false;
        }
        int total = 1 << k;
        boolean[] seen = new boolean[total];
        int mask = total - 1;
        int val = 0;
        for (int i = 0; i < k; i++) {
            val = ((val << 1) | (s.charAt(i) - '0')) & mask;
        }
        int count = 0;
        if (!seen[val]) {
            seen[val] = true;
            count++;
        }
        for (int i = k; i < n; i++) {
            val = ((val << 1) | (s.charAt(i) - '0')) & mask;
            if (!seen[val]) {
                seen[val] = true;
                count++;
            }
        }
        return count == total;
    }

    public boolean hasAllCodesSet(String s, int k) {
        int n = s.length();
        if (n < k) {
            return false;
        }
        int total = 1 << k;
        int mask = total - 1;
        int val = 0;
        for (int i = 0; i < k; i++) {
            val = ((val << 1) | (s.charAt(i) - '0')) & mask;
        }
        HashSet<Integer> set = new HashSet<Integer>();
        set.add(val);
        for (int i = k; i < n; i++) {
            val = ((val << 1) | (s.charAt(i) - '0')) & mask;
            set.add(val);
            if (set.size() == total) {
                return true;
            }
        }
        return set.size() == total;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.hasAllCodes("00110110", 2)); // true
        System.out.println(s.hasAllCodes("0110", 1)); // true
        System.out.println(s.hasAllCodes("0110", 2)); // false
        System.out.println(s.hasAllCodes("0000000001011100", 4)); // false
        System.out.println(s.hasAllCodesSet("00110110", 2)); // true
        System.out.println(s.hasAllCodesSet("0110", 1)); // true
        System.out.println(s.hasAllCodesSet("0110", 2)); // false
    }
}

复杂度分析

复杂度:O(n),空间 O(2^k)。


1462. Course Schedule IV(课程表 IV)

题目描述

你总共需要上 numCourses 门课,课程编号依次为 0 到 numCourses-1。

你会得到一个数组 prerequisites,其中 prerequisitesi = ai, bi 表示如果你想选 bi 课程,

你必须先选 ai 课程(先决条件可以为间接的:a 是 b 的先决条件、b 是 c 的先决条件,

那么 a 也是 c 的先决条件)。

你也得到一个数组 queries,其中 queriesj = uj, vj。

对于第 j 个查询,你应该回答课程 uj 是否是课程 vj 的先决条件。

返回一个布尔数组 answer,其中 answerj 是第 j 个查询的答案。

示例 1

复制代码
numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]] -> [false,true]

示例 3

复制代码
numCourses = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]] -> [true,true]

约束:

  • 2 <= numCourses <= 100;0 <= prerequisites.length <= numCourses*(numCourses-1)/2;
  • 先修课程图中没有环;1 <= queries.length <= 10^4。

解题思路

解法一:Floyd 传递闭包(主提交版本)

用邻接矩阵 reachuv 表示 u 是否是 v 的直接或间接先修。

先用入度为零的点做拓扑排序(或直接按 i<k<j 的 Floyd):

对中间节点 k,若 u 可达 k 且 k 可达 v,则 u 可达 v。

因 n<=100,O(n^3) 完全可接受,之后每条查询 O(1)。

复杂度:O(n^3 + q)。

解法二:反向 DFS 记忆化(对照)

把先修关系反向建图(v -> 所有必须以 v 为前提的课?)。标准反向:

给每个课程 u 计算所有"以 u 为先修"的课程集合(即 u 的直接/间接后继),

查询 (u, v) 即判断 v 是否在 u 的后继集合中。

这里实现为:对每个查询做一次 DFS(带全局记忆化 visited),

复杂度 O(q * (n + m)),因 n 小也可接受。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
        boolean[][] reach = new boolean[numCourses][numCourses];
        for (int[] p : prerequisites) {
            reach[p[0]][p[1]] = true;
        }
        for (int k = 0; k < numCourses; k++) {
            for (int i = 0; i < numCourses; i++) {
                if (!reach[i][k]) {
                    continue;
                }
                for (int j = 0; j < numCourses; j++) {
                    if (reach[k][j]) {
                        reach[i][j] = true;
                    }
                }
            }
        }
        List<Boolean> ans = new ArrayList<Boolean>();
        for (int[] q : queries) {
            ans.add(reach[q[0]][q[1]]);
        }
        return ans;
    }

    public List<Boolean> checkIfPrerequisiteDfs(int numCourses, int[][] prerequisites, int[][] queries) {
        // 反向图:adj[v] 中存所有直接先修 u(u -> v 正向,反向建 v -> u)
        List<List<Integer>> adj = new ArrayList<List<Integer>>();
        for (int i = 0; i < numCourses; i++) {
            adj.add(new ArrayList<Integer>());
        }
        for (int[] p : prerequisites) {
            adj.get(p[1]).add(p[0]); // v 的先修是 u
        }
        List<Boolean> ans = new ArrayList<Boolean>();
        for (int[] q : queries) {
            ans.add(canReach(q[0], q[1], adj, numCourses));
        }
        return ans;
    }

    private boolean canReach(int start, int end, List<List<Integer>> adj, int n) {
        boolean[] visited = new boolean[n];
        java.util.ArrayDeque<Integer> stack = new java.util.ArrayDeque<Integer>();
        stack.push(end);
        while (!stack.isEmpty()) {
            int cur = stack.pop();
            if (cur == start) {
                return true;
            }
            if (visited[cur]) {
                continue;
            }
            visited[cur] = true;
            for (int pre : adj.get(cur)) {
                if (!visited[pre]) {
                    stack.push(pre);
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.checkIfPrerequisite(2, new int[][]{{1, 0}}, new int[][]{{0, 1}, {1, 0}}));
        // [false, true]
        System.out.println(s.checkIfPrerequisite(2, new int[][]{}, new int[][]{{1, 0}, {0, 1}}));
        // [false, false]
        System.out.println(s.checkIfPrerequisite(3, new int[][]{{1, 2}, {1, 0}, {2, 0}}, new int[][]{{1, 0}, {1, 2}}));
        // [true, true]
        System.out.println(s.checkIfPrerequisiteDfs(2, new int[][]{{1, 0}}, new int[][]{{0, 1}, {1, 0}}));
        // [false, true]
        System.out.println(s.checkIfPrerequisiteDfs(3, new int[][]{{1, 2}, {1, 0}, {2, 0}}, new int[][]{{1, 0}, {1, 2}}));
        // [true, true]
    }
}

复杂度分析

复杂度:O(n^3 + q)。


1463. Cherry Pickup II(摘樱桃 II)

题目描述

给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。grid 中每个格子的数字表示你能获得的樱桃数目。

你有两个机器人帮你收集樱桃:机器人 1 从左上角格子 (0,0) 出发,机器人 2 从右上角格子 (0, cols-1) 出发。

规则:

  • 从格子 (i,j) 出发,机器人可以移动到格子 (i+1, j-1)、(i+1, j) 或 (i+1, j+1);
  • 当一个机器人经过某个格子时,它会把该格子内所有的樱桃都摘走,然后这个位置会变成空格子;
  • 当两个机器人同时到达同一个格子时,它们中只有一个可以摘到樱桃;
  • 两个机器人在任意时刻都不能移动到 grid 外面;
  • 两个机器人最后都要到达 grid 最底下一行。
    返回两个机器人能收集的最多樱桃数目。

示例 1

复制代码
grid = [[3,1,1],[2,5,1],[1,5,5],[2,1,1]] -> 24

示例 2

复制代码
grid = [[1,0,0,0,0,0,1],[2,0,0,0,0,3,0],[2,0,9,0,0,0,0],[0,3,0,5,4,0,0],[1,0,2,3,0,0,6]] -> 28

约束:

  • 2 <= rows, cols <= 70;0 <= grid[i][j] <= 100。

解题思路

解法一:同步 DP 状态压缩(主提交版本)

两个机器人第 i 行时列位置分别为 c1、c2,且两机器人同时逐行向下走,

则"已用步数"相同 → 同步 DP:dpic1c2 = 两机器人都到达第 i 行、

分别位于列 c1、c2 时收集的最大樱桃数。

转移:上一行位置 (pc1 in {c1-1,c1,c1+1}, pc2 in {c2-1,c2,c2+1}) 的最大值 +

当前行两位置的樱桃(同列只计一次)。

复杂度:O(rows * cols^2 * 9),空间 O(cols^2) 滚动。

解法二:记忆化递归(对照)

直接写 dfs(i, c1, c2) 递归向下,返回收集樱桃数;等价转移。

复杂度:O(rows * cols^2)。

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int cherryPickup(int[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;
        int neg = Integer.MIN_VALUE / 2;
        int[][] dp = new int[cols][cols];
        for (int[] row : dp) {
            Arrays.fill(row, neg);
        }
        dp[0][cols - 1] = grid[0][0] + grid[0][cols - 1];
        for (int i = 1; i < rows; i++) {
            int[][] ndp = new int[cols][cols];
            for (int[] row : ndp) {
                Arrays.fill(row, neg);
            }
            for (int c1 = 0; c1 < cols; c1++) {
                for (int c2 = 0; c2 < cols; c2++) {
                    int best = neg;
                    for (int d1 = -1; d1 <= 1; d1++) {
                        for (int d2 = -1; d2 <= 1; d2++) {
                            int pc1 = c1 + d1;
                            int pc2 = c2 + d2;
                            if (pc1 >= 0 && pc1 < cols && pc2 >= 0 && pc2 < cols) {
                                best = Math.max(best, dp[pc1][pc2]);
                            }
                        }
                    }
                    if (best == neg) {
                        continue;
                    }
                    int gain = grid[i][c1] + (c1 == c2 ? 0 : grid[i][c2]);
                    ndp[c1][c2] = best + gain;
                }
            }
            dp = ndp;
        }
        int ans = 0;
        for (int c1 = 0; c1 < cols; c1++) {
            for (int c2 = 0; c2 < cols; c2++) {
                ans = Math.max(ans, dp[c1][c2]);
            }
        }
        return ans;
    }

    public int cherryPickupDfs(int[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;
        int[][][] memo = new int[rows][cols][cols];
        for (int[][] a : memo) {
            for (int[] b : a) {
                Arrays.fill(b, -1);
            }
        }
        return dfs(0, 0, cols - 1, grid, memo);
    }

    private int dfs(int r, int c1, int c2, int[][] grid, int[][][] memo) {
        if (memo[r][c1][c2] != -1) {
            return memo[r][c1][c2];
        }
        int rows = grid.length;
        int cols = grid[0].length;
        int gain = grid[r][c1] + (c1 == c2 ? 0 : grid[r][c2]);
        if (r == rows - 1) {
            memo[r][c1][c2] = gain;
            return gain;
        }
        int best = 0;
        for (int d1 = -1; d1 <= 1; d1++) {
            for (int d2 = -1; d2 <= 1; d2++) {
                int nc1 = c1 + d1;
                int nc2 = c2 + d2;
                if (nc1 >= 0 && nc1 < cols && nc2 >= 0 && nc2 < cols) {
                    best = Math.max(best, dfs(r + 1, nc1, nc2, grid, memo));
                }
            }
        }
        memo[r][c1][c2] = gain + best;
        return gain + best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.cherryPickup(new int[][]{{3, 1, 1}, {2, 5, 1}, {1, 5, 5}, {2, 1, 1}})); // 24
        System.out.println(s.cherryPickup(new int[][]{{1, 0, 0, 0, 0, 0, 1}, {2, 0, 0, 0, 0, 3, 0},
                {2, 0, 9, 0, 0, 0, 0}, {0, 3, 0, 5, 4, 0, 0}, {1, 0, 2, 3, 0, 0, 6}})); // 28
        System.out.println(s.cherryPickup(new int[][]{{1, 0, 0, 3}, {0, 0, 0, 3}, {0, 0, 3, 3}, {9, 0, 3, 3}})); // 22
        System.out.println(s.cherryPickup(new int[][]{{1, 1}, {1, 1}})); // 4
        System.out.println(s.cherryPickupDfs(new int[][]{{3, 1, 1}, {2, 5, 1}, {1, 5, 5}, {2, 1, 1}})); // 24
        System.out.println(s.cherryPickupDfs(new int[][]{{1, 0, 0, 0, 0, 0, 1}, {2, 0, 0, 0, 0, 3, 0},
                {2, 0, 9, 0, 0, 0, 0}, {0, 3, 0, 5, 4, 0, 0}, {1, 0, 2, 3, 0, 0, 6}})); // 28
        System.out.println(s.cherryPickupDfs(new int[][]{{1, 0, 0, 3}, {0, 0, 0, 3}, {0, 0, 3, 3}, {9, 0, 3, 3}})); // 22
    }
}

复杂度分析

复杂度:O(rows * cols^2 * 9),空间 O(cols^2) 滚动。

复杂度:O(rows * cols^2)。


1464. Maximum Product of Two Elements in an Array(数组中两元素的最大乘积)

题目描述

给你一个整数数组 nums。请你选择数组的两个不同下标 i 和 j,

返回 (nums[i] - 1) * (nums[j] - 1) 的最大值。

示例 1

复制代码
nums = [3,4,5,2] -> 12((4-1)*(5-1))

示例 2

复制代码
nums = [1,5,4,5] -> 16

示例 3

复制代码
nums = [3,7] -> 12

约束:

  • 2 <= nums.length <= 500;1 <= nums[i] <= 10^3。

解题思路

解法一:线性扫描维护两个最大值(主提交版本)

一遍扫描维护第一大 max1 与第二大 max2,答案为 (max1-1)*(max2-1)。

复杂度:O(n),空间 O(1)。

解法二:排序取前二(对照)

排序后取最大两个元素。

复杂度:O(n log n),空间 O(1)。

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int maxProduct(int[] nums) {
        int max1 = 0, max2 = 0;
        for (int v : nums) {
            if (v > max1) {
                max2 = max1;
                max1 = v;
            } else if (v > max2) {
                max2 = v;
            }
        }
        return (max1 - 1) * (max2 - 1);
    }

    public int maxProductSort(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        return (nums[n - 1] - 1) * (nums[n - 2] - 1);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.maxProduct(new int[]{3, 4, 5, 2})); // 12
        System.out.println(s.maxProduct(new int[]{1, 5, 4, 5})); // 16
        System.out.println(s.maxProduct(new int[]{3, 7})); // 12
        System.out.println(s.maxProductSort(new int[]{3, 4, 5, 2})); // 12
        System.out.println(s.maxProductSort(new int[]{1, 5, 4, 5})); // 16
        System.out.println(s.maxProductSort(new int[]{3, 7})); // 12
    }
}

复杂度分析

复杂度:O(n),空间 O(1)。

复杂度:O(n log n),空间 O(1)。


1465. Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts(切割后面积最大的蛋糕)

题目描述

矩形蛋糕的高度为 h 且宽度为 w,给你两个整数数组 horizontalCuts 和 verticalCuts,其中:

  • horizontalCuts[i] 是从矩形蛋糕顶部到第 i 个水平切口的距离;
  • verticalCuts[j] 是从矩形蛋糕的左侧到第 j 个竖直切口的距离。
    请你按数组中提供的水平和竖直位置切割后,找出面积最大的那份蛋糕,并返回其面积。
    由于答案可能是一个很大的数字,因此需要将结果对 10^9+7 取余后返回。

示例 1

复制代码
h = 5, w = 4, horizontalCuts = [1,2,4], verticalCuts = [1,3] -> 4

示例 2

复制代码
h = 5, w = 4, horizontalCuts = [3,1], verticalCuts = [1] -> 6

示例 3

复制代码
h = 5, w = 4, horizontalCuts = [3], verticalCuts = [3] -> 9

约束:

  • 2 <= h, w <= 10^9;1 <= horizontalCuts.length <= min(h-1, 10^5)。

解题思路

解法一:排序求相邻最大间隔(主提交版本)

切割后水平方向各块的高度 = 相邻水平切口之间的高度差(含边界 0 与 h),

最大高度 = 这些间隔的最大值;同理最大宽度 = 相邻竖直切口间隔最大值。

面积 = 最大高度 * 最大宽度(取模)。

复杂度:O(a log a + b log b),空间 O(1)。

解法二:布尔数组标记切割位置(对照)

当 h、w 较小时可用 boolean 数组标记切口并两遍扫描找最长连续段;

此处展示该思路(h/w 大时退化为排序法统计,这里以数组模拟演示)。

复杂度:O(h + w + a + b)。

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int maxArea(int h, int w, int[] horizontalCuts, int[] verticalCuts) {
        int MOD = 1000000007;
        long maxH = maxGap(h, horizontalCuts);
        long maxW = maxGap(w, verticalCuts);
        return (int) ((maxH % MOD) * (maxW % MOD) % MOD);
    }

    private long maxGap(int total, int[] cuts) {
        Arrays.sort(cuts);
        long best = cuts[0] - 0; // 第一段
        for (int i = 1; i < cuts.length; i++) {
            best = Math.max(best, (long) cuts[i] - cuts[i - 1]);
        }
        best = Math.max(best, (long) total - cuts[cuts.length - 1]); // 最后一段
        return best;
    }

    public int maxAreaTree(int h, int w, int[] horizontalCuts, int[] verticalCuts) {
        int MOD = 1000000007;
        long maxH = maxGapTree(h, horizontalCuts);
        long maxW = maxGapTree(w, verticalCuts);
        return (int) ((maxH % MOD) * (maxW % MOD) % MOD);
    }

    private long maxGapTree(int total, int[] cuts) {
        java.util.TreeSet<Integer> set = new java.util.TreeSet<Integer>();
        set.add(0);
        set.add(total);
        for (int x : cuts) {
            set.add(x);
        }
        long best = 0;
        Integer prev = null;
        for (int v : set) {
            if (prev != null) {
                best = Math.max(best, (long) v - prev);
            }
            prev = v;
        }
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.maxArea(5, 4, new int[]{1, 2, 4}, new int[]{1, 3})); // 4
        System.out.println(s.maxArea(5, 4, new int[]{3, 1}, new int[]{1})); // 6
        System.out.println(s.maxArea(5, 4, new int[]{3}, new int[]{3})); // 9
        System.out.println(s.maxArea(1000000000, 1000000000, new int[]{2}, new int[]{2})); // 4
        System.out.println(s.maxAreaTree(5, 4, new int[]{1, 2, 4}, new int[]{1, 3})); // 4
        System.out.println(s.maxAreaTree(5, 4, new int[]{3, 1}, new int[]{1})); // 6
        System.out.println(s.maxAreaTree(5, 4, new int[]{3}, new int[]{3})); // 9
    }
}

复杂度分析

复杂度:O(a log a + b log b),空间 O(1)。

复杂度:O(h + w + a + b)。


1466. Reorder Routes to Make All Paths Lead to the City Zero(重新规划路线)

题目描述

n 座城市,从 0 到 n-1 编号,其间共有 n-1 条路线(路线网形成一棵树)。

路线用 connections 表示,其中 connectionsi = `a`, `b` 表示从城市 a 到 b 的一条有向路线。

今年,城市 0 将会举办一场大型比赛,很多游客都想前往城市 0。

请你帮助重新规划路线方向,使每个城市都可以访问城市 0。返回需要变更方向的最小路线数。

题目数据保证每个城市在重新规划路线方向后都能到达城市 0。

示例 1

复制代码
n = 6, connections = [[0,1],[1,3],[2,3],[4,0],[4,5]] -> 3

示例 2

复制代码
n = 5, connections = [[1,0],[1,2],[3,2],[3,4]] -> 2

示例 3

复制代码
n = 3, connections = [[1,0],[2,0]] -> 0

约束:

  • 2 <= n <= 5 * 10^4;connections.length == n - 1。

解题思路

解法一:无向建图 + DFS 计数反向边(主提交版本)

对每条边 a, b 建立双向邻接:正向 a->b 标记 dir=1(按原始方向),

b->a 标记 dir=0(表示逆边)。从城市 0 出发 DFS 整棵树:

走到邻接节点时,若原边方向是"离开城市 0"(dir=1),则需要翻转该边,答案+1;

逆边(dir=0)表示已经朝向城市 0,不需要翻转。

因为图是树,从 0 出发 DFS 每条边只访问一次。

复杂度:O(n),空间 O(n)。

解法二:BFS 层序计数(对照)

同样建双向图,用队列 BFS 从 0 出发,逐层向外扩展,统计需要翻转的边数。

复杂度:O(n),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public int minReorder(int n, int[][] connections) {
        List<List<int[]>> g = new ArrayList<List<int[]>>();
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<int[]>());
        }
        for (int[] e : connections) {
            g.get(e[0]).add(new int[]{e[1], 1}); // 原始方向 0->1
            g.get(e[1]).add(new int[]{e[0], 0}); // 反向 1->0
        }
        boolean[] visited = new boolean[n];
        visited[0] = true;
        int[] cnt = new int[1];
        dfs(0, g, visited, cnt);
        return cnt[0];
    }

    private void dfs(int u, List<List<int[]>> g, boolean[] visited, int[] cnt) {
        for (int[] next : g.get(u)) {
            int v = next[0];
            int dir = next[1];
            if (visited[v]) {
                continue;
            }
            visited[v] = true;
            if (dir == 1) { // 原始方向是从 u 指向 v(离开 0),需要翻转
                cnt[0]++;
            }
            dfs(v, g, visited, cnt);
        }
    }

    public int minReorderBfs(int n, int[][] connections) {
        List<List<int[]>> g = new ArrayList<List<int[]>>();
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<int[]>());
        }
        for (int[] e : connections) {
            g.get(e[0]).add(new int[]{e[1], 1});
            g.get(e[1]).add(new int[]{e[0], 0});
        }
        boolean[] visited = new boolean[n];
        visited[0] = true;
        ArrayDeque<Integer> q = new ArrayDeque<Integer>();
        q.add(0);
        int cnt = 0;
        while (!q.isEmpty()) {
            int u = q.poll();
            for (int[] next : g.get(u)) {
                int v = next[0];
                int dir = next[1];
                if (visited[v]) {
                    continue;
                }
                visited[v] = true;
                if (dir == 1) {
                    cnt++;
                }
                q.add(v);
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minReorder(6, new int[][]{{0, 1}, {1, 3}, {2, 3}, {4, 0}, {4, 5}})); // 3
        System.out.println(s.minReorder(5, new int[][]{{1, 0}, {1, 2}, {3, 2}, {3, 4}})); // 2
        System.out.println(s.minReorder(3, new int[][]{{1, 0}, {2, 0}})); // 0
        System.out.println(s.minReorderBfs(6, new int[][]{{0, 1}, {1, 3}, {2, 3}, {4, 0}, {4, 5}})); // 3
        System.out.println(s.minReorderBfs(5, new int[][]{{1, 0}, {1, 2}, {3, 2}, {3, 4}})); // 2
        System.out.println(s.minReorderBfs(3, new int[][]{{1, 0}, {2, 0}})); // 0
    }
}

复杂度分析

复杂度:O(n),空间 O(n)。


1467. Probability of a Two Boxes Having The Same Number of Distinct Balls(两个盒子中球的颜色数相同的概率)

题目描述

桌面上有 2n 个颜色不完全相同的球,球的颜色共有 k 种。

给你一个大小为 k 的整数数组 balls,其中 ballsi 是颜色为 i 的球的数量。

所有的球都已经随机打乱顺序,前 n 个球放入第一个盒子,后 n 个球放入另一个盒子。

注意:这两个盒子是不同的。

请返回「两个盒子中球的颜色数相同」的情况的概率。答案与真实值误差在 10^-5 以内即可。

示例 1

复制代码
balls = [1,1] -> 1.00000

示例 2

复制代码
balls = [2,1,1] -> 0.66667

示例 3

复制代码
balls = [1,2,1,2] -> 0.60000

约束:

  • 1 <= balls.length <= 8;1 <= ballsi <= 6;sum(balls) 是偶数。

解题思路

解法一:组合计数(主提交版本)

总方案数 = 把 2n 个球分成两组(各 n 个)的方案数 = Π C(ballsi, take_i) 的所有

take_i 之和 = C(2n, n)(本质上每种"取出前半组"的方式对应一种分组)。

合法方案数 = 满足"第一盒颜色数 == 第二盒颜色数"的取出方案数。

DFS 枚举每种颜色 i 取 take_i 个放入第一盒(0 <= take_i <= ballsi,总数= n),

累加组合数乘积:ways_first = Π C(ballsi, take_i)。

条件:第一盒颜色数 c1 = #{i | take_i > 0} 与第二盒颜色数 c2 = #{i | ballsi-take_i > 0} 相等。

概率 = 合法方案数 / C(2n, n)。

组合数用 double 或 long 预处理(数值不超过 C(12,6)=924 的组合乘法,double 足够精确)。

复杂度:O(Π(ballsi+1)) 以 k<=8、balls<=6 为限,状态数很少。

解法二:记忆化 + 递归剩余(对照)

与解法一同构,只是把 DFS 换成显式的递归函数并用组合表直接相乘;

这里同样实现,但把累计合法数/总数表达为分数相加,逻辑一致。

复杂度与解法一同。

代码实现

java 复制代码
public class Solution {

    private long[][] comb;
    private long totalWays;
    private long goodWays;

    public double getProbability(int[] balls) {
        int k = balls.length;
        int total = 0;
        for (int b : balls) {
            total += b;
        }
        int half = total / 2;
        // 预处理组合数 C[n][r],n <= 12
        comb = new long[13][13];
        for (int i = 0; i <= 12; i++) {
            comb[i][0] = 1;
            for (int j = 1; j <= i; j++) {
                comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
            }
        }
        totalWays = comb[total][half]; // 总取法数 C(2n, n)
        goodWays = 0;
        dfs(0, half, 0, 0, 1L, balls);
        return (double) goodWays / totalWays;
    }

    private void dfs(int idx, int remaining, int c1, int c2, long ways, int[] balls) {
        if (idx == balls.length) {
            if (remaining == 0 && c1 == c2) {
                goodWays += ways;
            }
            return;
        }
        int maxTake = Math.min(balls[idx], remaining);
        // 枚举第一盒取 take 个该颜色球
        for (int take = 0; take <= maxTake; take++) {
            int left = balls[idx] - take; // 第二盒该颜色球数
            int n1 = take > 0 ? 1 : 0;
            int n2 = left > 0 ? 1 : 0;
            long next = ways * comb[balls[idx]][take];
            dfs(idx + 1, remaining - take, c1 + n1, c2 + n2, next, balls);
        }
    }

    public double getProbability2(int[] balls) {
        int k = balls.length;
        int total = 0;
        for (int b : balls) {
            total += b;
        }
        int half = total / 2;
        long[][] c = new long[13][13];
        for (int i = 0; i <= 12; i++) {
            c[i][0] = 1;
            for (int j = 1; j <= i; j++) {
                c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
            }
        }
        // 枚举所有第一盒取出方案
        long[] good = new long[1];
        long[] all = new long[1];
        all[0] = c[total][half];
        dfs2(0, half, 0, 0, 1L, balls, c, good);
        return (double) good[0] / all[0];
    }

    private void dfs2(int idx, int remaining, int c1, int c2, long ways,
                      int[] balls, long[][] c, long[] good) {
        if (idx == balls.length) {
            if (remaining == 0 && c1 == c2) {
                good[0] += ways;
            }
            return;
        }
        int maxTake = Math.min(balls[idx], remaining);
        for (int take = 0; take <= maxTake; take++) {
            int left = balls[idx] - take;
            dfs2(idx + 1, remaining - take, c1 + (take > 0 ? 1 : 0),
                    c2 + (left > 0 ? 1 : 0), ways * c[balls[idx]][take], balls, c, good);
        }
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.printf("%.5f%n", s.getProbability(new int[]{1, 1})); // 1.00000
        System.out.printf("%.5f%n", s.getProbability(new int[]{2, 1, 1})); // 0.66667
        System.out.printf("%.5f%n", s.getProbability(new int[]{1, 2, 1, 2})); // 0.60000
        System.out.printf("%.5f%n", s.getProbability2(new int[]{1, 1})); // 1.00000
        System.out.printf("%.5f%n", s.getProbability2(new int[]{2, 1, 1})); // 0.66667
        System.out.printf("%.5f%n", s.getProbability2(new int[]{1, 2, 1, 2})); // 0.60000
    }
}

复杂度分析

复杂度:O(Π(ballsi+1)) 以 k<=8、balls<=6 为限,状态数很少。


1468. Calculate Salaries(计算税后工资 🔒)

题目类型:数据库(SQL)

题目描述

Salaries 表:

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| company_id    | int     |
| employee_id   | int     |
| employee_name | varchar |
| salary        | int     |
+---------------+---------+
在 SQL 中,(company_id, employee_id) 是这个表的主键
这个表包括员工的company id, id, name 和 salary

查找出每个员工的税后工资

每个公司的税率计算依照以下规则

复制代码
- 如果这个公司员工最高工资不到 `$1000` ,税率为 `0%`

- 如果这个公司员工最高工资在 `[1000, 10000]` 之间,税率为 `24%`

- 如果这个公司员工最高工资大于 `$10000` ,税率为 `49%`

按 任意顺序 返回结果。

返回结果的格式如下例所示。

示例 1:

复制代码
**输入:**
Salaries 表:
+------------+-------------+---------------+--------+
| company_id | employee_id | employee_name | salary |
+------------+-------------+---------------+--------+
| 1          | 1           | Tony          | 2000   |
| 1          | 2           | Pronub        | 21300  |
| 1          | 3           | Tyrrox        | 10800  |
| 2          | 1           | Pam           | 300    |
| 2          | 7           | Bassem        | 450    |
| 2          | 9           | Hermione      | 700    |
| 3          | 7           | Bocaben       | 100    |
| 3          | 2           | Ognjen        | 2200   |
| 3          | 13          | Nyancat       | 3300   |
| 3          | 15          | Morninngcat   | 7777   |
+------------+-------------+---------------+--------+
**输出:**
+------------+-------------+---------------+--------+
| company_id | employee_id | employee_name | salary |
+------------+-------------+---------------+--------+
| 1          | 1           | Tony          | 1020   |
| 1          | 2           | Pronub        | 10863  |
| 1          | 3           | Tyrrox        | 5508   |
| 2          | 1           | Pam           | 300    |
| 2          | 7           | Bassem        | 450    |
| 2          | 9           | Hermione      | 700    |
| 3          | 7           | Bocaben       | 76     |
| 3          | 2           | Ognjen        | 1672   |
| 3          | 13          | Nyancat       | 2508   |
| 3          | 15          | Morninngcat   | 5911   |
+------------+-------------+---------------+--------+
**解释:**
对于公司 1,最高薪资为 21300。公司 1 的员工税率为 49%。
对于公司 2,最高薪资为 700。公司 2 的员工税率为 0%。
对于公司 3,最高薪资为 7777。公司 3 的员工税率为 24%。
薪资扣除税后的金额计算公式为:薪资 - (税率百分比 / 100) * 薪资
例如,Morninngcat(员工号 3,薪资为 7777)扣除税后的薪资为:7777 - 7777 * (24 / 100) = 7777 - 1866.48 = 5910.52,四舍五入为 5911。

解题思路

思考

税率由公司最高工资决定,需先按公司求 MAX ( s a l a r y ) \textit{MAX}(salary) MAX(salary),再与每位员工连接。按阈值 1000 1000 1000、 10000 10000 10000 用 CASE 打折并 ROUND。

考察CASE + 子查询/窗口函数:按公司分组求出最高/最低工资,再按比例计算税前/税后。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT
    s.company_id,
    employee_id,
    employee_name,
    ROUND(
        CASE
            WHEN top < 1000 THEN salary
            WHEN top >= 1000
            AND top <= 10000 THEN salary * 0.76
            ELSE salary * 0.51
        END
    ) AS salary
FROM
    Salaries AS s
    JOIN (
        SELECT company_id, MAX(salary) AS top
        FROM Salaries
        GROUP BY company_id
    ) AS t
        ON s.company_id = t.company_id;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。


1469. Find All The Lonely Nodes(寻找所有的独生节点)

题目描述

二叉树中,如果一个节点是其父节点的唯一子节点,则称这样的节点为"独生节点"。

二叉树的根节点不会是独生节点,因为它没有父节点。

给定一棵二叉树的根节点 root,返回树中所有的独生节点的值所构成的数组。数组的顺序不限。

示例 1

复制代码
root = [1,2,3,null,4] -> [4]

示例 2

复制代码
root = [7,1,4,6,null,5,3,null,null,null,null,null,2] -> [6,2]

示例 3

复制代码
root = [11,99,88,77,null,null,66,55,null,null,44,33,null,null,22]
-> [77,55,33,66,44,22]

约束:

  • 节点个数 1, 1000;1 <= Node.val <= 10^6。

解题思路

解法一:DFS 判定(主提交版本)

递归时传入当前节点是否为"独生"(即它是其父节点的唯一孩子)。

对每个节点:若它是独生节点则把值加入列表;然后分别以

左孩子(唯一孩子?= 右孩子为空)、右孩子(唯一孩子?= 左孩子为空)递归。

复杂度:O(n),空间 O(h)。

解法二:栈迭代(对照)

用显式栈存 (node, isLonely) 状态,逻辑同上。

复杂度:O(n),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) {
            val = x;
        }
    }

    public List<Integer> getLonelyNodes(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if (root == null) {
            return ans;
        }
        dfs(root, false, ans);
        return ans;
    }

    private void dfs(TreeNode node, boolean lonely, List<Integer> ans) {
        if (lonely) {
            ans.add(node.val);
        }
        if (node.left != null) {
            dfs(node.left, node.right == null, ans);
        }
        if (node.right != null) {
            dfs(node.right, node.left == null, ans);
        }
    }

    public List<Integer> getLonelyNodesStack(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if (root == null) {
            return ans;
        }
        ArrayDeque<Object[]> stack = new ArrayDeque<Object[]>();
        stack.push(new Object[]{root, Boolean.FALSE});
        while (!stack.isEmpty()) {
            Object[] top = stack.pop();
            TreeNode node = (TreeNode) top[0];
            boolean lonely = (Boolean) top[1];
            if (lonely) {
                ans.add(node.val);
            }
            // 后入先出:先压右再压左,保证左先处理(顺序不限,无要求)
            if (node.right != null) {
                stack.push(new Object[]{node.right, node.left == null});
            }
            if (node.left != null) {
                stack.push(new Object[]{node.left, node.right == null});
            }
        }
        return ans;
    }

    private static TreeNode build(Integer[] a) {
        if (a == null || a.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(a[0]);
        java.util.LinkedList<TreeNode> q = new java.util.LinkedList<TreeNode>();
        q.add(root);
        int i = 1;
        while (!q.isEmpty() && i < a.length) {
            TreeNode cur = q.poll();
            if (a[i] != null) {
                cur.left = new TreeNode(a[i]);
                q.add(cur.left);
            }
            i++;
            if (i < a.length && a[i] != null) {
                cur.right = new TreeNode(a[i]);
                q.add(cur.right);
            }
            i++;
        }
        return root;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.getLonelyNodes(build(new Integer[]{1, 2, 3, null, 4}))); // [4]
        System.out.println(s.getLonelyNodes(build(new Integer[]{7, 1, 4, 6, null, 5, 3, null, null, null, null, null, 2})));
        // [6, 2]
        System.out.println(s.getLonelyNodesStack(build(new Integer[]{1, 2, 3, null, 4}))); // [4]
        System.out.println(s.getLonelyNodesStack(build(new Integer[]{7, 1, 4, 6, null, 5, 3, null, null, null, null, null, 2})));
        // [6, 2]
    }
}

复杂度分析

复杂度:O(n),空间 O(h)。

复杂度:O(n),空间 O(n)。


1470. Shuffle the Array(重新排列数组)

题目描述

给你一个数组 nums,数组中有 2n 个元素,按 x1,x2,...,xn,y1,y2,...,yn 的格式排列。

请你将数组按 x1,y1,x2,y2,...,xn,yn 格式重新排列,返回重排后的数组。

示例 1

复制代码
nums = [2,5,1,3,4,7], n = 3 -> [2,3,5,4,1,7]

示例 2

复制代码
nums = [1,2,3,4,4,3,2,1], n = 4 -> [1,4,2,3,3,2,4,1]

示例 3

复制代码
nums = [1,1,2,2], n = 2 -> [1,2,1,2]

约束:

  • 1 <= n <= 500;nums.length == 2n;1 <= nums[i] <= 10^3。

解题思路

解法一:双指针交错填充(主提交版本)

结果数组按 x1,y1,x2,y2,... 交错:下标 2i 放 x_{i+1}=numsi,

下标 2i+1 放 y_{i+1}=numsn+i。

复杂度:O(n),空间 O(n)(结果)。

解法二:单指针 + 数学映射(对照)

对结果位置 k,其来源为:k 偶数 -> numsk/2;k 奇数 -> numsn + k/2。

复杂度:O(n),空间 O(n)。

代码实现

java 复制代码
public class Solution {

    public int[] shuffle(int[] nums, int n) {
        int[] res = new int[2 * n];
        for (int i = 0; i < n; i++) {
            res[2 * i] = nums[i];
            res[2 * i + 1] = nums[n + i];
        }
        return res;
    }

    public int[] shuffleMap(int[] nums, int n) {
        int[] res = new int[2 * n];
        for (int k = 0; k < 2 * n; k++) {
            if ((k & 1) == 0) {
                res[k] = nums[k / 2];
            } else {
                res[k] = nums[n + k / 2];
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(java.util.Arrays.toString(s.shuffle(new int[]{2, 5, 1, 3, 4, 7}, 3)));
        // [2, 3, 5, 4, 1, 7]
        System.out.println(java.util.Arrays.toString(s.shuffle(new int[]{1, 2, 3, 4, 4, 3, 2, 1}, 4)));
        // [1, 4, 2, 3, 3, 2, 4, 1]
        System.out.println(java.util.Arrays.toString(s.shuffle(new int[]{1, 1, 2, 2}, 2)));
        // [1, 2, 1, 2]
        System.out.println(java.util.Arrays.toString(s.shuffleMap(new int[]{2, 5, 1, 3, 4, 7}, 3)));
        // [2, 3, 5, 4, 1, 7]
        System.out.println(java.util.Arrays.toString(s.shuffleMap(new int[]{1, 2, 3, 4, 4, 3, 2, 1}, 4)));
        // [1, 4, 2, 3, 3, 2, 4, 1]
    }
}

复杂度分析

复杂度:O(n),空间 O(n)(结果)。

复杂度:O(n),空间 O(n)。


1471. The k Strongest Values in an Array(数组中的 k 个最强值)

题目描述

给你一个整数数组 arr 和一个整数 k。

设 m 为数组的中位数(有序列表中位于 ((n-1)/2) 的元素),只要满足下述两个前提之一,

就可以判定 arr[i] 的值比 arr[j] 的值更强:

  • |arr[i] - m| > |arr[j] - m|;
  • |arr[i] - m| == |arr[j] - m|,且 arr[i] > arr[j]。
    请返回由数组中最强的 k 个值组成的列表。答案可以以任意顺序返回。

示例 1

复制代码
arr = [1,2,3,4,5], k = 2 -> [5,1]

示例 2

复制代码
arr = [1,1,3,5,5], k = 2 -> [5,5]

示例 3

复制代码
arr = [6,7,11,7,6,8], k = 5 -> [11,8,6,6,7]

约束:

  • 1 <= arr.length <= 105;-105 <= arr[i] <= 10^5;1 <= k <= arr.length。

解题思路

解法一:排序 + 双指针取最强(主提交版本)

排序后取中位数 m = sorted(n-1)/2。

用左右两个指针(l=0, r=n-1):比较 sortedl 与 sortedr 谁更强

(距离 m 更远者强;等距时值更大者强------由于已排序,右端值更大),

较强的一个放入结果并移动对应指针,直至取满 k 个。

复杂度:O(n log n),空间 O(n)。

解法二:自定义排序(对照)

按"强度"定义写比较器对整个数组排序,取前 k 个。

复杂度:O(n log n),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Solution {

    public int[] getStrongest(int[] arr, int k) {
        int n = arr.length;
        int[] sorted = arr.clone();
        Arrays.sort(sorted);
        int m = sorted[(n - 1) / 2];
        int l = 0, r = n - 1;
        int[] res = new int[k];
        int idx = 0;
        while (idx < k) {
            int a = sorted[l];
            int b = sorted[r];
            int da = Math.abs(a - m);
            int db = Math.abs(b - m);
            if (da > db || (da == db && a > b)) {
                res[idx++] = a;
                l++;
            } else {
                res[idx++] = b;
                r--;
            }
        }
        return res;
    }

    public List<Integer> getStrongestSort(Integer[] arr, int k) {
        int n = arr.length;
        Integer[] copy = arr.clone();
        Arrays.sort(copy);
        final int m = copy[(n - 1) / 2];
        Arrays.sort(copy, (x, y) -> {
            int dx = Math.abs(x - m);
            int dy = Math.abs(y - m);
            if (dx != dy) {
                return dy - dx; // 距离大的排前面
            }
            return y - x; // 值大的排前面
        });
        List<Integer> res = new ArrayList<Integer>();
        for (int i = 0; i < k; i++) {
            res.add(copy[i]);
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(java.util.Arrays.toString(s.getStrongest(new int[]{1, 2, 3, 4, 5}, 2)));
        // [5, 1]
        System.out.println(java.util.Arrays.toString(s.getStrongest(new int[]{1, 1, 3, 5, 5}, 2)));
        // [5, 5]
        System.out.println(java.util.Arrays.toString(s.getStrongest(new int[]{6, 7, 11, 7, 6, 8}, 5)));
        // [11, 8, 6, 6, 7] 任一排列
        System.out.println(s.getStrongestSort(new Integer[]{1, 2, 3, 4, 5}, 2)); // [5, 1]
        System.out.println(s.getStrongestSort(new Integer[]{1, 1, 3, 5, 5}, 2)); // [5, 5]
    }
}

复杂度分析

复杂度:O(n log n),空间 O(n)。


1472. Design Browser History(设计浏览器历史记录)

题目描述

你有一个只支持单个标签页的浏览器,最开始你浏览的网页是 homepage,你可以访问其他的网站 url,

也可以在浏览历史中后退 steps 步或前进 steps 步。请你实现 BrowserHistory 类:

  • BrowserHistory(string homepage):用 homepage 初始化浏览器类;
  • void visit(string url):从当前页跳转访问 url 对应的页面,执行此操作会把浏览历史前进的记录全部删除;
  • string back(int steps):在浏览历史中后退 steps 步,如果 steps 超出边界则只后退边界,返回后退后的 url;
  • string forward(int steps):在浏览历史中前进 steps 步,同样受限。

示例

复制代码
["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"]
[["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.com"],[2],[2],[7]]
-> [null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"]

约束:

  • 最多调用 5000 次 visit、back 和 forward。

解题思路

解法一:双栈/数组 + 游标(主提交版本)

用 ArrayList history 保存访问历史,pos 指向当前页下标。

visit(url):截断 pos 之后的记录(前进记录作废),加入 url,pos 指向它。

back(steps):pos = max(0, pos - steps),返回 history.get(pos)。

forward(steps):pos = min(len-1, pos + steps),返回 history.get(pos)。

复杂度:visit O(1) 均摊(截断用 subList 清空),back/forward O(1)。

解法二:双向链表节点(对照)

每个节点保存 url 与 prev/next 指针;visit 时新建节点接到当前之后;

back/forward 沿链表移动至多 steps 步。

复杂度:visit O(1),back/forward O(steps)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public static class BrowserHistory {
        private List<String> history;
        private int pos;

        public BrowserHistory(String homepage) {
            history = new ArrayList<String>();
            history.add(homepage);
            pos = 0;
        }

        public void visit(String url) {
            // 清空前进记录
            while (history.size() - 1 > pos) {
                history.remove(history.size() - 1);
            }
            history.add(url);
            pos++;
        }

        public String back(int steps) {
            pos = Math.max(0, pos - steps);
            return history.get(pos);
        }

        public String forward(int steps) {
            pos = Math.min(history.size() - 1, pos + steps);
            return history.get(pos);
        }
    }

    public static class BrowserHistoryList {
        private static class Node {
            String url;
            Node prev;
            Node next;

            Node(String u) {
                url = u;
            }
        }

        private Node cur;

        public BrowserHistoryList(String homepage) {
            cur = new Node(homepage);
        }

        public void visit(String url) {
            Node node = new Node(url);
            node.prev = cur;
            cur.next = node; // 断开前进记录
            cur = node;
        }

        public String back(int steps) {
            int s = 0;
            while (s < steps && cur.prev != null) {
                cur = cur.prev;
                s++;
            }
            return cur.url;
        }

        public String forward(int steps) {
            int s = 0;
            while (s < steps && cur.next != null) {
                cur = cur.next;
                s++;
            }
            return cur.url;
        }
    }

    public static void main(String[] args) {
        BrowserHistory bh = new BrowserHistory("leetcode.com");
        bh.visit("google.com");
        bh.visit("facebook.com");
        bh.visit("youtube.com");
        System.out.println(bh.back(1)); // facebook.com
        System.out.println(bh.back(1)); // google.com
        System.out.println(bh.forward(1)); // facebook.com
        bh.visit("linkedin.com");
        System.out.println(bh.forward(2)); // linkedin.com
        System.out.println(bh.back(2)); // google.com
        System.out.println(bh.back(7)); // leetcode.com

        BrowserHistoryList bh2 = new BrowserHistoryList("leetcode.com");
        bh2.visit("google.com");
        bh2.visit("facebook.com");
        bh2.visit("youtube.com");
        System.out.println(bh2.back(1)); // facebook.com
        System.out.println(bh2.back(1)); // google.com
        System.out.println(bh2.forward(1)); // facebook.com
        bh2.visit("linkedin.com");
        System.out.println(bh2.forward(2)); // linkedin.com
        System.out.println(bh2.back(2)); // google.com
        System.out.println(bh2.back(7)); // leetcode.com
    }
}

复杂度分析

复杂度:visit O(1) 均摊(截断用 subList 清空),back/forward O(1)。

复杂度:visit O(1),back/forward O(steps)。


1473. Paint House III(粉刷房子 III)

题目描述

在一个小城市里,有 m 个房子排成一排,你需要给每个房子涂上 n 种颜色之一(颜色编号为 1 到 n)。

有的房子去年夏天已经涂过颜色了,所以这些房子不可以被重新涂色。

我们将连续相同颜色尽可能多的房子称为一个街区。

给你一个数组 houses(0 表示未涂色)、一个 m * n 的矩阵 cost 和一个整数 target,其中:

  • housesi 是第 i 个房子的颜色,0 表示还没有涂色;
  • cost[i][j] 是将第 i 个房子涂成颜色 j+1 的花费。
    请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成 target 个街区。
    如果没有可用的涂色方案,请返回 -1。

示例 1

复制代码
houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3 -> 9

示例 2

复制代码
houses = [0,2,1,2,0], ... -> 11

示例 4

复制代码
houses = [3,1,2,3], cost = [[1,1,1],...], m = 4, n = 3, target = 3 -> -1

约束:

  • 1 <= m <= 100;1 <= n <= 20;1 <= target <= m;0 <= housesi <= n。

解题思路

解法一:三维 DP(主提交版本)

dpict = 处理完前 i 个房子,第 i 个房子颜色为 c(1...n),总街区数为 t 的最小花费。

初始化:-inf。

转移:若 housei 已固定(housesi != 0),只能取 c == housesi,花费不变;

否则 c 可取 1...n,花费 costic-1。

对上一颜色 pc:若 pc == c 街区数不变(t),否则街区数 +1(t-1 -> t,即 dpict = min(dpi-1pct-1, ...))。

答案 = min over c of dpmctarget(>= 0 才有效)。

复杂度:O(m * n^2 * target)。

解法二:滚动数组优化(对照)

只用到上一行,将 i 维压缩为 2 层滚动。

复杂度:O(m * n^2 * target),空间 O(n * target)。

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
        int INF = Integer.MAX_VALUE / 2;
        // dp[house][color][blocks]
        int[][][] dp = new int[m + 1][n + 1][target + 1];
        for (int[][] a : dp) {
            for (int[] b : a) {
                Arrays.fill(b, INF);
            }
        }
        // 第 0 个房子之前无所谓颜色,看作"无颜色"(0)且 0 个街区
        // 用虚拟第 0 层:初始 dp[0][0][0] = 0
        for (int c = 0; c <= n; c++) {
            dp[0][c][0] = 0;
        }
        for (int i = 1; i <= m; i++) {
            int fixed = houses[i - 1]; // 0 表示未涂
            for (int c = 1; c <= n; c++) {
                if (fixed != 0 && fixed != c) {
                    continue; // 已固定颜色且不是 c,跳过
                }
                int add = (fixed == 0) ? cost[i - 1][c - 1] : 0;
                for (int t = 1; t <= target; t++) {
                    // 上一房子颜色 pc
                    for (int pc = 0; pc <= n; pc++) {
                        if (pc == c) {
                            // 颜色相同,街区数不变
                            if (dp[i - 1][pc][t] != INF) {
                                dp[i][c][t] = Math.min(dp[i][c][t], dp[i - 1][pc][t] + add);
                            }
                        } else {
                            // 颜色不同,街区数 +1(从 t-1 到 t)
                            if (t - 1 >= 0 && dp[i - 1][pc][t - 1] != INF) {
                                dp[i][c][t] = Math.min(dp[i][c][t], dp[i - 1][pc][t - 1] + add);
                            }
                        }
                    }
                }
            }
        }
        int ans = INF;
        for (int c = 1; c <= n; c++) {
            ans = Math.min(ans, dp[m][c][target]);
        }
        return ans >= INF ? -1 : ans;
    }

    public int minCostRoll(int[] houses, int[][] cost, int m, int n, int target) {
        int INF = Integer.MAX_VALUE / 2;
        int[][] dp = new int[n + 1][target + 1];
        for (int[] row : dp) {
            Arrays.fill(row, INF);
        }
        for (int c = 0; c <= n; c++) {
            dp[c][0] = 0; // 虚拟第 0 层
        }
        for (int i = 1; i <= m; i++) {
            int[][] ndp = new int[n + 1][target + 1];
            for (int[] row : ndp) {
                Arrays.fill(row, INF);
            }
            int fixed = houses[i - 1];
            for (int c = 1; c <= n; c++) {
                if (fixed != 0 && fixed != c) {
                    continue;
                }
                int add = (fixed == 0) ? cost[i - 1][c - 1] : 0;
                for (int t = 1; t <= target; t++) {
                    // 来自上一颜色 pc == c:街区数不变
                    if (dp[c][t] != INF) {
                        ndp[c][t] = Math.min(ndp[c][t], dp[c][t] + add);
                    }
                    // 来自上一颜色 pc != c:街区数从 t-1 到 t
                    for (int pc = 1; pc <= n; pc++) {
                        if (pc != c && dp[pc][t - 1] != INF) {
                            ndp[c][t] = Math.min(ndp[c][t], dp[pc][t - 1] + add);
                        }
                    }
                }
            }
            dp = ndp;
        }
        int ans = INF;
        for (int c = 1; c <= n; c++) {
            ans = Math.min(ans, dp[c][target]);
        }
        return ans >= INF ? -1 : ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minCost(new int[]{0, 0, 0, 0, 0},
                new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 9
        System.out.println(s.minCost(new int[]{0, 2, 1, 2, 0},
                new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 11
        System.out.println(s.minCost(new int[]{3, 1, 2, 3},
                new int[][]{{1, 1, 1}, {1, 1, 1}, {1, 1, 1}, {1, 1, 1}}, 4, 3, 3)); // -1
        System.out.println(s.minCostRoll(new int[]{0, 0, 0, 0, 0},
                new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 9
        System.out.println(s.minCostRoll(new int[]{0, 2, 1, 2, 0},
                new int[][]{{1, 10}, {10, 1}, {10, 1}, {1, 10}, {5, 1}}, 5, 2, 3)); // 11
        System.out.println(s.minCostRoll(new int[]{3, 1, 2, 3},
                new int[][]{{1, 1, 1}, {1, 1, 1}, {1, 1, 1}, {1, 1, 1}}, 4, 3, 3)); // -1
    }
}

复杂度分析

复杂度:O(m * n^2 * target)。

复杂度:O(m * n^2 * target),空间 O(n * target)。


1474. Delete N Nodes After M Nodes of a Linked List(删除链表 M 个节点之后的 N 个节点)

题目描述

给定链表 head 和两个整数 m 和 n。遍历该链表并按照如下方式删除节点:

  • 开始时以头节点作为当前节点;
  • 保留以当前节点开始的前 m 个节点;
  • 删除接下来的 n 个节点;
  • 重复步骤 2 和 3,直到到达链表结尾。
    在删除了指定结点之后,返回修改过后的链表的头节点。

示例 1

复制代码
head = [1,2,3,4,5,6,7,8,9,10,11,12,13], m = 2, n = 3 -> [1,2,6,7,11,12]

示例 2

复制代码
head = [1,2,3,4,5,6,7,8,9,10,11], m = 1, n = 3 -> [1,5,9]

示例 4

复制代码
head = [9,3,7,7,9,10,8,2], m = 1, n = 2 -> [9,7,8]

约束:

  • 节点数 1, 10\^4;1 <= Node.val <= 10^6;1 <= m, n <= 1000。

解题思路

解法一:双指针模拟保留/删除(主提交版本)

用一个指针 cur 每次先走 m 步(保留段末尾),若中途到链表尾则结束;

再从保留段末尾继续走 n 步(跳过要删除的节点),把 cur.next 接到删除段之后的节点。

复杂度:O(nodes),空间 O(1)。

解法二:计数循环一(对照)

用一个变量 state 与计数 cnt 交替执行步数,实现与解法一等价但用单一循环。

复杂度:O(nodes),空间 O(1)。

代码实现

java 复制代码
public class Solution {

    public static class ListNode {
        int val;
        ListNode next;

        ListNode(int x) {
            val = x;
        }
    }

    public ListNode deleteNodes(ListNode head, int m, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode cur = dummy;
        while (cur != null) {
            // 保留 m 步
            ListNode keep = cur;
            int keepCount = 0;
            while (keepCount < m && cur.next != null) {
                cur = cur.next;
                keepCount++;
            }
            if (cur.next == null) {
                break; // 已到链表尾,无需再删除
            }
            // 跳过 n 个要删除的节点
            int deleteCount = 0;
            while (deleteCount < n && cur.next != null) {
                cur = cur.next;
                deleteCount++;
            }
            keep.next = cur.next; // 接上删除段之后的节点
        }
        return dummy.next;
    }

    public ListNode deleteNodesLoop(ListNode head, int m, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode prev = dummy;
        ListNode cur = head;
        while (cur != null) {
            // 保留 m 个
            int k = 0;
            while (cur != null && k < m) {
                prev = cur;
                cur = cur.next;
                k++;
            }
            // 删除 n 个
            k = 0;
            while (cur != null && k < n) {
                cur = cur.next;
                k++;
            }
            prev.next = cur; // 保留段末尾直接接上删除段之后
        }
        return dummy.next;
    }

    private static ListNode build(int[] a) {
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        for (int v : a) {
            cur.next = new ListNode(v);
            cur = cur.next;
        }
        return dummy.next;
    }

    private static String toStr(ListNode head) {
        StringBuilder sb = new StringBuilder();
        while (head != null) {
            if (sb.length() > 0) {
                sb.append(",");
            }
            sb.append(head.val);
            head = head.next;
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(toStr(s.deleteNodes(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}), 2, 3)));
        // 1,2,6,7,11,12
        System.out.println(toStr(s.deleteNodes(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), 1, 3)));
        // 1,5,9
        System.out.println(toStr(s.deleteNodes(build(new int[]{9, 3, 7, 7, 9, 10, 8, 2}), 1, 2)));
        // 9,7,8
        System.out.println(toStr(s.deleteNodesLoop(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}), 2, 3)));
        // 1,2,6,7,11,12
        System.out.println(toStr(s.deleteNodesLoop(build(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), 1, 3)));
        // 1,5,9
        System.out.println(toStr(s.deleteNodesLoop(build(new int[]{9, 3, 7, 7, 9, 10, 8, 2}), 1, 2)));
        // 9,7,8
    }
}

复杂度分析

复杂度:O(nodes),空间 O(1)。


1475. Final Prices With a Special Discount in a Shop(商品折扣后的最终价格)

题目描述

给你一个数组 prices,其中 prices[i] 是商店里第 i 件商品的价格。

商店里正在进行促销活动,如果你要买第 i 件商品,那么你可以得到与 prices[j] 相等的折扣,

其中 j 是满足 j > i 且 prices[j] <= prices[i] 的最小下标,如果没有满足条件的 j,你将没有任何折扣。

请你返回一个数组,数组中第 i 个元素是折扣后你购买商品 i 最终需要支付的价格。

示例 1

复制代码
prices = [8,4,6,2,3] -> [4,2,4,2,3]

示例 2

复制代码
prices = [1,2,3,4,5] -> [1,2,3,4,5]

示例 3

复制代码
prices = [10,1,1,6] -> [9,0,1,6]

约束:

  • 1 <= prices.length <= 500;1 <= prices[i] <= 10^3。

解题思路

解法一:单调栈(主提交版本)

从右向左遍历,维护一个单调递增栈(栈内价格递增,栈顶是「右侧第一个 <= 当前价格」的候选)。

对当前 pricesi,栈顶即折扣价;出栈所有 > pricesi 的元素(它们不可能成为更左边元素

的第一个 <= 的候选,因为当前 pricesi 更小且更靠左)。

复杂度:O(n),空间 O(n)。

解法二:两重循环暴力(对照)

对每个 i 向右找第一个 <= pricesi 的 j。

复杂度:O(n^2)(n<=500 可行)。

代码实现

java 复制代码
import java.util.ArrayDeque;

public class Solution {

    public int[] finalPrices(int[] prices) {
        int n = prices.length;
        int[] ans = new int[n];
        ArrayDeque<Integer> stack = new ArrayDeque<Integer>(); // 存下标,价格单调递增
        for (int i = n - 1; i >= 0; i--) {
            // 弹出所有价格 > prices[i] 的(它们对更左的元素也会被 prices[i] 屏蔽)
            while (!stack.isEmpty() && prices[stack.peek()] > prices[i]) {
                stack.pop();
            }
            int discount = stack.isEmpty() ? 0 : prices[stack.peek()];
            ans[i] = prices[i] - discount;
            stack.push(i);
        }
        return ans;
    }

    public int[] finalPricesBrute(int[] prices) {
        int n = prices.length;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            int discount = 0;
            for (int j = i + 1; j < n; j++) {
                if (prices[j] <= prices[i]) {
                    discount = prices[j];
                    break;
                }
            }
            ans[i] = prices[i] - discount;
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(java.util.Arrays.toString(s.finalPrices(new int[]{8, 4, 6, 2, 3})));
        // [4, 2, 4, 2, 3]
        System.out.println(java.util.Arrays.toString(s.finalPrices(new int[]{1, 2, 3, 4, 5})));
        // [1, 2, 3, 4, 5]
        System.out.println(java.util.Arrays.toString(s.finalPrices(new int[]{10, 1, 1, 6})));
        // [9, 0, 1, 6]
        System.out.println(java.util.Arrays.toString(s.finalPricesBrute(new int[]{8, 4, 6, 2, 3})));
        // [4, 2, 4, 2, 3]
        System.out.println(java.util.Arrays.toString(s.finalPricesBrute(new int[]{10, 1, 1, 6})));
        // [9, 0, 1, 6]
    }
}

复杂度分析

复杂度:O(n),空间 O(n)。

复杂度:O(n^2)(n<=500 可行)。


1476. Subrectangle Queries(子矩形查询)

题目描述

请你实现一个类 SubrectangleQueries,它的构造函数的参数是一个 rows x cols 的矩形

(用整数矩阵表示),并支持以下两种操作:

  1. void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue)
    ------用 newValue 更新以 (row1,col1) 为左上角、(row2,col2) 为右下角的子矩形;
  2. int getValue(int row, int col) ------返回矩形中坐标 (row,col) 的当前值。

示例 1

复制代码
["SubrectangleQueries","getValue","updateSubrectangle","getValue","getValue","updateSubrectangle","getValue","getValue"]
[[[[1,2,1],[4,3,4],[3,2,1],[1,1,1]]],[0,2],[0,0,3,2,5],[0,2],[3,1],[3,0,3,2,10],[3,1],[0,2]]
-> [null,1,null,5,5,null,10,5]

约束:

  • 最多有 500 次 updateSubrectangle 和 getValue 操作;1 <= rows, cols <= 100。

解题思路

解法一:直接改矩阵(主提交版本)

用 int\[\]\[\] rect 保存矩阵;updateSubrectangle 直接双重循环覆盖子矩形;

getValue 直接返回 rectrowcol。

分析:更新 O(rows*cols)(最坏 10^4),500 次操作内完全可行。

解法二:记录操作日志(对照)

不直接改矩阵,每次 update 只记录 (row1, col1, row2, col2, newValue);

getValue(row, col) 倒序遍历操作日志,第一个覆盖 (row,col) 的操作值即为答案,

若均未覆盖则返回初始矩阵值。

更新 O(1),查询 O(#updates)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Solution {

    public static class SubrectangleQueries {
        private int[][] rect;

        public SubrectangleQueries(int[][] rectangle) {
            rect = rectangle;
        }

        public void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
            for (int r = row1; r <= row2; r++) {
                for (int c = col1; c <= col2; c++) {
                    rect[r][c] = newValue;
                }
            }
        }

        public int getValue(int row, int col) {
            return rect[row][col];
        }
    }

    public static class SubrectangleQueriesLog {
        private int[][] base;
        private List<int[]> ops; // {row1, col1, row2, col2, newValue}

        public SubrectangleQueriesLog(int[][] rectangle) {
            base = rectangle;
            ops = new ArrayList<int[]>();
        }

        public void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
            ops.add(new int[]{row1, col1, row2, col2, newValue});
        }

        public int getValue(int row, int col) {
            for (int i = ops.size() - 1; i >= 0; i--) {
                int[] op = ops.get(i);
                if (row >= op[0] && row <= op[2] && col >= op[1] && col <= op[3]) {
                    return op[4];
                }
            }
            return base[row][col];
        }
    }

    public static void main(String[] args) {
        int[][] rect = {{1, 2, 1}, {4, 3, 4}, {3, 2, 1}, {1, 1, 1}};
        SubrectangleQueries srq = new SubrectangleQueries(rect);
        System.out.println(srq.getValue(0, 2)); // 1
        srq.updateSubrectangle(0, 0, 3, 2, 5);
        System.out.println(srq.getValue(0, 2)); // 5
        System.out.println(srq.getValue(3, 1)); // 5
        srq.updateSubrectangle(3, 0, 3, 2, 10);
        System.out.println(srq.getValue(3, 1)); // 10
        System.out.println(srq.getValue(0, 2)); // 5

        SubrectangleQueriesLog srq2 = new SubrectangleQueriesLog(rect);
        System.out.println(srq2.getValue(0, 2)); // 1
        srq2.updateSubrectangle(0, 0, 3, 2, 5);
        System.out.println(srq2.getValue(0, 2)); // 5
        System.out.println(srq2.getValue(3, 1)); // 5
        srq2.updateSubrectangle(3, 0, 3, 2, 10);
        System.out.println(srq2.getValue(3, 1)); // 10
        System.out.println(srq2.getValue(0, 2)); // 5
    }
}

复杂度分析


1477. Find Two Non-overlapping Sub-arrays Each With Target Sum(找两个和为目标值且不重叠的子数组)

题目描述

给你一个整数数组 arr 和一个整数值 target。

请你在 arr 中找两个互不重叠的子数组且它们的和都等于 target。

可能会有多种方案,请你返回满足要求的两个子数组长度和的最小值。

请返回满足要求的最小长度和,如果无法找到这样的两个子数组,请返回 -1。

示例 1

复制代码
arr = [3,2,2,4,3], target = 3 -> 2

示例 2

复制代码
arr = [7,3,4,7], target = 7 -> 2

示例 3

复制代码
arr = [4,3,2,6,2,3,4], target = 6 -> -1

示例 5

复制代码
arr = [3,1,1,1,5,1,2,1], target = 3 -> 3

约束:

  • 1 <= arr.length <= 10^5;1 <= arr[i] <= 1000;1 <= target <= 10^8。

解题思路

解法一:前缀和 + 左右最小长度(主提交版本)

由于元素为正数,可以用滑动窗口找到所有和为 target 的区间 l, r。

定义 lefti = arr0...i-1 中"和为 target 的最短子数组长度"(没有则 INF);

处理时按右端点递增扫描:对每个右端点 r,若窗口和为 target 则长度 len = r-l+1,

使 leftr+1 = min(leftr, len)。

同理从小到大计算右半部分 righti = arri...n-1 中"和为 target 的最短子数组长度"。

枚举分割点 i(0...n),答案 = min(lefti + righti)。

复杂度:O(n),空间 O(n)。

解法二:仅从左到右扫描并配对(对照)

用滑动窗口找区间并把每个区间存入 (l, r, len),再对区间按某种序检查

是否与先前区间不重叠且取最小和。这里采用"对每个以 i 结尾的区间,与

之前所有以 < i 结尾的区间配对",用前缀最小长度数组加速。

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int minSumOfLengths(int[] arr, int target) {
        int n = arr.length;
        int INF = Integer.MAX_VALUE / 2;
        int[] left = new int[n + 1];
        Arrays.fill(left, INF);
        int sum = 0, l = 0;
        // 计算 left[i]:arr[0..i-1] 内和为 target 的最短子数组长度
        for (int r = 0; r < n; r++) {
            sum += arr[r];
            while (sum > target) {
                sum -= arr[l];
                l++;
            }
            left[r + 1] = left[r];
            if (sum == target) {
                left[r + 1] = Math.min(left[r + 1], r - l + 1);
            }
        }
        int[] right = new int[n + 1];
        Arrays.fill(right, INF);
        sum = 0;
        int rr = n;
        // 计算 right[i]:arr[i..n-1] 内和为 target 的最短子数组长度
        for (int l2 = n - 1; l2 >= 0; l2--) {
            sum += arr[l2];
            while (sum > target) {
                sum -= arr[rr - 1];
                rr--;
            }
            right[l2] = right[l2 + 1];
            if (sum == target) {
                right[l2] = Math.min(right[l2], rr - l2);
            }
        }
        int ans = INF;
        for (int i = 0; i <= n; i++) {
            if (left[i] < INF && right[i] < INF) {
                ans = Math.min(ans, left[i] + right[i]);
            }
        }
        return ans >= INF ? -1 : ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minSumOfLengths(new int[]{3, 2, 2, 4, 3}, 3)); // 2
        System.out.println(s.minSumOfLengths(new int[]{7, 3, 4, 7}, 7)); // 2
        System.out.println(s.minSumOfLengths(new int[]{4, 3, 2, 6, 2, 3, 4}, 6)); // -1
        System.out.println(s.minSumOfLengths(new int[]{5, 5, 4, 4, 5}, 3)); // -1
        System.out.println(s.minSumOfLengths(new int[]{3, 1, 1, 1, 5, 1, 2, 1}, 3)); // 3
    }
}

复杂度分析

复杂度:O(n),空间 O(n)。


1478. Allocate Mailboxes(安排邮筒)

题目描述

给你一个房屋数组 houses 和一个整数 k,其中 housesi 是第 i 栋房子在一条街上的位置,

现需要在这条街上安排 k 个邮筒。

请你返回每栋房子与离它最近的邮筒之间的距离的最小总和。

答案保证在 32 位有符号整数范围以内。

示例 1

复制代码
houses = [1,4,8,10,20], k = 3 -> 5

示例 2

复制代码
houses = [2,3,5,12,18], k = 2 -> 9

示例 3

复制代码
houses = [7,4,6,1], k = 1 -> 8

示例 4

复制代码
houses = [3,6,14,10], k = 4 -> 0

约束:

  • 1 <= n <= 100;1 <= housesi <= 10^4;1 <= k <= n。

解题思路

解法一:排序 + 区间成本 + 划分 DP(主提交版本)

邮筒最优放在区间房子的中位位置。先对 houses 排序。

costij = 用一个邮筒服务 housesi...j 的最小总距离(放中位数的位置):

递推 costij = costij-1 + housesj - houses(i+j)/2。

DP:dpti = 用 t 个邮筒服务前 i 个房子(下标 0...i-1)的最小总距离,

dpti = min over j < i of dpt-1j + costji-1。

答案 dpkn。

复杂度:O(k * n^2)。

解法二:记忆化递归(对照)

f(t, i) 表示用 t 个邮筒服务 houses0...i-1;与解法一同构,用 memo 记录。

复杂度:O(k * n^2)。

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int minDistance(int[] houses, int k) {
        int n = houses.length;
        Arrays.sort(houses);
        int[][] cost = new int[n][n];
        for (int i = 0; i < n; i++) {
            cost[i][i] = 0;
            for (int j = i + 1; j < n; j++) {
                cost[i][j] = cost[i][j - 1] + houses[j] - houses[(i + j) / 2];
            }
        }
        int INF = Integer.MAX_VALUE / 2;
        int[] dp = new int[n + 1]; // dp[i] = 前 i 个房子
        Arrays.fill(dp, INF);
        dp[0] = 0;
        // 使用一个邮筒时
        for (int t = 1; t <= k; t++) {
            int[] ndp = new int[n + 1];
            Arrays.fill(ndp, INF);
            ndp[0] = 0;
            for (int i = 1; i <= n; i++) {
                for (int j = 0; j < i; j++) {
                    if (dp[j] != INF) {
                        ndp[i] = Math.min(ndp[i], dp[j] + cost[j][i - 1]);
                    }
                }
            }
            dp = ndp;
        }
        return dp[n];
    }

    public int minDistanceMemo(int[] houses, int k) {
        int n = houses.length;
        Arrays.sort(houses);
        int[][] cost = new int[n][n];
        for (int i = 0; i < n; i++) {
            cost[i][i] = 0;
            for (int j = i + 1; j < n; j++) {
                cost[i][j] = cost[i][j - 1] + houses[j] - houses[(i + j) / 2];
            }
        }
        int[][] memo = new int[k + 1][n + 1];
        for (int[] row : memo) {
            Arrays.fill(row, -1);
        }
        return rec(k, n, cost, memo);
    }

    private int rec(int t, int i, int[][] cost, int[][] memo) {
        if (i == 0) {
            return 0;
        }
        if (t == 0) {
            return Integer.MAX_VALUE / 2; // 没有邮筒但还有房子
        }
        if (memo[t][i] != -1) {
            return memo[t][i];
        }
        int best = Integer.MAX_VALUE / 2;
        for (int j = 0; j < i; j++) {
            int sub = rec(t - 1, j, cost, memo);
            if (sub != Integer.MAX_VALUE / 2) {
                best = Math.min(best, sub + cost[j][i - 1]);
            }
        }
        memo[t][i] = best;
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minDistance(new int[]{1, 4, 8, 10, 20}, 3)); // 5
        System.out.println(s.minDistance(new int[]{2, 3, 5, 12, 18}, 2)); // 9
        System.out.println(s.minDistance(new int[]{7, 4, 6, 1}, 1)); // 8
        System.out.println(s.minDistance(new int[]{3, 6, 14, 10}, 4)); // 0
        System.out.println(s.minDistanceMemo(new int[]{1, 4, 8, 10, 20}, 3)); // 5
        System.out.println(s.minDistanceMemo(new int[]{2, 3, 5, 12, 18}, 2)); // 9
        System.out.println(s.minDistanceMemo(new int[]{7, 4, 6, 1}, 1)); // 8
        System.out.println(s.minDistanceMemo(new int[]{3, 6, 14, 10}, 4)); // 0
    }
}

复杂度分析

复杂度:O(k * n^2)。


1479. Sales by Day of the Week(周内每天的销售情况 🔒)

题目类型:数据库(SQL)

题目描述

表:Orders

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| order_id      | int     |
| customer_id   | int     |
| order_date    | date    |
| item_id       | varchar |
| quantity      | int     |
+---------------+---------+
(order_id, item_id) 是该表主键(具有唯一值的列的组合)
该表包含了订单信息
order_date 是id为 item_id 的商品被id为 customer_id 的消费者订购的日期.

表:Items

复制代码
+---------------------+---------+
| Column Name         | Type    |
+---------------------+---------+
| item_id             | varchar |
| item_name           | varchar |
| item_category       | varchar |
+---------------------+---------+
item_id 是该表主键(具有唯一值的列)
item_name 是商品的名字
item_category 是商品的类别

你是企业主,想要获得分类商品和周内每天的销售报告。

编写解决方案,报告 **周内每天 **每个商品类别下订购了多少单位。

返回结果表单** 按商品类别排序 **。

结果格式如下例所示。

示例 1:

复制代码
`**输入:**
Orders` 表:
+------------+--------------+-------------+--------------+-------------+
| order_id   | customer_id  | order_date  | item_id      | quantity    |
+------------+--------------+-------------+--------------+-------------+
| 1          | 1            | 2020-06-01  | 1            | 10          |
| 2          | 1            | 2020-06-08  | 2            | 10          |
| 3          | 2            | 2020-06-02  | 1            | 5           |
| 4          | 3            | 2020-06-03  | 3            | 5           |
| 5          | 4            | 2020-06-04  | 4            | 1           |
| 6          | 4            | 2020-06-05  | 5            | 5           |
| 7          | 5            | 2020-06-05  | 1            | 10          |
| 8          | 5            | 2020-06-14  | 4            | 5           |
| 9          | 5            | 2020-06-21  | 3            | 5           |
+------------+--------------+-------------+--------------+-------------+

`Items` 表:
+------------+----------------+---------------+
| item_id    | item_name      | item_category |
+------------+----------------+---------------+
| 1          | LC Alg. Book   | Book          |
| 2          | LC DB. Book    | Book          |
| 3          | LC SmarthPhone | Phone         |
| 4          | LC Phone 2020  | Phone         |
| 5          | LC SmartGlass  | Glasses       |
| 6          | LC T-Shirt XL  | T-Shirt       |
+------------+----------------+---------------+
**输出:**
+------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Category   | Monday    | Tuesday   | Wednesday | Thursday  | Friday    | Saturday  | Sunday    |
+------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Book       | 20        | 5         | 0         | 0         | 10        | 0         | 0         |
| Glasses    | 0         | 0         | 0         | 0         | 5         | 0         | 0         |
| Phone      | 0         | 0         | 5         | 1         | 0         | 0         | 10        |
| T-Shirt    | 0         | 0         | 0         | 0         | 0         | 0         | 0         |
+------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
**解释:**
在周一(2020-06-01, 2020-06-08),Book分类(ids: 1, 2)下,总共销售了20个单位(10 + 10)
在周二(2020-06-02),Book分类(ids: 1, 2)下,总共销售了5个单位
在周三(2020-06-03),Phone分类(ids: 3, 4)下,总共销售了5个单位
在周四(2020-06-04),Phone分类(ids: 3, 4)下,总共销售了1个单位
在周五(2020-06-05),Book分类(ids: 1, 2)下,总共销售了10个单位,Glasses分类(ids: 5)下,总共销售了5个单位
在周六, 没有商品销售
在周天(2020-06-14, 2020-06-21),Phone分类(ids: 3, 4)下,总共销售了10个单位(5 + 5)
没有销售 T-Shirt 类别的商品

解题思路

思考

要按品类输出周一至周日的销量,没有订单的品类也应出现。以 Items 为右表连接 Orders,用 DAYOFWEEK 把数量加到对应列,再按品类分组排序。

考察GROUP BY + CASE WHEN 列转行:把每天销量按周一到周日展开为列。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT
    item_category AS category,
    SUM(IF(DAYOFWEEK(order_date) = '2', quantity, 0)) AS Monday,
    SUM(IF(DAYOFWEEK(order_date) = '3', quantity, 0)) AS Tuesday,
    SUM(IF(DAYOFWEEK(order_date) = '4', quantity, 0)) AS Wednesday,
    SUM(IF(DAYOFWEEK(order_date) = '5', quantity, 0)) AS Thursday,
    SUM(IF(DAYOFWEEK(order_date) = '6', quantity, 0)) AS Friday,
    SUM(IF(DAYOFWEEK(order_date) = '7', quantity, 0)) AS Saturday,
    SUM(IF(DAYOFWEEK(order_date) = '1', quantity, 0)) AS Sunday
FROM
    Orders AS o
    RIGHT JOIN Items AS i ON o.item_id = i.item_id
GROUP BY category
ORDER BY category;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。


1480. Running Sum of 1d Array(一维数组的动态和)

题目描述

给你一个数组 nums。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]...nums[i])。

请返回 nums 的动态和。

示例 1

复制代码
nums = [1,2,3,4] -> [1,3,6,10]

示例 2

复制代码
nums = [1,1,1,1,1] -> [1,2,3,4,5]

示例 3

复制代码
nums = [3,1,2,10,1] -> [3,4,6,16,17]

约束:

  • 1 <= nums.length <= 1000;-10^6 <= nums[i] <= 10^6。

解题思路

解法一:原地累加(主提交版本)

从下标 1 开始,令 numsi += numsi-1,直接在原数组上累加得出前缀和。

复杂度:O(n) 时间,O(1) 额外空间。

解法二:新数组(对照)

新建 ans 数组逐一记录前缀和,不修改原数组。

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

代码实现

java 复制代码
import java.util.Arrays;

public class Solution {

    public int[] runningSum(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            nums[i] += nums[i - 1];
        }
        return nums;
    }

    public int[] runningSumNew(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += nums[i];
            ans[i] = sum;
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(Arrays.toString(s.runningSum(new int[]{1, 2, 3, 4}))); // [1,3,6,10]
        System.out.println(Arrays.toString(s.runningSum(new int[]{1, 1, 1, 1, 1}))); // [1,2,3,4,5]
        System.out.println(Arrays.toString(s.runningSum(new int[]{3, 1, 2, 10, 1}))); // [3,4,6,16,17]
        System.out.println(Arrays.toString(s.runningSumNew(new int[]{1, 2, 3, 4}))); // [1,3,6,10]
        System.out.println(Arrays.toString(s.runningSumNew(new int[]{1, 1, 1, 1, 1}))); // [1,2,3,4,5]
        System.out.println(Arrays.toString(s.runningSumNew(new int[]{3, 1, 2, 10, 1}))); // [3,4,6,16,17]
    }
}

复杂度分析

复杂度:O(n) 时间,O(1) 额外空间。

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

相关推荐
2301_780789668 小时前
跨平台适配!SDK 游戏盾兼容全终端游戏场景
java·网络·ddos
学长毕设9 小时前
基于springboot的食品仓库管理系统
java·spring boot·后端
步行cgn10 小时前
FormHttpMessageConverter 详解:Spring MVC 中表单数据的读写转换器
java·spring·mvc
龙亘川10 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
金銀銅鐵10 小时前
[Java] 用 CLI 展示 EnumMap 增删键值对时的内部变化
java·源码·ai编程
秋天的怀念_David10 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Raas10011 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
Pniubi11 小时前
力扣283移动零(双指针法)
算法·leetcode·职场和发展
niucloud-admin11 小时前
NIUCLOUD PHP SAAS上门家政使用文档:城市管理
java·前端·php