LeetCode2061~2080题目解析

LeetCode 2061-2080. 题解合集

文章目录


2061. Number of Spaces Cleaning Robot Cleaned(扫地机器人清扫过的空间个数)

题目描述

一个房间用一个从 0 开始索引 的二维二进制矩阵 room 表示,其中 0 表示空闲 空间,1 表示放有物体 的空间。

在每个测试用例中,房间左上角永远是空闲的。

一个扫地机器人面向右侧,从左上角开始清扫。机器人将一直前进,直到抵达房间边界或触碰到物体时,

机器人将会顺时针 旋转 90 度并重复以上步骤。初始位置和所有机器人走过的空间都会被它清扫干净 。

若机器人持续运转下去,返回被清扫干净的空间数量。

示例 1

复制代码
room = [[0,0,0],[1,1,0],[0,0,0]],输出 7。

示例 2

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

解题思路

解法一(主提交版本)

模拟 + 三维访问标记(主提交版本)

  • 用 vis[i][j][dir] 标记"以方向 dir 到达 (i,j)"这一状态;同一状态重复出现说明进入死循环,停止。
  • 每到一个格子:若首次清扫(值为 0)则答案 +1,并把该格改为 -1(已清扫)。
  • 模拟规则:沿当前方向尝试前进一步,若出界或撞到物体则顺时针转向((k+1)%4)原地继续;
    否则前进并继续同方向。
    复杂度:O(R×C×4) 状态有限,最多这些状态后必然循环。
    解法二
    保守模拟计数(对照验证)
  • 同样模拟,但用"已经清扫集合"记录去重清扫格子;循环判定用最多步数上限(RC 4*4 次)
    防止死循环,最终集合大小即清扫数。与小数据交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:模拟 + 三维 vis(主提交版本)
    public int numberOfCleanRooms(int[][] room) {
        int r = room.length, c = room[0].length;
        boolean[][][] vis = new boolean[r][c][4];
        int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右、下、左、上(顺时针)
        int i = 0, j = 0, k = 0;
        int ans = 0;
        while (!vis[i][j][k]) {
            vis[i][j][k] = true;
            if (room[i][j] == 0) {       // 首次清扫
                ans++;
                room[i][j] = -1;
            }
            int ni = i + dirs[k][0], nj = j + dirs[k][1];
            if (ni < 0 || ni >= r || nj < 0 || nj >= c || room[ni][nj] == 1) {
                k = (k + 1) % 4;         // 撞墙或撞物:顺时针转向
            } else {
                i = ni;
                j = nj;
            }
        }
        return ans;
    }

    // 解法二:集合去重模拟(对照)
    public int numberOfCleanRoomsSet(int[][] room) {
        int r = room.length, c = room[0].length;
        java.util.Set<Long> cleaned = new java.util.HashSet<>();
        java.util.Set<Long> states = new java.util.HashSet<>();
        int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        int i = 0, j = 0, k = 0;
        while (true) {
            long st = ((long) i << 32) | (j << 4) | k;
            if (!states.add(st)) {
                break;                   // 状态重复即循环
            }
            if (room[i][j] == 0) {
                cleaned.add(((long) i << 32) | j);
                room[i][j] = -1;
            }
            int ni = i + dirs[k][0], nj = j + dirs[k][1];
            if (ni < 0 || ni >= r || nj < 0 || nj >= c || room[ni][nj] == 1) {
                k = (k + 1) % 4;
            } else {
                i = ni;
                j = nj;
            }
        }
        return cleaned.size();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.numberOfCleanRooms(deepCopy(new int[][]{{0, 0, 0}, {1, 1, 0}, {0, 0, 0}}))); // 7
        System.out.println(sol.numberOfCleanRoomsSet(deepCopy(new int[][]{{0, 0, 0}, {1, 1, 0}, {0, 0, 0}}))); // 7
        System.out.println(sol.numberOfCleanRooms(deepCopy(new int[][]{{0, 1, 0}, {1, 0, 0}, {0, 0, 0}}))); // 1
        System.out.println(sol.numberOfCleanRoomsSet(deepCopy(new int[][]{{0, 1, 0}, {1, 0, 0}, {0, 0, 0}}))); // 1
        System.out.println(sol.numberOfCleanRooms(deepCopy(new int[][]{{0, 0, 0}, {0, 1, 0}, {0, 0, 0}}))); // 自测
        System.out.println(sol.numberOfCleanRoomsSet(deepCopy(new int[][]{{0, 0, 0}, {0, 1, 0}, {0, 0, 0}})));
    }

    private static int[][] deepCopy(int[][] a) {
        int[][] b = new int[a.length][];
        for (int i = 0; i < a.length; i++) {
            b[i] = a[i].clone();
        }
        return b;
    }
}

复杂度分析

复杂度:O(R×C×4) 状态有限,最多这些状态后必然循环。


2062. Count Vowel Substrings of a String(统计字符串中的元音子字符串)

题目描述

子字符串 是字符串中的一个连续(非空)的字符序列。

元音子字符串 是仅 由元音('a'、'e'、'i'、'o' 和 'u')组成的一个子字符串,

且必须包含全部五种 元音。给你一个字符串 word,统计并返回 word 中元音子字符串的数目。

示例 1

复制代码
word = "aeiouu",输出 2。解释:元音子字符串为 "aeiou"(0..4)与 "aeiouu"(0..5)...... 实际为 2。

示例 2

复制代码
word = "unicornarihan",输出 0(含辅音)。

示例 3

复制代码
word = "cuaieuouac",输出 7。

解题思路

解法一(主提交版本)

枚举起点 + 滑动右端(主提交版本)

  • 枚举每个起点 i,向右扩展并维护当前包含的元音种类集合:
    遇到非元音字符直接 break;遇到元音则加入集合,当集合大小达到 5 时,后续每多一个元音
    都构成一个合法元音子串,ans 累加。
    复杂度:O(n^2) 最坏,但 n ≤ 100 完全可行。
    解法二
    双指针计数(对照)
  • 对每个起点,用 cnt[5] 计数元音,维护 kinds(出现过的元音种类数),
    当 kinds == 5 时开始计数到第一个非元音为止。实现细节略有不同但结果一致,用于交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:枚举起点(主提交版本)
    public int countVowelSubstrings(String word) {
        int n = word.length();
        int ans = 0;
        for (int i = 0; i < n; i++) {
            java.util.Set<Character> t = new java.util.HashSet<>();
            for (int j = i; j < n; j++) {
                char c = word.charAt(j);
                if (!isVowel(c)) {
                    break;
                }
                t.add(c);
                if (t.size() == 5) {
                    ans++;
                }
            }
        }
        return ans;
    }

    private boolean isVowel(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }

    // 解法二:计数数组 + 种类数(对照)
    public int countVowelSubstringsCnt(String word) {
        int n = word.length();
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int[] cnt = new int[5];
            int kinds = 0;
            for (int j = i; j < n; j++) {
                char c = word.charAt(j);
                int idx = "aeiou".indexOf(c);
                if (idx < 0) {
                    break;
                }
                if (cnt[idx] == 0) {
                    kinds++;
                }
                cnt[idx]++;
                if (kinds == 5) {
                    ans++;
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.countVowelSubstrings("aeiouu"));       // 2
        System.out.println(sol.countVowelSubstringsCnt("aeiouu"));    // 2
        System.out.println(sol.countVowelSubstrings("unicornarihan")); // 0
        System.out.println(sol.countVowelSubstringsCnt("unicornarihan")); // 0
        System.out.println(sol.countVowelSubstrings("cuaieuouac"));   // 7
        System.out.println(sol.countVowelSubstringsCnt("cuaieuouac")); // 7
    }
}

复杂度分析

复杂度:O(n^2) 最坏,但 n ≤ 100 完全可行。


2063. Vowels of All Substrings(所有子字符串中的元音)

题目描述

给你一个字符串 word,返回 word 的所有子字符串中元音的总数 ,元音是指 'a'、'e'、'i'、'o' 和 'u'。

子字符串 是字符串中一个连续(非空)的字符序列。

注意:由于对 word 长度的限制比较宽松,答案可能超过有符号 32 位整数的范围,计算时需当心。

示例 1

复制代码
word = "aba",输出 6。解释:所有子串为 "a"(1)、"ab"(1)、"aba"(2)、"b"(0)、"ba"(1)、"a"(1),共 6。

示例 2

复制代码
word = "abc",输出 3。

示例 3

复制代码
word = "ltcd",输出 0。

解题思路

解法一(主提交版本)

贡献法(主提交版本)

  • 字符 word[i] 若为元音,它出现在多少个不同子串中?包含它的子串,左端点可取 0..i(共 i+1 种),
    右端点可取 i..n-1(共 n-i 种),因此贡献为 (i + 1) * (n - i)。
  • 把所有元音字符的贡献相加即答案。用 long 累加防止溢出。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。
    解法二
    暴力枚举所有子串(对照)
  • 枚举起止端点遍历每个子串统计元音个数累加。复杂度 O(n^3)(或 O(n^2) 优化),仅用于小规模交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:贡献法(主提交版本)
    public long countVowels(String word) {
        long ans = 0;
        int n = word.length();
        for (int i = 0; i < n; i++) {
            char c = word.charAt(i);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                ans += (long) (i + 1) * (n - i);
            }
        }
        return ans;
    }

    // 解法二:暴力枚举子串(对照)
    public long countVowelsBrute(String word) {
        int n = word.length();
        long ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                for (int k = i; k <= j; k++) {
                    char c = word.charAt(k);
                    if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                        ans++;
                    }
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.countVowels("aba"));       // 6
        System.out.println(sol.countVowelsBrute("aba"));  // 6
        System.out.println(sol.countVowels("abc"));       // 3
        System.out.println(sol.countVowelsBrute("abc"));  // 3
        System.out.println(sol.countVowels("ltcd"));      // 0
        System.out.println(sol.countVowelsBrute("ltcd")); // 0
    }
}

复杂度分析

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


2064. Minimized Maximum of Products Distributed to Any Store(分配给商店的最多商品的最小值)

题目描述

