LeetCode1181~1200题目解析

LeetCode 1181-1200. 题解合集

文章目录


1181. Before and After Puzzle(前后拼接 🔒)

题目描述

给你一个「短语」列表 phrases,请你帮忙按规则生成拼接后的「新短语」列表。

「前后拼接」是合并两个「短语」形成「新短语」的方法:第一个短语的最后一个单词和

第二个短语的第一个单词必须相同。

返回每两个短语 phrases`i` 和 phrases`j`(i != j)进行「前后拼接」得到的新短语;

两个短语拼接时的顺序也很重要(需要考虑两个方向)。同一个短语可以多次参与拼接,

但「新短语」不能再参与拼接。按字典序排列并返回,字符串不重复。

示例 1

复制代码
phrases = ["writing code","code rocks"] → ["writing code rocks"]

示例 2

复制代码
phrases = ["mission statement", "a quick bite to eat", "a chip off the old block",
"chocolate bar", "mission impossible", "a man on a mission", "block party",
"eat my words", "bar of soap"]
→ ["a chip off the old block party", "a man on a mission impossible",
"a man on a mission statement", "a quick bite to eat my words", "chocolate bar of soap"]

示例 3

复制代码
phrases = ["a","b","a"] → ["a"]

示例 4

复制代码
phrases = ["ab ba","ba ab","ab ba"] → ["ab ba ab","ba ab ba"]

约束:

  • 1 <= phrases.length <= 100;1 <= phrases`i`.length <= 100。

解题思路

解法一:单词分割 + 匹配拼接

  1. 对每个短语计算首单词 firsti 和尾单词 lasti(按空格拆分);
  2. 对任意 i != j,若 lasti 等于 firstj,则拼接 i + j:s = phrasesi 去掉最后一个单词
    再加上 phrasesj(或直接 phrasesi + phrasesj 去掉重复单词);
  3. 用 Set 去重,最后 TreeSet 排序(或先排序再收集)。
    复杂度:时间 O(n^2 * L),空间 O(n^2)。
    解法二:Map 首字索引优化
  4. 预处理 Map<firstWord, List>;
  5. 对每个 i,只枚举 lasti 对应的所有 j 直接拼接,避免全量扫描;
  6. 去重 + 排序同解法一。
    复杂度:时间 O(n * k)(k 为首单词相同的短语数),空间 O(n)。

代码实现

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

public class Solution {

    private String lastWord(String p) {
        int idx = p.lastIndexOf(' ');
        return idx < 0 ? p : p.substring(idx + 1);
    }

    private String firstWord(String p) {
        int idx = p.indexOf(' ');
        return idx < 0 ? p : p.substring(0, idx);
    }

    public List<String> beforeAndAfterPuzzles(String[] phrases) {
        int n = phrases.length;
        String[] first = new String[n];
        String[] last = new String[n];
        for (int i = 0; i < n; i++) {
            first[i] = firstWord(phrases[i]);
            last[i] = lastWord(phrases[i]);
        }
        Set<String> res = new HashSet<>();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == j) {
                    continue;
                }
                if (last[i].equals(first[j])) {
                    // 拼接:phrases[i] + phrases[j] 去掉中间的重复单词
                    String joined = phrases[i] + phrases[j].substring(first[j].length());
                    res.add(joined);
                }
            }
        }
        List<String> list = new ArrayList<>(res);
        list.sort(null); // 字典序
        return list;
    }

    public List<String> beforeAndAfterPuzzlesMap(String[] phrases) {
        int n = phrases.length;
        String[] first = new String[n];
        String[] last = new String[n];
        java.util.Map<String, List<Integer>> byFirst = new java.util.HashMap<>();
        for (int i = 0; i < n; i++) {
            first[i] = firstWord(phrases[i]);
            last[i] = lastWord(phrases[i]);
            byFirst.computeIfAbsent(first[i], k -> new ArrayList<>()).add(i);
        }
        Set<String> res = new HashSet<>();
        for (int i = 0; i < n; i++) {
            List<Integer> js = byFirst.get(last[i]);
            if (js == null) {
                continue;
            }
            for (int j : js) {
                if (i == j) {
                    continue;
                }
                res.add(phrases[i] + phrases[j].substring(first[j].length()));
            }
        }
        List<String> list = new ArrayList<>(res);
        list.sort(null);
        return list;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.beforeAndAfterPuzzles(new String[]{"writing code", "code rocks"})
                + "(期望 [writing code rocks])");
        System.out.println("示例1 解法二: " + s.beforeAndAfterPuzzlesMap(new String[]{"writing code", "code rocks"})
                + "(期望 [writing code rocks])");

        String[] p2 = {"mission statement", "a quick bite to eat", "a chip off the old block",
                "chocolate bar", "mission impossible", "a man on a mission",
                "block party", "eat my words", "bar of soap"};
        System.out.println("示例2 解法一: " + s.beforeAndAfterPuzzles(p2));
        System.out.println("示例2 解法二: " + s.beforeAndAfterPuzzlesMap(p2));
        System.out.println("期望: [a chip off the old block party, a man on a mission impossible, "
                + "a man on a mission statement, a quick bite to eat my words, chocolate bar of soap]");

        System.out.println("示例3 解法一: " + s.beforeAndAfterPuzzles(new String[]{"a", "b", "a"}) + "(期望 [a])");
        System.out.println("示例3 解法二: " + s.beforeAndAfterPuzzlesMap(new String[]{"a", "b", "a"}) + "(期望 [a])");

        System.out.println("示例4 解法一: " + s.beforeAndAfterPuzzles(new String[]{"ab ba", "ba ab", "ab ba"})
                + "(期望 [ab ba ab, ba ab ba])");
        System.out.println("示例4 解法二: " + s.beforeAndAfterPuzzlesMap(new String[]{"ab ba", "ba ab", "ab ba"})
                + "(期望 [ab ba ab, ba ab ba])");
    }
}

复杂度分析

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

复杂度:时间 O(n * k)(k 为首单词相同的短语数),空间 O(n)。


1182. Shortest Distance to Target Color(与目标颜色间的最短距离 🔒)

题目描述

给你一个数组 colors,里面有 1、2、3 三种颜色。

我们需要在 colors 上进行一些查询操作 queries,其中每个待查项都由两个整数 i 和 c 组成。

查找从索引 i 到具有目标颜色 c 的元素之间的最短距离。如果不存在解决方案,请返回 -1。

示例 1

复制代码
colors = [1,1,2,1,3,2,2,3,3], queries = [[1,3],[2,2],[6,1]] → [3,0,3]

示例 2

复制代码
colors = [1,2], queries = [[0,3]] → [-1]

约束:

  • 1 <= colors.length <= 510^4;1 <= colors[i] <= 3;1 <= queries.length <= 510^4。

解题思路

解法一:三遍动态规划(左右交替扫描)

  1. 对三种颜色分别预处理离每个位置最近的该色下标距离:
    前向扫描(从左到右)维护最近下标,后向扫描(从右到左)维护最近下标,取 min;
  2. 对每种颜色 c 得一数组 distci,查询 O(1) 返回 distci(初始化为极大值,最终仍为
    极大值表示不存在);
  3. 三遍 × 两方向 ≈ 6 次扫描,O(n) 时间。
    复杂度:时间 O(n * 3 + m),空间 O(n * 3)。
    解法二:位置列表 + 二分查找
  4. 对三种颜色分别记录出现位置列表 pos1, pos2, pos3(天然升序);
  5. 查询 (i, c):在 posc 中二分插入位置,取左右最近的两个位置计算距离最小值;
  6. 若 posc 为空则返回 -1。
    复杂度:时间 O(n + m log n),空间 O(n)。

代码实现

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

public class Solution {

    public List<Integer> shortestDistanceColor(int[] colors, int[][] queries) {
        int n = colors.length;
        int[][] dist = new int[4][n];
        int INF = Integer.MAX_VALUE / 2;
        for (int[] d : dist) {
            Arrays.fill(d, INF);
        }
        // 每种颜色独立计算
        for (int c = 1; c <= 3; c++) {
            // 前向:最近的左侧
            int last = -1;
            for (int i = 0; i < n; i++) {
                if (colors[i] == c) {
                    last = i;
                }
                if (last >= 0) {
                    dist[c][i] = i - last;
                }
            }
            // 后向:最近的右侧
            last = -1;
            for (int i = n - 1; i >= 0; i--) {
                if (colors[i] == c) {
                    last = i;
                }
                if (last >= 0) {
                    dist[c][i] = Math.min(dist[c][i], last - i);
                }
            }
        }
        List<Integer> res = new ArrayList<>();
        for (int[] q : queries) {
            int d = dist[q[1]][q[0]];
            res.add(d >= INF ? -1 : d);
        }
        return res;
    }

    public List<Integer> shortestDistanceColorBinary(int[] colors, int[][] queries) {
        List<List<Integer>> pos = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            pos.add(new ArrayList<Integer>());
        }
        for (int i = 0; i < colors.length; i++) {
            pos.get(colors[i]).add(i);
        }
        List<Integer> res = new ArrayList<>();
        for (int[] q : queries) {
            int i = q[0];
            int c = q[1];
            List<Integer> list = pos.get(c);
            if (list.isEmpty()) {
                res.add(-1);
                continue;
            }
            // 二分插入位置
            int lo = 0, hi = list.size();
            while (lo < hi) {
                int mid = (lo + hi) >>> 1;
                if (list.get(mid) < i) {
                    lo = mid + 1;
                } else {
                    hi = mid;
                }
            }
            int best = Integer.MAX_VALUE;
            if (lo < list.size()) {
                best = Math.min(best, Math.abs(list.get(lo) - i));
            }
            if (lo > 0) {
                best = Math.min(best, Math.abs(list.get(lo - 1) - i));
            }
            res.add(best);
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.shortestDistanceColor(new int[]{1,1,2,1,3,2,2,3,3},
                new int[][]{{1,3},{2,2},{6,1}}) + "(期望 [3,0,3])");
        System.out.println("示例1 解法二: " + s.shortestDistanceColorBinary(new int[]{1,1,2,1,3,2,2,3,3},
                new int[][]{{1,3},{2,2},{6,1}}) + "(期望 [3,0,3])");

        System.out.println("示例2 解法一: " + s.shortestDistanceColor(new int[]{1,2},
                new int[][]{{0,3}}) + "(期望 [-1])");
        System.out.println("示例2 解法二: " + s.shortestDistanceColorBinary(new int[]{1,2},
                new int[][]{{0,3}}) + "(期望 [-1])");

        System.out.println("自身颜色 解法一: " + s.shortestDistanceColor(new int[]{1},
                new int[][]{{0,1}}) + "(期望 [0])");
        System.out.println("自身颜色 解法二: " + s.shortestDistanceColorBinary(new int[]{1},
                new int[][]{{0,1}}) + "(期望 [0])");
    }
}

复杂度分析

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

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


1183. Maximum Number of Ones(矩阵中 1 的最大数量 🔒)

题目描述

现在有一个尺寸为 width * height 的矩阵 M,矩阵中的每个单元格的值不是 0 就是 1。

矩阵 M 中每个大小为 sideLength * sideLength 的正方形子阵中,1 的数量不得超过 maxOnes。

请设计一个算法,计算矩阵中最多可以有多少个 1。

示例 1

复制代码
width = 3, height = 3, sideLength = 2, maxOnes = 1 → 4
放置:1 0 1 / 0 0 0 / 1 0 1

示例 2

复制代码
width = 3, height = 3, sideLength = 2, maxOnes = 2 → 6
放置:1 0 1 / 1 0 1 / 1 0 1

约束:

  • 1 <= width, height <= 100;1 <= sideLength <= width, height;
  • 0 <= maxOnes <= sideLength * sideLength。

解题思路

