LeetCode1721~1740题目解析

LeetCode 1721-1740. 题解合集

文章目录


1721. Swapping Nodes in a Linked List(交换链表中的节点)

题目描述

给你链表的头节点 head 和一个整数 k。

交换链表正数第 k 个节点和倒数第 k 个节点的值后,返回链表的头节点(链表从 1 开始索引)。

示例

复制代码
head = [1,2,3,4,5], k = 2,输出 [1,4,3,2,5]。

解题思路

解法一:双指针找两节点(主提交版本)

  • 第一遍快指针先走 k 步到达正数第 k 个节点 nodeK;
  • 之后慢指针从头出发,与快指针同步走,快指针走到末尾时慢指针正好在倒数第 k 个节点;
  • 交换两个节点的 val。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。
    解法二:先求长度再定位(对照)
  • 先遍历一次求链表长度 len,再分别找到正数第 k 个与倒数第 k 个(正数第 len-k+1 个)节点交换。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    private static class ListNode {
        int val;
        ListNode next;
        ListNode(int v) { val = v; }
    }

    // 解法一:双指针找两节点(主提交版本)
    public ListNode swapNodes(ListNode head, int k) {
        ListNode fast = head;
        for (int i = 1; i < k; i++) {
            fast = fast.next;
        }
        ListNode nodeK = fast; // 正数第 k 个
        ListNode slow = head;
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        // 此时 slow 为倒数第 k 个
        int tmp = nodeK.val;
        nodeK.val = slow.val;
        slow.val = tmp;
        return head;
    }

    // 解法二:先求长度再定位(对照)
    public ListNode swapNodesLen(ListNode head, int k) {
        int len = 0;
        ListNode p = head;
        while (p != null) {
            len++;
            p = p.next;
        }
        ListNode a = head, b = head;
        for (int i = 1; i < k; i++) {
            a = a.next;
        }
        for (int i = 1; i < len - k + 1; i++) {
            b = b.next;
        }
        int tmp = a.val;
        a.val = b.val;
        b.val = tmp;
        return head;
    }

    private static ListNode of(int... vals) {
        ListNode dummy = new ListNode(0), t = dummy;
        for (int v : vals) {
            t.next = new ListNode(v);
            t = t.next;
        }
        return dummy.next;
    }

    private static String show(ListNode h) {
        StringBuilder sb = new StringBuilder();
        while (h != null) {
            sb.append(h.val).append(' ');
            h = h.next;
        }
        return sb.toString().trim();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(show(sol.swapNodes(of(1, 2, 3, 4, 5), 2))); // 1 4 3 2 5
        System.out.println(show(sol.swapNodes(of(1), 1))); // 1
        System.out.println(show(sol.swapNodesLen(of(1, 2, 3, 4, 5), 2))); // 1 4 3 2 5
        System.out.println(show(sol.swapNodesLen(of(1), 1))); // 1
    }
}

复杂度分析

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


1722. Minimize Hamming Distance After Swap Operations(执行交换操作后的最小汉明距离)

题目描述

给你两个整数数组 source 和 target,长度都是 n。还有一个数组 allowedSwaps,

其中每个 allowedSwaps[i] = `ai`, `bi` 表示你可以交换数组 source 中下标为 ai 和 bi 的两个元素

(下标从 0 开始)。注意,你可以按任意顺序多次交换一对特定下标指向的元素。

相同长度的两个数组 source 和 target 间的汉明距离是元素不同的下标数量。

在使用若干次 allowedSwaps 中的交换之后,请你返回 source 和 target 间的最小汉明距离。

示例 1

复制代码
source = [1,2,3,4], target = [2,1,4,5], allowedSwaps = [[0,1],[2,3]],输出 1。

示例 2

复制代码
source = [1,2,3,4], target = [1,3,2,4], allowedSwaps = [],输出 2。

解题思路

解法一:并查集 + 连通分量内计数(主提交版本)

  • 可以通过 allowedSwaps 交换等价于:同一连通分量(并查集合并)内的下标可以随意重排。
  • 对每个连通分量,统计 source 中的值频次,然后与同分量 target 中的值匹配消费:
    差值部分即为该分量无法匹配的个数,累加所有分量即最小汉明距离。
    复杂度:时间复杂度 O(n α(n)),空间复杂度 O(n)。
    解法二:BFS 找连通块(对照)
  • 用邻接表 + BFS 求连通分量,其余逻辑相同。
    复杂度:时间复杂度 O(n + m),空间复杂度 O(n + m)。

代码实现

java 复制代码
public class Solution {

    // 解法一:并查集 + 连通分量内计数(主提交版本)
    private static class DSU {
        int[] p;
        DSU(int n) {
            p = new int[n];
            for (int i = 0; i < n; i++) p[i] = i;
        }
        int find(int x) {
            while (p[x] != x) {
                p[x] = p[p[x]];
                x = p[x];
            }
            return x;
        }
        void union(int a, int b) {
            int ra = find(a), rb = find(b);
            if (ra != rb) p[ra] = rb;
        }
    }

    public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
        int n = source.length;
        DSU dsu = new DSU(n);
        for (int[] e : allowedSwaps) {
            dsu.union(e[0], e[1]);
        }
        java.util.Map<Integer, java.util.List<Integer>> groups = new java.util.HashMap<>();
        for (int i = 0; i < n; i++) {
            int r = dsu.find(i);
            groups.computeIfAbsent(r, k -> new java.util.ArrayList<>()).add(i);
        }
        int ans = 0;
        for (java.util.List<Integer> list : groups.values()) {
            java.util.Map<Integer, Integer> cnt = new java.util.HashMap<>();
            for (int idx : list) {
                cnt.put(source[idx], cnt.getOrDefault(source[idx], 0) + 1);
            }
            for (int idx : list) {
                int v = target[idx];
                int c = cnt.getOrDefault(v, 0);
                if (c > 0) {
                    cnt.put(v, c - 1);
                } else {
                    ans++; // 该分量中 target 的这个值无法与 source 匹配
                }
            }
        }
        return ans;
    }

    // 解法二:BFS 求连通块(对照)
    public int minimumHammingDistanceBfs(int[] source, int[] target, int[][] allowedSwaps) {
        int n = source.length;
        java.util.List<Integer>[] g = new java.util.ArrayList[n];
        for (int i = 0; i < n; i++) {
            g[i] = new java.util.ArrayList<>();
        }
        for (int[] e : allowedSwaps) {
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        boolean[] vis = new boolean[n];
        int ans = 0;
        for (int i = 0; i < n; i++) {
            if (vis[i]) {
                continue;
            }
            java.util.List<Integer> comp = new java.util.ArrayList<>();
            java.util.ArrayDeque<Integer> dq = new java.util.ArrayDeque<>();
            dq.add(i);
            vis[i] = true;
            while (!dq.isEmpty()) {
                int u = dq.poll();
                comp.add(u);
                for (int v : g[u]) {
                    if (!vis[v]) {
                        vis[v] = true;
                        dq.add(v);
                    }
                }
            }
            java.util.Map<Integer, Integer> cnt = new java.util.HashMap<>();
            for (int idx : comp) {
                cnt.put(source[idx], cnt.getOrDefault(source[idx], 0) + 1);
            }
            for (int idx : comp) {
                int v = target[idx];
                int c = cnt.getOrDefault(v, 0);
                if (c > 0) {
                    cnt.put(v, c - 1);
                } else {
                    ans++;
                }
            }
        }
        return ans;
    }

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

复杂度分析

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

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


1723. Find Minimum Time to Finish All Jobs(完成所有工作的最短时间)

题目描述

给你一个整数数组 jobs,其中 jobs[i] 是完成第 i 项工作要花费的时间。

请你将这些工作分配给 k 位工人。所有工作都应该分配给工人,且每项工作只能分配给一位工人。

工人的工作时间是完成分配给他们的所有工作花费时间的总和。

请你设计一套最佳的工作分配方案,使工人的最大工作时间得以最小化,返回尽可能小的最大工作时间。

示例 1

复制代码
jobs = [3,2,3], k = 3,输出 3。

示例 2

复制代码
jobs = [1,2,4,7,8], k = 2,输出 11。

解题思路

解法一:回溯 + 剪枝(主提交版本)

  • 降序排序 jobs(先分配大任务更易剪枝)。
  • 回溯尝试把每个任务分配给某个工人,用"当前最大值"剪枝:若某方案最大值已 >= 已知最优则放弃。
  • 典型优化:若 workeri == workerj(负载相同),跳过重复分配。
    复杂度:时间复杂度 O(k^n) 最坏(剪枝后实际远小于),空间复杂度 O(k)。
    解法二:状态压缩 DP 二分答案(对照)
  • 二分答案 limit,用 dpmask 表示分配 mask 内工作所需的"最少工人越界" -> 直接判定
    (mask 中工作时长总和 / limit) 是否可行:dpmask = 是否能把 mask 分成若干组,每组和 <= limit。
  • 采用子集枚举 DP:canmask 表示 mask 是否能被合法分成若干 <= limit 的组。
    由于 k 较小,也可用"最小分组数" dpmask 判定 dpmask <= k。
    复杂度:时间复杂度 O(3^n)(子集枚举),空间复杂度 O(2^n)。

代码实现

java 复制代码
public class Solution {

    // 解法一:回溯 + 剪枝(主提交版本)
    public int minimumTimeRequired(int[] jobs, int k) {
        int n = jobs.length;
        // 从大到小排序
        int[] sorted = jobs.clone();
        java.util.Arrays.sort(sorted);
        for (int i = 0; i < n / 2; i++) {
            int t = sorted[i];
            sorted[i] = sorted[n - 1 - i];
            sorted[n - 1 - i] = t;
        }
        int[] load = new int[k];
        int[] best = {Integer.MAX_VALUE};
        backtrack(sorted, 0, k, load, 0, best);
        return best[0];
    }

    private void backtrack(int[] jobs, int idx, int k, int[] load, int curMax, int[] best) {
        if (curMax >= best[0]) {
            return; // 剪枝:当前最大已不小于已知最优
        }
        if (idx == jobs.length) {
            best[0] = curMax;
            return;
        }
        int v = jobs[idx];
        java.util.Set<Integer> seen = new java.util.HashSet<>();
        for (int i = 0; i < k; i++) {
            if (seen.contains(load[i])) {
                continue; // 相同负载的工人等价,避免重复
            }
            seen.add(load[i]);
            load[i] += v;
            backtrack(jobs, idx + 1, k, load, Math.max(curMax, load[i]), best);
            load[i] -= v;
        }
    }

    // 解法二:二分答案 + 状态压缩 DP(对照)
    public int minimumTimeRequiredDp(int[] jobs, int k) {
        int n = jobs.length;
        int full = 1 << n;
        // sum[mask]
        int[] sum = new int[full];
        for (int mask = 1; mask < full; mask++) {
            int low = Integer.numberOfTrailingZeros(mask);
            sum[mask] = sum[mask ^ (1 << low)] + jobs[low];
        }
        int lo = 0, hi = sum[full - 1];
        for (int s : jobs) {
            lo = Math.max(lo, s);
        }
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (can(jobs, k, mid, sum, full)) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }

    private boolean can(int[] jobs, int k, int limit, int[] sum, int full) {
        // dp[mask] = mask 所需最少组数,转移时拆出一个可行子集
        int[] dp = new int[full];
        java.util.Arrays.fill(dp, k + 1);
        dp[0] = 0;
        for (int mask = 1; mask < full; mask++) {
            // 枚举 mask 的子集 sub(不含 mask 本身),且 sum[sub] <= limit
            for (int sub = mask; sub > 0; sub = (sub - 1) & mask) {
                if (sum[sub] <= limit && dp[mask ^ sub] + 1 < dp[mask]) {
                    dp[mask] = dp[mask ^ sub] + 1;
                }
            }
        }
        return dp[full - 1] <= k;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minimumTimeRequired(new int[]{3, 2, 3}, 3)); // 3
        System.out.println(sol.minimumTimeRequired(new int[]{1, 2, 4, 7, 8}, 2)); // 11
        System.out.println(sol.minimumTimeRequiredDp(new int[]{3, 2, 3}, 3)); // 3
        System.out.println(sol.minimumTimeRequiredDp(new int[]{1, 2, 4, 7, 8}, 2)); // 11
    }
}