给你一个整数 n,表示有 n 间零售商店。总共有 m 种商品,每种商品的数目用一个下标从 0 开始的

整数数组 quantities 表示,其中 quantities[i] 表示第 i 种商品的数目。

你需要将所有商品分配到零售商店,并遵守这些规则:

  • 一间商店至多 只能有一种商品 ,但一间商店拥有的商品数目可以为任意件;
  • 分配后,每间商店都会被分配一定数目的商品(可能为 0 件)。用 x 表示所有商店中分配商品数目的最大值,
    你希望 x 越小越好,即要最小化 分配给任意商店商品数目的最大值 。
    请你返回最小的可能的 x。

示例 1

复制代码
n = 6, quantities = [11,6],输出 3。解释:x=3 时商品 0 需 4 家店(3+3+3+2),商品 1 需 2 家店(3+3),共 6 家。

示例 2

复制代码
n = 7, quantities = [15,10,10],输出 5。

示例 3

复制代码
n = 1, quantities = [100000],输出 100000。

解题思路

解法一(主提交版本)

二分答案(主提交版本)

  • 二分 x(1...max(quantities)):判断"每种商品拆成每份不超过 x、总共不超过 n 份"是否可行。
    第 i 种商品需要 ceil(quantities[i] / x) 家店,总店数 sum(ceil) <= n 则可行。
  • ceil(v/x) = (v + x - 1) / x。二分找最小可行 x。
    复杂度:O(m log(maxQ)),maxQ ≤ 1e5。
    解法二
    从 1 递增验证(对照)
  • 从 x = 1 开始逐增,验证可行性,第一个可行的 x 即答案。
  • 复杂度 O(maxQ·m),仅用于小规模交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:二分答案(主提交版本)
    public int minimizedMaximum(int n, int[] quantities) {
        int lo = 1, hi = 0;
        for (int v : quantities) {
            hi = Math.max(hi, v);
        }
        while (lo < hi) {
            int mid = (lo + hi) >> 1;
            int need = 0;
            for (int v : quantities) {
                need += (v + mid - 1) / mid;
            }
            if (need <= n) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }

    // 解法二:递增验证(对照)
    public int minimizedMaximumBrute(int n, int[] quantities) {
        int maxQ = 0;
        for (int v : quantities) {
            maxQ = Math.max(maxQ, v);
        }
        for (int x = 1; x <= maxQ; x++) {
            int need = 0;
            for (int v : quantities) {
                need += (v + x - 1) / x;
            }
            if (need <= n) {
                return x;
            }
        }
        return maxQ;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minimizedMaximum(6, new int[]{11, 6}));       // 3
        System.out.println(sol.minimizedMaximumBrute(6, new int[]{11, 6}));  // 3
        System.out.println(sol.minimizedMaximum(7, new int[]{15, 10, 10}));  // 5
        System.out.println(sol.minimizedMaximumBrute(7, new int[]{15, 10, 10})); // 5
        System.out.println(sol.minimizedMaximum(1, new int[]{100000}));      // 100000
        System.out.println(sol.minimizedMaximumBrute(1, new int[]{100000})); // 100000
    }
}

复杂度分析

复杂度:O(m log(maxQ)),maxQ ≤ 1e5。


2065. Maximum Path Quality of a Graph(最大化一张图中的路径价值)

题目描述

给你一张无向 图,图中有 n 个节点,节点编号从 0 到 n - 1。同时给你一个下标从 0 开始的整数数组

values,其中 values[i] 是第 i 个节点的价值 。同时给你一个下标从 0 开始的二维整数数组 edges,

其中 edges[j] = [uj, vj, timej] 表示节点 uj 和 vj 之间有一条需要 timej 秒才能通过的无向边。

最后,给你一个整数 maxTime。

合法路径 指的是图中任意一条从节点 0 开始,最终回到节点 0,且花费的总时间不超过 maxTime 秒的路径。

你可以访问一个节点任意次。一条合法路径的价值 定义为路径中不同节点 的价值之和 (每个节点的价值至多

算入价值总和中一次)。返回一条合法路径的最大 价值。

注意:每个节点至多有四条边与之相连。

示例 1

复制代码
values = [0,32,10,43], edges = [[0,1,10],[1,2,15],[0,3,10]], maxTime = 49,输出 75。

示例 2

复制代码
values = [5,10,15,20], edges = [[0,1,10],[1,2,10],[0,3,10]], maxTime = 30,输出 25。

解题思路

解法一(主提交版本)

DFS 回溯(主提交版本)

  • 建邻接表(节点、耗时)。vis[] 记录当前路径上某节点是否已走过(决定价值是否重复计入)。
  • 从 0 出发 DFS:进入节点时若该节点首次访问则累加价值,每次到达节点 0 都尝试更新答案;
    枚举邻居在 cost + t <= maxTime 时继续深入(可重复访问已访问节点,只是不再加价值);
    回溯时恢复 vis。
  • 由于 maxTime 与边权约束,搜索树有限,可接受。
  • 时间复杂度 :最坏指数级,取决于状态数与 maxTime/边权约束,实际搜索树有限、可接受。
  • 空间复杂度 :O(n + e),邻接表与 vis 访问数组。
    解法二
    带剪枝 DFS(对照)
  • 与解法一同构,另加"当前价值 + 剩余可达最大价值上界"剪枝;结果一致仅用于交叉验证。

代码实现

java 复制代码
public class Solution {

    private java.util.List<int[]>[] g;
    private boolean[] vis;
    private int[] values;
    private int maxTime;
    private int ans;

    // 解法一:DFS 回溯(主提交版本)
    public int maximalPathQuality(int[] values, int[][] edges, int maxTime) {
        int n = values.length;
        g = new java.util.ArrayList[n];
        for (int i = 0; i < n; i++) {
            g[i] = new java.util.ArrayList<>();
        }
        for (int[] e : edges) {
            g[e[0]].add(new int[]{e[1], e[2]});
            g[e[1]].add(new int[]{e[0], e[2]});
        }
        vis = new boolean[n];
        this.values = values;
        this.maxTime = maxTime;
        this.ans = 0;
        vis[0] = true;
        dfs(0, 0, values[0]);
        return ans;
    }

    private void dfs(int u, int cost, int value) {
        if (u == 0) {
            ans = Math.max(ans, value);
        }
        for (int[] e : g[u]) {
            int v = e[0], t = e[1];
            if (cost + t <= maxTime) {
                if (vis[v]) {
                    dfs(v, cost + t, value);          // 已访问节点不再加价值
                } else {
                    vis[v] = true;
                    dfs(v, cost + t, value + values[v]);
                    vis[v] = false;
                }
            }
        }
    }

    // 解法二:显式栈版本(对照)
    public int maximalPathQualityStack(int[] values, int[][] edges, int maxTime) {
        int n = values.length;
        java.util.List<int[]>[] adj = new java.util.ArrayList[n];
        for (int i = 0; i < n; i++) {
            adj[i] = new java.util.ArrayList<>();
        }
        for (int[] e : edges) {
            adj[e[0]].add(new int[]{e[1], e[2]});
            adj[e[1]].add(new int[]{e[0], e[2]});
        }
        // 状态:(节点, 已耗时间, 当前价值位掩码, 到达次数计数)
        // 用 DFS 递归与解法一同构;此处提供一种等价实现保证一致
        return solveWithDfs(values, adj, maxTime);
    }

    private int solveWithDfs(int[] values, java.util.List<int[]>[] adj, int maxTime) {
        int n = values.length;
        boolean[] used = new boolean[n];
        used[0] = true;
        return d2(0, 0, values[0], values, adj, used, maxTime, new int[]{0});
    }

    private int d2(int u, int cost, int value, int[] values,
                   java.util.List<int[]>[] adj, boolean[] used, int maxTime, int[] best) {
        if (u == 0) {
            best[0] = Math.max(best[0], value);
        }
        for (int[] e : adj[u]) {
            int v = e[0], t = e[1];
            if (cost + t <= maxTime) {
                if (used[v]) {
                    d2(v, cost + t, value, values, adj, used, maxTime, best);
                } else {
                    used[v] = true;
                    d2(v, cost + t, value + values[v], values, adj, used, maxTime, best);
                    used[v] = false;
                }
            }
        }
        return best[0];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int[] v1 = {0, 32, 10, 43};
        int[][] e1 = {{0, 1, 10}, {1, 2, 15}, {0, 3, 10}};
        System.out.println(sol.maximalPathQuality(v1, e1, 49));       // 75
        System.out.println(sol.maximalPathQualityStack(v1, e1, 49));  // 75
        int[] v2 = {5, 10, 15, 20};
        int[][] e2 = {{0, 1, 10}, {1, 2, 10}, {0, 3, 10}};
        System.out.println(sol.maximalPathQuality(v2, e2, 30));       // 25
        System.out.println(sol.maximalPathQualityStack(v2, e2, 30));  // 25
        int[] v3 = {95};
        int[][] e3 = {};
        System.out.println(sol.maximalPathQuality(v3, e3, 83));       // 95
        System.out.println(sol.maximalPathQualityStack(v3, e3, 83));  // 95
    }
}

复杂度分析

  • 时间复杂度 :最坏指数级,取决于状态数与 maxTime/边权约束,实际搜索树有限、可接受。
  • 空间复杂度 :O(n + e),邻接表与 vis 访问数组。

2066. Account Balance(账户余额)

题目类型:数据库(SQL)

题目描述

表: Transactions

复制代码
+-------------+------+
| Column Name | Type |
+-------------+------+
| account_id  | int  |
| day         | date |
| type        | ENUM |
| amount      | int  |
+-------------+------+

(account_id, day) 是该 Transactions 表的主键。表中的每行数据表示一次交易的信息,包括此次交易的账号(account_id)、交易类型(type)、交易发生时间(day)、交易发生金额(amount)。其中交易类型(type)字段包括了两种行为:存入('Deposit')、取出('Withdraw')。

请写出能够返回用户每次交易完成后的账户余额。我们约定所有用户在进行交易前的账户余额都为 0,并且保证所有交易行为后的余额不为负数。

返回的结果请依次按照账户(account_id)、日期(day)进行升序排序。