解法一:等价类(同余类)计数贪心

  1. 关键观察:任意两个位置 (i,j) 与 (i+sideLength, j)(或横向同余)出现在完全相同的一组
    正方形子阵中,因此它们属于同一「等价类」。我们要做的只是决定每个等价类放多少个 1;
  2. 完全类似地,(i, j) 的等价类由 (i % sideLength, j % sideLength) 唯一确定;
    该等价类中的位置数为 cnt = (可放 i 的行数) * (可放 j 的列数),
    其中可放行数 = (width - ® + sideLength - 1)/sideLength(0 基),列数同理;
  3. 每个 size*size 子阵恰好从每个等价类取恰好一个位置,约束等价于「每个等价类的 1 数
    不超过 maxOnes」。为使 1 总数最大,把每个等价类计数 cnt 降序排序,取前 maxOnes 个
    等价类全部放满 1,其余不放(或部分放不满,但按容量贪心最优)。
    复杂度:时间 O(sideLength^2 log(sideLength^2)),空间 O(sideLength^2)。
    解法二:暴力验证式构造器,仅演示思路正确性
  4. 按等价类决定放置后,构造矩阵并核验每一个 sideLengthsideLength 子阵 1 数不超过 maxOnes,
    与原约束一致(用于在 main 中做交叉验证逻辑演示)。
    复杂度:时间 O(width
    heightsideLength^2),空间 O(widthheight)。仅小规模演示。

代码实现

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

public class Solution {

    public int maximumNumberOfOnes(int width, int height, int sideLength, int maxOnes) {
        List<Integer> counts = new ArrayList<>();
        for (int r = 0; r < sideLength; r++) {
            for (int c = 0; c < sideLength; c++) {
                // 与 (r, c) 属于同一等价类的单元格个数
                int rows = (width - 1 - r) / sideLength + 1;   // 行方向同余 r
                int cols = (height - 1 - c) / sideLength + 1;  // 列方向同余 c
                counts.add(rows * cols);
            }
        }
        Collections.sort(counts, Collections.reverseOrder());
        int total = 0;
        for (int i = 0; i < maxOnes && i < counts.size(); i++) {
            total += counts.get(i);
        }
        return total;
    }

    public int maximumNumberOfOnesGrid(int width, int height, int sideLength, int maxOnes) {
        int[] cap = new int[sideLength * sideLength];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                cap[(i % sideLength) * sideLength + (j % sideLength)]++;
            }
        }
        Arrays.sort(cap);
        int total = 0;
        for (int i = cap.length - 1; i >= 0 && i >= cap.length - maxOnes; i--) {
            total += cap[i];
        }
        return total;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.maximumNumberOfOnes(3, 3, 2, 1) + "(期望 4)");
        System.out.println("示例1 解法二: " + s.maximumNumberOfOnesGrid(3, 3, 2, 1) + "(期望 4)");

        System.out.println("示例2 解法一: " + s.maximumNumberOfOnes(3, 3, 2, 2) + "(期望 6)");
        System.out.println("示例2 解法二: " + s.maximumNumberOfOnesGrid(3, 3, 2, 2) + "(期望 6)");

        System.out.println("满放 解法一: " + s.maximumNumberOfOnes(3, 3, 1, 1) + "(期望 9)");
        System.out.println("满放 解法二: " + s.maximumNumberOfOnesGrid(3, 3, 1, 1) + "(期望 9)");

        System.out.println("零限制 解法一: " + s.maximumNumberOfOnes(2, 2, 2, 0) + "(期望 0)");
        System.out.println("零限制 解法二: " + s.maximumNumberOfOnesGrid(2, 2, 2, 0) + "(期望 0)");
    }
}

复杂度分析

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

复杂度:时间 O(widthheightsideLength^2),空间 O(width*height)。仅小规模演示。


1184. Distance Between Bus Stops(公交站间的距离)

题目描述

环形公交路线上有 n 个站,按次序从 0 到 n - 1 进行编号。

我们已知每一对相邻公交站之间的距离,distance`i` 表示编号为 i 的车站和编号为 (i + 1) % n

的车站之间的距离。环线上的公交车都可以按顺时针和逆时针的方向行驶。

返回乘客从出发点 start 到目的地 destination 之间的最短距离。

示例 1

复制代码
distance = [1,2,3,4], start = 0, destination = 1 → 1

示例 2

复制代码
distance = [1,2,3,4], start = 0, destination = 2 → 3

示例 3

复制代码
distance = [1,2,3,4], start = 0, destination = 3 → 4

约束:

  • 1 <= n <= 10^4;0 <= distance`i` <= 10^4;0 <= start, destination < n。

解题思路

解法一:两方向距离取最小

  1. 保证 start <= destination(否则交换),顺时针距离 clockwise = sum(distancestart...dest-1);
  2. 总环长 total = sum(distance),逆时针距离 = total - clockwise;
  3. 返回 min(clockwise, total - clockwise)。
    复杂度:时间 O(n),空间 O(1)。
    解法二:前缀和数组
  4. 预处理前缀和 pre,则顺时针距离 = predest - prestart;
  5. 逆时针距离 = total - 顺时针;取最小值。
    复杂度:时间 O(n),空间 O(n)(每条查询 O(1),适合多次查询)。

代码实现

java 复制代码
public class Solution {

    public int distanceBetweenBusStops(int[] distance, int start, int destination) {
        if (start > destination) {
            int t = start;
            start = destination;
            destination = t;
        }
        int total = 0;
        for (int d : distance) {
            total += d;
        }
        int clockwise = 0;
        for (int i = start; i < destination; i++) {
            clockwise += distance[i];
        }
        return Math.min(clockwise, total - clockwise);
    }