复杂度分析

复杂度:时间复杂度 O(k^n) 最坏(剪枝后实际远小于),空间复杂度 O(k)。

复杂度:时间复杂度 O(3^n)(子集枚举),空间复杂度 O(2^n)。


1724. Checking Existence of Edge Length Limited Paths II(检查边长度限制的路径是否存在 II)

题目描述

一张有 n 个节点的无向图以边的列表 edgeList 的形式定义,其中 edgeList[i] = `ui`, `vi`, `disi`

表示一条连接 ui 和 vi,距离为 disi 的边。同一对节点间可能有多条边,且图可能不是连通的。

实现 DistanceLimitedPathsExist 类:

  • DistanceLimitedPathsExist(int n, int[][] edgeList) 以给定的无向图初始化对象。
  • boolean query(int p, int q, int limit) 当存在一条从 p 到 q 的路径,且路径中每条边的距离都
    严格小于 limit 时,返回 true,否则返回 false。

示例

复制代码
n=6, edges=[[0,2,4],[0,3,2],[1,2,3],[2,3,1],[4,5,5]];
query(2,3,2)=true, query(1,3,3)=false, query(2,0,3)=true, query(0,5,6)=false。

解题思路

解法一:边权递增并查集(主提交版本)

  • 与"路径上最大边权尽量小"等价:p 与 q 能在 limit 内连通 ⇔ 把所有边权 < limit 的边加入并查集后
    p 与 q 连通。
  • 将边按权排序,维护并查集按时间戳逐步加边:query 时把所有权 < limit 的边合并,再判断连通性。
  • 为支持多次 query 不重复扫描,预处理每个 limit 的"并查集版本":借助持久化思路不可行时,
    采用拆分为"每当 limit 变大时把新边并入",查询间只加边不删边。
    复杂度:预处理排序 O(m log m),每次 query 增量加边均摊 O((m + q) α(n)),空间 O(n)。
    解法二:离线排序查询(对照)
  • 把所有 query 按 limit 升序离线排序,用同一个并查集从小到大加边回答,结果写回原序。
    复杂度:时间复杂度 O((m+q) log (m+q) + (m+q) α(n)),空间 O(n + q)。

代码实现

java 复制代码
public class Solution {

    private int[] p;

    private int find(int x) {
        while (p[x] != x) {
            p[x] = p[p[x]];
            x = p[x];
        }
        return x;
    }

    private void union(int a, int b) {
        int ra = find(a), rb = find(b);
        if (ra != rb) p[ra] = rb;
    }

    private boolean connected(int a, int b) {
        return find(a) == find(b);
    }

    // 解法一:边权递增并查集(主提交版本:逐步加边缓存版本)
    // 为简洁可读,本实现采用"排序边 + 二分每个查询对应加边进度"(find & union 单调性)
    private java.util.List<int[]> sortedEdges;
    private java.util.Map<Integer, Integer> progressCache; // 已加到的边下标(闭区间)

    public Solution(int n, int[][] edgeList) {
        p = new int[n];
        java.util.Arrays.sort(edgeList, (a, b) -> a[2] - b[2]);
        sortedEdges = new java.util.ArrayList<>();
        for (int[] e : edgeList) {
            sortedEdges.add(e);
        }
        progressCache = new java.util.HashMap<>();
    }

    public boolean query(int p1, int q1, int limit) {
        // 重置并查集
        for (int i = 0; i < p.length; i++) {
            p[i] = i;
        }
        int global = -1;
        for (int i = 0; i < sortedEdges.size(); i++) {
            if (sortedEdges.get(i)[2] < limit) {
                union(sortedEdges.get(i)[0], sortedEdges.get(i)[1]);
                global = i;
            }
        }
        progressCache.put(limit, global);
        return connected(p1, q1);
    }

    // 解法二:离线排序查询(对照)------静态方法,直接在解法内模拟
    public static boolean[] offlineQuery(int n, int[][] edgeList, int[][] queries) {
        int q = queries.length;
        // queries[i] = [p, qnode, limit],构造 (limit, p, qnode, idx)
        java.util.List<int[]> qs = new java.util.ArrayList<>();
        for (int i = 0; i < q; i++) {
            qs.add(new int[]{queries[i][2], queries[i][0], queries[i][1], i});
        }
        qs.sort((a, b) -> a[0] - b[0]);
        java.util.Arrays.sort(edgeList, (a, b) -> a[2] - b[2]);
        int[] par = new int[n];
        for (int i = 0; i < n; i++) par[i] = i;
        class D {
            int find(int x) {
                while (par[x] != x) {
                    par[x] = par[par[x]];
                    x = par[x];
                }
                return x;
            }
            void union(int a, int b) {
                int ra = find(a), rb = find(b);
                if (ra != rb) par[ra] = rb;
            }
            boolean conn(int a, int b) {
                return find(a) == find(b);
            }
        }
        D d = new D();
        boolean[] ans = new boolean[q];
        int e = 0;
        for (int[] qu : qs) {
            int limit = qu[0];
            while (e < edgeList.length && edgeList[e][2] < limit) {
                d.union(edgeList[e][0], edgeList[e][1]);
                e++;
            }
            ans[qu[3]] = d.conn(qu[1], qu[2]);
        }
        return ans;
    }

    public static void main(String[] args) {
        int[][] edges = {{0, 2, 4}, {0, 3, 2}, {1, 2, 3}, {2, 3, 1}, {4, 5, 5}};
        Solution sol = new Solution(6, edges);
        System.out.println(sol.query(2, 3, 2)); // true
        System.out.println(sol.query(1, 3, 3)); // false
        System.out.println(sol.query(2, 0, 3)); // true
        System.out.println(sol.query(0, 5, 6)); // false

        boolean[] off = offlineQuery(6, new int[][]{{0, 2, 4}, {0, 3, 2}, {1, 2, 3}, {2, 3, 1}, {4, 5, 5}},
                new int[][]{{2, 3, 2}, {1, 3, 3}, {2, 0, 3}, {0, 5, 6}});
        System.out.println(java.util.Arrays.toString(off)); // [true, false, true, false]
    }
}

复杂度分析

复杂度:预处理排序 O(m log m),每次 query 增量加边均摊 O((m + q) α(n)),空间 O(n)。

复杂度:时间复杂度 O((m+q) log (m+q) + (m+q) α(n)),空间 O(n + q)。


1725. Number Of Rectangles That Can Form The Largest Square(可以形成最大正方形的矩形数目)

题目描述

给你一个数组 rectangles,其中 rectangles[i] = `li`, `wi` 表示第 i 个矩形的长度为 li、宽度为 wi。

如果存在 k 同时满足 k <= li 和 k <= wi,就可以将第 i 个矩形切成边长为 k 的正方形。

设 maxLen 为可以从矩形数组 rectangles 切分得到的最大正方形的边长。

请你统计有多少个矩形能够切出边长为 maxLen 的正方形,并返回矩形数目。

示例 1

复制代码
rectangles = [[5,8],[3,9],[5,12],[16,5]],输出 3。

示例 2

复制代码
rectangles = [[2,3],[3,7],[4,3],[3,7]],输出 3。

解题思路