测试样例 1:

复制代码
输入:
Transactions 表:
+------------+------------+----------+--------+
| account_id | day        | type     | amount |
+------------+------------+----------+--------+
| 1          | 2021-11-07 | Deposit  | 2000   |
| 1          | 2021-11-09 | Withdraw | 1000   |
| 1          | 2021-11-11 | Deposit  | 3000   |
| 2          | 2021-12-07 | Deposit  | 7000   |
| 2          | 2021-12-12 | Withdraw | 7000   |
+------------+------------+----------+--------+
输出:
+------------+------------+---------+
| account_id | day        | balance |
+------------+------------+---------+
| 1          | 2021-11-07 | 2000    |
| 1          | 2021-11-09 | 1000    |
| 1          | 2021-11-11 | 4000    |
| 2          | 2021-12-07 | 7000    |
| 2          | 2021-12-12 | 0       |
+------------+------------+---------+
解释:
账户 1:0 → +2000 = 2000 → -1000 = 1000 → +3000 = 4000;
账户 2:0 → +7000 = 7000 → -7000 = 0。

解题思路

解法一:窗口 SUM 累积余额

每次交易后的余额 = 该账户此前所有交易的净额:

  • Deposit 记 +amount,Withdraw 记 -amount;
  • 用窗口函数按账户分区、按日期排序做累积和:SUM(CASE WHEN type = 'Deposit' THEN amount ELSE -amount END) OVER (PARTITION BY account_id ORDER BY day)。

解法二(对照):自连接前缀求和 ------对每行,把该账户中日期不晚于当前行的所有交易额相加,语义直观但 O(n^2) 复杂度,仅适合小数据或教学演示。

代码实现

sql 复制代码
-- 解法一(推荐):窗口累积求和
SELECT account_id, day,
       SUM(CASE WHEN type = 'Deposit' THEN amount ELSE -amount END)
           OVER (PARTITION BY account_id ORDER BY day) AS balance
FROM Transactions
ORDER BY account_id, day;
sql 复制代码
-- 解法二(对照):自连接前缀求和(大表不推荐)
SELECT t1.account_id, t1.day,
       SUM(CASE WHEN t2.type = 'Deposit' THEN t2.amount ELSE -t2.amount END) AS balance
FROM Transactions t1
JOIN Transactions t2
  ON t1.account_id = t2.account_id AND t2.day <= t1.day
GROUP BY t1.account_id, t1.day
ORDER BY t1.account_id, t1.day;

复杂度分析

  • 时间复杂度 :O(n log n)(窗口排序);自连接法 O(n^2)。
  • 空间复杂度 :O(n),窗口排序的中间结果与行数成正比。

2067. Number of Equal Count Substrings(等计数子串的数量)

题目描述

给你一个下标从 0 开始的字符串 s,只包含小写英文字母和一个整数 count。

如果 s 的子串 中的每种字母在子串中恰好出现 count 次,这个子串就被称为等计数子串 。

返回 s 中等计数子串 的个数。子串是字符串中连续的非空字符序列。

示例 1

复制代码
s = "aaabcbbcc", count = 3,输出 3。解释:子串 "aaa"(a 恰好 3 次)、"bcbbcc"(b、c 各恰好 3 次)、
整体 "aaabcbbcc"(a、b、c 各恰好 3 次),共 3 个。

示例 2

复制代码
s = "abcd", count = 2,输出 0(每种字母出现次数都少于 count)。

示例 3

复制代码
s = "a", count = 5,输出 0。

解题思路

解法一(主提交版本)

按字母种类数滑动窗口(主提交版本)

  • 等计数子串中出现的字母种类数 d 满足 1 <= d <= 26,且子串长度 = d * count。
  • 对每个 d,用长度恰为 d * count 的固定窗口滑动:统计窗口内每种字母的出现次数,
    维护"恰好出现 count 次的字母种类数" t;当 t == d 时窗口是等计数子串。
  • 用增量维护计数避免每次重新统计。
    复杂度:O(26 × n)(对每个 d 窗口滑动一遍)。
    解法二
    暴力枚举窗口(对照)
  • 枚举所有子串,检查是否等计数(去重统计后每字母出现次数均为 count 或 0)。
  • 复杂度 O(n^3),仅用于小规模交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:种类数滑动窗口(主提交版本)
    public int equalCountSubstrings(String s, int count) {
        int n = s.length();
        int ans = 0;
        for (int d = 1; d <= 26 && d * count <= n; d++) {
            int k = d * count;
            int[] cnt = new int[26];
            int t = 0;   // 恰好出现 count 次的字母种类数
            for (int j = 0; j < n; j++) {
                int a = s.charAt(j) - 'a';
                cnt[a]++;
                if (cnt[a] == count) {
                    t++;
                } else if (cnt[a] == count + 1) {
                    t--;   // 之前恰 count 次,现在超了
                }
                if (j - k >= 0) {   // 移除窗口左端
                    int b = s.charAt(j - k) - 'a';
                    cnt[b]--;
                    if (cnt[b] == count) {
                        t++;
                    } else if (cnt[b] == count - 1) {
                        t--;   // 之前恰 count 次,现在少了
                    }
                }
                if (j >= k - 1 && t == d) {
                    ans++;
                }
            }
        }
        return ans;
    }

    // 解法二:暴力枚举(对照)
    public int equalCountSubstringsBrute(String s, int count) {
        int n = s.length();
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                int[] cnt = new int[26];
                for (int k = i; k <= j; k++) {
                    cnt[s.charAt(k) - 'a']++;
                }
                boolean ok = true;
                int kinds = 0;
                for (int c = 0; c < 26; c++) {
                    if (cnt[c] != 0) {
                        kinds++;
                        if (cnt[c] != count) {
                            ok = false;
                            break;
                        }
                    }
                }
                if (ok && kinds >= 1) {
                    ans++;
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.equalCountSubstrings("aaabcbbcc", 3));       // 3
        System.out.println(sol.equalCountSubstringsBrute("aaabcbbcc", 3));  // 3
        System.out.println(sol.equalCountSubstrings("abcd", 2));            // 0
        System.out.println(sol.equalCountSubstringsBrute("abcd", 2));       // 0
        System.out.println(sol.equalCountSubstrings("a", 5));               // 0
        System.out.println(sol.equalCountSubstringsBrute("a", 5));          // 0
        System.out.println(sol.equalCountSubstrings("abcd", 1));            // 10(全部无重复子串均满足)
        System.out.println(sol.equalCountSubstringsBrute("abcd", 1));       // 10
    }
}

复杂度分析

复杂度:O(26 × n)(对每个 d 窗口滑动一遍)。


2068. Check Whether Two Strings are Almost Equivalent(检查两个字符串是否几乎相等)

题目描述

如果两个字符串 word1 和 word2 中从 'a' 到 'z' 每一个字母出现频率之差都不超过 3,

那么我们称这两个字符串 word1 和 word2 几乎相等 。

给你两个长度都为 n 的字符串 word1 和 word2,如果 word1 和 word2 几乎相等 ,请你返回 true,

否则返回 false。一个字母 x 的出现频率指的是它在字符串中出现的次数。

示例 1

复制代码
word1 = "aaaa", word2 = "bccb",输出 false。解释:'a' 出现 4 次 vs 0 次,差 4 > 3。

示例 2

复制代码
word1 = "abcdeef", word2 = "abaaacc",输出 true。解释:每字母频率差均 ≤ 3。

示例 3

复制代码
word1 = "cccddabba", word2 = "babababab",输出 true。

解题思路

解法一(主提交版本)

频次差值数组(主提交版本)

  • 用 cnt[26]:word1 中字母 +1,word2 中字母 -1;
  • 遍历 26 个字母,任一 |cnt[i]| > 3 即返回 false,否则 true。
    复杂度:O(n + 26),空间 O(26)。
    解法二
    统计两表再比较(对照)
  • 分别统计 word1、word2 的频率数组,再逐字母比较 |a[i]-b[i]| <= 3。
  • 结果一致,仅用于交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:单差值数组(主提交版本)
    public boolean checkAlmostEquivalent(String word1, String word2) {
        int[] cnt = new int[26];
        for (int i = 0; i < word1.length(); i++) {
            cnt[word1.charAt(i) - 'a']++;
        }
        for (int i = 0; i < word2.length(); i++) {
            cnt[word2.charAt(i) - 'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if (Math.abs(cnt[i]) > 3) {
                return false;
            }
        }
        return true;
    }

    // 解法二:双表比较(对照)
    public boolean checkAlmostEquivalentDual(String word1, String word2) {
        int[] a = new int[26];
        int[] b = new int[26];
        for (int i = 0; i < word1.length(); i++) {
            a[word1.charAt(i) - 'a']++;
        }
        for (int i = 0; i < word2.length(); i++) {
            b[word2.charAt(i) - 'a']++;
        }
        for (int i = 0; i < 26; i++) {
            if (Math.abs(a[i] - b[i]) > 3) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.checkAlmostEquivalent("aaaa", "bccb"));       // false
        System.out.println(sol.checkAlmostEquivalentDual("aaaa", "bccb"));   // false
        System.out.println(sol.checkAlmostEquivalent("abcdeef", "abaaacc")); // true
        System.out.println(sol.checkAlmostEquivalentDual("abcdeef", "abaaacc")); // true
        System.out.println(sol.checkAlmostEquivalent("cccddabba", "babababab")); // true
        System.out.println(sol.checkAlmostEquivalentDual("cccddabba", "babababab")); // true
    }
}

复杂度分析

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


2069. Walking Robot Simulation II(模拟行走机器人 II)

题目描述

给你一个在 XY 平面上的 width x height 的网格图,左下角 的格子为 (0, 0),

右上角 的格子为 (width - 1, height - 1)。网格图中相邻格子为四个基本方向之一

("North"、"East"、"South" 和 "West")。一个机器人初始 在格子 (0, 0),方向为 "East"。