    public int distanceBetweenBusStopsPrefix(int[] distance, int start, int destination) {
        int n = distance.length;
        int[] pre = new int[n + 1];
        for (int i = 0; i < n; i++) {
            pre[i + 1] = pre[i] + distance[i];
        }
        int total = pre[n];
        int clockwise;
        if (start <= destination) {
            clockwise = pre[destination] - pre[start];
        } else {
            clockwise = pre[n] - pre[start] + pre[destination]; // 绕一圈
        }
        return Math.min(clockwise, total - clockwise);
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 0, 1) + "(期望 1)");
        System.out.println("示例1 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 0, 1) + "(期望 1)");

        System.out.println("示例2 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 0, 2) + "(期望 3)");
        System.out.println("示例2 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 0, 2) + "(期望 3)");

        System.out.println("示例3 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 0, 3) + "(期望 4)");
        System.out.println("示例3 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 0, 3) + "(期望 4)");

        System.out.println("反向点 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 3, 0) + "(期望 4)");
        System.out.println("反向点 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 3, 0) + "(期望 4)");
    }
}

复杂度分析

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

复杂度:时间 O(n),空间 O(n)(每条查询 O(1),适合多次查询)。


1185. Day of the Week(一周中的第几天)

题目描述

给你一个日期,请你设计一个算法来判断它是对应一周中的哪一天。

输入为三个整数:day、month 和 year,分别表示日、月、年。

返回的结果必须是 {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} 之一。

注意:1971 年 1 月 1 日是星期五。

示例 1

复制代码
day = 31, month = 8, year = 2019 → "Saturday"

示例 2

复制代码
day = 18, month = 7, year = 1999 → "Sunday"

示例 3

复制代码
day = 15, month = 8, year = 1993 → "Sunday"

约束:

  • 给定日期一定在 1971~2100 年之间的有效日期。

解题思路

解法一:基准日期天数差计算

  1. 已知 1971-01-01 是星期五,因此先把「1971-01-01 距离该日期的天数」算出来;
  2. 计算天数差 = 该日期相对 1971-01-01 的天数 N;(N + 4) % 7 即为星期几(0=Sunday,1=Monday...)
    因为 1971-01-01 是 Friday,Friday 在数组 index = 5,所以 (N + 4) % 7 即 (N + 5 - 1) % 7;
  3. 处理闰年判断 + 逐月累加天数,注意截至当天完整经过的月份。
    复杂度:时间 O(年份跨度 + 月份数) ≈ O(1),空间 O(1)。
    解法二:GregorianCalendar 标准库计算
  4. 用 Calendar/GregorianCalendar 设置日期;
  5. 取出 DAY_OF_WEEK(1=Sunday, 2=Monday...),转到字符串数组下标(day - 1)返回。
    复杂度:时间 O(1),空间 O(1)。

代码实现

java 复制代码
import java.util.Calendar;
import java.util.GregorianCalendar;

public class Solution {

    private static final String[] DAYS = {"Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday"};
    private static final int[] MON = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    private boolean isLeap(int y) {
        return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
    }

    public String dayOfTheWeek(int day, int month, int year) {
        // 计算 1971-01-01 到 (year,month,day) 的天数差
        int days = 0;
        for (int y = 1971; y < year; y++) {
            days += isLeap(y) ? 366 : 365;
        }
        for (int m = 1; m < month; m++) {
            days += MON[m - 1];
            if (m == 2 && isLeap(year)) {
                days++; // 闰年二月多一天
            }
        }
        days += day - 1; // 1971-01-01 当天为 0
        // 1971-01-01 是 Friday(下标5):days + 5 对 7 取模
        return DAYS[(days + 5) % 7];
    }

    public String dayOfTheWeekCalendar(int day, int month, int year) {
        Calendar cal = new GregorianCalendar(year, month - 1, day);
        return DAYS[cal.get(Calendar.DAY_OF_WEEK) - 1];
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.dayOfTheWeek(31, 8, 2019) + "(期望 Saturday)");
        System.out.println("示例1 解法二: " + s.dayOfTheWeekCalendar(31, 8, 2019) + "(期望 Saturday)");

        System.out.println("示例2 解法一: " + s.dayOfTheWeek(18, 7, 1999) + "(期望 Sunday)");
        System.out.println("示例2 解法二: " + s.dayOfTheWeekCalendar(18, 7, 1999) + "(期望 Sunday)");

        System.out.println("示例3 解法一: " + s.dayOfTheWeek(15, 8, 1993) + "(期望 Sunday)");
        System.out.println("示例3 解法二: " + s.dayOfTheWeekCalendar(15, 8, 1993) + "(期望 Sunday)");

        System.out.println("基准日 解法一: " + s.dayOfTheWeek(1, 1, 1971) + "(期望 Friday)");
        System.out.println("基准日 解法二: " + s.dayOfTheWeekCalendar(1, 1, 1971) + "(期望 Friday)");

        System.out.println("闰日 解法一: " + s.dayOfTheWeek(29, 2, 2000) + "(期望 Tuesday)");
        System.out.println("闰日 解法二: " + s.dayOfTheWeekCalendar(29, 2, 2000) + "(期望 Tuesday)");
    }
}

复杂度分析

复杂度:时间 O(年份跨度 + 月份数) ≈ O(1),空间 O(1)。

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


1186. Maximum Subarray Sum with One Deletion(删除一次得到子数组最大和)

题目描述

给你一个整数数组,返回它的某个非空子数组(连续元素)在执行一次可选的删除操作后,

所能得到的最大元素总和。可以决定要不要从中删除一个元素(只能删一次),

删除后子数组中至少应当有一个元素。

示例 1

复制代码
arr = [1,-2,0,3] → 4(选 [1,-2,0,3],删掉 -2 得 [1,0,3])

示例 2

复制代码
arr = [1,-2,-2,3] → 3(直接选 [3])

示例 3

复制代码
arr = [-1,-1,-1,-1] → -1(不能删成空)

约束:

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

解题思路

解法一:前后缀动态规划

  1. 维护两个 DP 数组:
  • lefti = 以 i 结尾且不删除元素的子数组最大和(经典 Kadane);
  • righti = 从 i 开始且不删除元素的子数组最大和(反向 Kadane);
  1. 不删除的情况:ans = max(lefti);
    删除 arrk 的情况:ans = max(leftk-1 + rightk+1),即删掉 k 后把左右两段拼起来;
  2. 遍历所有 k(1...n-2)即可覆盖「删一个」的最优。
    复杂度:时间 O(n),空间 O(n)。
    解法二:带状态的 Kadane(dp0/dp1 滚动)
  3. dp0 = 当前未删除元素的最大子数组和;dp1 = 当前已删除过 1 个元素的最大子数组和;
  4. 每步:dp1 = max(dp1 + arri, dp0)(dp0 表示删除 arri 这一格,或之前已删继续扩展);
    dp0 = max(dp0 + arri, arri);ans = max(ans, dp0, dp1);
  5. 边界:dp1 初始为 -inf,代表还未删除任何元素。
    复杂度:时间 O(n),空间 O(1)。

代码实现

java 复制代码
public class Solution {

    public int maximumSum(int[] arr) {
        int n = arr.length;
        int[] left = new int[n];  // 以 i 结尾
        int[] right = new int[n]; // 从 i 开始
        left[0] = arr[0];
        int best = arr[0];
        for (int i = 1; i < n; i++) {
            left[i] = Math.max(left[i - 1] + arr[i], arr[i]);
            best = Math.max(best, left[i]);
        }
        right[n - 1] = arr[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            right[i] = Math.max(right[i + 1] + arr[i], arr[i]);
        }
        // 删除第 k 个元素(不删边界之外),左右拼接
        for (int k = 1; k < n - 1; k++) {
            best = Math.max(best, left[k - 1] + right[k + 1]);
        }
        return best;
    }

    public int maximumSumState(int[] arr) {
        int n = arr.length;
        int dp0 = arr[0];      // 未删除任何元素
        int dp1 = Integer.MIN_VALUE / 2; // 已删除一个元素
        int best = arr[0];
        for (int i = 1; i < n; i++) {
            // 删除 arr[i]:取之前的未删除段 dp0;继续扩展已删除段:dp1 + arr[i]
            int newDp1 = Math.max(dp0, dp1 + arr[i]);
            // 未删除:经典 Kadane
            int newDp0 = Math.max(dp0 + arr[i], arr[i]);
            dp0 = newDp0;
            dp1 = newDp1;
            best = Math.max(best, Math.max(dp0, dp1));
        }
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.maximumSum(new int[]{1,-2,0,3}) + "(期望 4)");
        System.out.println("示例1 解法二: " + s.maximumSumState(new int[]{1,-2,0,3}) + "(期望 4)");

        System.out.println("示例2 解法一: " + s.maximumSum(new int[]{1,-2,-2,3}) + "(期望 3)");
        System.out.println("示例2 解法二: " + s.maximumSumState(new int[]{1,-2,-2,3}) + "(期望 3)");

        System.out.println("示例3 解法一: " + s.maximumSum(new int[]{-1,-1,-1,-1}) + "(期望 -1)");
        System.out.println("示例3 解法二: " + s.maximumSumState(new int[]{-1,-1,-1,-1}) + "(期望 -1)");

        System.out.println("全正 解法一: " + s.maximumSum(new int[]{1,2,3}) + "(期望 6)");
        System.out.println("全正 解法二: " + s.maximumSumState(new int[]{1,2,3}) + "(期望 6)");

        System.out.println("首尾负 解法一: " + s.maximumSum(new int[]{8,-1,6,-7,-4,5,-4,7,-6}) + "(期望 17)");
        System.out.println("首尾负 解法二: " + s.maximumSumState(new int[]{8,-1,6,-7,-4,5,-4,7,-6}) + "(期望 17)");
    }
}

复杂度分析

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

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


1187. Make Array Strictly Increasing(使数组严格递增)

题目描述

给你两个整数数组 arr1 和 arr2,返回使 arr1 严格递增所需要的最小「操作」数(可能为 0)。

每一步「操作」中,你可以分别从 arr1 和 arr2 中各选出一个索引 i 和 j,然后进行赋值

arr1`i` = arr2`j`。如果无法让 arr1 严格递增,请返回 -1。

示例 1

复制代码
arr1 = [1,5,3,6,7], arr2 = [1,3,2,4] → 1

示例 2

复制代码
arr1 = [1,5,3,6,7], arr2 = [4,3,1] → 2

示例 3

复制代码
arr1 = [1,5,3,6,7], arr2 = [1,6,3,3] → -1

约束:

  • 1 <= arr1.length, arr2.length <= 2000;0 <= arr1`i`, arr2`i` <= 10^9。

解题思路

解法一:dpi 保持原值 + 二分替换段

  1. 先把 arr2 去重排序得到候选数组 B(升序、无重复);
  2. 设 dpi = 使 arr10...i 严格递增、且 arr1i「保持原值」所需的最小替换数(INF 表示不可行);
    dp0 = 0;
  3. 对每个 i:
  • 不替换任何元素:若 arr1i-1 < arr1i,dpi = min(dpi, dpi-1);
  • 替换一段「尾部」:把 arr1j+1...i-1 共 need = i-1-j 个元素连续替换成 B 中
    某个严格递增连续段(B 已排序,天然严格递增),要求段内最小值 > arr1j 且最大值 < arr1i。
    贪心选「紧贴 arr1i 的最大连续段」成功率最高:取 B 中前 leftLimit 个 < arr1i 的值,
    从后往前取 need 个,即 BleftLimit-need ... leftLimit-1;若 BleftLimit-need > arr1j 则可转移
    dpi = min(dpi, dpj + need);
  1. 最后再考虑「把包括最后一个在内的尾部整段替换」的情况,取整体最小值。
    复杂度:时间 O(n^2),空间 O(n)。
    解法二:记忆化搜索 dfs(i, prev)
  2. 递归函数 dfs(i, prev):处理到下标 i、前一个元素(已确定的)值为 prev 时的最小替换数;
  3. 不替换:若 arr1i > prev,dfs(i+1, arr1i);
    替换:二分找 B 中第一个 > prev 的值 blo,dfs(i+1, blo) + 1;
  4. 取两者最小值,用 (i, prev) 作记忆化键。
    复杂度:时间 O(n log n),空间 O(n)。

代码实现

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

public class Solution {

    public int makeArrayIncreasing(int[] arr1, int[] arr2) {
        TreeSet<Integer> set = new TreeSet<>();
        for (int v : arr2) {
            set.add(v);
        }
        int[] b = new int[set.size()];
        int idx = 0;
        for (int v : set) {
            b[idx++] = v;
        }
        int m = b.length;
        int n = arr1.length;
        int INF = Integer.MAX_VALUE / 2;
        int[] dp = new int[n];
        Arrays.fill(dp, INF);
        dp[0] = 0;

        for (int i = 1; i < n; i++) {
            // 不替换 arr1[i],且前一步也不替换
            if (dp[i - 1] < INF && arr1[i - 1] < arr1[i]) {
                dp[i] = Math.min(dp[i], dp[i - 1]);
            }
            // 替换 arr1[j+1..i-1] 整段(j 处保持原值,j 可为 -1 表示整段从头替换)
            // 候选必须严格 < arr1[i]:用 lowerBound(第一个 >= arr1[i] 的下标)
            int leftLimit = lowerBound(b, arr1[i]);
            for (int j = i - 1; j >= -1; j--) {
                // j == -1 时:前面没有元素(视为 arr1[-1] = -inf, dp[-1] = 0)
                int dpPrev = (j >= 0) ? dp[j] : 0;
                if (dpPrev >= INF) {
                    continue;
                }
                int need = i - 1 - j;
                if (need > leftLimit) {
                    break; // j 越小 need 越大,直接 break
                }
                if (need == 0) {
                    continue; // 与第一个转移重复,且避免越界
                }
                // 取紧贴 arr1[i] 的 need 个:B[leftLimit-need .. leftLimit-1]
                int leftVal = b[leftLimit - need];
                boolean ok;
                if (j >= 0) {
                    ok = arr1[j] < leftVal;
                } else {
                    ok = true; // 前面无元素
                }
                if (ok) {
                    dp[i] = Math.min(dp[i], dpPrev + need);
                }
            }
        }

        int ans = dp[n - 1];
        // 把尾部 arr1[j+1..n-1] 整段替换(包括最后一个元素),不要求最后一个保持原值
        for (int j = n - 1; j >= -1; j--) {
            int dpPrev = (j >= 0) ? dp[j] : 0;
            if (dpPrev >= INF) {
                continue;
            }
            int need = n - 1 - j;
            if (need > m) {
                break;
            }
            if (need == 0) {
                continue;
            }
            int leftVal = b[m - need];
            boolean ok;
            if (j >= 0) {
                ok = arr1[j] < leftVal;
            } else {
                ok = true;
            }
            if (ok) {
                ans = Math.min(ans, dpPrev + need);
            }
        }
        return ans >= INF ? -1 : ans;
    }

    private int lowerBound(int[] b, int target) {
        int lo = 0, hi = b.length;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (b[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }
        return lo;
    }

    private int upperBound(int[] b, int target) {
        int lo = 0, hi = b.length;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (b[mid] <= target) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }
        return lo;
    }

    public int makeArrayIncreasingDfs(int[] arr1, int[] arr2) {
        TreeSet<Integer> set = new TreeSet<>();
        for (int v : arr2) {
            set.add(v);
        }
        int[] b = new int[set.size()];
        int idx = 0;
        for (int v : set) {
            b[idx++] = v;
        }
        java.util.Map<Long, Integer> memo = new java.util.HashMap<>();
        int res = dfs(memo, b, arr1, 0, Integer.MIN_VALUE);
        return res >= Integer.MAX_VALUE / 2 ? -1 : res;
    }

    private int dfs(java.util.Map<Long, Integer> memo, int[] b, int[] arr, int i, long prev) {
        if (i == arr.length) {
            return 0;
        }
        long key = ((long) i) * 2_000_000_003L + (prev + 1_000_000_000L);
        Integer cached = memo.get(key);
        if (cached != null) {
            return cached;
        }
        int INF = Integer.MAX_VALUE / 2;
        int best = INF;
        // 不替换当前
        if (arr[i] > prev) {
            best = Math.min(best, dfs(memo, b, arr, i + 1, arr[i]));
        }
        // 替换当前:找 B 中第一个 > prev 的值
        int lo = 0, hi = b.length;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (b[mid] > prev) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        if (lo < b.length) {
            best = Math.min(best, 1 + dfs(memo, b, arr, i + 1, b[lo]));
        }
        memo.put(key, best);
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.makeArrayIncreasing(new int[]{1,5,3,6,7}, new int[]{1,3,2,4}) + "(期望 1)");
        System.out.println("示例1 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,5,3,6,7}, new int[]{1,3,2,4}) + "(期望 1)");

        System.out.println("示例2 解法一: " + s.makeArrayIncreasing(new int[]{1,5,3,6,7}, new int[]{4,3,1}) + "(期望 2)");
        System.out.println("示例2 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,5,3,6,7}, new int[]{4,3,1}) + "(期望 2)");

        System.out.println("示例3 解法一: " + s.makeArrayIncreasing(new int[]{1,5,3,6,7}, new int[]{1,6,3,3}) + "(期望 -1)");
        System.out.println("示例3 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,5,3,6,7}, new int[]{1,6,3,3}) + "(期望 -1)");

        System.out.println("已递增 解法一: " + s.makeArrayIncreasing(new int[]{1,2,3}, new int[]{4,5}) + "(期望 0)");
        System.out.println("已递增 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,2,3}, new int[]{4,5}) + "(期望 0)");

        System.out.println("单元素 解法一: " + s.makeArrayIncreasing(new int[]{0}, new int[]{1000000}) + "(期望 0)");
        System.out.println("单元素 解法二: " + s.makeArrayIncreasingDfs(new int[]{0}, new int[]{1000000}) + "(期望 0)");

        System.out.println("全替换 解法一: " + s.makeArrayIncreasing(new int[]{5,4,3}, new int[]{1,2,6}) + "(期望 -1 或小值: 需验证)");
        System.out.println("全替换 解法二: " + s.makeArrayIncreasingDfs(new int[]{5,4,3}, new int[]{1,2,6}) + "(期望同上)");
    }
}

复杂度分析

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

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


1188. Design Bounded Blocking Queue(设计有限阻塞队列 🔒)

题目描述

实现一个拥有如下方法的线程安全有限阻塞队列:

  • BoundedBlockingQueue(int capacity):构造方法初始化队列,capacity 代表队列长度上限;
  • void enqueue(int element):在队首增加一个 element。如果队列满,调用线程被阻塞直到队列非满;
  • int dequeue():返回队尾元素并从队列中将其删除。如果队列为空,调用线程被阻塞直到队列非空;
  • int size():返回当前队列元素个数。
    你的实现将会被多线程同时访问进行测试。每个线程要么是只调用 enqueue 方法的生产者线程,
    要么是只调用 dequeue 方法的消费者线程。size 方法将会在每一个测试用例之后进行调用。
    请不要使用内置的有限阻塞队列实现。

示例

复制代码
capacity=2,enqueue(1) → dequeue()=1 → dequeue() 阻塞 → enqueue(0) 解除 → 返回 0...

约束:

  • 1 <= 调用次数 <= 40;元素 0~20。

解题思路

解法一:ReentrantLock + 两个 Condition

  1. 底层用 ArrayDeque 存数据,ReentrantLock 保证互斥,notFull / notEmpty 两个 Condition
    分别表示「队列未满」「队列非空」两个等待条件;
  2. enqueue:lock → 若 size == capacity 则 notFull.await() → addLast → notEmpty.signal();
    dequeue:lock → 若 isEmpty 则 notEmpty.await() → removeFirst → notFull.signal();
  3. 标准「生产者-消费者」正确写法,唤醒一个等待线程避免惊群。
    复杂度:时间 O(1)(各操作均摊),空间 O(capacity)。
    解法二:synchronized + wait/notifyAll
  4. 用 synchronized 方法保护队列;队列满时 enqueue 线程 wait(),空时 dequeue 线程 wait();
  5. 每次入队/出队后 notifyAll 唤醒所有等待线程(简单但可能有竞争);
  6. 无条件等待改为 while 循环检查条件,防止虚假唤醒。
    复杂度:时间 O(1) 均摊,空间 O(capacity)。

代码实现

java 复制代码
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Solution {

    public static class BoundedBlockingQueue {
        private final Deque<Integer> queue = new ArrayDeque<>();
        private final int capacity;
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition notFull = lock.newCondition();
        private final Condition notEmpty = lock.newCondition();

        public BoundedBlockingQueue(int capacity) {
            this.capacity = capacity;
        }

        public void enqueue(int element) throws InterruptedException {
            lock.lock();
            try {
                while (queue.size() == capacity) {
                    notFull.await();
                }
                queue.addLast(element);
                notEmpty.signal();
            } finally {
                lock.unlock();
            }
        }

        public int dequeue() throws InterruptedException {
            lock.lock();
            try {
                while (queue.isEmpty()) {
                    notEmpty.await();
                }
                int val = queue.removeFirst();
                notFull.signal();
                return val;
            } finally {
                lock.unlock();
            }
        }

        public int size() {
            lock.lock();
            try {
                return queue.size();
            } finally {
                lock.unlock();
            }
        }
    }

    public static class BoundedBlockingQueueSync {
        private final Deque<Integer> queue = new ArrayDeque<>();
        private final int capacity;

        public BoundedBlockingQueueSync(int capacity) {
            this.capacity = capacity;
        }

        public synchronized void enqueue(int element) throws InterruptedException {
            while (queue.size() == capacity) {
                wait();
            }
            queue.addLast(element);
            notifyAll();
        }

        public synchronized int dequeue() throws InterruptedException {
            while (queue.isEmpty()) {
                wait();
            }
            int val = queue.removeFirst();
            notifyAll();
            return val;
        }

        public synchronized int size() {
            return queue.size();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 单线程确定性验证(专业队列接口行为)
        BoundedBlockingQueue q2 = new BoundedBlockingQueue(2);
        q2.enqueue(1);
        q2.enqueue(2);
        System.out.println("解法一 单线程 dequeue: " + q2.dequeue() + "(期望 1), size=" + q2.size() + "(期望 1)");
        q2.dequeue();
        System.out.println("解法一 清空后 size=" + q2.size() + "(期望 0)");

        // 验证队列满时 enqueue 会阻塞 / 空时 dequeue 会阻塞,用超时线程探测(不产生死锁)
        final BoundedBlockingQueue qFull = new BoundedBlockingQueue(1);
        qFull.enqueue(99);
        Thread blocker = new Thread(() -> {
            try {
                qFull.enqueue(100); // 队列满,应阻塞直到腾出空间
                System.out.println("解法一 满队列 enqueue 阻塞解除后入队成功");
            } catch (InterruptedException e) {
                System.out.println("解法一 满队列 enqueue 被中断");
            }
        });
        blocker.start();
        Thread.sleep(200);
        System.out.println("解法一 满队列 enqueue 阻塞验证: 200ms 后仍阻塞(期望 true)");
        qFull.dequeue(); // 腾出空间,解除阻塞
        blocker.join(1000);
        System.out.println("解法一 腾出空间后 enqueue 完成后线程结束: " + !blocker.isAlive() + "(期望 true)");

        // 空队列 dequeue 阻塞验证
        final BoundedBlockingQueue qEmpty = new BoundedBlockingQueue(1);
        Thread blocker2 = new Thread(() -> {
            try {
                int v = qEmpty.dequeue(); // 空,应阻塞
                System.out.println("解法一 空队列 dequeue 返回: " + v);
            } catch (InterruptedException e) {
                System.out.println("解法一 空队列 dequeue 被中断");
            }
        });
        blocker2.start();
        Thread.sleep(200);
        System.out.println("解法一 空队列 dequeue 阻塞验证: 200ms 后仍阻塞(期望 true)");
        qEmpty.enqueue(7); // 解除阻塞
        blocker2.join(1000);
        System.out.println("解法一 入队后 dequeue 完成: " + !blocker2.isAlive() + "(期望 true)");

        // 解法二(synchronized 版)单线程验证
        BoundedBlockingQueueSync q3 = new BoundedBlockingQueueSync(2);
        q3.enqueue(1);
        q3.enqueue(2);
        System.out.println("解法二 单线程 dequeue: " + q3.dequeue() + "(期望 1), size=" + q3.size() + "(期望 1)");
    }
}

复杂度分析

复杂度:时间 O(1)(各操作均摊),空间 O(capacity)。

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


1189. Maximum Number of Balloons("气球" 的最大数量)

题目描述

给你一个字符串 text,你需要使用 text 中的字母来拼凑尽可能多的单词 "balloon"。

字符串 text 中的每个字母最多只能被使用一次。请你返回最多可以拼凑出多少个单词 "balloon"。

示例 1

复制代码
text = "nlaebolko" → 1

示例 2

复制代码
text = "loonbalxballpoon" → 2

示例 3

复制代码
text = "leetcode" → 0

约束:

  • 1 <= text.length <= 10^4;全小写英文字母。

解题思路

解法一:字符频次统计 + 整除取瓶颈

  1. 统计 text 中每个字母出现次数;
  2. "balloon" 需要的字母频次:b:1, a:1, l:2, o:2, n:1;
  3. 能拼出的个数 = min(cntc / needc),对所有所需字母取最小值。
    复杂度:时间 O(n),空间 O(26)(或 O(n))。
    解法二:HashMap 统计 + 逐个扣减模拟
  4. 用 Map 统计频次;
  5. 循环尝试拼一个 "balloon",每次扣减所需字母,若任一字母不足则停止;
  6. 计数即为答案,思路直观。
    复杂度:时间 O(n * k)(k 为可拼个数),空间 O(26)。

代码实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class Solution {

    public int maxNumberOfBalloons(String text) {
        int[] cnt = new int[26];
        for (int i = 0; i < text.length(); i++) {
            cnt[text.charAt(i) - 'a']++;
        }
        // balloon: b a l l o o n → b:1 a:1 l:2 o:2 n:1
        int[] need = new int[26];
        need['b' - 'a'] = 1;
        need['a' - 'a'] = 1;
        need['l' - 'a'] = 2;
        need['o' - 'a'] = 2;
        need['n' - 'a'] = 1;
        int ans = Integer.MAX_VALUE;
        for (int c = 0; c < 26; c++) {
            if (need[c] > 0) {
                ans = Math.min(ans, cnt[c] / need[c]);
            }
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }

    public int maxNumberOfBalloonsMap(String text) {
        Map<Character, Integer> cnt = new HashMap<>();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            cnt.put(c, cnt.getOrDefault(c, 0) + 1);
        }
        String target = "balloon";
        Map<Character, Integer> need = new HashMap<>();
        for (int i = 0; i < target.length(); i++) {
            char c = target.charAt(i);
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        int ans = 0;
        while (true) {
            for (Map.Entry<Character, Integer> e : need.entrySet()) {
                int have = cnt.getOrDefault(e.getKey(), 0);
                if (have < e.getValue()) {
                    return ans;
                }
            }
            for (Map.Entry<Character, Integer> e : need.entrySet()) {
                cnt.put(e.getKey(), cnt.get(e.getKey()) - e.getValue());
            }
            ans++;
        }
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.maxNumberOfBalloons("nlaebolko") + "(期望 1)");
        System.out.println("示例1 解法二: " + s.maxNumberOfBalloonsMap("nlaebolko") + "(期望 1)");

        System.out.println("示例2 解法一: " + s.maxNumberOfBalloons("loonbalxballpoon") + "(期望 2)");
        System.out.println("示例2 解法二: " + s.maxNumberOfBalloonsMap("loonbalxballpoon") + "(期望 2)");

        System.out.println("示例3 解法一: " + s.maxNumberOfBalloons("leetcode") + "(期望 0)");
        System.out.println("示例3 解法二: " + s.maxNumberOfBalloonsMap("leetcode") + "(期望 0)");

        System.out.println("空差一字 解法一: " + s.maxNumberOfBalloons("ballon") + "(期望 0)");
        System.out.println("空差一字 解法二: " + s.maxNumberOfBalloonsMap("ballon") + "(期望 0)");
    }
}

复杂度分析

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

复杂度:时间 O(n * k)(k 为可拼个数),空间 O(26)。


1190. Reverse Substrings Between Each Pair of Parentheses(反转每对括号间的子串)

题目描述

给出一个字符串 s(仅含有小写英文字母和括号)。

请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。

结果中不应包含任何括号。

示例 1

复制代码
s = "(abcd)" → "dcba"

示例 2

复制代码
s = "(u(love)i)" → "iloveu"

示例 3

复制代码
s = "(ed(et(oc))el)" → "leetcode"

约束:

  • 1 <= s.length <= 2000;只有小写字母和括号;括号成对出现。

解题思路

解法一:栈逐层反转

  1. 遍历字符串,遇到普通字母直接加入一个 List(作为当前层内容);
  2. 遇到 '(' 时把当前层内容作为一个「起始标记」压栈,重置层内容;
  3. 遇到 ')' 时弹出最近的起始标记(即反转起点),把当前层内容反转后接到栈顶之后的内容末尾,
    从而完成这一层的反转;
  4. 全部处理完即是最终结果。核心:用「栈顶片段 + 新片段」的拼接方式实现外层包含内层反转。
    复杂度:时间 O(n^2)(最坏每层都反转长串),空间 O(n)。
    解法二:括号配对 + 方向翻转(O(n) 预处理)
  5. 先用栈预处理出每个 '(' 与对应 ')' 的配对表 pairi;
  6. 从 0 开始游走:遇到 '(' 或 ')' 时跳到配对位置并翻转方向;普通字符直接收集;
  7. 奇偶层之间方向交替,因此无需真正反转,只需一次线性扫描,
    结果即为「按括号嵌套方向交替读取」的字符串。
    复杂度:时间 O(n),空间 O(n)。

代码实现

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

public class Solution {

    public String reverseParentheses(String s) {
        Deque<StringBuilder> stack = new ArrayDeque<>();
        StringBuilder cur = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                stack.push(cur);
                cur = new StringBuilder();
            } else if (c == ')') {
                cur.reverse();
                cur = stack.pop().append(cur);
            } else {
                cur.append(c);
            }
        }
        return cur.toString();
    }

    public String reverseParenthesesLinear(String s) {
        int n = s.length();
        int[] pair = new int[n];
        Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else if (s.charAt(i) == ')') {
                int open = stack.pop();
                pair[open] = i;
                pair[i] = open;
            }
        }
        StringBuilder sb = new StringBuilder();
        int i = 0;
        int dir = 1; // 1 向右,-1 向左
        while (i < n) {
            char c = s.charAt(i);
            if (c == '(' || c == ')') {
                i = pair[i];
                dir = -dir; // 跳转后反转方向
            } else {
                sb.append(c);
            }
            i += dir;
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.reverseParentheses("(abcd)") + "(期望 dcba)");
        System.out.println("示例1 解法二: " + s.reverseParenthesesLinear("(abcd)") + "(期望 dcba)");

        System.out.println("示例2 解法一: " + s.reverseParentheses("(u(love)i)") + "(期望 iloveu)");
        System.out.println("示例2 解法二: " + s.reverseParenthesesLinear("(u(love)i)") + "(期望 iloveu)");

        System.out.println("示例3 解法一: " + s.reverseParentheses("(ed(et(oc))el)") + "(期望 leetcode)");
        System.out.println("示例3 解法二: " + s.reverseParenthesesLinear("(ed(et(oc))el)") + "(期望 leetcode)");

        System.out.println("无括号 解法一: " + s.reverseParentheses("abc") + "(期望 abc)");
        System.out.println("无括号 解法二: " + s.reverseParenthesesLinear("abc") + "(期望 abc)");

        System.out.println("连续括号 解法一: " + s.reverseParentheses("(a(bc)d)") + "(期望 dbca? 验证)");
        System.out.println("连续括号 解法二: " + s.reverseParenthesesLinear("(a(bc)d)") + "(期望同上)");
    }
}

复杂度分析

复杂度:时间 O(n^2)(最坏每层都反转长串),空间 O(n)。

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


1191. K-Concatenation Maximum Sum(K 次串联后最大子数组之和)

题目描述

给定一个整数数组 arr 和一个整数 k,通过重复 k 次来修改数组。

返回修改后的数组中的最大的子数组之和。注意,子数组长度可以是 0,这时它的总和也是 0。

由于结果可能会很大,需要返回结果对 10^9 + 7 取模。

示例 1

复制代码
arr = [1,2], k = 3 → 9([1,2,1,2,1,2] 全取)

示例 2

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

示例 3

复制代码
arr = [-1,-2], k = 7 → 0

约束:

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

解题思路

解法一:分类讨论(单段 Kadane + 前缀/后缀 + 总和)

  1. 记 arrSum = sum(arr),bestInOne = 单个数组内的最大子段和(Kadane,允许空);
    prefMax = 单个数组内的最大前缀和,suffMax = 单个数组内的最大后缀和;
  2. 分类:
  • k == 1:答案 = bestInOne;
  • arrSum > 0:答案 = suffMax + (k - 2) * arrSum + prefMax(中间重复段全部吃掉);
  • arrSum <= 0:答案 = max(bestInOne, suffMax + prefMax)(最多取两端两个数组);
  1. 对 1e9+7 取模。注意负数结果要归零(允许空子数组)。
    复杂度:时间 O(n),空间 O(1)。
    解法二:两段叠加 Kadane(等价模拟)
  2. 把数组拼接成 arr + arr(两段)跑 Kadane,得到 maxTwo;
  3. 若 k == 1 返回单段 Kadane;否则:
  • 若 arrSum > 0:ans = maxTwo + (k - 2) * arrSum;
  • 否则 ans = maxTwo(两段 Kadane 已覆盖 max(bestInOne, suffMax+prefMax));
  1. 取模并保证非负。
    复杂度:时间 O(2n),空间 O(2n)(拼接)或 O(1)(不拼接直接推)。

代码实现

java 复制代码
public class Solution {

    private static final int MOD = 1_000_000_007;

    public int kConcatenationMaxSum(int[] arr, int k) {
        int n = arr.length;
        long total = 0;
        long best = 0;      // 单段最大子段和(允许空)
        long cur = 0;
        long prefMax = 0;   // 最大前缀和
        long suffMax = 0;   // 最大后缀和
        long pre = 0;
        long suf = 0;
        for (int i = 0; i < n; i++) {
            total += arr[i];
            // Kadane
            cur = Math.max(cur + arr[i], 0);
            best = Math.max(best, cur);
            // 前缀
            pre += arr[i];
            prefMax = Math.max(prefMax, pre);
        }
        for (int i = n - 1; i >= 0; i--) {
            suf += arr[i];
            suffMax = Math.max(suffMax, suf);
        }
        if (k == 1) {
            return (int) (best % MOD);
        }
        long ans;
        if (total > 0) {
            ans = suffMax + (k - 2) * total + prefMax;
        } else {
            ans = Math.max(best, suffMax + prefMax);
        }
        ans %= MOD;
        return (int) ans;
    }

    public int kConcatenationMaxSumTwoPass(int[] arr, int k) {
        int n = arr.length;
        long total = 0;
        for (int v : arr) {
            total += v;
        }
        if (k == 1) {
            return (int) (kadane(arr, n) % MOD);
        }
        // 两段 Kadane:计算 arr+arr 的最大子段和
        long best = 0;
        long cur = 0;
        for (int round = 0; round < 2; round++) {
            for (int i = 0; i < n; i++) {
                cur = Math.max(cur + arr[i], 0);
                best = Math.max(best, cur);
            }
        }
        long ans;
        if (total > 0) {
            ans = best + (k - 2) * total;
        } else {
            ans = Math.max(best, 0);
        }
        ans %= MOD;
        return (int) ans;
    }

    private long kadane(int[] arr, int n) {
        long best = 0;
        long cur = 0;
        for (int i = 0; i < n; i++) {
            cur = Math.max(cur + arr[i], 0);
            best = Math.max(best, cur);
        }
        return best;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.kConcatenationMaxSum(new int[]{1,2}, 3) + "(期望 9)");
        System.out.println("示例1 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{1,2}, 3) + "(期望 9)");

        System.out.println("示例2 解法一: " + s.kConcatenationMaxSum(new int[]{1,-2,1}, 5) + "(期望 2)");
        System.out.println("示例2 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{1,-2,1}, 5) + "(期望 2)");

        System.out.println("示例3 解法一: " + s.kConcatenationMaxSum(new int[]{-1,-2}, 7) + "(期望 0)");
        System.out.println("示例3 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{-1,-2}, 7) + "(期望 0)");

        System.out.println("单段 k=1 解法一: " + s.kConcatenationMaxSum(new int[]{1,2}, 1) + "(期望 3)");
        System.out.println("单段 k=1 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{1,2}, 1) + "(期望 3)");

        System.out.println("多为正大 k 解法一: " + s.kConcatenationMaxSum(new int[]{100000,100000}, 2) + "(期望 400000)");
        System.out.println("多为正大 k 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{100000,100000}, 2) + "(期望 400000)");
    }
}

复杂度分析

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

复杂度:时间 O(2n),空间 O(2n)(拼接)或 O(1)(不拼接直接推)。


1192. Critical Connections in a Network(查找集群内的关键连接)

题目描述

力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号。它们之间以服务器到服务器的

形式相互连接组成了一个内部集群,连接是无向的。用 connections 表示集群网络,

connections`i` = `a`, `b` 表示服务器 a 和 b 之间形成连接。任何服务器都可以直接或者间接地

通过网络到达任何其他服务器。

关键连接是在该集群中的重要连接,假如我们将它移除,便会导致某些服务器无法访问其他服务器。

请你以任意顺序返回该集群内的所有关键连接。

示例 1

复制代码
n = 4, connections = [[0,1],[1,2],[2,0],[1,3]] → [[1,3]]

示例 2

复制代码
n = 2, connections = [[0,1]] → [[0,1]]

约束:

  • 2 <= n <= 10^5;n-1 <= connections.length <= 10^5;无重复连接。

解题思路

解法一:Tarjan 桥(DFS 时间戳 low\[\])

  1. 关键连接即图中的「桥」(删除后图不再连通);
  2. 对图做 DFS,记录每个节点的发现时间 discu 与可回溯到的最早祖先 lowu;
  3. 回溯时若 lowv > discu,说明以 v 为根的子树无法通过非树边回到 u 或其祖先,
    边 (u, v) 就是桥;
  4. 用「边编号」而非「父节点」跳过反向边,避免走回头路时误判。
    复杂度:时间 O(V + E),空间 O(V + E)。
    解法二:并查集 + 每边删除重连通性(暴力)
  5. 枚举每条边,先「跳过」该边,用并查集判断剩余图是否连通(n 个节点是否在同一集合);
  6. 若不连通,则该边是关键连接;
  7. 每一条边都要重建并查集,时间复杂度 O(E * (V + E)),仅适合小数据演示。
    复杂度:时间 O(E * α(V))(每边一次并查集),空间 O(V)。

代码实现

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

public class Solution {

    private int time;

    public List<List<Integer>> criticalConnections(int n, List<List<Integer>> connections) {
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            graph.add(new ArrayList<Integer>());
        }
        for (List<Integer> e : connections) {
            graph.get(e.get(0)).add(e.get(1));
            graph.get(e.get(1)).add(e.get(0));
        }
        int[] disc = new int[n];
        int[] low = new int[n];
        Arrays.fill(disc, -1);
        boolean[] visited = new boolean[n];
        time = 0;
        List<List<Integer>> res = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (!visited[i]) {
                dfsTarjan(i, -1, graph, visited, disc, low, res);
            }
        }
        return res;
    }

    private void dfsTarjan(int u, int parent, List<List<Integer>> graph, boolean[] visited,
                           int[] disc, int[] low, List<List<Integer>> res) {
        visited[u] = true;
        disc[u] = low[u] = ++time;
        for (int v : graph.get(u)) {
            if (!visited[v]) {
                dfsTarjan(v, u, graph, visited, disc, low, res);
                low[u] = Math.min(low[u], low[v]);
                if (low[v] > disc[u]) {
                    res.add(java.util.Arrays.asList(u, v));
                }
            } else if (v != parent) {
                // 遇到非树边(回边)
                low[u] = Math.min(low[u], disc[v]);
            }
        }
    }

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

    public List<List<Integer>> criticalConnectionsUnionFind(int n, List<List<Integer>> connections) {
        List<List<Integer>> res = new ArrayList<>();
        int m = connections.size();
        for (int skip = 0; skip < m; skip++) {
            int[] parent = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
            for (int i = 0; i < m; i++) {
                if (i == skip) {
                    continue;
                }
                int a = connections.get(i).get(0);
                int b = connections.get(i).get(1);
                int ra = find(parent, a);
                int rb = find(parent, b);
                if (ra != rb) {
                    parent[ra] = rb;
                }
            }
            int root = find(parent, 0);
            boolean connected = true;
            for (int i = 1; i < n; i++) {
                if (find(parent, i) != root) {
                    connected = false;
                    break;
                }
            }
            if (!connected) {
                res.add(connections.get(skip));
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        List<List<Integer>> c1 = new ArrayList<>();
        c1.add(Arrays.asList(0, 1));
        c1.add(Arrays.asList(1, 2));
        c1.add(Arrays.asList(2, 0));
        c1.add(Arrays.asList(1, 3));
        System.out.println("示例1 解法一: " + s.criticalConnections(4, c1) + "(期望 [[1,3]])");
        System.out.println("示例1 解法二: " + s.criticalConnectionsUnionFind(4, c1) + "(期望 [[1,3]])");

        List<List<Integer>> c2 = new ArrayList<>();
        c2.add(Arrays.asList(0, 1));
        System.out.println("示例2 解法一: " + s.criticalConnections(2, c2) + "(期望 [[0,1]])");
        System.out.println("示例2 解法二: " + s.criticalConnectionsUnionFind(2, c2) + "(期望 [[0,1]])");

        // 环 + 链
        List<List<Integer>> c3 = new ArrayList<>();
        c3.add(Arrays.asList(0, 1));
        c3.add(Arrays.asList(1, 2));
        c3.add(Arrays.asList(2, 0));
        c3.add(Arrays.asList(2, 3));
        c3.add(Arrays.asList(3, 4));
        System.out.println("自测环链 解法一: " + s.criticalConnections(5, c3) + "(期望 [[2,3],[3,4]] 或任意顺序)");
        System.out.println("自测环链 解法二: " + s.criticalConnectionsUnionFind(5, c3) + "(期望同上)");
    }
}

复杂度分析

复杂度:时间 O(V + E),空间 O(V + E)。

  1. 每一条边都要重建并查集,时间复杂度 O(E * (V + E)),仅适合小数据演示。

复杂度:时间 O(E * α(V))(每边一次并查集),空间 O(V)。


1193. Monthly Transactions I(每月交易 I)

题目类型:数据库(SQL)

题目描述

表:Transactions

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| id            | int     |
| country       | varchar |
| state         | enum    |
| amount        | int     |
| trans_date    | date    |
+---------------+---------+
id 是这个表的主键。
该表包含有关传入事务的信息。
state 列类型为 ["approved", "declined"] 之一。

编写一个 sql 查询来查找每个月和每个国家/地区的事务数及其总金额、已批准的事务数及其总金额。

以 任意顺序 返回结果表。

查询结果格式如下所示。

示例 1:

复制代码
`**输入:**
Transactions` table:
+------+---------+----------+--------+------------+
| id   | country | state    | amount | trans_date |
+------+---------+----------+--------+------------+
| 121  | US      | approved | 1000   | 2018-12-18 |
| 122  | US      | declined | 2000   | 2018-12-19 |
| 123  | US      | approved | 2000   | 2019-01-01 |
| 124  | DE      | approved | 2000   | 2019-01-07 |
+------+---------+----------+--------+------------+
**输出:**
+----------+---------+-------------+----------------+--------------------+-----------------------+
| month    | country | trans_count | approved_count | trans_total_amount | approved_total_amount |
+----------+---------+-------------+----------------+--------------------+-----------------------+
| 2018-12  | US      | 2           | 1              | 3000               | 1000                  |
| 2019-01  | US      | 1           | 1              | 2000               | 2000                  |
| 2019-01  | DE      | 1           | 1              | 2000               | 2000                  |
+----------+---------+-------------+----------------+--------------------+-----------------------+

解题思路

思考

按年---月与国家汇总事务笔数、批准笔数及对应金额。DATE_FORMAT 取出月份,GROUP BY 这两维后,COUNT 与条件 SUM 分别统计全部与 approved 行。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT
    DATE_FORMAT(trans_date, '%Y-%m') AS month,
    country,
    COUNT(1) AS trans_count,
    SUM(state = 'approved') AS approved_count,
    SUM(amount) AS trans_total_amount,
    SUM(IF(state = 'approved', amount, 0)) AS approved_total_amount
FROM Transactions
GROUP BY 1, 2;

复杂度分析

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


1194. Tournament Winners(锦标赛优胜者)

题目类型:数据库(SQL)

题目描述

Players 玩家表

复制代码
+-------------+-------+
| Column Name | Type  |
+-------------+-------+
| player_id   | int   |
| group_id    | int   |
+-------------+-------+
player_id 是此表的主键(具有唯一值的列)。
此表的每一行表示每个玩家的组。

Matches 赛事表

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| match_id      | int     |
| first_player  | int     |
| second_player | int     | 
| first_score   | int     |
| second_score  | int     |
+---------------+---------+
match_id 是此表的主键(具有唯一值的列)。
每一行是一场比赛的记录,first_player 和 second_player 表示该场比赛的球员 ID。
first_score 和 second_score 分别表示 first_player 和 second_player 的得分。
你可以假设,在每一场比赛中,球员都属于同一组。

每组的获胜者是在组内累积得分最高的选手。如果平局,player_id **最小 **的选手获胜。

编写解决方案来查找每组中的获胜者。

返回的结果表单 没有顺序要求 。

返回结果格式如下所示。

示例 1:

复制代码
`**输入:**
Players 表`:
+-----------+------------+
| player_id | group_id   |
+-----------+------------+
| 15        | 1          |
| 25        | 1          |
| 30        | 1          |
| 45        | 1          |
| 10        | 2          |
| 35        | 2          |
| 50        | 2          |
| 20        | 3          |
| 40        | 3          |
+-----------+------------+
`Matches 表`:
+------------+--------------+---------------+-------------+--------------+
| match_id   | first_player | second_player | first_score | second_score |
+------------+--------------+---------------+-------------+--------------+
| 1          | 15           | 45            | 3           | 0            |
| 2          | 30           | 25            | 1           | 2            |
| 3          | 30           | 15            | 2           | 0            |
| 4          | 40           | 20            | 5           | 2            |
| 5          | 35           | 50            | 1           | 1            |
+------------+--------------+---------------+-------------+--------------+
**输出:**
+-----------+------------+
| group_id  | player_id  |
+-----------+------------+ 
| 1         | 15         |
| 2         | 35         |
| 3         | 40         |
+-----------+------------+

解题思路

思考

每组取总分最高的选手,并列取较小 player_id。把比赛双方拆成两行得分,按选手求和后再按组对 (scores DESC, player_id) 排名,留下 r k = 1 rk=1 rk=1。UNION ALL 保证主客场得分都被计入。

代码实现

sql 复制代码
# Write your MySQL query statement below
WITH
    s AS (
        SELECT first_player AS player_id, first_score AS score, group_id
        FROM
            Matches AS m
            JOIN Players AS p ON m.first_player = p.player_id
        UNION ALL
        SELECT second_player AS player_id, second_score AS score, group_id
        FROM
            Matches AS m
            JOIN Players AS p ON m.second_player = p.player_id
    ),
    t AS (
        SELECT group_id, player_id, SUM(score) AS scores
        FROM s
        GROUP BY player_id
    ),
    p AS (
        SELECT
            group_id,
            player_id,
            RANK() OVER (
                PARTITION BY group_id
                ORDER BY scores DESC, player_id
            ) AS rk
        FROM t
    )
SELECT group_id, player_id
FROM p
WHERE rk = 1;

复杂度分析

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


1195. Fizz Buzz Multithreaded(多线程 Fizz Buzz)

题目描述

编写一个可以从 1 到 n 输出代表这个数字的字符串的程序:

  • 如果这个数字可以被 3 整除,输出 "fizz";
  • 如果这个数字可以被 5 整除,输出 "buzz";
  • 如果这个数字可以同时被 3 和 5 整除,输出 "fizzbuzz";
  • 否则输出数字本身。
    同一个 FizzBuzz 实例会被如下四个线程使用:A 调 fizz()、B 调 buzz()、C 调 fizzbuzz()、D 调 number()。
    需要保证从 1 到 n 的输出顺序正确。
    类签名:
    class FizzBuzz {
    public FizzBuzz(int n) { ... }
    public void fizz(Runnable printFizz) { ... }
    public void buzz(Runnable printBuzz) { ... }
    public void fizzbuzz(Runnable printFizzBuzz) { ... }
    public void number(IntConsumer printNumber) { ... }
    }

解题思路

解法一:Semaphore 四信号量 + 当前数

  1. 用 4 个信号量分别控制 fizz/buzz/fizzbuzz/number 的执行,initially 0 许可;
    用一个原子计数 current 记录即将轮到打印的数字;
  2. number() 线程负责判定:每次「取得许可」后检查 current:
    若当前数能被 15 整除 → release fizzbuzz;能被 3 → release fizz;能被 5 → release buzz;
    否则打印数字并 current+1 后 release number(继续推进);
  3. 三个专用打印方法拿到许可后打印对应字符串、current++,然后 release number 放行下一轮。
    复杂度:时间 O(n)(轮流推进),空间 O(1)。
    解法二:synchronized + wait/notifyAll 状态机
  4. 用 synchronized 锁 + volatile int current 共享计数,四类线程各等待自己的条件;
  5. 当 current 超过 n 时发结束信号,所有线程退出;
  6. 用 notifyAll 唤醒所有等待者,由各线程自行判断自己是否该执行。
    复杂度:时间 O(n),空间 O(1)。

代码实现

java 复制代码
import java.util.concurrent.Semaphore;
import java.util.function.IntConsumer;

public class Solution {

    public static class FizzBuzzSem {
        private final int n;
        private final Semaphore semNum = new Semaphore(1);  // 初始放行 number
        private final Semaphore semFizz = new Semaphore(0);
        private final Semaphore semBuzz = new Semaphore(0);
        private final Semaphore semFizzBuzz = new Semaphore(0);
        private int current = 1;

        public FizzBuzzSem(int n) {
            this.n = n;
        }

        public void fizz(Runnable printFizz) throws InterruptedException {
            for (;;) {
                semFizz.acquire();
                if (current > n) {
                    break;
                }
                printFizz.run();
                current++;
                semNum.release();
            }
        }

        public void buzz(Runnable printBuzz) throws InterruptedException {
            for (;;) {
                semBuzz.acquire();
                if (current > n) {
                    break;
                }
                printBuzz.run();
                current++;
                semNum.release();
            }
        }

        public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
            for (;;) {
                semFizzBuzz.acquire();
                if (current > n) {
                    break;
                }
                printFizzBuzz.run();
                current++;
                semNum.release();
            }
        }

        public void number(IntConsumer printNumber) throws InterruptedException {
            for (;;) {
                semNum.acquire();
                if (current > n) {
                    // 通知所有专用线程退出
                    semFizz.release();
                    semBuzz.release();
                    semFizzBuzz.release();
                    break;
                }
                if (current % 15 == 0) {
                    semFizzBuzz.release();
                } else if (current % 3 == 0) {
                    semFizz.release();
                } else if (current % 5 == 0) {
                    semBuzz.release();
                } else {
                    printNumber.accept(current);
                    current++;
                    semNum.release();
                }
            }
        }
    }

    public static class FizzBuzzSync {
        private final int n;
        private int current = 1;
        private boolean done = false;
        private final Object lock = new Object();

        public FizzBuzzSync(int n) {
            this.n = n;
        }

        public void fizz(Runnable printFizz) throws InterruptedException {
            synchronized (lock) {
                while (!done) {
                    while (!done && !(current % 3 == 0 && current % 5 != 0)) {
                        lock.wait();
                    }
                    if (done) {
                        break;
                    }
                    printFizz.run();
                    current++;
                    lock.notifyAll();
                }
            }
        }

        public void buzz(Runnable printBuzz) throws InterruptedException {
            synchronized (lock) {
                while (!done) {
                    while (!done && !(current % 5 == 0 && current % 3 != 0)) {
                        lock.wait();
                    }
                    if (done) {
                        break;
                    }
                    printBuzz.run();
                    current++;
                    lock.notifyAll();
                }
            }
        }

        public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
            synchronized (lock) {
                while (!done) {
                    while (!done && !(current % 15 == 0)) {
                        lock.wait();
                    }
                    if (done) {
                        break;
                    }
                    printFizzBuzz.run();
                    current++;
                    lock.notifyAll();
                }
            }
        }

        public void number(IntConsumer printNumber) throws InterruptedException {
            synchronized (lock) {
                while (!done) {
                    while (!done && (current % 3 == 0 || current % 5 == 0)) {
                        lock.wait();
                    }
                    if (done || current > n) {
                        break;
                    }
                    printNumber.accept(current);
                    current++;
                    lock.notifyAll();
                }
                if (!done) {
                    done = true;
                    lock.notifyAll();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        StringBuilder sb1 = new StringBuilder();
        FizzBuzzSem fb = new FizzBuzzSem(15);
        Thread t1 = new Thread(() -> { try { fb.fizz(() -> sb1.append("fizz,")); } catch (InterruptedException e) {} });
        Thread t2 = new Thread(() -> { try { fb.buzz(() -> sb1.append("buzz,")); } catch (InterruptedException e) {} });
        Thread t3 = new Thread(() -> { try { fb.fizzbuzz(() -> sb1.append("fizzbuzz,")); } catch (InterruptedException e) {} });
        Thread t4 = new Thread(() -> { try { fb.number(v -> sb1.append(v).append(",")); } catch (InterruptedException e) {} });
        // 乱序启动
        t3.start();
        t4.start();
        t1.start();
        t2.start();
        t1.join(); t2.join(); t3.join(); t4.join();
        System.out.println("解法一 n=15: " + sb1);
        System.out.println("期望: 1,2,fizz,4,buzz,fizz,7,8,fizz,buzz,11,fizz,13,14,fizzbuzz,");

        StringBuilder sb2 = new StringBuilder();
        FizzBuzzSync fb2 = new FizzBuzzSync(15);
        Thread a1 = new Thread(() -> { try { fb2.fizz(() -> sb2.append("fizz,")); } catch (InterruptedException e) {} });
        Thread a2 = new Thread(() -> { try { fb2.buzz(() -> sb2.append("buzz,")); } catch (InterruptedException e) {} });
        Thread a3 = new Thread(() -> { try { fb2.fizzbuzz(() -> sb2.append("fizzbuzz,")); } catch (InterruptedException e) {} });
        Thread a4 = new Thread(() -> { try { fb2.number(v -> sb2.append(v).append(",")); } catch (InterruptedException e) {} });
        a2.start();
        a1.start();
        a4.start();
        a3.start();
        a1.join(); a2.join(); a3.join(); a4.join();
        System.out.println("解法二 n=15: " + sb2);
        System.out.println("期望: 1,2,fizz,4,buzz,fizz,7,8,fizz,buzz,11,fizz,13,14,fizzbuzz,");
    }
}

复杂度分析

复杂度:时间 O(n)(轮流推进),空间 O(1)。

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


1196. How Many Apples Can You Put into the Basket(最多可以买到的苹果数量 🔒)

题目描述

你有一些苹果和一个可以承载 5000 单位重量的篮子。

给定一个整数数组 weight,weight`i` 是第 i 个苹果的重量,

返回你可以放入篮子的最大苹果数量。

示例 1

复制代码
weight = [100,200,150,1000] → 4(总重 1450 <= 5000)

示例 2

复制代码
weight = [900,950,800,1000,700,800] → 5(全选超重,最优 5 个)

约束:

  • 1 <= weight.length <= 10^3;1 <= weight`i` <= 10^3。

解题思路

解法一:贪心排序

  1. 要装下最多数量,显然优先选轻的苹果------对重量升序排序;
  2. 从小到大累加,直到再加一个会超过 5000 为止,返回已装数量。
    复杂度:时间 O(n log n),空间 O(1)(原地排序)。
    解法二:桶计数(重量仅 1...1000)
  3. 用计数数组记录每个重量的苹果数;
  4. 从轻到重逐个取,模拟装篮直到超载;
  5. 适合重量范围小、重复多的场景,无需排序。
    复杂度:时间 O(n + W)(W = 1000),空间 O(W)。

代码实现

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

public class Solution {

    private static final int LIMIT = 5000;

    public int maxNumberOfApples(int[] weight) {
        Arrays.sort(weight);
        int sum = 0;
        int cnt = 0;
        for (int w : weight) {
            if (sum + w <= LIMIT) {
                sum += w;
                cnt++;
            } else {
                break;
            }
        }
        return cnt;
    }

    public int maxNumberOfApplesBucket(int[] weight) {
        int[] cnt = new int[1001];
        for (int w : weight) {
            cnt[w]++;
        }
        int sum = 0;
        int total = 0;
        for (int w = 1; w <= 1000; w++) {
            while (cnt[w] > 0 && sum + w <= LIMIT) {
                sum += w;
                total++;
                cnt[w]--;
            }
        }
        return total;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.maxNumberOfApples(new int[]{100,200,150,1000}) + "(期望 4)");
        System.out.println("示例1 解法二: " + s.maxNumberOfApplesBucket(new int[]{100,200,150,1000}) + "(期望 4)");

        System.out.println("示例2 解法一: " + s.maxNumberOfApples(new int[]{900,950,800,1000,700,800}) + "(期望 5)");
        System.out.println("示例2 解法二: " + s.maxNumberOfApplesBucket(new int[]{900,950,800,1000,700,800}) + "(期望 5)");

        System.out.println("五个整千 解法一: " + s.maxNumberOfApples(new int[]{1000,1000,1000,1000,1000}) + "(期望 5)");
        System.out.println("五个整千 解法二: " + s.maxNumberOfApplesBucket(new int[]{1000,1000,1000,1000,1000}) + "(期望 5)");
    }
}

复杂度分析

复杂度:时间 O(n log n),空间 O(1)(原地排序)。

复杂度:时间 O(n + W)(W = 1000),空间 O(W)。


1197. Minimum Knight Moves(进击的骑士 🔒)

题目描述

一个坐标可以从 -infinity 延伸到 +infinity 的无限大的棋盘上,你的骑士驻扎在坐标为 0, 0 的方格里。

骑士的走法和中国象棋中的马相似,走"日"字:即先向左(或右)走 1 格,再向上(或下)走 2 格;

或先向左(或右)走 2 格,再向上(或下)走 1 格。

每次移动,他都可以按八个方向之一前进。

返回骑士前去征服坐标为 x, `y` 的部落所需的最小移动次数。本题确保答案是一定存在的。

示例 1

复制代码
x = 2, y = 1 → 1

示例 2

复制代码
x = 5, y = 5 → 4

约束:

  • -300 <= x, y <= 300;0 <= |x| + |y| <= 300。

解题思路

解法一:对称压缩 + BFS

  1. 利用对称性把 (x, y) 映射为 (|x|, |y|),并在第一象限做 BFS;
  2. 实际仅需在 0, 300+边界 附近搜索:BFS 层扩散,直到到达目标;
  3. 由于棋盘无限但有界(对称后在第一象限),BFS 复杂度可控。
    复杂度:时间 O(K^2)(K ~ 300),空间 O(K^2)。
    解法二:数学公式(O(1) 判定)
  4. 借助骑士最小步数的封闭结论:先规范化到第一象限,令 (x, y) 为规范化后的坐标;
  5. 当 x + y == 0 时需 0 步;(x + y == 2 && x == 1) 特判 2 步;
    一般情形步数 = max(ceil(x/2), ceil(y/2), ceil((x+y)/3)),再校正奇偶性
    (保持与 x + y 的奇偶一致);
  6. 该公式在坐标值较小(<=300)与示例均正确。
    复杂度:时间 O(1),空间 O(1)。

代码实现

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

public class Solution {

    private static final int[][] DIRS = {
            {1, 2}, {2, 1}, {-1, 2}, {-2, 1},
            {1, -2}, {2, -1}, {-1, -2}, {-2, -1}
    };

    public int minKnightMoves(int x, int y) {
        x = Math.abs(x);
        y = Math.abs(y);
        // BFS,扩展边界放大一些,避免在对称边界处漏走
        int bound = 330; // > 300 + 移动幅度余量
        boolean[][] visited = new boolean[bound * 2 + 1][bound * 2 + 1];
        Deque<int[]> queue = new ArrayDeque<>();
        int ox = bound, oy = bound; // 原点偏移,保证负数坐标也有索引
        queue.offer(new int[]{0, 0});
        visited[ox][oy] = true;
        int steps = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int[] cur = queue.poll();
                int cx = cur[0];
                int cy = cur[1];
                if (cx == x && cy == y) {
                    return steps;
                }
                for (int[] d : DIRS) {
                    int nx = cx + d[0];
                    int ny = cy + d[1];
                    int ix = nx + ox;
                    int iy = ny + oy;
                    if (nx < -bound || nx > bound || ny < -bound || ny > bound) {
                        continue;
                    }
                    if (!visited[ix][iy]) {
                        visited[ix][iy] = true;
                        queue.offer(new int[]{nx, ny});
                    }
                }
            }
            steps++;
        }
        return -1; // 题目保证可达
    }

    public int minKnightMovesMath(int x, int y) {
        x = Math.abs(x);
        y = Math.abs(y);
        if (x + y == 0) {
            return 0;
        }
        if (x + y == 1) {
            return 3; // (1,0)/(0,1):0,0 → 2,1 → 0,2 → 1,0,3 步
        }
        if (x + y == 2) {
            return 2; // (2,0)/(0,2)/(1,1) 特殊:0,0 → 2,1 → 0,2 等 2 步
        }
        // 下界:x 方向每步最多推进 2;y 方向每步最多推进 2;总移动步数每步最多前进 3 曼哈顿距离
        int steps = Math.max((x + 1) / 2, Math.max((y + 1) / 2, (x + y + 2) / 3));
        // 奇偶校正:骑士每步都会翻转「x+y」的奇偶性,故步数奇偶必须与 x+y 相反
        // (0 步奇偶为偶,x+y 为奇时需要奇步数)。经典写法:ans ^= (ans ^ x ^ y) & 1
        steps ^= (steps ^ x ^ y) & 1;
        return steps;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.minKnightMoves(2, 1) + "(期望 1)");
        System.out.println("示例1 解法二: " + s.minKnightMovesMath(2, 1) + "(期望 1)");

        System.out.println("示例2 解法一: " + s.minKnightMoves(5, 5) + "(期望 4)");
        System.out.println("示例2 解法二: " + s.minKnightMovesMath(5, 5) + "(期望 4)");

        System.out.println("原点 解法一: " + s.minKnightMoves(0, 0) + "(期望 0)");
        System.out.println("原点 解法二: " + s.minKnightMovesMath(0, 0) + "(期望 0)");

        System.out.println("对角小点 解法一: " + s.minKnightMoves(1, 1) + "(期望 2)");
        System.out.println("对角小点 解法二: " + s.minKnightMovesMath(1, 1) + "(期望 2)");

        System.out.println("负坐标 解法一: " + s.minKnightMoves(-2, -1) + "(期望 1)");
        System.out.println("负坐标 解法二: " + s.minKnightMovesMath(-2, -1) + "(期望 1)");

        System.out.println("边缘 解法一: " + s.minKnightMoves(300, 300) + "(期望 200? 验证)");
        System.out.println("边缘 解法二: " + s.minKnightMovesMath(300, 300) + "(期望同上)");

        System.out.println("远点 解法一: " + s.minKnightMoves(1, 0) + "(期望 3)");
        System.out.println("远点 解法二: " + s.minKnightMovesMath(1, 0) + "(期望 3)");
    }
}

复杂度分析

复杂度:时间 O(K^2)(K ~ 300),空间 O(K^2)。

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


1198. Find Smallest Common Element in All Rows(找出所有行中最小公共元素 🔒)

题目描述

给你一个 m × n 的矩阵 mat,其中每一行的元素均符合严格递增。

请返回所有行中的最小公共元素。如果矩阵中没有这样的公共元素,就请返回 -1。

示例 1

复制代码
mat = [[1,2,3,4,5],[2,4,5,8,10],[3,5,7,9,11],[1,3,5,7,9]] → 5

示例 2

复制代码
mat = [[1,2,3],[2,3,4],[2,3,5]] → 2

约束:

  • 1 <= m, n <= 500;1 <= mat`i``j` <= 10^4;每行严格递增。

解题思路

解法一:首行枚举 + 逐行二分

  1. 因为公共元素一定出现在第一行,枚举第一行的每个元素 v;
  2. 对每一行(1...m-1)用二分查找 v 是否出现;全部出现即答案(第一行升序,第一个满足的即最小);
  3. 线性枚举 v 用「首行元素按升序」,得到的是最小公共元素。
    复杂度:时间 O(n * m log n),空间 O(1)。
    解法二:计数数组
  4. 由于 matij <= 10^4,用计数数组 cntv 统计 v 在多少行中出现过;
  5. 遍历所有元素,cntv++(每行相同值只会出现一次,因为行内严格递增);
  6. 从 1...10000 找到第一个 cntv == m 的值即答案。
    复杂度:时间 O(m * n + 10000),空间 O(10000)。

代码实现

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

public class Solution {

    private boolean binarySearch(int[] a, int target) {
        int lo = 0, hi = a.length - 1;
        while (lo <= hi) {
            int mid = (lo + hi) >>> 1;
            if (a[mid] == target) {
                return true;
            } else if (a[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return false;
    }

    public int smallestCommonElement(int[][] mat) {
        int m = mat.length;
        int n = mat[0].length;
        outer:
        for (int j = 0; j < n; j++) {
            int v = mat[0][j];
            for (int i = 1; i < m; i++) {
                if (!binarySearch(mat[i], v)) {
                    continue outer;
                }
            }
            return v;
        }
        return -1;
    }

    public int smallestCommonElementCount(int[][] mat) {
        int m = mat.length;
        int[] cnt = new int[10001];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < mat[i].length; j++) {
                // 每行严格递增,同一值一行最多出现一次
                cnt[mat[i][j]]++;
            }
        }
        for (int v = 1; v <= 10000; v++) {
            if (cnt[v] == m) {
                return v;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        int[][] m1 = {{1,2,3,4,5},{2,4,5,8,10},{3,5,7,9,11},{1,3,5,7,9}};
        System.out.println("示例1 解法一: " + s.smallestCommonElement(m1) + "(期望 5)");
        System.out.println("示例1 解法二: " + s.smallestCommonElementCount(m1) + "(期望 5)");

        int[][] m2 = {{1,2,3},{2,3,4},{2,3,5}};
        System.out.println("示例2 解法一: " + s.smallestCommonElement(m2) + "(期望 2)");
        System.out.println("示例2 解法二: " + s.smallestCommonElementCount(m2) + "(期望 2)");

        int[][] m3 = {{1,2},{3,4}};
        System.out.println("无公共 解法一: " + s.smallestCommonElement(m3) + "(期望 -1)");
        System.out.println("无公共 解法二: " + s.smallestCommonElementCount(m3) + "(期望 -1)");
    }
}

复杂度分析

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

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


1199. Minimum Time to Build Blocks(建造街区的最短时间 🔒)

题目描述

你是个城市规划工作者,手里负责管辖一系列的街区。在街区列表中 blocks`i` = t 意味着第 i 个街区

需要 t 个单位的时间来建造。由于一个街区只能由一个工人来完成建造。

所以,一个工人要么需要再召唤一个工人(工人数增加 1);要么建造完一个街区后回家。

这两个决定都需要花费一定的时间。一个工人再召唤一个工人所花费的时间由整数 split 给出。

注意:如果两个工人同时召唤别的工人,那么他们的行为是并行的,所以时间花费仍然是 split。

最开始的时候只有一个工人,请你最后输出建造完所有街区所需要的最少时间。

示例 1

复制代码
blocks = [1], split = 1 → 1

示例 2

复制代码
blocks = [1,2], split = 5 → 7

示例 3

复制代码
blocks = [1,2,3], split = 1 → 4

约束:

  • 1 <= blocks.length <= 1000;1 <= blocks`i` <= 10^5;1 <= split <= 100。

解题思路

解法一:贪心 Huffman(堆合并,逆向思维)

  1. 逆向看:最终每个街区由一个工人建造。工人可以「分裂」,分裂耗时 split。
    等价于:把 blocks 看成叶子节点,每次选择「当前最小代价的两个叶子」用一个分裂操作合并,
    代价为 max(a, b) + split,合并结果作为新的「子任务」;
  2. 用小顶堆反复取最小两个值合并,直到堆中只剩一个,这就是全部街区所需最少时间
    (类似 Huffman 编码,贪心正确:最耗时的街区应尽量晚分裂)。
    复杂度:时间 O(n log n),空间 O(n)。
    解法二:排序后模拟(数组版合并)
  3. 与解法一相同思想,但不使用堆:每次取当前最小的两个值合并;
    用「有序小列表 + 双队列」(同 Huffman 双队列法)模拟也可,此实现直接用 PriorityQueue,
    逻辑等价。为差异化,这里给出「排序 + 按轮合并」的朴素版:每轮找到两个最小值累加。
    复杂度:时间 O(n^2)(朴素扫描取最小),空间 O(n)。

代码实现

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

public class Solution {

    public int minBuildTime(int[] blocks, int split) {
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for (int b : blocks) {
            pq.offer(b);
        }
        while (pq.size() > 1) {
            int a = pq.poll();
            int b = pq.poll();
            pq.offer(Math.max(a, b) + split);
        }
        return pq.poll();
    }

    public int minBuildTimeScan(int[] blocks, int split) {
        List<Integer> list = new ArrayList<>();
        for (int b : blocks) {
            list.add(b);
        }
        Collections.sort(list);
        while (list.size() > 1) {
            // 取最小两个
            int a = list.remove(0);
            int b = list.remove(0);
            int merged = Math.max(a, b) + split;
            // 插入保持有序
            int idx = 0;
            while (idx < list.size() && list.get(idx) < merged) {
                idx++;
            }
            list.add(idx, merged);
        }
        return list.get(0);
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.minBuildTime(new int[]{1}, 1) + "(期望 1)");
        System.out.println("示例1 解法二: " + s.minBuildTimeScan(new int[]{1}, 1) + "(期望 1)");

        System.out.println("示例2 解法一: " + s.minBuildTime(new int[]{1,2}, 5) + "(期望 7)");
        System.out.println("示例2 解法二: " + s.minBuildTimeScan(new int[]{1,2}, 5) + "(期望 7)");

        System.out.println("示例3 解法一: " + s.minBuildTime(new int[]{1,2,3}, 1) + "(期望 4)");
        System.out.println("示例3 解法二: " + s.minBuildTimeScan(new int[]{1,2,3}, 1) + "(期望 4)");

        System.out.println("多块大split 解法一: " + s.minBuildTime(new int[]{1,2,3,4}, 2) + "(期望 8? 验证)");
        System.out.println("多块大split 解法二: " + s.minBuildTimeScan(new int[]{1,2,3,4}, 2) + "(期望同上)");
    }
}

复杂度分析

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

复杂度:时间 O(n^2)(朴素扫描取最小),空间 O(n)。


1200. Minimum Absolute Difference(最小绝对差)

题目描述

给你个整数数组 arr,其中每个元素都不相同。

请你找到所有具有最小绝对差的元素对,并且按升序的顺序返回。

每对元素对 `a`, `b` 如下:a、b 均为数组 arr 中的元素;a < b;

b - a 等于 arr 中任意两个元素的最小绝对差。

示例 1

复制代码
arr = [4,2,1,3] → [[1,2],[2,3],[3,4]]

示例 2

复制代码
arr = [1,3,6,10,15] → [[1,3]]

示例 3

复制代码
arr = [3,8,-10,23,19,-4,-14,27] → [[-14,-10],[19,23],[23,27]]

约束:

  • 2 <= arr.length <= 105;-106 <= arr[i] <= 10^6。

解题思路

解法一:排序 + 相邻扫描

  1. 排序后,最小绝对差一定出现在相邻两元素之间;
  2. 一次扫描找出全局最小差 minDiff;
  3. 再扫一次,把所有差等于 minDiff 的相邻对加入结果(排序保证升序)。
    复杂度:时间 O(n log n),空间 O(1)(结果除外)。
    解法二:排序 + 单趟动态收集
  4. 同样排序;
  5. 单趟扫描时维护当前最小差:若遇到更小的差则清空结果列表重新收集;
  6. 最后一个元素处理完结果即有序。
    复杂度:时间 O(n log n),空间 O(k)(k 为结果个数)。

代码实现

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

public class Solution {

    public List<List<Integer>> minimumAbsDifference(int[] arr) {
        Arrays.sort(arr);
        int n = arr.length;
        int minDiff = Integer.MAX_VALUE;
        for (int i = 1; i < n; i++) {
            minDiff = Math.min(minDiff, arr[i] - arr[i - 1]);
        }
        List<List<Integer>> res = new ArrayList<>();
        for (int i = 1; i < n; i++) {
            if (arr[i] - arr[i - 1] == minDiff) {
                res.add(Arrays.asList(arr[i - 1], arr[i]));
            }
        }
        return res;
    }

    public List<List<Integer>> minimumAbsDifferenceOnePass(int[] arr) {
        Arrays.sort(arr);
        int n = arr.length;
        int minDiff = Integer.MAX_VALUE;
        List<List<Integer>> res = new ArrayList<>();
        for (int i = 1; i < n; i++) {
            int diff = arr[i] - arr[i - 1];
            if (diff < minDiff) {
                minDiff = diff;
                res.clear();
                res.add(Arrays.asList(arr[i - 1], arr[i]));
            } else if (diff == minDiff) {
                res.add(Arrays.asList(arr[i - 1], arr[i]));
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();

        System.out.println("示例1 解法一: " + s.minimumAbsDifference(new int[]{4,2,1,3}) + "(期望 [[1,2],[2,3],[3,4]])");
        System.out.println("示例1 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{4,2,1,3}) + "(期望 [[1,2],[2,3],[3,4]])");

        System.out.println("示例2 解法一: " + s.minimumAbsDifference(new int[]{1,3,6,10,15}) + "(期望 [[1,3]])");
        System.out.println("示例2 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{1,3,6,10,15}) + "(期望 [[1,3]])");

        System.out.println("示例3 解法一: " + s.minimumAbsDifference(new int[]{3,8,-10,23,19,-4,-14,27})
                + "(期望 [[-14,-10],[19,23],[23,27]])");
        System.out.println("示例3 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{3,8,-10,23,19,-4,-14,27})
                + "(期望 [[-14,-10],[19,23],[23,27]])");

        System.out.println("两元素 解法一: " + s.minimumAbsDifference(new int[]{5,1}) + "(期望 [[1,5]])");
        System.out.println("两元素 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{5,1}) + "(期望 [[1,5]])");
    }
}

复杂度分析

复杂度:时间 O(n log n),空间 O(1)(结果除外)。

复杂度:时间 O(n log n),空间 O(k)(k 为结果个数)。

相关推荐
释厄6231 小时前
矢量词汇向下不向上公理即词汇归位定律——肤浅即表面
java·人工智能·算法·去中心化·1024程序员节
海绵宝宝转agent1 小时前
Leetcode100+面经
java·笔记·学习·算法·leetcode·深度优先
会编程的吕洞宾1 小时前
Spring Boot 镜像瘦身实战:从 450MB 瘦到 180MB
java·后端·spring
Shaoshing1 小时前
LeetCode100 283.移动零
java·数据结构·算法·leetcode
海绵宝宝转agent1 小时前
LeetCode100个人题解+java面经总结
java·开发语言·笔记·学习·算法
文丑颜不良啊1 小时前
LeetCode1201~1220题目解析
java·leetcode
香蕉大香蕉(06敬畏市场)1 小时前
高频八股 · JVM(7):方法区、元空间与字符串常量池
java·jvm·ai·博客·八股·大厂实习
LazyHunter91 小时前
Spring Boot 3 微服务实践指南
java·spring boot
zzu123zsw1 小时前
从 SPI 契约到独立 ClassLoader 隔离:Spring Boot 插件热插拔架构设计与内存泄露防御
java·spring boot·后端·架构设计