解法一:求最大边长并计数(主提交版本)

  • 每个矩形最大正方形边长 = min(l, w);第一遍求所有这些最小值的最大值 maxLen;
    第二遍统计多少个矩形的 min(l, w) == maxLen。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。
    解法二:一次遍历维护(对照)
  • 单次遍历同时维护 maxLen 与计数:遇到更大则计数重置为 1,相等则计数 +1。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    // 解法一:求最大边长并计数(主提交版本)
    public int countGoodRectangles(int[][] rectangles) {
        int maxLen = 0;
        for (int[] r : rectangles) {
            maxLen = Math.max(maxLen, Math.min(r[0], r[1]));
        }
        int cnt = 0;
        for (int[] r : rectangles) {
            if (Math.min(r[0], r[1]) == maxLen) {
                cnt++;
            }
        }
        return cnt;
    }

    // 解法二:一次遍历维护(对照)
    public int countGoodRectanglesOnePass(int[][] rectangles) {
        int maxLen = 0, cnt = 0;
        for (int[] r : rectangles) {
            int side = Math.min(r[0], r[1]);
            if (side > maxLen) {
                maxLen = side;
                cnt = 1;
            } else if (side == maxLen) {
                cnt++;
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.countGoodRectangles(new int[][]{{5, 8}, {3, 9}, {5, 12}, {16, 5}})); // 3
        System.out.println(sol.countGoodRectangles(new int[][]{{2, 3}, {3, 7}, {4, 3}, {3, 7}})); // 3
        System.out.println(sol.countGoodRectanglesOnePass(new int[][]{{5, 8}, {3, 9}, {5, 12}, {16, 5}})); // 3
        System.out.println(sol.countGoodRectanglesOnePass(new int[][]{{2, 3}, {3, 7}, {4, 3}, {3, 7}})); // 3
    }
}

复杂度分析

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


1726. Tuple with Same Product(同积元组)

题目描述

给你一个由不同正整数组成的数组 nums,请你返回满足 a * b = c * d 的元组 (a, b, c, d) 的数量。

其中 a、b、c 和 d 都是 nums 中的元素,且 a、b、c、d 互不相同。

(相当于选择两个乘积相等的数对,每个数对可形成 8 个有序元组。)

示例 1

复制代码
nums = [2,3,4,6],输出 8。

示例 2

复制代码
nums = [1,2,4,5,10],输出 16。

解题思路

解法一:哈希表统计乘积出现次数(主提交版本)

  • 两两枚举所有数对 (i<j),统计每种乘积 p 的出现次数 cntp。
  • 乘积为 p 的不同数对有 C(cntp, 2) 组,每组数对可组成 (p 对一 q 对二) 4 种有序(a,b)内序
    × 2 种数对排列 = 8 个元组,因此贡献 cntp*(cntp-1)/2 * 8。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n^2)。
    解法二:边枚举边累加(对照)
  • 遍历过程中,对当前数对 (numsi, numsj) 的乘积 p,直接累加前面出现过的相同乘积的
    数对个数 * 8,再更新 cntp。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n^2)。

代码实现

java 复制代码
public class Solution {

    // 解法一:哈希表统计乘积出现次数(主提交版本)
    public int tupleSameProduct(int[] nums) {
        java.util.Map<Integer, Integer> cnt = new java.util.HashMap<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int prod = nums[i] * nums[j];
                cnt.put(prod, cnt.getOrDefault(prod, 0) + 1);
            }
        }
        int ans = 0;
        for (int c : cnt.values()) {
            ans += c * (c - 1) / 2 * 8;
        }
        return ans;
    }

    // 解法二:边枚举边累加(对照)
    public int tupleSameProductOnline(int[] nums) {
        java.util.Map<Integer, Integer> cnt = new java.util.HashMap<>();
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int prod = nums[i] * nums[j];
                ans += cnt.getOrDefault(prod, 0) * 8;
                cnt.put(prod, cnt.getOrDefault(prod, 0) + 1);
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.tupleSameProduct(new int[]{2, 3, 4, 6})); // 8
        System.out.println(sol.tupleSameProduct(new int[]{1, 2, 4, 5, 10})); // 16
        System.out.println(sol.tupleSameProductOnline(new int[]{2, 3, 4, 6})); // 8
        System.out.println(sol.tupleSameProductOnline(new int[]{1, 2, 4, 5, 10})); // 16
    }
}

复杂度分析

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


1727. Largest Submatrix With Rearrangements(重新排列后的最大子矩阵)

题目描述

给你一个二进制矩阵 matrix,它的大小为 m x n,你可以将 matrix 中的列按任意顺序重新排列。

请你返回最优方案下将 matrix 重新排列后,全是 1 的最大子矩阵面积。

示例 1

复制代码
matrix = [[0,0,1],[1,1,1],[1,0,1]],输出 4。

示例 2

复制代码
matrix = [[1,0,1,0,1]],输出 3。

示例 3

复制代码
matrix = [[1,1,0],[1,0,1]],输出 2。

解题思路

解法一:统计连续 1 高度 + 排序(主提交版本)

  • 先对每列做"连续 1 高度":upij 表示从 (i,j) 向上连续 1 的个数。
  • 对每一行 i,取出 upi\* 列高度数组并按降序排序:
    若将列重排,选 k 列组成子矩阵,其高度下界为第 k 大的高度 h_k,面积 = h_k * k。
    取 max(h_k * k) 即为该行作为底边时的最大全 1 子矩阵面积。
  • 对所有行取最大值。
    复杂度:时间复杂度 O(m*n log n),空间复杂度 O(n)。
    解法二:直接对每行高度排序(对照)
  • 思路同解法一,但记录整个 up 矩阵,代码结构更直观(便于与解法一交叉验证)。
    复杂度:时间复杂度 O(mn log n),空间复杂度 O(mn)。

代码实现

java 复制代码
public class Solution {

    // 解法一:统计连续 1 高度 + 排序(主提交版本)
    public int largestSubmatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        int ans = 0;
        int[] height = new int[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 1) {
                    height[j]++;
                } else {
                    height[j] = 0;
                }
            }
            int[] h = height.clone();
            java.util.Arrays.sort(h); // 升序
            for (int j = 0; j < n; j++) {
                // 取 h[j] 为高度,有 n - j 列高度 >= h[j]
                ans = Math.max(ans, h[j] * (n - j));
            }
        }
        return ans;
    }

    // 解法二:up 矩阵 + 排序(对照)
    public int largestSubmatrixUp(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        int[][] up = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 1) {
                    up[i][j] = (i == 0 ? 0 : up[i - 1][j]) + 1;
                }
            }
        }
        int ans = 0;
        for (int i = 0; i < m; i++) {
            int[] h = up[i].clone();
            java.util.Arrays.sort(h);
            for (int j = 0; j < n; j++) {
                ans = Math.max(ans, h[j] * (n - j));
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.largestSubmatrix(new int[][]{{0, 0, 1}, {1, 1, 1}, {1, 0, 1}})); // 4
        System.out.println(sol.largestSubmatrix(new int[][]{{1, 0, 1, 0, 1}})); // 3
        System.out.println(sol.largestSubmatrix(new int[][]{{1, 1, 0}, {1, 0, 1}})); // 2
        System.out.println(sol.largestSubmatrixUp(new int[][]{{0, 0, 1}, {1, 1, 1}, {1, 0, 1}})); // 4
        System.out.println(sol.largestSubmatrixUp(new int[][]{{1, 0, 1, 0, 1}})); // 3
        System.out.println(sol.largestSubmatrixUp(new int[][]{{1, 1, 0}, {1, 0, 1}})); // 2
    }
}

复杂度分析

复杂度:时间复杂度 O(mn log n),空间复杂度 O(n)。
复杂度:时间复杂度 O(m
n log n),空间复杂度 O(m*n)。


1728. Cat and Mouse II(猫和老鼠 II)

题目描述

一只猫和一只老鼠在玩一个叫做猫和老鼠的游戏。环境是一个 rows x cols 的方格 grid:

  • 'C' 代表猫,'M' 代表老鼠,'F' 代表食物,'.' 是地板,'#' 是墙。
  • 老鼠先移动,然后轮流移动。每次可以上下左右跳 1...jump 步(不能穿墙、不能出界),也可停留原地。
  • 老鼠可以跳过猫的位置。
    游戏结束:猫与老鼠同格则猫胜;猫先到食物则猫胜;老鼠先到食物则老鼠胜;
    1000 步内老鼠没到食物则猫胜(实际是超过行数列数 状态数即认为猫胜)。
    双方最优,若老鼠获胜返回 true,否则 false。

示例 1

复制代码
grid = ["####F","#C...","M...."], catJump=1, mouseJump=2,输出 true。

示例 2

复制代码
grid = ["M.C...F"], catJump=1, mouseJump=4,输出 true。

示例 3

复制代码
grid = ["M.C...F"], catJump=1, mouseJump=3,输出 false。

解题思路

解法一:记忆化搜索博弈(主提交版本)

  • 状态 (turn, mc, cc, step):turn=0 老鼠走、1 猫走;mc/cc 分别是老鼠/猫位置(一维化)。
  • 深度优先搜索判定当前状态是否老鼠胜(turn=0 时存在一站胜、turn=1 时所有猫走法都使老鼠胜)。
  • 为防止环导致无限递归,用 step 限制深度:超过 rowscols 2(状态空间上界)视为猫胜
    (在 1000 步内到达食物的剪枝)。
    复杂度:最坏状态数 O(rows*cols)^2 * step,空间 O(状态数),实际剪枝后可行。
    解法二:限制步数的博弈 DFS(对照)
  • 与解法一逻辑完全一致,但把"超步数判负"明确为超过 catJump+mouseJump 步内的跳跃空间上界,
    通过外部传入 maxSteps 参数,便于对比不同上界。
    复杂度:同解法一。

代码实现

java 复制代码
public class Solution {

    private int rows, cols;
    private char[][] g;
    private int food;
    private int catJ, mouseJ;
    private String[] gridArr;
    private int maxSteps;
    // 状态:step * 2 * total * total 太大,用 HashMap 记忆
    private java.util.Map<Long, Boolean> memo;