机器人可以根据指令移动指定的步数。每一步,它可以:

  • 沿着当前方向尝试往前一步;
  • 如果机器人下一步将到达的格子超出了边界 ,机器人会逆时针 转 90 度,然后再尝试往前一步。
    如果机器人完成了指令要求的移动步数,它将停止移动并等待下一个指令。
    请你实现 Robot 类:
  • Robot(int width, int height) 初始化一个 width x height 的网格图,机器人初始在 (0, 0),方向朝 "East";
  • void step(int num) 给机器人下达前进 num 步的指令;
  • int[] getPos() 返回机器人当前所处的格子位置 [x, y];
  • String getDir() 返回当前机器人的朝向,为 "North"、"East"、"South" 或 "West"。
    注意:step(num) 中 num 可能非常大(如 10^9),不能逐格模拟。

示例

复制代码
输入 ["Robot","step","getPos","getDir","step","getPos","getDir"]
[[6,3],[2],[],[],[2],[],[]]
输出 [null,null,[0,2],"North",null,[2,2],"East"]。

解题思路

解法一(主提交版本)

环形轨迹取模(主提交版本)

  • 机器人的完整轨迹是绕边界一圈(周长 p = 2*(width-1) + 2*(height-1)):从 (0,0) 出发沿 East 到
    (w-1,0),North 到 (w-1,h-1),West 到 (0,h-1),South 回到 (0,0)......注意回到 (0,0) 后方向仍是 South ,
    而不是初始的 East(除非中途停下)。
  • 用累计弧长 cur 表示机器人走过的总步数取模 p:cur = (cur + num) % p。
    getPos() 根据 cur 落在边界四段的哪一段直接计算坐标;
    getDir() 根据 cur 所在段返回方向------特别注意:若从未调用过 step(moved == false)返回 "East";
    若调用过且 cur == 0(刚好在 (0,0),处于 South 段末尾),方向为 "South"。
    复杂度:step O(1),getPos/getDir O(1),空间 O(1)。
    解法二
    模拟循环队列取模(对照)
  • 将边界上的所有格子按顺序编码成 0...p-1 的循环序列(从 (0,0) 起按 East/North/West/South 走向),
    每个格子记录 (坐标, 朝向下一个格子的方向)。cur 取模后直接查表。
  • 与解法一等价,仅实现方式不同,用于交叉验证。

代码实现

java 复制代码
public class Solution {

    private int w, h, mx, my, p, cur;
    private boolean moved;

    // 解法一:环形轨迹取模(主提交版本)
    public Solution(int width, int height) {
        w = width;
        h = height;
        mx = width - 1;
        my = height - 1;
        p = 2 * mx + 2 * my;    // 边界总弧长
        cur = 0;
        moved = false;
    }

    public void step(int num) {
        moved = true;
        cur = (cur + num) % p;
    }

    public int[] getPos() {
        int d = cur;
        if (d <= mx) {                    // East 段:y = 0, x = d
            return new int[]{d, 0};
        }
        if (d <= mx + my) {               // North 段:x = mx, y = d - mx
            return new int[]{mx, d - mx};
        }
        if (d <= 2 * mx + my) {           // West 段:y = my, x = mx - (d - mx - my)
            return new int[]{mx - (d - (mx + my)), my};
        }
        return new int[]{0, my - (d - (2 * mx + my))};   // South 段
    }

    public String getDir() {
        int d = cur;
        if (!moved) {
            return "East";
        }
        if (d >= 1 && d <= mx) {
            return "East";
        }
        if (d > mx && d <= mx + my) {
            return "North";
        }
        if (d > mx + my && d <= 2 * mx + my) {
            return "West";
        }
        return "South";   // 含 d == 0(绕回起点)与 South 段
    }

    public static void main(String[] args) {
        // 官方示例:Robot(6,3) 沿边界走
        Solution main = new Solution(6, 3);
        main.step(2);
        System.out.println(java.util.Arrays.toString(main.getPos())); // [2,0]
        System.out.println(main.getDir());                            // East
        main.step(2);
        System.out.println(java.util.Arrays.toString(main.getPos())); // [4,0]
        System.out.println(main.getDir());                            // East

        // 特例:step 后恰好回到起点时方向应为 South
        Solution r2 = new Solution(2, 2);
        r2.step(4);   // 绕一圈回 (0,0),方向 South
        System.out.println(java.util.Arrays.toString(r2.getPos()));   // [0,0]
        System.out.println(r2.getDir());                              // South

        // 未移动时默认 East
        Solution r3 = new Solution(4, 4);
        System.out.println(java.util.Arrays.toString(r3.getPos()));   // [0,0]
        System.out.println(r3.getDir());                              // East
    }
}

复杂度分析

复杂度:step O(1),getPos/getDir O(1),空间 O(1)。


2070. Most Beautiful Item for Each Query(每一个查询的最大美丽值)

题目描述

给你一个二维整数数组 items,其中 items[i] = [pricei, beautyi] 分别表示每一个物品的价格 和美丽值 。

同时给你一个下标从 0 开始的整数数组 queries。对于每个查询 queries[j],你想求出价格小于等于

queries[j] 的物品中,最大的美丽值 是多少。如果不存在符合条件的物品,那么查询的结果为 0。

请你返回一个长度与 queries 相同的数组 answer,其中 answer[j] 是第 j 个查询的答案。

示例 1

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

示例 2

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

示例 3

复制代码
items = [[10,1000]], queries = [5],输出 [0]。

解题思路

解法一(主提交版本)

排序 + 按价格排序查询(主提交版本)

  • 按价格升序排序 items,同时把查询的下标按金额升序排序。
  • 从小到大处理每个查询:把价格 ≤ 当前查询金额的商品逐项并入,维护已见商品的美丽值最大值 mx;
    该查询的答案即 mx。
  • 由于查询按金额升序,指针只前进一次,O(n + m)(排序不计)。
    复杂度:O((n+m) log(n+m)) 排序主导。
    解法二
    前缀最大 + 二分(对照)
  • 排序 items 后,构造去重价格数组与其对应的"该价格及以下的最大美丽值"前缀数组;
    对每个查询二分定位最后一个 ≤ 查询金额的价格,读前缀最大。结果一致用于交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:排序双指针(主提交版本)
    public int[] maximumBeauty(int[][] items, int[] queries) {
        java.util.Arrays.sort(items, (a, b) -> a[0] - b[0]);
        int m = queries.length;
        Integer[] idx = new Integer[m];
        for (int i = 0; i < m; i++) {
            idx[i] = i;
        }
        java.util.Arrays.sort(idx, (i, j) -> queries[i] - queries[j]);
        int[] ans = new int[m];
        int i = 0, mx = 0, n = items.length;
        for (int q : idx) {
            while (i < n && items[i][0] <= queries[q]) {
                mx = Math.max(mx, items[i][1]);
                i++;
            }
            ans[q] = mx;
        }
        return ans;
    }

    // 解法二:前缀最大 + 二分(对照)
    public int[] maximumBeautyPre(int[][] items, int[] queries) {
        java.util.Arrays.sort(items, (a, b) -> a[0] - b[0]);
        java.util.List<Integer> prices = new java.util.ArrayList<>();
        java.util.List<Integer> preBest = new java.util.ArrayList<>();
        int cur = 0;
        for (int[] it : items) {
            if (!prices.isEmpty() && prices.get(prices.size() - 1) == it[0]) {
                // 同价格取最大值
                int last = preBest.size() - 1;
                cur = Math.max(cur, it[1]);
                preBest.set(last, Math.max(preBest.get(last), cur));
                continue;
            }
            cur = Math.max(cur, it[1]);
            prices.add(it[0]);
            preBest.add(cur);
        }
        int[] ans = new int[queries.length];
        for (int q = 0; q < queries.length; q++) {
            int lo = 0, hi = prices.size();
            while (lo < hi) {   // 找最后一个 price <= queries[q]
                int mid = (lo + hi) >> 1;
                if (prices.get(mid) <= queries[q]) {
                    lo = mid + 1;
                } else {
                    hi = mid;
                }
            }
            ans[q] = lo == 0 ? 0 : preBest.get(lo - 1);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int[][] i1 = {{1, 2}, {3, 2}, {2, 4}, {5, 6}, {3, 5}};
        int[] q1 = {1, 2, 3, 4, 5, 6};
        System.out.println(java.util.Arrays.toString(sol.maximumBeauty(i1, q1)));     // [2,4,5,5,6,6]
        System.out.println(java.util.Arrays.toString(sol.maximumBeautyPre(i1, q1)));  // [2,4,5,5,6,6]
        int[][] i2 = {{1, 2}, {1, 2}, {1, 3}, {1, 4}};
        int[] q2 = {1};
        System.out.println(java.util.Arrays.toString(sol.maximumBeauty(i2, q2)));     // [4]
        System.out.println(java.util.Arrays.toString(sol.maximumBeautyPre(i2, q2)));  // [4]
        int[][] i3 = {{10, 1000}};
        int[] q3 = {5};
        System.out.println(java.util.Arrays.toString(sol.maximumBeauty(i3, q3)));     // [0]
        System.out.println(java.util.Arrays.toString(sol.maximumBeautyPre(i3, q3)));  // [0]
    }
}

复杂度分析

复杂度:O((n+m) log(n+m)) 排序主导。


2071. Maximum Number of Tasks You Can Assign(你可以安排的最多任务数目)

题目描述

给你 n 个任务和 m 个工人。每个任务需要一定的力量值才能完成,需要的力量值保存在下标从 0 开始的

整数数组 tasks 中,第 i 个任务需要 tasks[i] 的力量才能完成。每个工人的力量值保存在下标从 0 开始的

整数数组 workers 中,第 j 个工人的力量值为 workers[j]。每个工人只能完成一个 任务,且力量值需要

大于等于 该任务的力量要求值(即 workers[j] >= tasks[i])。

除此以外,你还有 pills 个神奇药丸,可以给一个工人的力量值 增加 strength。你可以决定给哪些工人使用药丸,

但每个工人最多 只能使用一片 药丸。返回最多有多少个任务可以被完成。

示例 1

复制代码
tasks = [3,2,1], workers = [0,3,3], pills = 1, strength = 1,输出 3。解释:给 0 号工人用药丸,
0 号完成任务 2(0+1 ≥ 1)、1 号完成任务 1(3 ≥ 2)、2 号完成任务 0(3 ≥ 3)。