    private int pos(int r, int c) {
        return r * cols + c;
    }

    private int[] rc(int p) {
        return new int[]{p / cols, p % cols};
    }

    private java.util.List<Integer> moves(int p, int dist, boolean cat) {
        java.util.List<Integer> res = new java.util.ArrayList<>();
        int r = p / cols, c = p % cols;
        // 上下左右 1..dist 步
        int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        for (int[] d : dirs) {
            int nr = r, nc = c;
            for (int s = 0; s < dist; s++) {
                nr += d[0];
                nc += d[1];
                if (nr < 0 || nr >= rows || nc < 0 || nc >= cols || g[nr][nc] == '#') {
                    break;
                }
                res.add(pos(nr, nc));
            }
        }
        res.add(p); // 停留原地
        return res;
    }

    private boolean win(int step, int who, int m, int c) {
        if (m == c) {
            return false; // 猫在同格,老鼠输
        }
        if (m == food) {
            return true; // 老鼠到食物(且猫不在同格)
        }
        if (c == food) {
            return false; // 猫先到食物
        }
        if (step >= maxSteps) {
            return false; // 步数耗尽,猫胜
        }
        Long key = ((long) step) << 32 | (long) who << 31 | (long) m << 15 | c;
        Boolean cached = memo.get(key);
        if (cached != null) {
            return cached;
        }
        boolean res;
        if (who == 0) { // 老鼠走:任一可行走法可赢即可
            res = false;
            for (int nm : moves(m, mouseJ, false)) {
                if (win(step + 1, 1, nm, c)) {
                    res = true;
                    break;
                }
            }
        } else { // 猫走:猫的所有走法中必须全部老鼠仍赢
            res = true;
            for (int nc : moves(c, catJ, true)) {
                if (!win(step + 1, 0, m, nc)) {
                    res = false;
                    break;
                }
            }
        }
        memo.put(key, res);
        return res;
    }

    // 解法一:记忆化搜索博弈(主提交版本)
    public boolean canMouseWin(String[] grid, int catJump, int mouseJump) {
        this.gridArr = grid;
        rows = grid.length;
        cols = grid[0].length();
        this.catJ = catJump;
        this.mouseJ = mouseJump;
        g = new char[rows][cols];
        int cat = -1, mouse = -1;
        food = -1;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                char ch = grid[i].charAt(j);
                g[i][j] = ch;
                if (ch == 'C') {
                    cat = pos(i, j);
                } else if (ch == 'M') {
                    mouse = pos(i, j);
                } else if (ch == 'F') {
                    food = pos(i, j);
                }
            }
        }
        // 状态空间上界:所有 (m, c) 组合的两倍,防止无限递归
        maxSteps = rows * cols * cols * 2;
        memo = new java.util.HashMap<>();
        return win(0, 0, mouse, cat);
    }

    // 解法二:限制步数的博弈 DFS(对照,显式步数上限)
    public boolean canMouseWinSteps(String[] grid, int catJump, int mouseJump) {
        this.gridArr = grid;
        rows = grid.length;
        cols = grid[0].length();
        this.catJ = catJump;
        this.mouseJ = mouseJump;
        g = new char[rows][cols];
        int cat = -1, mouse = -1;
        food = -1;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                char ch = grid[i].charAt(j);
                g[i][j] = ch;
                if (ch == 'C') {
                    cat = pos(i, j);
                } else if (ch == 'M') {
                    mouse = pos(i, j);
                } else if (ch == 'F') {
                    food = pos(i, j);
                }
            }
        }
        maxSteps = 1000; // 题意:老鼠 1000 次操作内获胜
        memo = new java.util.HashMap<>();
        return win(0, 0, mouse, cat);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.canMouseWin(new String[]{"####F", "#C...", "M...."}, 1, 2)); // true
        System.out.println(sol.canMouseWin(new String[]{"M.C...F"}, 1, 4)); // true
        System.out.println(sol.canMouseWin(new String[]{"M.C...F"}, 1, 3)); // false
        System.out.println(sol.canMouseWinSteps(new String[]{"####F", "#C...", "M...."}, 1, 2)); // true
        System.out.println(sol.canMouseWinSteps(new String[]{"M.C...F"}, 1, 4)); // true
        System.out.println(sol.canMouseWinSteps(new String[]{"M.C...F"}, 1, 3)); // false
    }
}

复杂度分析

复杂度:最坏状态数 O(rows*cols)^2 * step,空间 O(状态数),实际剪枝后可行。


1729. Find Followers Count(求关注者的数量)

题目类型:数据库(SQL)

题目描述

表:Followers

复制代码
+-------------+------+
| Column Name | Type |
+-------------+------+
| user_id     | int  |
| follower_id | int  |
+-------------+------+

(user_id, follower_id) 是这个表的主键。该表包含一个关注关系中关注者和用户的编号,其中关注者关注用户。

编写解决方案,对于每一个用户,返回该用户的关注者数量。按 user_id 的顺序返回结果表。

示例 1:

复制代码
输入:
Followers 表:
+---------+-------------+
| user_id | follower_id |
+---------+-------------+
| 0       | 1           |
| 1       | 0           |
| 2       | 0           |
| 2       | 1           |
+---------+-------------+
输出:
+---------+----------------+
| user_id | followers_count|
+---------+----------------+
| 0       | 1              |
| 1       | 1              |
| 2       | 2              |
+---------+----------------+

解题思路

直接按 user_id 分组,数每组行数(即关注者人数),输出前按 user_id 升序排序。

代码实现

sql 复制代码
SELECT user_id, COUNT(*) AS followers_count
FROM Followers
GROUP BY user_id
ORDER BY user_id;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度;考察重点为 JOIN / 窗口函数 / 自连接 / 聚合过滤等 SQL 语法与查询逻辑的正确性,执行效率由数据库优化器决定。


1730. Shortest Path to Get Food(获取食物的最短路径)

题目描述

你现在很饿,想要尽快找东西吃。你需要找到最短的路径到达一个食物所在的格子。

给定一个 m x n 的字符矩阵 grid,包含下列不同类型的格子:

  • '*' 是你的位置(有且只有一个)
  • '#' 是食物(有且只有一个)
  • 'O' 是空地,你可以通过
  • 'X' 是障碍物,无法通过
    返回你到食物所在格子的最短路径的长度(边的数量);如果你无法到达食物,返回 -1。

示例 1

复制代码
grid = [["X","X","X","X","X","X"],["X","*","O","O","O","X"],["X","O","O","#","O","X"]],
输出 3。

示例 2

复制代码
grid = [["X","X","X","X","X"],["X","*","X","O","X"],["X","O","X","#","X"],
["X","X","X","X","X"]],输出 -1。

解题思路

解法一:BFS 最短路(主提交版本)

  • 从 '*' 出发做四方向 BFS,'X' 不能走,'O' 与 '#' 可走,第一次遇到 '#' 时的层数即最短路径长度。
    复杂度:时间复杂度 O(mn),空间复杂度 O(m n)。
    解法二:DFS 带剪枝(对照)
  • 用 DFS + visited 剪枝搜索,维护当前路径长度,到 '#' 时更新答案;可能超时但作为小数据验证。
    复杂度:时间复杂度 O(4^(mn)) 最坏,空间复杂度 O(mn)。

代码实现

java 复制代码
public class Solution {

    // 解法一:BFS 最短路(主提交版本)
    public int getFood(char[][] grid) {
        int m = grid.length, n = grid[0].length;
        int sr = -1, sc = -1;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '*') {
                    sr = i;
                    sc = j;
                }
            }
        }
        java.util.ArrayDeque<int[]> dq = new java.util.ArrayDeque<>();
        boolean[][] vis = new boolean[m][n];
        dq.add(new int[]{sr, sc});
        vis[sr][sc] = true;
        int steps = 0;
        int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        while (!dq.isEmpty()) {
            int sz = dq.size();
            for (int k = 0; k < sz; k++) {
                int[] cur = dq.poll();
                for (int[] d : dirs) {
                    int nr = cur[0] + d[0], nc = cur[1] + d[1];
                    if (nr < 0 || nr >= m || nc < 0 || nc >= n || vis[nr][nc]
                            || grid[nr][nc] == 'X') {
                        continue;
                    }
                    if (grid[nr][nc] == '#') {
                        return steps + 1;
                    }
                    vis[nr][nc] = true;
                    dq.add(new int[]{nr, nc});
                }
            }
            steps++;
        }
        return -1;
    }

    // 解法二:DFS 带剪枝(对照)
    private int best;
    private char[][] grid;
    private int m, n;

    public int getFoodDfs(char[][] grid) {
        this.grid = grid;
        m = grid.length;
        n = grid[0].length;
        best = Integer.MAX_VALUE;
        int sr = -1, sc = -1;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '*') {
                    sr = i;
                    sc = j;
                }
            }
        }
        boolean[][] vis = new boolean[m][n];
        vis[sr][sc] = true;
        dfs(sr, sc, 0, vis);
        return best == Integer.MAX_VALUE ? -1 : best;
    }

    private void dfs(int r, int c, int dist, boolean[][] vis) {
        if (dist >= best) {
            return;
        }
        int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        for (int[] d : dirs) {
            int nr = r + d[0], nc = c + d[1];
            if (nr < 0 || nr >= m || nc < 0 || nc >= n || vis[nr][nc] || grid[nr][nc] == 'X') {
                continue;
            }
            if (grid[nr][nc] == '#') {
                best = Math.min(best, dist + 1);
                continue;
            }
            vis[nr][nc] = true;
            dfs(nr, nc, dist + 1, vis);
            vis[nr][nc] = false;
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.getFood(new char[][]{
                {'X','X','X','X','X','X'}, {'X','*','O','O','O','X'},
                {'X','O','O','#','O','X'}})); // 3
        System.out.println(sol.getFood(new char[][]{
                {'X','X','X','X','X'}, {'X','*','X','O','X'},
                {'X','O','X','#','X'}, {'X','X','X','X','X'}})); // -1
        System.out.println(sol.getFoodDfs(new char[][]{
                {'X','X','X','X','X','X'}, {'X','*','O','O','O','X'},
                {'X','O','O','#','O','X'}})); // 3
        System.out.println(sol.getFoodDfs(new char[][]{
                {'X','X','X','X','X'}, {'X','*','X','O','X'},
                {'X','O','X','#','X'}, {'X','X','X','X','X'}})); // -1
    }
}