示例 2

复制代码
tasks = [5,4], workers = [0,0,0], pills = 1, strength = 5,输出 1(只能完成 1 个)。

示例 3

复制代码
tasks = [10,15,30], workers = [0,10,10,10,10], pills = 3, strength = 10,输出 2。

解题思路

解法一(主提交版本)

二分答案 + 贪心校验(主提交版本)

  • 排序 tasks 与 workers(都是升序)。二分"能完成的任务数 x"(最多 min(m,n)):
  • check(x):安排力量最大的 x 个工人去完成最简单的 x 个任务(贪心最优)。
    用双端队列维护"在当前工人力量 + strength 范围内能完成的任务"的候选(按力量升序加入队列队尾):
  • 若队首任务 ≤ 工人裸力量:直接派出最弱工人完成最弱任务(队首弹出);
  • 否则必须用药丸:药丸用完则失败;否则给该工人吃药丸,让他完成队列中最难的任务(队尾弹出,最省药丸)。
  • 二分为"最大值 x 满足 check(x)"。
    复杂度:O((n+m) log n)(排序 + 二分 × 校验)。
    解法二
    DFS/组合枚举(对照)
  • 枚举任务子集(按复杂度),用回溯匹配工人与药丸;仅用于极小规模交叉验证。

代码实现

java 复制代码
public class Solution {

    private int[] tasks;
    private int[] workers;
    private int strength;
    private int pills;

    // 解法一:二分 + 贪心(主提交版本)
    public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) {
        java.util.Arrays.sort(tasks);
        java.util.Arrays.sort(workers);
        this.tasks = tasks;
        this.workers = workers;
        this.strength = strength;
        this.pills = pills;
        int lo = 0, hi = Math.min(workers.length, tasks.length);
        while (lo < hi) {
            int mid = (lo + hi + 1) >> 1;
            if (check(mid)) {
                lo = mid;
            } else {
                hi = mid - 1;
            }
        }
        return lo;
    }

    private boolean check(int x) {
        int p = pills;
        java.util.ArrayDeque<Integer> q = new java.util.ArrayDeque<>();
        int i = 0;
        // 用最强的 x 个工人(workers 末尾)完成最弱的 x 个任务(tasks 开头)
        for (int j = workers.length - x; j < workers.length; j++) {
            int w = workers[j];
            while (i < x && tasks[i] <= w + strength) {
                q.offerLast(tasks[i]);
                i++;
            }
            if (q.isEmpty()) {
                return false;
            }
            if (q.peekFirst() <= w) {
                q.pollFirst();             // 不用药丸:完成最弱任务
            } else if (p == 0) {
                return false;
            } else {
                p--;
                q.pollLast();              // 用药丸:完成最强候选任务
            }
        }
        return true;
    }

    // 解法二:回溯枚举匹配(对照)
    public int maxTaskAssignBrute(int[] tasks, int[] workers, int pills, int strength) {
        java.util.Arrays.sort(tasks);
        java.util.Arrays.sort(workers);
        int n = tasks.length, m = workers.length;
        for (int k = Math.min(n, m); k >= 1; k--) {
            boolean[] usedW = new boolean[m];
            if (assign(tasks, workers, pills, strength, k, 0, usedW)) {
                return k;
            }
        }
        return 0;
    }

    // 回溯:给前 k 个任务逐一分配工人(可用可不用药丸),存在方案则 true
    private boolean assign(int[] tasks, int[] workers, int pills, int strength,
                           int k, int idx, boolean[] usedW) {
        if (idx == k) {
            return true;
        }
        int need = tasks[idx];
        for (int j = 0; j < workers.length; j++) {
            if (usedW[j]) {
                continue;
            }
            int w = workers[j];
            if (w >= need) {
                usedW[j] = true;
                if (assign(tasks, workers, pills, strength, k, idx + 1, usedW)) {
                    usedW[j] = false;
                    return true;
                }
                usedW[j] = false;
            } else if (pills > 0 && w + strength >= need) {
                usedW[j] = true;
                if (assign(tasks, workers, pills - 1, strength, k, idx + 1, usedW)) {
                    usedW[j] = false;
                    return true;
                }
                usedW[j] = false;
            }
        }
        return false;
    }

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

复杂度分析

复杂度:O((n+m) log n)(排序 + 二分 × 校验)。


2072. The Winner University(赢得比赛的大学)

题目类型:数据库(SQL)

题目描述

表:NewYork

复制代码
+-------------+------+
| Column Name | Type |
+-------------+------+
| student_id  | int  |
| score       | int  |
+-------------+------+

在 SQL 中,student_id 是这个表的主键。每一行包含纽约大学(New York University)中一名学生一次考试的成绩。

表:California

复制代码
+-------------+------+
| Column Name | Type |
+-------------+------+
| student_id  | int  |
| score       | int  |
+-------------+------+

在 SQL 中,student_id 是这个表的主键。每一行包含加州大学(California University)中一名学生一次考试的成绩。

纽约大学和加州大学之间举行了一场比赛,这场比赛由两所大学中相同数量的学生参加。拥有更多优秀学生 的大学赢得这场比赛;如果两所大学的优秀学生 数量相同,则这场比赛平局。优秀学生 是指在考试中获得 90 分或更高成绩的学生。

返回:

  • "New York University" 若纽约大学赢得这场比赛。
  • "California University" 若加州大学赢得这场比赛。
  • "No Winner" 若这场比赛平局。

示例 1:

复制代码
输入:
NewYork 表:
+------------+-------+
| student_id | score |
+------------+-------+
| 1          | 90    |
| 2          | 87    |
+------------+-------+
California 表:
+------------+-------+
| student_id | score |
+------------+-------+
| 2          | 89    |
| 3          | 88    |
+------------+-------+
输出:
+---------------------+
| winner              |
+---------------------+
| New York University |
+---------------------+
解释:
纽约大学有 1 名优秀学生,加州大学有 0 名优秀学生。

示例 2:

复制代码
输入:
NewYork 表:
+------------+-------+
| student_id | score |
+------------+-------+
| 1          | 89    |
| 2          | 88    |
+------------+-------+
California 表:
+------------+-------+
| student_id | score |
+------------+-------+
| 2          | 90    |
| 3          | 87    |
+------------+-------+
输出:
+-----------------------+
| winner                |
+-----------------------+
| California University |
+-----------------------+
解释:
纽约大学有 0 名优秀学生,加州大学有 1 名优秀学生。

示例 3:

复制代码
输入:
NewYork 表:
+------------+-------+
| student_id | score |
+------------+-------+
| 1          | 89    |
| 2          | 90    |
+------------+-------+
California 表:
+------------+-------+
| student_id | score |
+------------+-------+
| 2          | 89    |
| 3          | 88    |
+------------+-------+
输出:
+-----------+
| winner    |
+-----------+
| No Winner |
+-----------+
解释:
纽约大学和加州大学各有 1 名优秀学生,比赛平局。

解题思路

解法一:双表统计 + CASE 比较

分别统计两校优秀学生数(score >= 90),再用 CASE 比较两数大小:

  • 纽约多 → 输出 "New York University";
  • 加州多 → 输出 "California University";
  • 相等 → 输出 "No Winner"。

SQL 实现要点:COUNT(score >= 90) 在 MySQL 中等于对布尔表达式为真的行计数(等价于 SUM(CASE WHEN score >= 90 THEN 1 ELSE 0 END)),可直接统计优秀学生人数。

解法二(对照):差值与符号判定 ------先分别统计两校优秀人数,再计算差值 ny - ca,按差值的正负/零输出对应学校名,逻辑更直白,便于移植到其他数据库。

代码实现

sql 复制代码
-- 解法一(推荐):分别统计优秀学生数 + CASE 比较
WITH counts AS (
    SELECT
        (SELECT COUNT(score >= 90) FROM NewYork)     AS ny,
        (SELECT COUNT(score >= 90) FROM California)  AS ca
)
SELECT CASE
    WHEN ny > ca THEN 'New York University'
    WHEN ny < ca THEN 'California University'
    ELSE 'No Winner'
END AS winner
FROM counts;
sql 复制代码
-- 解法二(对照):差值符号判定
SELECT CASE
    WHEN ny - ca > 0 THEN 'New York University'
    WHEN ny - ca < 0 THEN 'California University'
    ELSE 'No Winner'
END AS winner
FROM (
    SELECT
        (SELECT SUM(score >= 90) FROM NewYork)    AS ny,
        (SELECT SUM(score >= 90) FROM California) AS ca
) t;

复杂度分析

  • 时间复杂度 :O(n + m),其中 n、m 分别为 NewYork、California 表的行数;各表只需一次扫描统计。
  • 空间复杂度 :O(1),仅存两个统计值,无额外排序或中间表。

2073. Time Needed to Buy Tickets(买票需要的时间)

题目描述

有 n 个人前来排队买票,其中第 0 人站在队伍最前方 ,第 (n - 1) 人站在队伍最后方 。

给你一个下标从 0 开始的整数数组 tickets,其中第 i 人想要购买的票数为 tickets[i]。

每个人买票都需要用掉恰好 1 秒 。一个人一次只能买一张票 ,如果需要购买更多票,他必须走到队尾

重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会离开 队伍。

返回位于位置 k(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。

示例 1

复制代码
tickets = [2,3,2], k = 2,输出 6。解释:第 0 人买 2 张、第 1 人买 3 张、第 2 人买 2 张,
模拟排队:1(人1),1(人2),1(人3),1(人1),1(人2),1(人3)→ 第 3 轮后第 k 人买完 2 张,用时 6。

示例 2

复制代码
tickets = [5,1,1,1], k = 0,输出 8。

示例 3

复制代码
tickets = [84,49,5,24,70,77,87,8], k = 3,输出 154。

解题思路

解法一(主提交版本)

数学 O(n)(主提交版本)

  • 站在 k 前面(i <= k)的人会完整经历 tickets[k] 轮(第 k 人买完后自己离开,后面的轮不再影响),
    付出 min(tickets[i], tickets[k]) 秒;
  • 站在 k 后面的人只在前 tickets[k]-1 轮出现,付出 min(tickets[i], tickets[k]-1) 秒;
  • 累加即答案。
  • 时间复杂度:O(n),一次线性数学计算。
  • 空间复杂度 :O(1)。
    解法二
    队列模拟(对照)
  • 用队列模拟排队:队首每次买一张票(1 秒),减到 0 出队(若队首是 k 人买完即返回时刻)。
  • 复杂度 O(总票数),仅用于小规模交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:数学(主提交版本)
    public int timeRequiredToBuy(int[] tickets, int k) {
        int n = tickets.length;
        int ans = 0;
        int need = tickets[k];
        for (int i = 0; i < n; i++) {
            ans += Math.min(tickets[i], i <= k ? need : need - 1);
        }
        return ans;
    }

    // 解法二:队列模拟(对照)
    public int timeRequiredToBuySim(int[] tickets, int k) {
        java.util.ArrayDeque<int[]> q = new java.util.ArrayDeque<>(); // {原下标, 剩余票数}
        for (int i = 0; i < tickets.length; i++) {
            q.offer(new int[]{i, tickets[i]});
        }
        int t = 0;
        while (!q.isEmpty()) {
            int[] p = q.poll();
            t++;
            p[1]--;
            if (p[1] > 0) {
                q.offer(p);
            } else if (p[0] == k) {
                return t;
            }
        }
        return t;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.timeRequiredToBuy(new int[]{2, 3, 2}, 2));     // 6
        System.out.println(sol.timeRequiredToBuySim(new int[]{2, 3, 2}, 2));  // 6
        System.out.println(sol.timeRequiredToBuy(new int[]{5, 1, 1, 1}, 0));  // 8
        System.out.println(sol.timeRequiredToBuySim(new int[]{5, 1, 1, 1}, 0)); // 8
        System.out.println(sol.timeRequiredToBuy(new int[]{84, 49, 5, 24, 70, 77, 87, 8}, 3)); // 154
        System.out.println(sol.timeRequiredToBuySim(new int[]{84, 49, 5, 24, 70, 77, 87, 8}, 3)); // 154
    }
}

复杂度分析

  • 时间复杂度:O(n),一次线性数学计算。
  • 空间复杂度:O(1)。

2074. Reverse Nodes in Even Length Groups(反转偶数长度组的节点)

题目描述

给你一个链表的头节点 head。链表中的节点按顺序 划分成若干非空 组,这些非空组的长度构成一个自然数序列

(1, 2, 3, 4, ...)。一个组的长度 就是组中分配到的节点数目。即:节点 1 分配给第一组,节点 2 和 3 分配给

第二组,节点 4、5 和 6 分配给第三组,以此类推。注意最后一组的长度可能小于或等于 1 + 倒数第二组的长度。

反转 每个偶数 长度组中的节点,并返回修改后链表的头节点 head。

示例 1

复制代码
head = [5,2,6,3,9,1,7,3,8,4],输出 [5,6,2,3,9,1,4,8,3,7]。

示例 2

复制代码
head = [1,2,3,4,5],输出 [1,3,2,4,5]。解释:第 2 组长度为 2(节点 2、3)反转;第 3 组只有 1 个节点不反转。

示例 3

复制代码
head = [10],输出 [10]。

解题思路

解法一(主提交版本)

分组遍历 + 局部反转(主提交版本)

  • 组长度依次为 1, 2, 3, ...,用 prev 指向每组头节点的前驱(dummy)。
  • 每轮先计算本组实际长度:(1+l)*l/2 <= n 时整组长度为 l,否则组尾是剩余 n - l*(l-1)/2 个节点。
  • 若该组长度为偶数,把整组就地反转(reverse(node, len) 返回新头并接好尾部);再让 prev 前进 len 步。
    复杂度:O(n)(每个节点只被访问常数次)。
    解法二
    提取成数组 + 重建(对照)
  • 把链表值取出存数组,按组号奇偶决定反转并重新接成链表;结果一致,用于交叉验证。

代码实现

java 复制代码
public class Solution {

    public static class ListNode {
        int val;
        ListNode next;
        ListNode() {}
        ListNode(int val) { this.val = val; }
        ListNode(int val, ListNode next) { this.val = val; this.next = next; }
    }

    // 解法一:分组就地反转(主提交版本)
    public ListNode reverseEvenLengthGroups(ListNode head) {
        int n = 0;
        for (ListNode t = head; t != null; t = t.next) {
            n++;
        }
        ListNode dummy = new ListNode(0, head);
        ListNode prev = dummy;
        int l = 1;
        // 处理前 l 组,直到剩余节点不足完整组
        for (; (1 + l) * l / 2 <= n && prev != null; l++) {
            if (l % 2 == 0) {
                ListNode node = prev.next;
                prev.next = reverse(node, l);
            }
            for (int i = 0; i < l && prev != null; i++) {
                prev = prev.next;
            }
        }
        int left = n - l * (l - 1) / 2;   // 最后一组剩余节点数
        if (left > 0 && left % 2 == 0) {
            ListNode node = prev.next;
            prev.next = reverse(node, left);
        }
        return dummy.next;
    }

    // 反转从 head 起前 l 个节点,返回新头并保持尾部拼接
    private ListNode reverse(ListNode head, int l) {
        ListNode prev = null;
        ListNode cur = head;
        ListNode tail = cur;
        int i = 0;
        while (cur != null && i < l) {
            ListNode t = cur.next;
            cur.next = prev;
            prev = cur;
            cur = t;
            i++;
        }
        tail.next = cur;
        return prev;
    }

    // 解法二:数组重建(对照)
    public ListNode reverseEvenLengthGroupsArr(ListNode head) {
        java.util.List<Integer> vals = new java.util.ArrayList<>();
        for (ListNode p = head; p != null; p = p.next) {
            vals.add(p.val);
        }
        int n = vals.size();
        java.util.List<Integer> out = new java.util.ArrayList<>();
        int idx = 0, len = 1;
        while (idx < n) {
            int curLen = Math.min(len, n - idx);
            java.util.List<Integer> seg = new java.util.ArrayList<>();
            for (int t = 0; t < curLen; t++) {
                seg.add(vals.get(idx + t));
            }
            if (curLen % 2 == 0) {
                java.util.Collections.reverse(seg);
            }
            out.addAll(seg);
            idx += curLen;
            len++;
        }
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;
        for (int v : out) {
            tail.next = new ListNode(v);
            tail = tail.next;
        }
        return dummy.next;
    }

    private static void print(ListNode h) {
        StringBuilder sb = new StringBuilder();
        while (h != null) {
            if (sb.length() > 0) {
                sb.append(",");
            }
            sb.append(h.val);
            h = h.next;
        }
        System.out.println(sb.toString());
    }

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

    public static void main(String[] args) {
        Solution sol = new Solution();
        print(sol.reverseEvenLengthGroups(build(new int[]{5, 2, 6, 3, 9, 1, 7, 3, 8, 4}))); // 5,6,2,3,9,1,4,8,3,7
        print(sol.reverseEvenLengthGroupsArr(build(new int[]{5, 2, 6, 3, 9, 1, 7, 3, 8, 4})));
        print(sol.reverseEvenLengthGroups(build(new int[]{1, 2, 3, 4, 5}))); // 1,3,2,4,5
        print(sol.reverseEvenLengthGroupsArr(build(new int[]{1, 2, 3, 4, 5})));
        print(sol.reverseEvenLengthGroups(build(new int[]{10}))); // 10
        print(sol.reverseEvenLengthGroupsArr(build(new int[]{10})));
    }
}

复杂度分析

复杂度:O(n)(每个节点只被访问常数次)。


2075. Decode the Slanted Ciphertext(解码斜向换位密码)

题目描述

字符串 originalText 使用斜向换位密码 ,经由行数固定 为 rows 的矩阵辅助,加密得到一个字符串 encodedText。

originalText 先按从左上到右下的方式(斜对角逐行)放置到矩阵中:

先填第 1 条斜线((0,0)),再填第 2 条斜线((0,1),(1,0)),依次类推,直到到达 originalText 末尾。

所有空单元格用 ' ' 填充。矩阵的列数需满足:用 originalText 填充之后,最右侧列不为空 。

接着按行将字符附加到矩阵中构造 encodedText。

给你编码后的字符串 encodedText 和矩阵的行数 rows,返回源字符串 originalText。

注意:originalText 不 含任何尾随空格 ' ';生成的测试用例满足仅存在一个 可能的 originalText。

示例 1

复制代码
encodedText = "ch   ie   pr", rows = 3,输出 "cipher"。

示例 2

复制代码
encodedText = "iveo    eed   l te   olc", rows = 4,输出 "i love leetcode"。

示例 3

复制代码
encodedText = "coding", rows = 1,输出 "coding"。

解题思路

解法一(主提交版本)

按列逆推 + 去尾随空格(主提交版本)

  • 行数为 rows,列数 cols = len(encodedText) / rows。
  • 斜向填充时,同一斜线上的格子满足 x + y = 常量;反向读取时,从第 0 列开始逐条斜线:
    (0,j), (1,j+1), (2,j+2), ...(x 增、y 增)取出字符连接,即还原 originalText 的放置顺序。
  • 连接后去掉末尾的空格(originalText 无尾随空格)。
    复杂度:O(len(encodedText))。
    解法二
    显式构造矩阵再读斜线(对照)
  • 把 encodedText 按行填入 rows×cols 矩阵,再按斜线顺序逐格读字符,逻辑等价。
  • 用于交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:按列逆推(主提交版本)
    public String decodeCiphertext(String encodedText, int rows) {
        int cols = encodedText.length() / rows;
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < cols; j++) {
            for (int x = 0, y = j; x < rows && y < cols; x++, y++) {
                sb.append(encodedText.charAt(x * cols + y));
            }
        }
        while (sb.length() > 0 && sb.charAt(sb.length() - 1) == ' ') {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    // 解法二:矩阵显式(对照)
    public String decodeCiphertextMatrix(String encodedText, int rows) {
        int cols = encodedText.length() / rows;
        char[][] m = new char[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                m[i][j] = encodedText.charAt(i * cols + j);
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < cols; j++) {
            for (int x = 0, y = j; x < rows && y < cols; x++, y++) {
                sb.append(m[x][y]);
            }
        }
        while (sb.length() > 0 && sb.charAt(sb.length() - 1) == ' ') {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.decodeCiphertext("ch   ie   pr", 3));      // cipher
        System.out.println(sol.decodeCiphertextMatrix("ch   ie   pr", 3)); // cipher
        System.out.println(sol.decodeCiphertext("iveo    eed   l te   olc", 4)); // i love leetcode
        System.out.println(sol.decodeCiphertextMatrix("iveo    eed   l te   olc", 4));
        System.out.println(sol.decodeCiphertext("coding", 1));            // coding
        System.out.println(sol.decodeCiphertextMatrix("coding", 1));
    }
}