复杂度分析

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

复杂度:时间复杂度 O(4^(mn)) 最坏,空间复杂度 O(mn)。


1731. The Number of Employees Which Report to Each Employee(每位经理的下属员工数量)

题目类型:数据库(SQL)

题目描述

表:Employees

复制代码
+-------------+----------+
| Column Name | Type     |
+-------------+----------+
| employee_id | int      |
| name        | varchar  |
| reports_to  | int      |
| age         | int      |
+-------------+----------+

employee_id 是这个表中具有不同值的列。该表包含员工以及需要听取他们汇报的上级经理的 ID 的信息。有些员工不需要向任何人汇报(reports_to 为空)。

对于此问题,我们将至少有一个其他员工需要向他汇报的员工,视为一个经理。编写一个解决方案来返回需要听取汇报的所有经理的 ID、名称、直接向该经理汇报的员工人数,以及这些员工的平均年龄(四舍五入到最接近的整数)。返回的结果集需要按照 employee_id 进行排序。

示例 1:

复制代码
输入:
Employees 表:
+-------------+---------+------------+-----+
| employee_id | name    | reports_to | age |
+-------------+---------+------------+-----+
| 9           | Hercy   | null       | 43  |
| 6           | Alice   | 9          | 41  |
| 4           | Bob     | 9          | 36  |
| 2           | Winston | null       | 37  |
+-------------+---------+------------+-----+
输出:
+-------------+-------+---------------+-------------+
| employee_id | name  | reports_count | average_age |
+-------------+-------+---------------+-------------+
| 9           | Hercy | 2             | 39          |
+-------------+-------+---------------+-------------+

解释:Hercy 有两个直接下属 Alice 和 Bob,平均年龄 (41+36)/2 = 38.5,四舍五入为 39。

解题思路

经理与下属都在同一张表里,用表自连接:Employees m 作为经理、Employees e 作为下属,

连接条件 m.employee_id = e.reports_to;按经理分组统计 COUNT(e.employee_id) 与 ROUND(AVG(e.age)),

最后按 employee_id 排序。

代码实现

sql 复制代码
SELECT m.employee_id, m.name,
       COUNT(e.employee_id) AS reports_count,
       ROUND(AVG(e.age)) AS average_age
FROM Employees m
JOIN Employees e ON m.employee_id = e.reports_to
GROUP BY m.employee_id, m.name
ORDER BY m.employee_id;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度;考察重点为 JOIN / 窗口函数 / 自连接 / 聚合过滤等 SQL 语法与查询逻辑的正确性,执行效率由数据库优化器决定。


1732. Find the Highest Altitude(找到最高海拔)

题目描述

有一个自行车手打算进行一场公路骑行,这条路线总共由 n + 1 个不同海拔的点组成。

自行车手从海拔为 0 的点 0 开始骑行。

给你一个长度为 n 的整数数组 gain,其中 gain[i] 是点 i 和点 i + 1 的净海拔高度差

(0 <= i < n)。请你返回最高点的海拔。

示例 1

复制代码
gain = [-5,1,5,0,-7],海拔依次为 [0,-5,-4,1,1,-6],最高海拔为 1。

示例 2

复制代码
gain = [-4,-3,-2,-1,4,3,2],海拔依次为 [0,-4,-7,-9,-10,-6,-3,-1],最高海拔为 0。

提示:

  • n == gain.length,1 <= n <= 100,-100 <= gaini <= 100。

解题思路

解法一:前缀和遍历(主提交版本)

  • cur 记录当前海拔(起点 0),每累加 gaini 后与 ans 取最大值即可。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。
    解法二:前缀和高低峰区间观察(对照)
  • 由于 n 很小,可先构造完整前缀和数组,再对数组整体求最大值。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。

代码实现

java 复制代码
public class Solution {

    // 解法一:前缀和遍历(主提交版本)
    public int largestAltitude(int[] gain) {
        int cur = 0, ans = 0;
        for (int g : gain) {
            cur += g;
            ans = Math.max(ans, cur);
        }
        return ans;
    }

    // 解法二:前缀和数组求最大(对照)
    public int largestAltitudeArray(int[] gain) {
        int n = gain.length;
        int[] pre = new int[n + 1];
        pre[0] = 0;
        for (int i = 0; i < n; i++) {
            pre[i + 1] = pre[i] + gain[i];
        }
        int ans = pre[0];
        for (int x : pre) {
            ans = Math.max(ans, x);
        }
        return ans;
    }

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

复杂度分析

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

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


1733. Minimum Number of People to Teach(需要教语言的最少人数)

题目描述

在一个由 m 个用户组成的社交网络里,我们获取到一些用户之间的好友关系。

两个用户之间可以相互沟通的条件是他们都掌握同一门语言。

给你一个整数 n,数组 languages 和数组 friendships,它们的含义如下:

  • 总共有 n 种语言,编号从 1 到 n。
  • languages[i] 是第 i 位用户掌握的语言集合。
  • friendships[i] = `ui`, `vi` 表示 ui 和 vi 为好友关系。
    你可以选择一门语言并教会一些用户,使得所有好友之间都可以相互沟通。
    请返回你最少需要教会多少名用户。
    请注意,好友关系没有传递性,也就是说如果 x 和 y 是好友,且 y 和 z 是好友,
    x 和 z 不一定是好友。

示例 1

复制代码
n = 2, languages = [[1],[2],[1,2]], friendships = [[1,2],[1,3],[2,3]]
输出 1(可以选择教用户 1 第二门语言,也可以选择教用户 2 第一门语言)。

示例 2

复制代码
n = 3, languages = [[2],[1,3],[1,2],[3]], friendships = [[1,4],[1,2],[3,4],[2,3]]
输出 2(教用户 1 和用户 3 第三门语言)。

提示:

  • 2 <= n <= 500,1 <= m <= 500,languages[i] 中包含的值互不相同。

解题思路

解法一:候选语言计数(主提交版本)