复杂度分析

复杂度:O(len(encodedText))。


2076. Process Restricted Friend Requests(处理含限制条件的好友请求)

题目描述

给你一个整数 n,表示网络上的用户数目,用户按从 0 到 n - 1 编号。

给你一个下标从 0 开始的二维整数数组 restrictions,其中 restrictions[i] = [xi, yi] 意味着用户 xi

和用户 yi不能 成为朋友 ,不管是直接 还是通过其他用户间接 。

最初,用户里没有人是其他用户的朋友。给你一个下标从 0 开始的二维整数数组 requests 表示好友请求的列表,

其中 requests[j] = [uj, vj] 是用户 uj 和用户 vj 之间的一条好友请求。

如果 uj 和 vj 可以成为朋友 ,那么好友请求将会成功 。每个好友请求都会按列表中给出的顺序处理。

一旦请求成功,那么对所有未来的好友请求而言,uj 和 vj 将会成为直接朋友 。

返回一个布尔数组 result:如果第 j 个好友请求成功 ,那么 result[j] 就是 true;否则为 false。

注意:如果 uj 和 vj 已经是直接朋友,那么他们之间的请求将仍然成功。

示例 1

复制代码
n = 3, restrictions = [[0,1]], requests = [[0,2],[2,1]],输出 [true,false]。

示例 2

复制代码
n = 3, restrictions = [[0,1]], requests = [[1,2],[0,2]],输出 [true,false]。

示例 3

复制代码
n = 5, restrictions = [[0,1],[1,2],[2,3]], requests = [[0,4],[1,2],[3,1],[3,4]],输出 [true,false,true,false]。

解题思路

解法一(主提交版本)

并查集 + 限制检查(主提交版本)

  • 用并查集维护"已是朋友"的连通块。处理请求 (u, v) 时:
  • 若 find(u) == find(v) 已同块,直接成功;
  • 否则临时假设 合并两块 pu、pv,检查所有限制 (x, y):若合并后 x 与 y 落入同一块
    (即 (find(x)==pu && find(y)==pv) 或相反),则该请求会违反限制,失败;全部不冲突则真正合并、成功。
  • 注意:检查时用的是"若合并"的连通关系,不需要真的先合并再回滚(用根编号比较即可)。
    复杂度:O(q × r × α(n)),q 为请求数,r 为限制数。
    解法二
    图的连通性暴力模拟(对照)
  • 维护邻接矩阵(同一连通块内任意两点视为直接朋友),对每条请求检查合并后是否会"打通"某条限制
    (两块中任一点与任一点的间接路径),相当于 Floyd 增量维护。仅用于小规模交叉验证。

代码实现

java 复制代码
public class Solution {

    private int[] p;

    // 解法一:并查集(主提交版本)
    public boolean[] friendRequests(int n, int[][] restrictions, int[][] requests) {
        p = new int[n];
        for (int i = 0; i < n; i++) {
            p[i] = i;
        }
        int m = requests.length;
        boolean[] ans = new boolean[m];
        for (int i = 0; i < m; i++) {
            int u = requests[i][0], v = requests[i][1];
            int pu = find(u), pv = find(v);
            if (pu == pv) {
                ans[i] = true;      // 已是朋友
                continue;
            }
            boolean ok = true;
            for (int[] r : restrictions) {
                int rx = find(r[0]), ry = find(r[1]);
                // 若合并 pu、pv 后 r 的两个端点被连到同一块,则冲突
                if ((rx == pu && ry == pv) || (rx == pv && ry == pu)) {
                    ok = false;
                    break;
                }
                // 注意:若 rx、ry 一个在 pu 下、一个在 pv 下才冲突;
                // 已在同块(rx == ry)不影响本次合并
            }
            if (ok) {
                ans[i] = true;
                p[pu] = pv;         // 真正合并
            }
        }
        return ans;
    }

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

    // 解法二:增量 Floyd 式连通模拟(对照)
    public boolean[] friendRequestsSim(int n, int[][] restrictions, int[][] requests) {
        boolean[][] g = new boolean[n][n];
        for (int i = 0; i < n; i++) {
            g[i][i] = true;
        }
        int m = requests.length;
        boolean[] ans = new boolean[m];
        for (int i = 0; i < m; i++) {
            int u = requests[i][0], v = requests[i][1];
            if (g[u][v]) {          // 已连通(直接或间接)
                ans[i] = true;
                continue;
            }
            // 模拟合并两个连通块:任意 a∈块(u)、b∈块(v) 相连
            boolean ok = true;
            java.util.List<Integer> bu = new java.util.ArrayList<>();
            java.util.List<Integer> bv = new java.util.ArrayList<>();
            for (int a = 0; a < n; a++) {
                if (g[u][a]) {
                    bu.add(a);
                }
                if (g[v][a]) {
                    bv.add(a);
                }
            }
            for (int a : bu) {
                for (int b : bv) {
                    for (int[] r : restrictions) {
                        if ((r[0] == a && r[1] == b) || (r[0] == b && r[1] == a)) {
                            ok = false;
                            break;
                        }
                    }
                    if (!ok) {
                        break;
                    }
                }
                if (!ok) {
                    break;
                }
            }
            if (ok) {
                ans[i] = true;
                for (int a : bu) {
                    for (int b : bv) {
                        g[a][b] = true;
                        g[b][a] = true;
                    }
                }
            }
        }
        return ans;
    }

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

复杂度分析

复杂度:O(q × r × α(n)),q 为请求数,r 为限制数。


2077. Paths in Maze That Lead to Same Room(殊途同归)

题目描述

迷宫由 n 个从 1 到 n 的房间组成,有些房间由走廊连接。给定一个二维整数数组 corridors,

其中 corridors[i] = [room1i, room2i] 表示有一条走廊连接 room1i 和 room2i,两个方向皆可通行。

迷宫的设计者想知道迷宫有多让人困惑。迷宫的混乱分数 是长度为 3 的不同的环的数量。

例如 1 → 2 → 3 → 1 是长度为 3 的环,但 1 → 2 → 3 → 4 和 1 → 2 → 3 → 2 → 1 不是。

如果在第一个环中访问的一个或多个房间不在 第二个环中,则认为两个环是不同 的。

返回迷宫的混乱分数。

示例 1

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

示例 2

复制代码
n = 4, corridors = [[1,2],[3,4]],输出 0。

示例 3

复制代码
n = 5, corridors = [[1,2],[2,3],[3,1]],输出 1。

解题思路

解法一(主提交版本)

枚举中间点 + 邻接集合(主提交版本)

  • 长度为 3 的环即一个三角形三元组 (a, b, c),三条边两两存在。
  • 对每个节点 c,枚举它的两个邻居 a、b,若 a 与 b 也相邻则构成一个三角形。
    每个三角形会被 3 个顶点各数到一次,故答案 = 总计数 / 3。
  • 用 Set<Integer>[] 存邻接表,contains 判断边是否存在。
    复杂度:O(sum(deg©^2)),n ≤ 100 可接受。
    解法二
    暴力三重枚举(对照)
  • 枚举所有 (i, j, k)(i<j<k)检查三条边是否都在,统计三角形个数。
  • 复杂度 O(n^3),仅用于交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:枚举中间点(主提交版本)
    public int numberOfPaths(int n, int[][] corridors) {
        java.util.Set<Integer>[] g = new java.util.HashSet[n + 1];
        for (int i = 1; i <= n; i++) {
            g[i] = new java.util.HashSet<>();
        }
        for (int[] c : corridors) {
            g[c[0]].add(c[1]);
            g[c[1]].add(c[0]);
        }
        int cnt = 0;
        for (int c = 1; c <= n; c++) {
            java.util.List<Integer> nxt = new java.util.ArrayList<>(g[c]);
            for (int i = 0; i < nxt.size(); i++) {
                for (int j = i + 1; j < nxt.size(); j++) {
                    int a = nxt.get(i), b = nxt.get(j);
                    if (g[b].contains(a)) {   // a 与 b 也相邻 -> 三角形
                        cnt++;
                    }
                }
            }
        }
        return cnt / 3;
    }

    // 解法二:三重枚举(对照)
    public int numberOfPathsBrute(int n, int[][] corridors) {
        boolean[][] adj = new boolean[n + 1][n + 1];
        for (int[] c : corridors) {
            adj[c[0]][c[1]] = true;
            adj[c[1]][c[0]] = true;
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = i + 1; j <= n; j++) {
                for (int k = j + 1; k <= n; k++) {
                    if (adj[i][j] && adj[j][k] && adj[i][k]) {
                        ans++;
                    }
                }
            }
        }
        return ans;
    }

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

复杂度分析

复杂度:O(sum(deg©^2)),n ≤ 100 可接受。


2078. Two Furthest Houses With Different Colors(两栋颜色不同且距离最远的房子)

题目描述

街上有 n 栋房子整齐地排成一列,每栋房子都粉刷上了漂亮的颜色。给你一个下标从 0 开始且长度为 n 的

整数数组 colors,其中 colors[i] 表示第 i 栋房子的颜色。

返回两栋 颜色不同 房子之间的最大 距离。第 i 栋房子和第 j 栋房子之间的距离是 abs(i - j)。

示例 1

复制代码
colors = [1,1,1,6,1,1,1],输出 3。解释:下标 0 与 3 颜色不同(1 vs 6),距离 3 为最大。

示例 2

复制代码
colors = [1,8,3,8,3],输出 4。解释:下标 0 与 4 颜色不同,距离 4 最大。

示例 3

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

解题思路

解法一(主提交版本)

双端贪心(主提交版本)

  • 最优的一对不同色房子,其中一栋必是首栋或尾栋:
  • 若首栋与尾栋颜色不同,答案即 n - 1;
  • 否则首尾同色 c:从左找第一个颜色 ≠ c 的下标 i(与首栋配对得 n-1-i),
    从右找第一个颜色 ≠ c 的下标 j(与尾栋配对得 j),取两者较大。
  • 实际上任何一对不同色房子,其端点之一必有一端是数组极左或极右,以上覆盖全部最优。
  • 时间复杂度:O(n),双端贪心单次扫描。
  • 空间复杂度 :O(1)。
    解法二
    双重循环(对照)
  • 枚举所有 (i, j),若颜色不同更新最大距离。O(n^2),仅用于交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:双端贪心(主提交版本)
    public int maxDistance(int[] colors) {
        int n = colors.length;
        if (colors[0] != colors[n - 1]) {
            return n - 1;
        }
        int c = colors[0];
        int i = 1;
        while (i < n && colors[i] == c) {
            i++;
        }
        int j = n - 2;
        while (j >= 0 && colors[j] == c) {
            j--;
        }
        return Math.max(n - 1 - i, j);
    }

    // 解法二:双重循环(对照)
    public int maxDistanceBrute(int[] colors) {
        int n = colors.length;
        int best = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (colors[i] != colors[j]) {
                    best = Math.max(best, j - i);
                }
            }
        }
        return best;
    }

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

复杂度分析

  • 时间复杂度:O(n),双端贪心单次扫描。
  • 空间复杂度:O(1)。

2079. Watering Plants(给植物浇水)

题目描述

你打算用一个水罐给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1。

其中,第 i 株植物的位置是 x = i。x = -1 处有一条河,你可以在那里重新灌满你的水罐。

每一株植物都需要浇特定量的水。你将会按下面描述的方式完成浇水:

  • 按从左到右的顺序给植物浇水;
  • 在给当前植物浇完水之后,如果你没有足够的水完全浇灌下一株植物,那么你就需要返回河边重新装满水罐;
  • 你不能 提前重新灌满水罐。
    最初,你在河边(x = -1),在 x 轴上每移动一个单位 都需要一步 。
    给你一个下标从 0 开始的整数数组 plants,其中 plants[i] 为第 i 株植物需要的水量,另有一个整数
    capacity 表示水罐的容量,返回浇灌所有植物需要的步数。

示例 1

复制代码
plants = [2,2,3,3], capacity = 5,输出 14。

示例 2

复制代码
plants = [1,1,1,4,2,3], capacity = 4,输出 30。

示例 3

复制代码
plants = [7,7,7,7,7,7,7], capacity = 8,输出 49。

解题思路

解法一(主提交版本)

线性模拟(主提交版本)

  • 从左到右处理每株植物:
  • 若水罐剩余水 ≥ 当前植物需水量:直接浇,步数 +1,水减少;
  • 否则先回河边装满(往返步数 i*2,当前在第 i 株位置,回河 i 步 + 回来 i 步),
    再浇当前植物步数 +1;装满后水 = capacity - plants[i]。
  • 时间复杂度:O(n),单次线性模拟。
  • 空间复杂度 :O(1)。
    解法二
    队列模拟(对照)
  • 逐株模拟,显式用"当前位置"变量累加步数并处理回边灌水,逻辑展开更直观;结果一致用于交叉验证。

代码实现

java 复制代码
public class Solution {

    // 解法一:线性模拟(主提交版本)
    public int wateringPlants(int[] plants, int capacity) {
        int ans = 0;
        int water = capacity;
        for (int i = 0; i < plants.length; i++) {
            if (water >= plants[i]) {
                water -= plants[i];
                ans += 1;
            } else {
                ans += i * 2 + 1;           // 从 i 回河边(i步) + 回 i(i步) + 浇(1步)
                water = capacity - plants[i];
            }
        }
        return ans;
    }

    // 解法二:显式位置模拟(对照)
    public int wateringPlantsSim(int[] plants, int capacity) {
        int pos = -1;           // 当前位置,-1 表示河边
        int water = capacity;
        int steps = 0;
        for (int i = 0; i < plants.length; i++) {
            if (water < plants[i]) {
                steps += Math.abs(-1 - pos);   // 回河边
                water = capacity;
                steps += Math.abs(i - (-1));   // 装满后从河边到 i
            } else {
                steps += Math.abs(i - pos);    // 直接走到 i
            }
            water -= plants[i];
            pos = i;
        }
        return steps;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.wateringPlants(new int[]{2, 2, 3, 3}, 5));    // 14
        System.out.println(sol.wateringPlantsSim(new int[]{2, 2, 3, 3}, 5)); // 14
        System.out.println(sol.wateringPlants(new int[]{1, 1, 1, 4, 2, 3}, 4)); // 30
        System.out.println(sol.wateringPlantsSim(new int[]{1, 1, 1, 4, 2, 3}, 4)); // 30
        System.out.println(sol.wateringPlants(new int[]{7, 7, 7, 7, 7, 7, 7}, 8)); // 49
        System.out.println(sol.wateringPlantsSim(new int[]{7, 7, 7, 7, 7, 7, 7}, 8)); // 49
    }
}

复杂度分析

  • 时间复杂度:O(n),单次线性模拟。
  • 空间复杂度:O(1)。

2080. Range Frequency Queries(区间内查询数字的频率)

题目描述

请你设计一个数据结构,它能求出给定子数组内一个给定值的频率 。子数组中一个值的频率 指的是这个子数组中

这个值的出现次数。

请你实现 RangeFreqQuery 类:

  • RangeFreqQuery(int[] arr) 用下标从 0 开始的整数数组 arr 构造一个类的实例;
  • int query(int left, int right, int value) 返回子数组 arr[left...right] 中 value 的频率 。
    一个子数组 指的是数组中一段连续的元素;arr[left...right] 包含下标 left 和 right 在内。

示例

复制代码
输入 ["RangeFreqQuery","query","query"]
[[[12,33,4,56,22,2,34,33,22,12,34,56]],[1,2,4],[0,11,33]]
输出 [null,1,2]。

解题思路

解法一(主提交版本)

值 -> 下标列表 + 二分(主提交版本)

  • 预处理:Map<Integer, List<Integer>>,把每个值出现的所有下标按升序存入列表。
  • 查询 [left, right] 中 value 的频率:在 value 的下标列表中二分求
    first >= left 的位置 l 与 first > right 的位置 r,答案 = r - l。
    复杂度:预处理 O(n),单次查询 O(log n)。
    解法二
    直接扫描子数组(对照)
  • 每次查询遍历 arr[left..right] 计数。O(区间长度),仅用于交叉验证。

代码实现

java 复制代码
public class Solution {

    private java.util.Map<Integer, java.util.List<Integer>> idx;

    // 解法一:哈希表 + 二分(主提交版本)
    public Solution(int[] arr) {
        idx = new java.util.HashMap<>();
        for (int i = 0; i < arr.length; i++) {
            java.util.List<Integer> list = idx.get(arr[i]);
            if (list == null) {
                list = new java.util.ArrayList<>();
                idx.put(arr[i], list);
            }
            list.add(i);
        }
    }

    public int query(int left, int right, int value) {
        java.util.List<Integer> list = idx.get(value);
        if (list == null) {
            return 0;
        }
        int l = lowerBound(list, left);        // 第一个 >= left
        int r = lowerBound(list, right + 1);   // 第一个 > right
        return r - l;
    }

    private int lowerBound(java.util.List<Integer> list, int target) {
        int lo = 0, hi = list.size();
        while (lo < hi) {
            int mid = (lo + hi) >> 1;
            if (list.get(mid) < target) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }
        return lo;
    }

    // 解法二:扫描(对照)
    private int[] raw;

    public void initBrute(int[] arr) {
        raw = arr;
    }

    public int queryBrute(int left, int right, int value) {
        int c = 0;
        for (int i = left; i <= right; i++) {
            if (raw[i] == value) {
                c++;
            }
        }
        return c;
    }

    public static void main(String[] args) {
        Solution main = new Solution(new int[]{12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56});
        System.out.println(main.query(1, 2, 4));     // 1
        System.out.println(main.query(0, 11, 33));   // 2

        Solution brute = new Solution(new int[]{});
        brute.initBrute(new int[]{12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56});
        System.out.println(brute.queryBrute(1, 2, 4));   // 1
        System.out.println(brute.queryBrute(0, 11, 33)); // 2
        System.out.println(main.query(2, 9, 22));        // 2
        System.out.println(brute.queryBrute(2, 9, 22));  // 2
    }
}

复杂度分析

复杂度:预处理 O(n),单次查询 O(log n)。

相关推荐
特立独行的猫A1 小时前
13MB 单文件、一条命令变服务:把仓颉写的 cjh 内嵌进你的应用,当它的 AI 大脑
java·后端·python
Brookty1 小时前
Java博客项目实战|工程环境与构建:工程目录、PowerShell命令调用与JDK/IDEA/Maven工作机制
java·项目实战
养军博客2 小时前
Spring Boot 3.x + Spring Security 6.x 整合 JWT 实战:jjwt 与 java-jwt 两种工具库详解
java·spring boot·springsecurity
TeamDev2 小时前
使用 JxBrowser 在桌面应用中集成 Spring
java·前端·后端·spring·混合应用·jxbrowser·浏览器控件
J.YA2 小时前
String字符串[面试题]
java·开发语言
金銀銅鐵11 小时前
为什么枚举类中可以定义抽象方法
java·后端·ai编程
文丑颜不良啊13 小时前
LeetCode1941~1960题目解析
java·leetcode
txxzjmzlh13 小时前
SpringBoot 统⼀功能处理
java·spring boot·后端
nice_lcj52015 小时前
Java Lambda 表达式入门:怎么写、怎么用、常用方法有哪些
java·开发语言·后端·面试