  • 先过滤出所有还不能沟通的好友对,这些对中的用户必须至少有一方学新语言。
  • 对每种候选语言 1...n,统计这些"必须沟通"用户中不掌握该语言的人数,
    取最小值即为答案(同一用户只会被统计一次,用 seen 集合去重)。
    复杂度:时间复杂度 O(n * (m + 总语言数)),空间复杂度 O(m * 语言数)。
    解法二:按好友对逐一对齐(对照)
  • 记录每对好友"缺哪些语言",然后枚举教哪门课使最多"缺口"被补上。
    复杂度:时间复杂度 O(n * #好友对),空间复杂度 O(#好友对)。

代码实现

java 复制代码
public class Solution {

    // 解法一:候选语言计数(主提交版本)
    public int minimumTeachings(int n, int[][] languages, int[][] friendships) {
        int m = languages.length;
        boolean[][] has = new boolean[m + 1][n + 1];
        for (int i = 0; i < m; i++) {
            for (int lang : languages[i]) {
                has[i + 1][lang] = true;
            }
        }
        // 需要沟通但还没共同语言的用户集合
        java.util.Set<Integer> need = new java.util.HashSet<>();
        for (int[] f : friendships) {
            int u = f[0], v = f[1];
            if (!canTalk(has, u, v, n)) {
                need.add(u);
                need.add(v);
            }
        }
        int ans = m;
        for (int lang = 1; lang <= n; lang++) {
            int cnt = 0;
            for (int uid : need) {
                if (!has[uid][lang]) {
                    cnt++;
                }
            }
            ans = Math.min(ans, cnt);
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }

    private boolean canTalk(boolean[][] has, int u, int v, int n) {
        for (int lang = 1; lang <= n; lang++) {
            if (has[u][lang] && has[v][lang]) {
                return true;
            }
        }
        return false;
    }

    // 解法二:按好友对逐一统计缺口(对照)
    public int minimumTeachingsBrute(int n, int[][] languages, int[][] friendships) {
        int m = languages.length;
        boolean[][] has = new boolean[m + 1][n + 1];
        for (int i = 0; i < m; i++) {
            for (int lang : languages[i]) {
                has[i + 1][lang] = true;
            }
        }
        // 需要沟通且没有共同语言的用户集合(去重)
        java.util.Set<Integer> need2 = new java.util.HashSet<>();
        for (int[] f : friendships) {
            int u = f[0], v = f[1];
            if (!canTalk(has, u, v, n)) {
                need2.add(u);
                need2.add(v);
            }
        }
        int[] miss = new int[n + 1];
        for (int uid : need2) {
            for (int lang = 1; lang <= n; lang++) {
                if (!has[uid][lang]) {
                    miss[lang]++;
                }
            }
        }
        int ans = Integer.MAX_VALUE;
        for (int lang = 1; lang <= n; lang++) {
            ans = Math.min(ans, miss[lang]);
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minimumTeachings(2,
                new int[][]{{1}, {2}, {1, 2}},
                new int[][]{{1, 2}, {1, 3}, {2, 3}})); // 1
        System.out.println(sol.minimumTeachings(3,
                new int[][]{{2}, {1, 3}, {1, 2}, {3}},
                new int[][]{{1, 4}, {1, 2}, {3, 4}, {2, 3}})); // 2
        System.out.println(sol.minimumTeachingsBrute(2,
                new int[][]{{1}, {2}, {1, 2}},
                new int[][]{{1, 2}, {1, 3}, {2, 3}})); // 1
        System.out.println(sol.minimumTeachingsBrute(3,
                new int[][]{{2}, {1, 3}, {1, 2}, {3}},
                new int[][]{{1, 4}, {1, 2}, {3, 4}, {2, 3}})); // 2
    }
}

复杂度分析

复杂度:时间复杂度 O(n * (m + 总语言数)),空间复杂度 O(m * 语言数)。

复杂度:时间复杂度 O(n * #好友对),空间复杂度 O(#好友对)。


1734. Decode XORed Permutation(解码异或后的排列)

题目描述

给你一个整数数组 perm,它是前 n 个正整数的排列,且 n 是个奇数。

它被加密成另一个长度为 n - 1 的整数数组 encoded,满足

encoded[i] = perm[i] XOR perm[i + 1]。比方说,如果 perm = 1,3,2,

那么 encoded = 2,1。

给你 encoded 数组,请你返回原始数组 perm。题目保证答案存在且唯一。

示例 1

复制代码
encoded = [3,1],输出 [1,2,3]。

示例 2

复制代码
encoded = [6,5,4,6],输出 [2,4,1,5,3]。

提示:

  • 3 <= n < 10^5,n 是奇数,encoded.length == n - 1。

解题思路

解法一:异或前缀求首元素(主提交版本)

  • 记全部 1...n 的异或为 total;encoded1encoded[3]...^encodedn-2
    恰好等于 perm1perm[2]...^permn-1(因为 perm 长度为奇数时,
    相隔一个的 encoded 下标会覆盖 perm 除首元素外的全部下标)。
    因此 perm0 = total ^ (encoded1encoded[3]...),
    再按 permi+1 = permi ^ encodedi 递推即可。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)(返回数组)。
    解法二:暴力枚举恢复(对照)
  • 枚举 perm0 的可能值(1...n),按递推重建整个数组,校验是否为 1...n 的排列。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n)。

代码实现

java 复制代码
public class Solution {

    // 解法一:异或前缀求首元素(主提交版本)
    public int[] decode(int[] encoded) {
        int n = encoded.length + 1;
        int total = 0;
        for (int i = 1; i <= n; i++) {
            total ^= i;
        }
        int first0 = 0;
        for (int i = 1; i < encoded.length; i += 2) {
            first0 ^= encoded[i];
        }
        int[] perm = new int[n];
        perm[0] = total ^ first0;
        for (int i = 1; i < n; i++) {
            perm[i] = perm[i - 1] ^ encoded[i - 1];
        }
        return perm;
    }

    // 解法二:暴力枚举 perm[0](对照)
    public int[] decodeBrute(int[] encoded) {
        int n = encoded.length + 1;
        for (int first = 1; first <= n; first++) {
            int[] perm = new int[n];
            perm[0] = first;
            boolean[] seen = new boolean[n + 1];
            seen[first] = true;
            boolean ok = true;
            for (int i = 1; i < n; i++) {
                perm[i] = perm[i - 1] ^ encoded[i - 1];
                if (perm[i] < 1 || perm[i] > n || seen[perm[i]]) {
                    ok = false;
                    break;
                }
                seen[perm[i]] = true;
            }
            if (ok) {
                return perm;
            }
        }
        return new int[0];
    }

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

复杂度分析

复杂度:时间复杂度 O(n),空间复杂度 O(n)(返回数组)。

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


1735. Count Ways to Make Array With Product(生成乘积数组的方案数)

题目描述

给你一个二维整数数组 queries,其中 queries[i] = `ni`, `ki`。第 i 个查询要求

构造长度为 ni、每个元素都是正整数的数组,且满足所有元素的乘积为 ki,

请你找出有多少种可行的方案。由于答案可能会很大,方案数需要对 10^9 + 7 取余。

返回一个整数数组 answer,其中 answer[i] 是第 i 个查询的结果。

示例 1

复制代码
queries = [[2,6],[5,1],[73,660]],输出 [4,1,50734910]。

示例 2

复制代码
queries = [[1,1],[2,2],[3,3],[4,4],[5,5]],输出 [1,2,3,10,5]。

解题思路

解法一:质因数分解 + 组合计数(主提交版本)

  • 将 ki 分解质因数:ki = p1^e1 * p2^e2 * ... * pt^et。
  • 长度为 ni 的数组,每个位置分配若干质因子,问题转化为:每个质因子的 e
    个幂分配(可重复)到 ni 个盒子,方案数为 C(e + ni - 1, ni - 1)(隔板法)。
  • 不同质因子之间互相独立,总方案数为各质因子方案数之积。
  • 预处理组合数 C(i, j)(i 最大 e + ni - 1 <= 15 + 10^4),模 10^9+7。
    复杂度:时间复杂度 O(maxN + 查询数 * sqrt(ki)),空间复杂度 O(maxN)。
    解法二:因数枚举 + 逐位 DP(对照)
  • 对单个查询,先枚举 ki 的所有因数,再用 Map 递推 dpv:
    已填 t 个位置、当前乘积为 v 的方案数;每个位置可以乘上任意因数。
    复杂度:时间复杂度 O(n * f^2)(f 为因数个数),空间复杂度 O(f),仅适合小数据验证。

代码实现

java 复制代码
public class Solution {

    private static final long MOD = 1_000_000_007L;

    private long[][] c;

    // 解法一:质因数分解 + 隔板法(主提交版本)
    public int[] waysToFillArray(int[][] queries) {
        // 组合数第二维上限:单质因子最多出现 30 次(2^30 > 1e9),取 32 足够,
        // 但为简单统一取 ni 最大 + 30。
        int maxN = 0;
        for (int[] q : queries) {
            maxN = Math.max(maxN, q[0]);
        }
        int lim = maxN + 40;
        c = new long[lim + 1][41];
        for (int i = 0; i <= lim; i++) {
            c[i][0] = 1;
            for (int j = 1; j <= Math.min(i, 40); j++) {
                c[i][j] = (c[i - 1][j - 1] + (j <= i - 1 ? c[i - 1][j] : 0)) % MOD;
            }
        }
        int[] ans = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int n = queries[i][0], k = queries[i][1];
            long res = 1;
            for (int p = 2; (long) p * p <= k; p++) {
                if (k % p == 0) {
                    int e = 0;
                    while (k % p == 0) {
                        k /= p;
                        e++;
                    }
                    // 把 e 个 p 分摊到 n 个位置:C(e + n - 1, n - 1)
                    res = res * comb(e + n - 1, e) % MOD;
                }
            }
            if (k > 1) {
                // 剩余一个大于 sqrt 的大质因子,指数为 1
                res = res * comb(n, 1) % MOD;
            }
            ans[i] = (int) res;
        }
        return ans;
    }

    private long comb(int n, int k) {
        if (k < 0 || k > n) {
            return 0;
        }
        return c[n][k];
    }

    // 解法二:因数枚举 + 逐位 DP(对照,小数据验证用)
    public int waysToFillArraySingle(int n, int k) {
        java.util.List<Integer> factors = new java.util.ArrayList<>();
        for (int d = 1; (long) d * d <= k; d++) {
            if (k % d == 0) {
                factors.add(d);
                if (d * d != k) {
                    factors.add(k / d);
                }
            }
        }
        java.util.Collections.sort(factors);
        java.util.Map<Integer, Long> dp = new java.util.HashMap<>();
        dp.put(1, 1L);
        for (int t = 0; t < n; t++) {
            java.util.Map<Integer, Long> ndp = new java.util.HashMap<>();
            for (java.util.Map.Entry<Integer, Long> en : dp.entrySet()) {
                long cur = en.getValue();
                int v = en.getKey();
                for (int f : factors) {
                    long nv = (long) v * f;
                    if (nv > k || k % nv != 0) {
                        continue;
                    }
                    ndp.merge((int) nv, cur, (a, b) -> (a + b) % MOD);
                }
            }
            dp = ndp;
        }
        return (int) (dp.getOrDefault(k, 0L) % MOD);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(java.util.Arrays.toString(sol.waysToFillArray(
                new int[][]{{2, 6}, {5, 1}, {73, 660}}))); // [4, 1, 50734910]
        System.out.println(java.util.Arrays.toString(sol.waysToFillArray(
                new int[][]{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}))); // [1, 2, 3, 10, 5]
        // 解法二小数据对照
        System.out.println(sol.waysToFillArraySingle(2, 6)); // 4
        System.out.println(sol.waysToFillArraySingle(5, 1)); // 1
        System.out.println(sol.waysToFillArraySingle(1, 1)); // 1
        System.out.println(sol.waysToFillArraySingle(2, 2)); // 2
        System.out.println(sol.waysToFillArraySingle(5, 5)); // 5
    }
}

复杂度分析

复杂度:时间复杂度 O(maxN + 查询数 * sqrt(ki)),空间复杂度 O(maxN)。

复杂度:时间复杂度 O(n * f^2)(f 为因数个数),空间复杂度 O(f),仅适合小数据验证。


1736. Latest Time by Replacing Hidden Digits(替换隐藏数字得到的最晚时间)

题目描述

给你一个字符串 time,格式为 hh:mm(小时:分钟),其中某几位数字被隐藏(用 '?' 表示)。

有效的时间为 00:00 到 23:59 之间的所有时间,包括 00:00 和 23:59。

替换 time 中隐藏的数字,返回你可以得到的最晚有效时间。

示例 1

复制代码
time = "2?:?0",输出 "23:50"。

示例 2

复制代码
time = "0?:3?",输出 "09:39"。

示例 3

复制代码
time = "1?:22",输出 "19:22"。

提示:

  • time 的格式为 hh:mm,题目数据保证你可以由输入的字符串生成有效的时间。

解题思路

解法一:按位置贪心(主提交版本)

  • 时、分位逐个判断:十位为 '?' 时依据个位决定填 2/1;个位为 '?' 时依据十位决定。
  • 分同理:十位最大 5,个位最大 9。
    复杂度:时间复杂度 O(1),空间复杂度 O(1)。
    解法二:枚举补全(对照)
  • 将 '?' 视为通配,枚举 2460 全部合法时间,取与模板匹配的最晚一个。
    复杂度:时间复杂度 O(24
    60),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    // 解法一:按位置贪心(主提交版本)
    public String maximumTime(String time) {
        char[] t = time.toCharArray();
        // 小时十位
        if (t[0] == '?') {
            t[0] = (t[1] == '?' || t[1] <= '3') ? '2' : '1';
        }
        // 小时个位
        if (t[1] == '?') {
            t[1] = (t[0] == '2') ? '3' : '9';
        }
        // 分钟十位
        if (t[3] == '?') {
            t[3] = '5';
        }
        // 分钟个位
        if (t[4] == '?') {
            t[4] = '9';
        }
        return new String(t);
    }

    // 解法二:枚举 24*60(对照)
    public String maximumTimeEnum(String time) {
        for (int h = 23; h >= 0; h--) {
            for (int m = 59; m >= 0; m--) {
                String cand = String.format("%02d:%02d", h, m);
                if (matches(time, cand)) {
                    return cand;
                }
            }
        }
        return "";
    }

    private boolean matches(String mask, String cand) {
        for (int i = 0; i < 5; i++) {
            if (mask.charAt(i) != '?' && mask.charAt(i) != cand.charAt(i)) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maximumTime("2?:?0")); // 23:50
        System.out.println(sol.maximumTime("0?:3?")); // 09:39
        System.out.println(sol.maximumTime("1?:22")); // 19:22
        System.out.println(sol.maximumTimeEnum("2?:?0")); // 23:50
        System.out.println(sol.maximumTimeEnum("0?:3?")); // 09:39
        System.out.println(sol.maximumTimeEnum("1?:22")); // 19:22
    }
}

复杂度分析

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

复杂度:时间复杂度 O(24*60),空间复杂度 O(1)。


1737. Change Minimum Characters to Satisfy One of Three Conditions(满足三条件之一需改变的最少字符数)

题目描述

给你两个字符串 a 和 b,二者均由小写字母组成。一步操作中,你可以将 a 或 b 中的

任一字符改变为任一小写字母。操作的最终目标是满足下列三个条件之一:

  • a 中的每个字母在字母表中严格小于 b 中的每个字母。
  • b 中的每个字母在字母表中严格小于 a 中的每个字母。
  • a 和 b 都由同一个字母组成。
    返回达成目标所需的最少操作数。

示例 1

复制代码
a = "aba", b = "caa",输出 2。

示例 2

复制代码
a = "dabadd", b = "cda",输出 3。

解题思路

解法一:26 字母分界点枚举(主提交版本)

  • 统计 a、b 各字母频次及前缀和。
  • 条件 3:把 a 全部改为 c、b 全部改为 c(同一个字母),操作数 = len(a)-cntAc + len(b)-cntBc。
  • 条件 1:存在分界字母 x,a 中所有字母 <= x、b 中所有字母 > x,
    操作数 = 把 a 中 > x 的改为小 + 把 b 中 <= x 的改为大。
  • 条件 2 对称。枚举 x 从 0...25 取最小。
    复杂度:时间复杂度 O(26 + |a| + |b|),空间复杂度 O(26)。
    解法二:逐分界点暴力计数(对照)
  • 不建前缀和,直接对每个分界 x 重新统计两次字符串中需要改的字符数。
  • 逻辑与解法一完全一致,仅用原始字符数组遍历,作为对照实现。
    复杂度:时间复杂度 O(26 * (|a|+|b|)),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    // 解法一:26 字母分界点枚举 + 前缀和(主提交版本)
    public int minCharacters(String a, String b) {
        int[] ca = count(a);
        int[] cb = count(b);
        int na = a.length(), nb = b.length();
        int[] pa = new int[27], pb = new int[27];
        for (int i = 0; i < 26; i++) {
            pa[i + 1] = pa[i] + ca[i];
            pb[i + 1] = pb[i] + cb[i];
        }
        int ans = Integer.MAX_VALUE;
        // 条件 3:a、b 都改成同一个字母 c
        for (int c = 0; c < 26; c++) {
            ans = Math.min(ans, na - ca[c] + nb - cb[c]);
        }
        // 条件 1:a 中每个字母严格小于 b 中每个字母
        //        => 存在 x,a 全 <= x 且 b 全 > x
        for (int x = 0; x < 25; x++) {
            int cost = (na - pa[x + 1]) + pb[x + 1];
            ans = Math.min(ans, cost);
        }
        // 条件 2:b 中每个字母严格小于 a 中每个字母
        for (int x = 0; x < 25; x++) {
            int cost = (nb - pb[x + 1]) + pa[x + 1];
            ans = Math.min(ans, cost);
        }
        return ans;
    }

    private int[] count(String s) {
        int[] c = new int[26];
        for (char ch : s.toCharArray()) {
            c[ch - 'a']++;
        }
        return c;
    }

    // 解法二:逐分界点暴力计数(对照)
    public int minCharactersBrute(String a, String b) {
        int best = Integer.MAX_VALUE;
        // 条件 3
        for (int c = 0; c < 26; c++) {
            int cost = 0;
            for (char ch : a.toCharArray()) {
                if (ch - 'a' != c) {
                    cost++;
                }
            }
            for (char ch : b.toCharArray()) {
                if (ch - 'a' != c) {
                    cost++;
                }
            }
            best = Math.min(best, cost);
        }
        // 条件 1:a 全 <= x 且 b 全 > x
        for (int x = 0; x < 25; x++) {
            int cost = 0;
            for (char ch : a.toCharArray()) {
                if (ch - 'a' > x) {
                    cost++;
                }
            }
            for (char ch : b.toCharArray()) {
                if (ch - 'a' <= x) {
                    cost++;
                }
            }
            best = Math.min(best, cost);
        }
        // 条件 2:b 全 <= x 且 a 全 > x
        for (int x = 0; x < 25; x++) {
            int cost = 0;
            for (char ch : b.toCharArray()) {
                if (ch - 'a' > x) {
                    cost++;
                }
            }
            for (char ch : a.toCharArray()) {
                if (ch - 'a' <= x) {
                    cost++;
                }
            }
            best = Math.min(best, cost);
        }
        return best;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minCharacters("aba", "caa")); // 2
        System.out.println(sol.minCharacters("dabadd", "cda")); // 3
        System.out.println(sol.minCharactersBrute("aba", "caa")); // 2
        System.out.println(sol.minCharactersBrute("dabadd", "cda")); // 3
    }
}

复杂度分析

复杂度:时间复杂度 O(26 + |a| + |b|),空间复杂度 O(26)。

复杂度:时间复杂度 O(26 * (|a|+|b|)),空间复杂度 O(1)。


1738. Find Kth Largest XOR Coordinate Value(找出第 K 大的异或坐标值)

题目描述

给你一个二维矩阵 matrix 和一个整数 k,矩阵大小为 m x n 由非负整数组成。

矩阵中坐标 (a, b) 的目标值可以通过对所有元素 matrix[i][j] 执行异或运算得到,

其中 i 和 j 满足 0 <= i <= a < m 且 0 <= j <= b < n。

请你找出 matrix 的所有坐标中第 k 大的目标值(k 的值从 1 开始计数)。

示例 1

复制代码
matrix = [[5,2],[1,6]], k = 1,输出 7。

示例 2

复制代码
matrix = [[5,2],[1,6]], k = 2,输出 5。

示例 3

复制代码
matrix = [[3,10,1],[9,3,7],[10,4,4]], k = 3,输出 5。

解题思路

解法一:二维前缀异或 + 全部值排序取第 k 大(主提交版本)

  • preij = prei-1j ^ preij-1 ^ prei-1j-1 ^ matrixi-1j-1。
  • 收集所有 pre 值,降序排序后取第 k-1 个。
    复杂度:时间复杂度 O(mn log(mn)),空间复杂度 O(m n)。
    解法二:小根堆维护前 k 大(对照)
  • 同样生成全部异或坐标值,用容量为 k 的小根堆过滤,堆顶即第 k 大。
    复杂度:时间复杂度 O(mnlog k),空间复杂度 O(k)。

代码实现

java 复制代码
public class Solution {

    // 解法一:前缀异或 + 排序(主提交版本)
    public int kthLargestValue(int[][] matrix, int k) {
        int m = matrix.length, n = matrix[0].length;
        int[][] pre = new int[m + 1][n + 1];
        java.util.List<Integer> all = new java.util.ArrayList<>();
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                pre[i][j] = pre[i - 1][j] ^ pre[i][j - 1]
                        ^ pre[i - 1][j - 1] ^ matrix[i - 1][j - 1];
                all.add(pre[i][j]);
            }
        }
        java.util.Collections.sort(all, java.util.Collections.reverseOrder());
        return all.get(k - 1);
    }

    // 解法二:小根堆维护前 k 大(对照)
    public int kthLargestValueHeap(int[][] matrix, int k) {
        int m = matrix.length, n = matrix[0].length;
        int[][] pre = new int[m + 1][n + 1];
        java.util.PriorityQueue<Integer> pq = new java.util.PriorityQueue<>();
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                pre[i][j] = pre[i - 1][j] ^ pre[i][j - 1]
                        ^ pre[i - 1][j - 1] ^ matrix[i - 1][j - 1];
                pq.offer(pre[i][j]);
                if (pq.size() > k) {
                    pq.poll();
                }
            }
        }
        return pq.peek();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.kthLargestValue(new int[][]{{5, 2}, {1, 6}}, 1)); // 7
        System.out.println(sol.kthLargestValue(new int[][]{{5, 2}, {1, 6}}, 2)); // 5
        System.out.println(sol.kthLargestValue(new int[][]{{3, 10, 1}, {9, 3, 7}, {10, 4, 4}}, 3)); // 5
        System.out.println(sol.kthLargestValueHeap(new int[][]{{5, 2}, {1, 6}}, 1)); // 7
        System.out.println(sol.kthLargestValueHeap(new int[][]{{5, 2}, {1, 6}}, 2)); // 5
        System.out.println(sol.kthLargestValueHeap(new int[][]{{3, 10, 1}, {9, 3, 7}, {10, 4, 4}}, 3)); // 5
    }
}

复杂度分析

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

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


1739. Building Boxes(放置盒子)

题目描述

有一个立方体房间,其长度、宽度和高度都等于 n 个单位。请你在房间里放置 n 个盒子,

每个盒子都是一个单位边长的立方体。放置规则如下:

  • 你可以把盒子放在地板上的任何地方。
  • 如果盒子 x 需要放置在盒子 y 的顶部,那么盒子 y 竖直的四个侧面都必须与另一个盒子或墙相邻。
    给你一个整数 n,返回接触地面的盒子的最少可能数量。

示例 1

复制代码
n = 3,输出 3。

示例 2

复制代码
n = 4,输出 3。

示例 3

复制代码
n = 10,输出 6。

解题思路

解法一:数学贪心(主提交版本)

  • 为了用最少的"底面盒子"支持尽可能多的总盒子,应把盒子堆成"完美塔堆":
    最底层是 kk 方格(k 层依次递减)。满 k 层塔共能放 k(k+1)*(k+2)/6 个盒子。
  • 先找最大的 k 使 sum_{t=1...k} t(t+1)/2 <= n,即 k 层完美塔。
  • 剩余盒子再沿最底层的一条边向上堆(每加一层需要 floor 新增一层:1、2、3...)。
    设剩余 r 个盒子,需要新增 x 层(1+2+...+x >= r)。
    复杂度:时间复杂度 O(cuberoot(n)),空间复杂度 O(1)。
    解法二:二分找层数(对照)
  • 同思路,把"找最大 k"改为在 0, 2000 上二分。
    复杂度:时间复杂度 O(log n),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    // 解法一:数学贪心(主提交版本)
    public int minimumBoxes(int n) {
        // 找最大 k:1 + (1+2) + (1+2+3) + ... + (1+...+k) <= n
        // 即 sum_{i=1..k} i*(i+1)/2 <= n
        int k = 1;
        while (k * (k + 1) * (k + 2) / 6 <= n) {
            k++;
        }
        k--; // 完美塔层数
        long used = (long) k * (k + 1) * (k + 2) / 6;
        long ground = (long) k * (k + 1) / 2; // 完美塔接触地面的盒子数
        int rest = n - (int) used;
        // 剩余 rest 个盒子沿一条边堆:加第 1 层放 1 个、第 2 层放 2 个......
        // 每放 1 层,ground 增加对应数量的盒子
        int layer = 1;
        while (rest > 0) {
            rest -= layer;
            ground += layer;
            layer++;
        }
        return (int) ground;
    }

    // 解法二:二分(对照)
    public int minimumBoxesBinary(int n) {
        int lo = 0, hi = 2000, k = 0;
        // 找最大的 k:sum_{i=1..k} i*(i+1)/2 <= n
        while (lo <= hi) {
            int mid = (lo + hi) >> 1;
            long total = (long) mid * (mid + 1) * (mid + 2) / 6;
            if (total <= n) {
                k = mid;
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        long used = (long) k * (k + 1) * (k + 2) / 6;
        long ground = (long) k * (k + 1) / 2;
        int rest = n - (int) used;
        int layer = 1;
        while (rest > 0) {
            rest -= layer;
            ground += layer;
            layer++;
        }
        return (int) ground;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minimumBoxes(3)); // 3
        System.out.println(sol.minimumBoxes(4)); // 3
        System.out.println(sol.minimumBoxes(10)); // 6
        System.out.println(sol.minimumBoxesBinary(3)); // 3
        System.out.println(sol.minimumBoxesBinary(4)); // 3
        System.out.println(sol.minimumBoxesBinary(10)); // 6
    }
}

复杂度分析

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

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


1740. Find Distance in a Binary Tree(找到二叉树中的距离)

题目描述

给定一棵二叉树的根节点 root 以及两个整数 p 和 q,返回该二叉树中值为 p 的结点

与值为 q 的结点间的距离。两个结点间的距离就是从一个结点到另一个结点的路径上边的数目。

示例 1

复制代码
root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 0,输出 3。

示例 2

复制代码
同树,p = 5, q = 7,输出 2。

示例 3

复制代码
同树,p = 5, q = 5,输出 0。

提示:

  • 树中结点个数的范围在 1, 10\^4,0 <= Node.val <= 10^9,
  • 树中所有结点的值都是唯一的,p 和 q 是树中结点的值。

解题思路

解法一:最近公共祖先 + 深度差(主提交版本)

  • 先求出 p、q 的最近公共祖先 anc,再分别求 p、q 到 anc 的深度(边数),
    距离 = d(p, anc) + d(q, anc)(当 anc 为 p 或 q 时公式同样成立)。
  • 求 LCA 用递归:根为 null 返回 null;根值等于 p 或 q 返回根;
    分别递归左、右子树,两边都有则当前根是 LCA。
    复杂度:时间复杂度 O(n),空间复杂度 O(h)(递归栈)。
    解法二:记录父指针向上爬(对照)
  • 一次 DFS 建立每个结点到父结点的映射,并记录 p、q 结点。
  • 从 p 向上爬并标记路径,再从 q 向上爬到第一个被标记的结点即 LCA,统计边数。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。

代码实现

java 复制代码
// 树结点定义(LeetCode 内置)
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

public class Solution {

    // 解法一:LCA + 深度(主提交版本)
    public int findDistance(TreeNode root, int p, int q) {
        TreeNode anc = lca(root, p, q);
        return depth(anc, p) + depth(anc, q);
    }

    private TreeNode lca(TreeNode root, int p, int q) {
        if (root == null || root.val == p || root.val == q) {
            return root;
        }
        TreeNode left = lca(root.left, p, q);
        TreeNode right = lca(root.right, p, q);
        if (left != null && right != null) {
            return root;
        }
        return left != null ? left : right;
    }

    private int depth(TreeNode node, int target) {
        if (node == null) {
            return -100000; // 找不到(不应发生)
        }
        if (node.val == target) {
            return 0;
        }
        int d = depth(node.left, target);
        if (d >= 0) {
            return d + 1;
        }
        d = depth(node.right, target);
        return d >= 0 ? d + 1 : -100000;
    }

    // 解法二:父指针路径标记(对照)
    public int findDistanceParent(TreeNode root, int p, int q) {
        java.util.Map<TreeNode, TreeNode> parent = new java.util.HashMap<>();
        java.util.Map<Integer, TreeNode> byVal = new java.util.HashMap<>();
        dfs(root, null, parent, byVal);
        TreeNode pNode = byVal.get(p);
        TreeNode qNode = byVal.get(q);
        java.util.Set<TreeNode> path = new java.util.HashSet<>();
        for (TreeNode cur = pNode; cur != null; cur = parent.get(cur)) {
            path.add(cur);
        }
        TreeNode anc = qNode;
        int up = 0;
        while (anc != null && !path.contains(anc)) {
            anc = parent.get(anc);
            up++;
        }
        int down = 0;
        for (TreeNode cur = pNode; cur != anc; cur = parent.get(cur)) {
            down++;
        }
        return up + down;
    }

    private void dfs(TreeNode node, TreeNode par,
                     java.util.Map<TreeNode, TreeNode> parent,
                     java.util.Map<Integer, TreeNode> byVal) {
        if (node == null) {
            return;
        }
        if (par != null) {
            parent.put(node, par);
        }
        byVal.put(node.val, node);
        dfs(node.left, node, parent, byVal);
        dfs(node.right, node, parent, byVal);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        TreeNode root = build(
                new Integer[]{3, 5, 1, 6, 2, 0, 8, null, null, 7, 4});
        System.out.println(sol.findDistance(root, 5, 0)); // 3
        System.out.println(sol.findDistance(root, 5, 7)); // 2
        System.out.println(sol.findDistance(root, 5, 5)); // 0
        System.out.println(sol.findDistanceParent(root, 5, 0)); // 3
        System.out.println(sol.findDistanceParent(root, 5, 7)); // 2
        System.out.println(sol.findDistanceParent(root, 5, 5)); // 0
    }

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

复杂度分析

复杂度:时间复杂度 O(n),空间复杂度 O(h)(递归栈)。

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

相关推荐
LabVIEW开发38 分钟前
LabVIEW 局部变量与数据流:这道 CLAD 题为什么答案是 Undefined
java·开发语言·labview
wangbing112543 分钟前
监控网页项目常用英文缩写
java
麻瓜code1 小时前
双写一致性:为什么是“删缓存“,而不是“改缓存“
java·缓存·mybatis
小蒜学长1 小时前
基于SpringBoot+Vue的云南地区扶贫助农系统的设计与实现(代码+数据库+LW)
java·vue.js·spring boot·后端·乡村振兴
砚底藏山河1 小时前
量化回测可复现流水线CI化
java·python·ci/cd·金融
骑着蜗牛撵大象3271 小时前
Spring Boot 4.1 + Spring AI 2.0:RAG 供文、图谱供关系、MCP 供工具的 Java Agent 平台
java·spring boot·spring·微服务·知识图谱·rag·mcp
衍生星球1 小时前
【Java 语言】— 面向对象:构造方法(无参、有参构造)
java·开发语言
SimonKing1 小时前
素笺( local-notes)全面优化,新增功能体验不一样的笔记
java·后端·程序员
techpupil1 小时前
IDEA基础配置及开发基础插件
java·ide·intellij-idea