LeetCode840~850题目解析

3 阅读12分钟

LeetCode 840-850. 题解合集


840. Magic Squares In Grid(矩阵中的幻方)

题目描述

3 x 3 的幻方是一个填充有从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行、每列以及两条
对角线上的各数之和都相等。
给定一个由整数组成的 row x col 的 grid,其中有多少个 3 x 3 的 "幻方" 子矩阵?
注意:虽然幻方只能包含 1 到 9 的数字,但 grid 可以包含最多 15 的数字。

示例 1

  • 输入:grid = [[4,3,8,4],[9,5,1,9],[2,7,6,2]]
  • 输出:1

示例 2

  • 输入:grid = [[8]]
  • 输出:0
    提示:
  • row == grid.length
  • col == grid[i].length
  • 1 <= row, col <= 10
  • 0 <= grid[i][j] <= 15

解题思路

解法一(枚举每个 3x3 窗口,主提交):对每个左上角 (i, j) 检查窗口:九个数字必须互不
相同且都在 [1, 9];三行、三列、两对角线之和相等。满足则计数。窗口合法的充要条件可直接
用集合 + 和值判断。
解法二(中心点判定,对照实现):3x3 幻方的中心必为 5,且从中心出发的任意直线两端和为 10。
枚举每个中心 (i, j)(i, j 取 1..row-2),只需检查通过中心的四条线(水平、竖直、两条
对角线)和为 15,以及 9 个数字恰好为 1..9 的一个排列,即可判定。

代码实现

class Solution {

    private int[][] g;
    private int m, n;

    // 解法一:枚举 3x3 窗口(推荐提交)
    public int numMagicSquaresInside(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        g = grid;
        int ans = 0;
        for (int i = 0; i + 2 < m; ++i) {
            for (int j = 0; j + 2 < n; ++j) {
                if (checkOne(i, j)) {
                    ++ans;
                }
            }
        }
        return ans;
    }

    private boolean checkOne(int i, int j) {
        boolean[] seen = new boolean[10];
        for (int r = i; r < i + 3; ++r) {
            for (int c = j; c < j + 3; ++c) {
                int v = g[r][c];
                if (v < 1 || v > 9 || seen[v]) {
                    return false;
                }
                seen[v] = true;
            }
        }
        int target = g[i][j] + g[i][j + 1] + g[i][j + 2];
        for (int r = i + 1; r < i + 3; ++r) {
            if (g[r][j] + g[r][j + 1] + g[r][j + 2] != target) {
                return false;
            }
        }
        for (int c = j; c < j + 3; ++c) {
            if (g[i][c] + g[i + 1][c] + g[i + 2][c] != target) {
                return false;
            }
        }
        return g[i][j] + g[i + 1][j + 1] + g[i + 2][j + 2] == target
                && g[i][j + 2] + g[i + 1][j + 1] + g[i + 2][j] == target;
    }

    // 解法二:中心点判定(对照实现)
    public int numMagicSquaresInside2(int[][] grid) {
        int rows = grid.length, cols = grid[0].length;
        int ans = 0;
        for (int i = 1; i + 1 < rows; ++i) {
            for (int j = 1; j + 1 < cols; ++j) {
                if (grid[i][j] != 5) {
                    continue;
                }
                // 中心向外扩展的四个方向端点和均为 10 且每线和为 15 等价于:
                // 中心四邻 + 四角形成一个 1..9 的排列且横竖斜线和为 15
                boolean[] seen = new boolean[10];
                boolean ok = true;
                for (int r = i - 1; r <= i + 1 && ok; ++r) {
                    for (int c = j - 1; c <= j + 1 && ok; ++c) {
                        int v = grid[r][c];
                        if (v < 1 || v > 9 || seen[v]) {
                            ok = false;
                            break;
                        }
                        seen[v] = true;
                    }
                }
                if (!ok) {
                    continue;
                }
                int v1 = grid[i - 1][j] + grid[i + 1][j]; // 竖
                int v2 = grid[i][j - 1] + grid[i][j + 1]; // 横
                int v3 = grid[i - 1][j - 1] + grid[i + 1][j + 1]; // 主对角
                int v4 = grid[i - 1][j + 1] + grid[i + 1][j - 1]; // 副对角
                if (v1 == 10 && v2 == 10 && v3 == 10 && v4 == 10) {
                    ++ans;
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.numMagicSquaresInside(new int[][]{{4,3,8,4},{9,5,1,9},{2,7,6,2}}) == 1) ++pass; else System.out.println("FAIL 840-1");
        ++total; if (sol.numMagicSquaresInside(new int[][]{{8}}) == 0) ++pass; else System.out.println("FAIL 840-2");
        ++total; if (sol.numMagicSquaresInside(new int[][]{{5,5,5},{5,5,5},{5,5,5}}) == 0) ++pass; else System.out.println("FAIL 840-3");
        ++total; if (sol.numMagicSquaresInside(new int[][]{{8,1,6},{3,5,7},{4,9,2}}) == 1) ++pass; else System.out.println("FAIL 840-4");
        ++total; if (sol.numMagicSquaresInside(new int[][]{{3,2,9,5},{7,5,5,3},{4,9,1,7},{5,3,7,5}}) == 0) ++pass; else System.out.println("FAIL 840-5");

        ++total; if (sol.numMagicSquaresInside2(new int[][]{{4,3,8,4},{9,5,1,9},{2,7,6,2}}) == 1) ++pass; else System.out.println("FAIL 840-6");
        ++total; if (sol.numMagicSquaresInside2(new int[][]{{8,1,6},{3,5,7},{4,9,2}}) == 1) ++pass; else System.out.println("FAIL 840-7");
        ++total; if (sol.numMagicSquaresInside2(new int[][]{{3,2,9,5},{7,5,5,3},{4,9,1,7},{5,3,7,5}}) == 0) ++pass; else System.out.println("FAIL 840-8");

        if (pass == total) {
            System.out.println("== 0840 PASS ==");
        } else {
            System.out.println("== 0840 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(row * col)O(1)
解法二O(row * col)O(1)

841. Keys and Rooms(钥匙和房间)

题目描述

有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。
你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。
当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙
可以打开的房间。你可以拿上所有钥匙去解锁其他房间。
给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入
所有房间返回 true,否则返回 false。

示例 1

  • 输入:rooms = [[1],[2],[3],[]]
  • 输出:true

示例 2

  • 输入:rooms = [[1,3],[3,0,1],[2],[0]]
  • 输出:false
    提示:
  • n == rooms.length
  • 2 <= n <= 1000
  • 0 <= rooms[i].length <= 1000
  • 1 <= sum(rooms[i].length) <= 3000
  • 0 <= rooms[i][j] < n

解题思路

房间构成有向图,钥匙即出边,问题是从 0 出发能否到达全部节点。
解法一(DFS,主提交):从 0 号房间开始递归 DFS,标记已访问的房间,最后比较访问数与 n。
解法二(BFS,对照实现):用队列从 0 出发逐层扩展,统计可达房间数,与 n 比较。

代码实现

import java.util.ArrayList;
import java.util.List;
class Solution {

    private boolean[] vis;

    // 解法一:DFS(推荐提交)
    public boolean canVisitAllRooms(List<List<Integer>> rooms) {
        int n = rooms.size();
        vis = new boolean[n];
        dfs(0, rooms);
        int cnt = 0;
        for (boolean b : vis) {
            if (b) {
                ++cnt;
            }
        }
        return cnt == n;
    }

    private void dfs(int i, List<List<Integer>> rooms) {
        vis[i] = true;
        for (int j : rooms.get(i)) {
            if (!vis[j]) {
                dfs(j, rooms);
            }
        }
    }

    // 解法二:BFS(对照实现)
    public boolean canVisitAllRooms2(List<List<Integer>> rooms) {
        int n = rooms.size();
        boolean[] seen = new boolean[n];
        java.util.ArrayDeque<Integer> q = new java.util.ArrayDeque<>();
        q.offer(0);
        seen[0] = true;
        int cnt = 0;
        while (!q.isEmpty()) {
            int i = q.poll();
            ++cnt;
            for (int j : rooms.get(i)) {
                if (!seen[j]) {
                    seen[j] = true;
                    q.offer(j);
                }
            }
        }
        return cnt == n;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        List<List<Integer>> rooms1 = new ArrayList<>();
        rooms1.add(new ArrayList<>(java.util.Arrays.asList(1)));
        rooms1.add(new ArrayList<>(java.util.Arrays.asList(2)));
        rooms1.add(new ArrayList<>(java.util.Arrays.asList(3)));
        rooms1.add(new ArrayList<>());

        List<List<Integer>> rooms2 = new ArrayList<>();
        rooms2.add(new ArrayList<>(java.util.Arrays.asList(1, 3)));
        rooms2.add(new ArrayList<>(java.util.Arrays.asList(3, 0, 1)));
        rooms2.add(new ArrayList<>(java.util.Arrays.asList(2)));
        rooms2.add(new ArrayList<>(java.util.Arrays.asList(0)));

        List<List<Integer>> rooms3 = new ArrayList<>();
        rooms3.add(new ArrayList<>(java.util.Arrays.asList(0)));

        ++total; if (sol.canVisitAllRooms(rooms1)) ++pass; else System.out.println("FAIL 841-1");
        ++total; if (!sol.canVisitAllRooms(rooms2)) ++pass; else System.out.println("FAIL 841-2");
        ++total; if (sol.canVisitAllRooms(rooms3)) ++pass; else System.out.println("FAIL 841-3");

        ++total; if (sol.canVisitAllRooms2(rooms1)) ++pass; else System.out.println("FAIL 841-4");
        ++total; if (!sol.canVisitAllRooms2(rooms2)) ++pass; else System.out.println("FAIL 841-5");
        ++total; if (sol.canVisitAllRooms2(rooms3)) ++pass; else System.out.println("FAIL 841-6");

        if (pass == total) {
            System.out.println("== 0841 PASS ==");
        } else {
            System.out.println("== 0841 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n + m)O(n)
解法二O(n + m)O(n)

842. Split Array into Fibonacci Sequence(将数组拆分成斐波那契序列)

题目描述

给定一个数字字符串 num,比如 "123456579",我们可以将它分成「斐波那契式」的序列
[123, 456, 579]。
形式上,斐波那契式序列是一个非负整数列表 f,且满足:

  1. 0 <= f[i] < 2^31(每个整数都符合 32 位有符号整数类型)
    2. f.length >= 3``
  2. 对于所有的 0 <= i < f.length - 2,都有 f[i] + f[i + 1] = f[i + 2]
    另外,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。
    返回从 num 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []。

示例 1

  • 输入:num = "1101111"
  • 输出:[11,0,11,11]

示例 2

  • 输入:num = "112358130"
  • 输出:[]

示例 3

  • 输入:num = "0123"
  • 输出:[]
    提示:
  • 1 <= num.length <= 200
  • num 中只含有数字

解题思路

解法一(回溯 + 剪枝,主提交):枚举第一个数 a 与第二个数 b 的取值(禁止前导零),后续
每一项必须是前两项之和:在剩余串中搜索该和,找到则继续,找不到则剪枝回溯。任一路径拆完
整个串且项数 >= 3 即返回。
解法二(DFS 状态回溯,对照实现):维护结果列表,从位置 pos 开始枚举下一项 x:长度超过
1 且前导零则跳过;已有两项时 x 必须等于前两项之和且不得超过 2^31-1;当 x 已是前两项
的第三项而仍有余串时继续向后拆。搜到 pos == n 且列表长度 >= 3 即为成功。

代码实现

import java.util.ArrayList;
import java.util.List;
class Solution {

    private List<Integer> path;
    private String numStr;

    // 解法一:枚举前两项 + 拼接验证(推荐提交)
    public List<Integer> splitIntoFibonacci(String num) {
        int n = num.length();
        for (int i = 1; i <= n - 2 && i <= 10; ++i) {
            if (i > 1 && num.charAt(0) == '0') {
                break;
            }
            long a = Long.parseLong(num.substring(0, i));
            if (a >= 1L << 31) {
                break;
            }
            for (int j = i + 1; j <= n - 1 && j - i <= 10; ++j) {
                if (j - i > 1 && num.charAt(i) == '0') {
                    break;
                }
                long b = Long.parseLong(num.substring(i, j));
                if (b >= 1L << 31) {
                    break;
                }
                List<Integer> cur = build(a, b, num, n);
                if (cur != null) {
                    return cur;
                }
            }
        }
        return new ArrayList<>();
    }

    // 从 a, b 出发按斐波那契规则扩展,拼接结果必须与 num 前缀一致且恰好覆盖整个串
    private List<Integer> build(long a, long b, String num, int n) {
        StringBuilder sb = new StringBuilder();
        sb.append(a).append(b);
        if (!num.startsWith(sb.toString())) {
            return null;
        }
        List<Integer> res = new ArrayList<>();
        res.add((int) a);
        res.add((int) b);
        while (sb.length() < n) {
            long c = a + b;
            if (c >= 1L << 31) {
                return null;
            }
            res.add((int) c);
            sb.append(c);
            if (!num.startsWith(sb.toString())) {
                return null;
            }
            a = b;
            b = c;
        }
        if (sb.length() == n) {
            return res;
        }
        return null;
    }

    // 解法二:DFS 状态回溯(对照实现)
    public List<Integer> splitIntoFibonacci2(String num) {
        numStr = num;
        path = new ArrayList<>();
        if (dfs(0)) {
            return path;
        }
        return new ArrayList<>();
    }

    private boolean dfs(int pos) {
        int n = numStr.length();
        if (pos == n) {
            return path.size() >= 3;
        }
        int maxLen = (numStr.charAt(pos) == '0') ? 1 : Math.min(10, n - pos);
        for (int len = 1; len <= maxLen; ++len) {
            long x = Long.parseLong(numStr.substring(pos, pos + len));
            if (x >= 1L << 31) {
                break;
            }
            int size = path.size();
            if (size >= 2) {
                long expect = (long) path.get(size - 1) + path.get(size - 2);
                if (x < expect) {
                    continue; // 当前取太短,还可增长
                }
                if (x > expect) {
                    break; // 已超过,继续加长只会更大
                }
            }
            path.add((int) x);
            if (dfs(pos + len)) {
                return true;
            }
            path.remove(path.size() - 1);
        }
        return false;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.splitIntoFibonacci("123456579").toString().equals("[123, 456, 579]")) ++pass; else System.out.println("FAIL 842-1");
        ++total; if (sol.splitIntoFibonacci("112358130").size() == 0) ++pass; else System.out.println("FAIL 842-2");
        ++total; if (sol.splitIntoFibonacci("0123").size() == 0) ++pass; else System.out.println("FAIL 842-3");
        ++total; if (sol.splitIntoFibonacci("1101111").toString().equals("[11, 0, 11, 11]")) ++pass; else System.out.println("FAIL 842-4");
        ++total; if (sol.splitIntoFibonacci("000").toString().equals("[0, 0, 0]")) ++pass; else System.out.println("FAIL 842-5");

        ++total; if (sol.splitIntoFibonacci2("123456579").toString().equals("[123, 456, 579]")) ++pass; else System.out.println("FAIL 842-6");
        ++total; if (sol.splitIntoFibonacci2("112358130").size() == 0) ++pass; else System.out.println("FAIL 842-7");
        ++total; if (sol.splitIntoFibonacci2("0123").size() == 0) ++pass; else System.out.println("FAIL 842-8");
        ++total; if (sol.splitIntoFibonacci2("1101111").toString().equals("[11, 0, 11, 11]")) ++pass; else System.out.println("FAIL 842-9");

        if (pass == total) {
            System.out.println("== 0842 PASS ==");
        } else {
            System.out.println("== 0842 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n^2)O(n)
解法二O(n^2)O(n)

843. Guess the Word(猜猜这个单词)

题目描述

给你一个由不同字符串组成的单词列表 words,其中 words[i] 长度均为 6。words 中的
一个单词将被选作秘密单词 secret。
另给你一个辅助对象 Master,你可以调用 Master.guess(word) 来猜单词,其中参数 word
长度为 6 且必须是 words 中的字符串。
Master.guess(word) 将会返回:如果 word 不是 words 中的字符串,返回 -1;否则返回
一个整数,表示所猜测的单词与秘密单词 secret 的准确匹配(值和位置同时匹配)的数目。
每组测试用例都会包含一个参数 allowedGuesses,其中 allowedGuesses 是你可以调用
Master.guess(word) 的最大次数。在不超过允许猜测的次数的前提下,你应该调用 Master.guess
来猜出秘密单词。

示例 1

  • 输入:secret = "acckzz", words = ["acckzz","ccbazz","eiowzz","abcczz"], allowedGuesses = 10
  • 输出:You guessed the secret word correctly.

示例 2

  • 输入:secret = "hamada", words = ["hamada","khaled"], allowedGuesses = 10
  • 输出:You guessed the secret word correctly.
    提示:
  • 1 <= words.length <= 100
  • words[i].length == 6
  • words[i] 仅由小写英文字母组成
  • secret 存在于 words 中
  • 10 <= allowedGuesses <= 30

解题思路

解法一(候选集收缩法,主提交):维护所有可能与 secret 相同的候选串。每次从候选中猜一个词
g,根据返回值 x(与 secret 相同位置的匹配数)把候选收窄为「与 g 恰好有 x 个位置
相同的词」。当返回值达到 6 即命中。每次猜词都利用反馈严格缩小搜索空间。
解法二(最小化最大分组贪心,对照实现):每次从当前候选中选择「按匹配数分组后最大组规模
最小」的词来猜,保证最坏情况下的收敛速度,然后按返回值继续收窄候选,逻辑同解法一。

代码实现

import java.util.ArrayList;
import java.util.List;
class Solution {

    // Master 桩:本地验证用,记录猜测次数与是否命中
    static class Master {
        private final String secret;
        int guesses = 0;
        boolean hit = false;
        Master(String s) {
            secret = s;
        }
        int guess(String word) {
            ++guesses;
            int cnt = 0;
            for (int i = 0; i < 6; ++i) {
                if (word.charAt(i) == secret.charAt(i)) {
                    ++cnt;
                }
            }
            if (cnt == 6) {
                hit = true;
            }
            return cnt;
        }
    }

    // 解法一:候选集收缩(推荐提交)
    public void findSecretWord(String[] wordlist, Master master) {
        List<String> cand = new ArrayList<>();
        for (String w : wordlist) {
            cand.add(w);
        }
        while (!cand.isEmpty()) {
            String g = cand.get(0);
            int x = master.guess(g);
            if (x == 6) {
                return;
            }
            List<String> next = new ArrayList<>();
            for (String w : cand) {
                if (matchCount(g, w) == x) {
                    next.add(w);
                }
            }
            cand = next;
        }
    }

    private int matchCount(String a, String b) {
        int cnt = 0;
        for (int i = 0; i < 6; ++i) {
            if (a.charAt(i) == b.charAt(i)) {
                ++cnt;
            }
        }
        return cnt;
    }

    // 解法二:最小化最大分组(对照实现)
    public void findSecretWord2(String[] wordlist, Master master) {
        List<String> cand = new ArrayList<>();
        for (String w : wordlist) {
            cand.add(w);
        }
        while (!cand.isEmpty()) {
            String g = bestGuess(cand);
            int x = master.guess(g);
            if (x == 6) {
                return;
            }
            List<String> next = new ArrayList<>();
            for (String w : cand) {
                if (matchCount(g, w) == x) {
                    next.add(w);
                }
            }
            cand = next;
        }
    }

    // 选择使「按匹配数分组后最大组最小」的词
    private String bestGuess(List<String> cand) {
        String best = cand.get(0);
        int bestMax = Integer.MAX_VALUE;
        for (String g : cand) {
            int[] bucket = new int[7];
            for (String w : cand) {
                if (!w.equals(g)) {
                    ++bucket[matchCount(g, w)];
                }
            }
            int mx = 0;
            for (int i = 0; i < 7; ++i) {
                mx = Math.max(mx, bucket[i]);
            }
            if (mx < bestMax) {
                bestMax = mx;
                best = g;
            }
        }
        return best;
    }

    // ============ 本地验证 ============
    // 对每个用例分别用两种解法运行,要求:allowedGuesses 内命中
    private static boolean runCase(Solution sol, String[] words, String secret,
                                   int allowed, boolean use2) {
        Master m = new Master(secret);
        if (use2) {
            sol.findSecretWord2(words, m);
        } else {
            sol.findSecretWord(words, m);
        }
        return m.hit && m.guesses <= allowed;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        String[] c1 = new String[]{"acckzz", "ccbazz", "eiowzz", "abcczz"};
        ++total; if (runCase(sol, c1, "acckzz", 10, false)) ++pass; else System.out.println("FAIL 843-1");
        ++total; if (runCase(sol, c1, "abcczz", 10, false)) ++pass; else System.out.println("FAIL 843-2");

        String[] c2 = new String[]{"hamada", "khaled"};
        ++total; if (runCase(sol, c2, "hamada", 10, false)) ++pass; else System.out.println("FAIL 843-3");
        ++total; if (runCase(sol, c2, "khaled", 10, false)) ++pass; else System.out.println("FAIL 843-4");

        // 稍大用例:随机单词表内任意 secret
        String[] c3 = new String[]{"acckzz", "ccbazz", "eiowzz", "abcczz", "hamada",
                "khaled", "aaaaaa", "bbbbbb", "cccccc", "dddddd"};
        ++total; if (runCase(sol, c3, "abcczz", 20, false)) ++pass; else System.out.println("FAIL 843-5");
        ++total; if (runCase(sol, c3, "khaled", 20, false)) ++pass; else System.out.println("FAIL 843-6");

        ++total; if (runCase(sol, c1, "acckzz", 10, true)) ++pass; else System.out.println("FAIL 843-7");
        ++total; if (runCase(sol, c2, "hamada", 10, true)) ++pass; else System.out.println("FAIL 843-8");
        ++total; if (runCase(sol, c3, "khaled", 20, true)) ++pass; else System.out.println("FAIL 843-9");

        if (pass == total) {
            System.out.println("== 0843 PASS ==");
        } else {
            System.out.println("== 0843 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n^2 * L) 最坏O(n)
解法二O(n^2 * L)O(n)

844. Backspace String Compare(比较含退格的字符串)

题目描述

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回
true。# 代表退格字符。注意:如果对空文本输入退格字符,文本继续为空。

示例 1

  • 输入:s = "ab#c", t = "ad#c"
  • 输出:true
  • 解释:s 和 t 都会变成 "ac"。

示例 2

  • 输入:s = "ab##", t = "c#d#"
  • 输出:true
  • 解释:s 和 t 都会变成 ""。

示例 3

  • 输入:s = "a#c", t = "b"
  • 输出:false
    提示:
  • 1 <= s.length, t.length <= 200
  • s 和 t 只含有小写字母以及字符 '#'
  • 进阶:可以用 O(n) 的时间和 O(1) 的空间解决该问题吗?

解题思路

解法一(双指针反向扫描,主提交):从两串末尾向前移动,用变量 skip 消化 #:遇到 #
时 skip++ 并跳过该字符,遇到普通字符且 skip > 0 时 skip-- 并继续跳过;当 skip == 0
时该字符是"有效比较字符"。两串各取一个有效字符比较,不等则 false,最终两串都走完才是
true。空间 O(1) 满足进阶要求。
解法二(栈构造最终串,对照实现):用栈模拟退格:遇 # 弹栈(栈空则忽略),否则入栈,
最后比较两栈内容。

代码实现

class Solution {

    // 解法一:双指针反向扫描(推荐提交,O(1) 空间)
    public boolean backspaceCompare(String s, String t) {
        int i = s.length() - 1, j = t.length() - 1;
        int skipS = 0, skipT = 0;
        while (i >= 0 || j >= 0) {
            while (i >= 0) {
                if (s.charAt(i) == '#') {
                    ++skipS;
                    --i;
                } else if (skipS > 0) {
                    --skipS;
                    --i;
                } else {
                    break;
                }
            }
            while (j >= 0) {
                if (t.charAt(j) == '#') {
                    ++skipT;
                    --j;
                } else if (skipT > 0) {
                    --skipT;
                    --j;
                } else {
                    break;
                }
            }
            if (i >= 0 && j >= 0) {
                if (s.charAt(i) != t.charAt(j)) {
                    return false;
                }
            } else if (i >= 0 || j >= 0) {
                return false;
            }
            --i;
            --j;
        }
        return true;
    }

    // 解法二:栈模拟(对照实现)
    public boolean backspaceCompare2(String s, String t) {
        return build(s).equals(build(t));
    }

    private String build(String str) {
        StringBuilder sb = new StringBuilder();
        for (char c : str.toCharArray()) {
            if (c == '#') {
                if (sb.length() > 0) {
                    sb.deleteCharAt(sb.length() - 1);
                }
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.backspaceCompare("ab#c", "ad#c")) ++pass; else System.out.println("FAIL 844-1");
        ++total; if (sol.backspaceCompare("ab##", "c#d#")) ++pass; else System.out.println("FAIL 844-2");
        ++total; if (!sol.backspaceCompare("a#c", "b")) ++pass; else System.out.println("FAIL 844-3");
        ++total; if (sol.backspaceCompare("a##c", "#a#c")) ++pass; else System.out.println("FAIL 844-4");
        ++total; if (!sol.backspaceCompare("bxj##tw", "bxj###tw")) ++pass; else System.out.println("FAIL 844-5");

        ++total; if (sol.backspaceCompare2("ab#c", "ad#c")) ++pass; else System.out.println("FAIL 844-6");
        ++total; if (sol.backspaceCompare2("ab##", "c#d#")) ++pass; else System.out.println("FAIL 844-7");
        ++total; if (!sol.backspaceCompare2("a#c", "b")) ++pass; else System.out.println("FAIL 844-8");
        ++total; if (!sol.backspaceCompare2("bxj##tw", "bxj###tw")) ++pass; else System.out.println("FAIL 844-9");

        if (pass == total) {
            System.out.println("== 0844 PASS ==");
        } else {
            System.out.println("== 0844 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n + m)O(1)
解法二O(n + m)O(n + m)

845. Longest Mountain in Array(数组中的最长山脉)

题目描述

把符合下列属性的数组 arr 称为山脉数组:
1. arr.length >= 3``
2. 存在下标 i(0 < i < arr.length - 1),满足
arr[0] < arr[1] < ... < arr[i-1] < arr[i] 且 arr[i] > arr[i+1] > ... > arr[arr.length-1]
给出一个整数数组 arr,返回最长山脉子数组的长度。如果不存在山脉子数组,返回 0。

示例 1

  • 输入:arr = [2,1,4,7,3,2,5]
  • 输出:5
  • 解释:最长的山脉子数组是 [1,4,7,3,2],长度为 5。

示例 2

  • 输入:arr = [2,2,2]
  • 输出:0
  • 解释:不存在山脉子数组。
    提示:
  • 1 <= arr.length <= 10^4
  • 0 <= arr[i] <= 10^4

解题思路

解法一(预处理上升/下降长度,主提交):left[i] 表示以 i 结尾的最长严格上升连续段长度
(含自身),right[i] 表示以 i 开头的最长严格下降连续段长度。当
left[i] > 1 && right[i] > 1 时 arr[i] 是山峰,长度为 left[i] + right[i] - 1。
解法二(单趟扫描,对照实现):线性扫描找"山脚→峰→再下降"的完整结构:先上升找到峰,再
下降直到谷底,记录长度;过程中跳过单调段,一趟完成 O(1) 额外空间。

代码实现

class Solution {

    // 解法一:预处理上升/下降长度(推荐提交)
    public int longestMountain(int[] arr) {
        int n = arr.length;
        int[] left = new int[n];
        int[] right = new int[n];
        for (int i = 1; i < n; ++i) {
            left[i] = arr[i] > arr[i - 1] ? left[i - 1] + 1 : 0;
        }
        for (int i = n - 2; i >= 0; --i) {
            right[i] = arr[i] > arr[i + 1] ? right[i + 1] + 1 : 0;
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            if (left[i] > 0 && right[i] > 0) {
                ans = Math.max(ans, left[i] + right[i] + 1);
            }
        }
        return ans;
    }

    // 解法二:单趟扫描(对照实现,O(1) 空间)
    public int longestMountain2(int[] arr) {
        int n = arr.length;
        int ans = 0;
        int i = 1;
        while (i < n) {
            // 找上升段起点
            while (i < n && arr[i - 1] >= arr[i]) {
                ++i;
            }
            int upStart = i - 1;
            while (i < n && arr[i - 1] < arr[i]) {
                ++i;
            }
            int peak = i - 1;
            if (peak == upStart) {
                continue;
            }
            while (i < n && arr[i - 1] > arr[i]) {
                ++i;
            }
            int downEnd = i - 1;
            if (downEnd > peak) {
                ans = Math.max(ans, downEnd - upStart + 1);
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.longestMountain(new int[]{2,1,4,7,3,2,5}) == 5) ++pass; else System.out.println("FAIL 845-1");
        ++total; if (sol.longestMountain(new int[]{2,2,2}) == 0) ++pass; else System.out.println("FAIL 845-2");
        ++total; if (sol.longestMountain(new int[]{1,2,3}) == 0) ++pass; else System.out.println("FAIL 845-3");
        ++total; if (sol.longestMountain(new int[]{3,2,1}) == 0) ++pass; else System.out.println("FAIL 845-4");
        ++total; if (sol.longestMountain(new int[]{0,1,2,3,4,5,4,3,2,1,0}) == 11) ++pass; else System.out.println("FAIL 845-5");

        ++total; if (sol.longestMountain2(new int[]{2,1,4,7,3,2,5}) == 5) ++pass; else System.out.println("FAIL 845-6");
        ++total; if (sol.longestMountain2(new int[]{2,2,2}) == 0) ++pass; else System.out.println("FAIL 845-7");
        ++total; if (sol.longestMountain2(new int[]{1,2,3}) == 0) ++pass; else System.out.println("FAIL 845-8");
        ++total; if (sol.longestMountain2(new int[]{0,1,2,3,4,5,4,3,2,1,0}) == 11) ++pass; else System.out.println("FAIL 845-9");

        if (pass == total) {
            System.out.println("== 0845 PASS ==");
        } else {
            System.out.println("== 0845 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n)O(n)
解法二O(n)O(1)

846. Hand of Straights(一手顺子)

题目描述

Alice 手中有一把牌,她想要重新排列这些牌,分成若干组,使每一组的牌数都是 groupSize,
并且由 groupSize 张连续的牌组成。
给你一个整数数组 hand 其中 hand[i] 是写在第 i 张牌上的数值。如果她可能重新排列这些
牌,返回 true;否则,返回 false。

示例 1

  • 输入:hand = [1,2,3,6,2,3,4,7,8], groupSize = 3
  • 输出:true
  • 解释:Alice 手中的牌可以被重新排列为 [1,2,3],[2,3,4],[6,7,8]。

示例 2

  • 输入:hand = [1,2,3,4,5], groupSize = 4
  • 输出:false
    提示:
  • 1 <= hand.length <= 10^4
  • 0 <= hand[i] <= 10^9
  • 1 <= groupSize <= hand.length

解题思路

解法一(哈希计数 + 排序贪心,主提交):若总张数不能被 groupSize 整除直接失败。用哈希表
统计每种牌面数量,牌面升序排序。遍历排序后的牌面,若当前牌面仍有剩余,则以它为起点连续
取 groupSize 张:从 x 到 x + groupSize - 1 各减一,缺牌即失败。
解法二(TreeMap 计数 + 逐段消费,对照实现):直接用平衡树(TreeMap)维护牌面 → 数量,
每次取最小的有剩余牌面作为组起点,连续 groupSize 张依次消费,任何一张不足则失败。

代码实现

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
class Solution {

    // 解法一:哈希计数 + 排序贪心(推荐提交)
    public boolean isNStraightHand(int[] hand, int groupSize) {
        int n = hand.length;
        if (n % groupSize != 0) {
            return false;
        }
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int x : hand) {
            cnt.put(x, cnt.getOrDefault(x, 0) + 1);
        }
        int[] sorted = new int[n];
        System.arraycopy(hand, 0, sorted, 0, n);
        java.util.Arrays.sort(sorted);
        for (int x : sorted) {
            if (cnt.getOrDefault(x, 0) == 0) {
                continue;
            }
            for (int y = x; y < x + groupSize; ++y) {
                int c = cnt.getOrDefault(y, 0);
                if (c == 0) {
                    return false;
                }
                if (c == 1) {
                    cnt.remove(y);
                } else {
                    cnt.put(y, c - 1);
                }
            }
        }
        return true;
    }

    // 解法二:TreeMap 逐段消费(对照实现)
    public boolean isNStraightHand2(int[] hand, int groupSize) {
        int n = hand.length;
        if (n % groupSize != 0) {
            return false;
        }
        TreeMap<Integer, Integer> tm = new TreeMap<>();
        for (int x : hand) {
            tm.put(x, tm.getOrDefault(x, 0) + 1);
        }
        while (!tm.isEmpty()) {
            int start = tm.firstKey();
            for (int y = start; y < start + groupSize; ++y) {
                int c = tm.getOrDefault(y, 0);
                if (c == 0) {
                    return false;
                }
                if (c == 1) {
                    tm.remove(y);
                } else {
                    tm.put(y, c - 1);
                }
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.isNStraightHand(new int[]{1,2,3,6,2,3,4,7,8}, 3)) ++pass; else System.out.println("FAIL 846-1");
        ++total; if (!sol.isNStraightHand(new int[]{1,2,3,4,5}, 4)) ++pass; else System.out.println("FAIL 846-2");
        ++total; if (sol.isNStraightHand(new int[]{1,2,3}, 1)) ++pass; else System.out.println("FAIL 846-3");
        ++total; if (!sol.isNStraightHand(new int[]{8,10,12}, 3)) ++pass; else System.out.println("FAIL 846-4");
        ++total; if (sol.isNStraightHand(new int[]{1,2,4,5}, 2)) ++pass; else System.out.println("FAIL 846-5");

        ++total; if (sol.isNStraightHand2(new int[]{1,2,3,6,2,3,4,7,8}, 3)) ++pass; else System.out.println("FAIL 846-6");
        ++total; if (!sol.isNStraightHand2(new int[]{1,2,3,4,5}, 4)) ++pass; else System.out.println("FAIL 846-7");
        ++total; if (sol.isNStraightHand2(new int[]{1,2,4,5}, 2)) ++pass; else System.out.println("FAIL 846-8");

        if (pass == total) {
            System.out.println("== 0846 PASS ==");
        } else {
            System.out.println("== 0846 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n log n)O(n)
解法二O(n log n)O(n)

847. Shortest Path Visiting All Nodes(访问所有节点的最短路径)

题目描述

存在一个由 n 个节点组成的无向连通图,图中的节点按从 0 到 n - 1 编号。
给你一个数组 graph 表示这个图。其中,graph[i] 是一个列表,由所有与节点 i 直接相连
的节点组成。
返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,
并且可以重用边。

示例 1

  • 输入:graph = [[1,2,3],[0],[0],[0]]
  • 输出:4
  • 解释:一种可能的路径为 [1,0,2,0,3]

示例 2

  • 输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]]
  • 输出:4
  • 解释:一种可能的路径为 [0,1,4,2,3]
    提示:
  • n == graph.length
  • 1 <= n <= 12
  • 0 <= graph[i].length < n
  • 输入的图总是连通图

解题思路

解法一(状态压缩 BFS,主提交):状态为 (当前节点 i, 已访问集合 mask),共 n * 2^n 个状态。
将每个节点作为起点入队,逐层 BFS,第一次出现 mask 为全 1 时的层数即最短路径长度。
解法二(状压 DP,对照实现):dp[mask][i] 表示已访问集合为 mask 且当前位于节点 i 的
最短步数,初始 dp[1<<i][i] = 0,按 mask 递增顺序松弛:从 i 走到邻接 j 时
dp[mask|1<<j][j] = min(dp[mask|1<<j][j], dp[mask][i] + 1),答案取 mask 为全集时的最小值。

代码实现

class Solution {

    // 解法一:状态压缩 BFS(推荐提交)
    public int shortestPathLength(int[][] graph) {
        int n = graph.length;
        int full = (1 << n) - 1;
        boolean[][] vis = new boolean[n][1 << n];
        java.util.ArrayDeque<int[]> q = new java.util.ArrayDeque<>();
        for (int i = 0; i < n; ++i) {
            q.offer(new int[]{i, 1 << i, 0});
            vis[i][1 << i] = true;
        }
        while (!q.isEmpty()) {
            int[] cur = q.poll();
            int u = cur[0], mask = cur[1], d = cur[2];
            if (mask == full) {
                return d;
            }
            for (int v : graph[u]) {
                int nmask = mask | (1 << v);
                if (!vis[v][nmask]) {
                    vis[v][nmask] = true;
                    q.offer(new int[]{v, nmask, d + 1});
                }
            }
        }
        return -1;
    }

    // 解法二:状压 DP(对照实现)
    public int shortestPathLength2(int[][] graph) {
        int n = graph.length;
        int full = (1 << n) - 1;
        int INF = Integer.MAX_VALUE / 2;
        int[][] dp = new int[1 << n][n];
        for (int[] row : dp) {
            java.util.Arrays.fill(row, INF);
        }
        for (int i = 0; i < n; ++i) {
            dp[1 << i][i] = 0;
        }
        // 同一 mask 内的游走(重访已访问节点)会级联更新,采用 Bellman-Ford 式
        // 反复松弛直到无更新,保证任何长度的最优路径都能收敛
        boolean updated = true;
        while (updated) {
            updated = false;
            for (int mask = 0; mask <= full; ++mask) {
                for (int u = 0; u < n; ++u) {
                    if (dp[mask][u] == INF) {
                        continue;
                    }
                    for (int v : graph[u]) {
                        int nmask = mask | (1 << v);
                        if (dp[mask][u] + 1 < dp[nmask][v]) {
                            dp[nmask][v] = dp[mask][u] + 1;
                            updated = true;
                        }
                    }
                }
            }
        }
        int ans = INF;
        for (int i = 0; i < n; ++i) {
            ans = Math.min(ans, dp[full][i]);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.shortestPathLength(new int[][]{{1,2,3},{0},{0},{0}}) == 4) ++pass; else System.out.println("FAIL 847-1");
        ++total; if (sol.shortestPathLength(new int[][]{{1},{0,2,4},{1,3,4},{2},{1,2}}) == 4) ++pass; else System.out.println("FAIL 847-2");
        ++total; if (sol.shortestPathLength(new int[][]{{1},{0}}) == 1) ++pass; else System.out.println("FAIL 847-3");
        ++total; if (sol.shortestPathLength(new int[][]{{}}) == 0) ++pass; else System.out.println("FAIL 847-4");
        ++total; if (sol.shortestPathLength(new int[][]{{1,2,3,4},{0,2,3,4},{0,1,3,4},{0,1,2,4},{0,1,2,3}}) == 4) ++pass; else System.out.println("FAIL 847-5");

        ++total; if (sol.shortestPathLength2(new int[][]{{1,2,3},{0},{0},{0}}) == 4) ++pass; else System.out.println("FAIL 847-6");
        ++total; if (sol.shortestPathLength2(new int[][]{{1},{0,2,4},{1,3,4},{2},{1,2}}) == 4) ++pass; else System.out.println("FAIL 847-7");
        ++total; if (sol.shortestPathLength2(new int[][]{{1},{0}}) == 1) ++pass; else System.out.println("FAIL 847-8");
        ++total; if (sol.shortestPathLength2(new int[][]{{}}) == 0) ++pass; else System.out.println("FAIL 847-9");

        if (pass == total) {
            System.out.println("== 0847 PASS ==");
        } else {
            System.out.println("== 0847 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n^2 * 2^n)O(n * 2^n)
解法二O(n^2 * 2^n)O(n * 2^n)

848. Shifting Letters(字母移位)

题目描述

有一个由小写字母组成的字符串 s,和一个长度相同的整数数组 shifts。
我们将字母表中的下一个字母称为原字母的移位 shift()(由于字母表是环绕的,'z' 将会变成
'a')。例如,shift('a') = 'b',shift('t') = 'u',以及 shift('z') = 'a'。
对于每个 shifts[i] = x,我们会将 s 中的前 i + 1 个字母移位 x 次。
返回将所有这些移位都应用到 s 后最终得到的字符串。

示例 1

  • 输入:s = "abc", shifts = [3,5,9]
  • 输出:"rpl"

示例 2

  • 输入:s = "aaa", shifts = [1,2,3]
  • 输出:"gfd"
    提示:
  • 1 <= s.length <= 10^5
  • s 由小写英文字母组成
  • shifts.length == s.length
  • 0 <= shifts[i] <= 10^9

解题思路

解法一(后缀和,主提交):第 i 个字符实际被移位的总次数为 `shifts[i] + shifts[i+1] + ...

  • shifts[n-1],即后缀和。从右向左累加(用 long 防溢出),对 26 取模后改写该位字符。 解法二(差分数组模拟,对照实现):对每个 shifts[i]做区间加法[0, i] += shifts[i]`,
    用差分数组 O(1) 完成每次区间更新,最后前缀和还原每个位置的总移位次数,再逐位取模变换。

代码实现

class Solution {

    // 解法一:后缀和(推荐提交)
    public String shiftingLetters(String s, int[] shifts) {
        char[] cs = s.toCharArray();
        int n = cs.length;
        long t = 0;
        for (int i = n - 1; i >= 0; --i) {
            t += shifts[i];
            int j = (int) ((cs[i] - 'a' + t) % 26);
            cs[i] = (char) ('a' + j);
        }
        return new String(cs);
    }

    // 解法二:差分数组区间加法(对照实现)
    public String shiftingLetters2(String s, int[] shifts) {
        int n = s.length();
        long[] diff = new long[n + 1];
        for (int i = 0; i < n; ++i) {
            diff[0] += shifts[i];      // [0, i] 区间加
            diff[i + 1] -= shifts[i];
        }
        char[] cs = s.toCharArray();
        long cur = 0;
        for (int i = 0; i < n; ++i) {
            cur += diff[i];
            int j = (int) ((cs[i] - 'a' + cur) % 26);
            cs[i] = (char) ('a' + j);
        }
        return new String(cs);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.shiftingLetters("abc", new int[]{3,5,9}).equals("rpl")) ++pass; else System.out.println("FAIL 848-1");
        ++total; if (sol.shiftingLetters("aaa", new int[]{1,2,3}).equals("gfd")) ++pass; else System.out.println("FAIL 848-2");
        ++total; if (sol.shiftingLetters("z", new int[]{1}).equals("a")) ++pass; else System.out.println("FAIL 848-3");
        ++total; if (sol.shiftingLetters("abc", new int[]{26,26,26}).equals("abc")) ++pass; else System.out.println("FAIL 848-4");

        ++total; if (sol.shiftingLetters2("abc", new int[]{3,5,9}).equals("rpl")) ++pass; else System.out.println("FAIL 848-5");
        ++total; if (sol.shiftingLetters2("aaa", new int[]{1,2,3}).equals("gfd")) ++pass; else System.out.println("FAIL 848-6");
        ++total; if (sol.shiftingLetters2("z", new int[]{1}).equals("a")) ++pass; else System.out.println("FAIL 848-7");
        ++total; if (sol.shiftingLetters2("abc", new int[]{26,26,26}).equals("abc")) ++pass; else System.out.println("FAIL 848-8");

        if (pass == total) {
            System.out.println("== 0848 PASS ==");
        } else {
            System.out.println("== 0848 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n)O(1)
解法二O(n)O(n)

849. Maximize Distance to Closest Person(到最近的人的最大距离)

题目描述

给你一个数组 seats 表示一排座位,其中 seats[i] = 1 代表有人坐在第 i 个座位上,
seats[i] = 0 代表座位 i 上是空的(下标从 0 开始)。
至少有一个空座位,且至少有一人已经坐在座位上。
亚历克斯希望坐在一个能够使他与离他最近的人之间的距离达到最大化的座位上。
返回他到离他最近的人的最大距离。

示例 1

  • 输入:seats = [1,0,0,0,1,0,1]
  • 输出:2
  • 解释:如果亚历克斯坐在第二个空位(seats[2])上,他到离他最近的人的距离为 2。

示例 2

  • 输入:seats = [1,0,0,0]
  • 输出:3

示例 3

  • 输入:seats = [0,1]
  • 输出:1
    提示:
  • 2 <= seats.length <= 2 * 10^4
  • seats[i] 为 0 或 1
  • 至少有一个空座位
  • 至少有一个座位上有人

解题思路

解法一(一次遍历维护首尾与间隔,主提交):记录最先出现的人 first、最后出现的人 last
以及相邻有人座位的最大间隔 d。答案 = max(最左端空段长度 first, 最右端空段长度
n-1-last, 中间最大间隔的一半 d/2)。
解法二(双扫描最近距离,对照实现):正向扫描求得每个空位到左侧最近人的距离,反向扫描求到
右侧最近人的距离,两者取小即该空位到最近人的距离,再取所有空位的最大值。

代码实现

class Solution {

    // 解法一:一次遍历(推荐提交)
    public int maxDistToClosest(int[] seats) {
        int n = seats.length;
        int first = -1, last = -1;
        int maxGap = 0;
        for (int i = 0; i < n; ++i) {
            if (seats[i] == 1) {
                if (first == -1) {
                    first = i;
                } else {
                    maxGap = Math.max(maxGap, i - last);
                }
                last = i;
            }
        }
        int ans = 0;
        if (first > 0) {
            ans = Math.max(ans, first);
        }
        ans = Math.max(ans, n - 1 - last);
        ans = Math.max(ans, maxGap / 2);
        return ans;
    }

    // 解法二:双扫描最近距离(对照实现)
    public int maxDistToClosest2(int[] seats) {
        int n = seats.length;
        int[] left = new int[n];
        int[] right = new int[n];
        java.util.Arrays.fill(left, Integer.MAX_VALUE / 2);
        java.util.Arrays.fill(right, Integer.MAX_VALUE / 2);
        int last = -1;
        for (int i = 0; i < n; ++i) {
            if (seats[i] == 1) {
                last = i;
            } else if (last != -1) {
                left[i] = i - last;
            }
        }
        last = -1;
        for (int i = n - 1; i >= 0; --i) {
            if (seats[i] == 1) {
                last = i;
            } else if (last != -1) {
                right[i] = last - i;
            }
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            if (seats[i] == 0) {
                ans = Math.max(ans, Math.min(left[i], right[i]));
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.maxDistToClosest(new int[]{1,0,0,0,1,0,1}) == 2) ++pass; else System.out.println("FAIL 849-1");
        ++total; if (sol.maxDistToClosest(new int[]{1,0,0,0}) == 3) ++pass; else System.out.println("FAIL 849-2");
        ++total; if (sol.maxDistToClosest(new int[]{0,1}) == 1) ++pass; else System.out.println("FAIL 849-3");
        ++total; if (sol.maxDistToClosest(new int[]{0,0,0,1}) == 3) ++pass; else System.out.println("FAIL 849-4");
        ++total; if (sol.maxDistToClosest(new int[]{0,1,1,0,1,0,0,0}) == 3) ++pass; else System.out.println("FAIL 849-5");

        ++total; if (sol.maxDistToClosest2(new int[]{1,0,0,0,1,0,1}) == 2) ++pass; else System.out.println("FAIL 849-6");
        ++total; if (sol.maxDistToClosest2(new int[]{1,0,0,0}) == 3) ++pass; else System.out.println("FAIL 849-7");
        ++total; if (sol.maxDistToClosest2(new int[]{0,1}) == 1) ++pass; else System.out.println("FAIL 849-8");
        ++total; if (sol.maxDistToClosest2(new int[]{0,0,0,1}) == 3) ++pass; else System.out.println("FAIL 849-9");

        if (pass == total) {
            System.out.println("== 0849 PASS ==");
        } else {
            System.out.println("== 0849 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n)O(1)
解法二O(n)O(n)

850. Rectangle Area II(矩形面积 II)

题目描述

给你一个轴对齐的二维数组 rectangles。对于 rectangle[i] = [x1, y1, x2, y2],其中
(x1, y1) 是该矩形左下角的坐标,(x2, y2) 是该矩形右上角的坐标。
计算平面中所有 rectangles 所覆盖的总面积。任何被两个或多个矩形覆盖的区域应只计算一次。
返回总面积。因为答案可能太大,返回 10^9 + 7 的模。

示例 1

  • 输入:rectangles = [[0,0,2,2],[1,0,2,3],[1,0,3,1]]
  • 输出:6

示例 2

  • 输入:rectangles = [[0,0,1000000000,1000000000]]
  • 输出:49
  • 解释:答案是 10^18 对 (10^9 + 7) 取模的结果,即 49。
    提示:
  • 1 <= rectangles.length <= 200
  • 0 <= x1, y1, x2, y2 <= 10^9
  • 所有矩阵面积不为 0

解题思路

解法一(离散化 + 扫描线,主提交):把所有 x 坐标与 y 坐标分别离散化。枚举每一对相邻
x 坐标 (xs[i], xs[i+1]) 形成的竖条,统计在该竖条内被至少一个矩形覆盖的 y 区间,累加
(xs[i+1] - xs[i]) * 覆盖的 y 总长度。覆盖 y 长度用差分标记后扫描即可。
解法二(y 轴差分扫描,对照实现):按 y 值从低到高维护「当前被覆盖的 x 总长度」:把每个
矩形的 y 下边作为 +1 事件、y 上边作为 -1 事件,对每个 y 层计算 (当前层高) * (覆盖的 x
长度),覆盖 x 长度用另一个差分数组求并。结果一致。

代码实现

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class Solution {

    private static final int MOD = 1_000_000_007;

    // 解法一:x 离散化 + 逐竖条统计(推荐提交)
    public int rectangleArea(int[][] rectangles) {
        int n = rectangles.length;
        List<Long> xs = new ArrayList<>();
        List<Long> ys = new ArrayList<>();
        for (int[] r : rectangles) {
            xs.add((long) r[0]);
            xs.add((long) r[2]);
            ys.add((long) r[1]);
            ys.add((long) r[3]);
        }
        xs = xs.stream().distinct().sorted().collect(java.util.stream.Collectors.toList());
        ys = ys.stream().distinct().sorted().collect(java.util.stream.Collectors.toList());

        int mx = xs.size();  // 每对相邻 xs 是一个竖条
        long ans = 0;
        for (int i = 0; i + 1 < mx; ++i) {
            long x1 = xs.get(i), x2 = xs.get(i + 1);
            if (x1 == x2) {
                continue;
            }
            // 统计在 [x1, x2) 内被覆盖的 y 区间总长度
            List<long[]> seg = new ArrayList<>();
            for (int[] r : rectangles) {
                if (r[0] <= x1 && x2 <= r[2]) {
                    seg.add(new long[]{r[1], r[3]});
                }
            }
            if (seg.isEmpty()) {
                continue;
            }
            long cover = 0;
            long curY = Long.MIN_VALUE;
            long startY = 0;
            boolean in = false;
            // 按 y 起点排序后合并区间
            seg.sort((a, b) -> Long.compare(a[0], b[0]));
            for (long[] s : seg) {
                long y1 = s[0], y2 = s[1];
                if (!in) {
                    startY = y1;
                    curY = y2;
                    in = true;
                } else if (y1 <= curY) {
                    curY = Math.max(curY, y2);
                } else {
                    cover += curY - startY;
                    startY = y1;
                    curY = y2;
                }
            }
            if (in) {
                cover += curY - startY;
            }
            ans = (ans + ((x2 - x1) % MOD) * (cover % MOD)) % MOD;
        }
        return (int) ans;
    }

    // 解法二:y 层差分扫描(对照实现)
    public int rectangleArea2(int[][] rectangles) {
        List<Long> ys = new ArrayList<>();
        for (int[] r : rectangles) {
            ys.add((long) r[1]);
            ys.add((long) r[3]);
        }
        ys.sort((a, b) -> Long.compare(a, b));
        // 去重
        List<Long> yList = new ArrayList<>();
        for (long y : ys) {
            if (yList.isEmpty() || yList.get(yList.size() - 1) != y) {
                yList.add(y);
            }
        }
        int m = yList.size();
        long ans = 0;
        for (int i = 0; i + 1 < m; ++i) {
            long y1 = yList.get(i), y2 = yList.get(i + 1);
            if (y1 == y2) {
                continue;
            }
            // 收集覆盖该 y 层 [y1, y2) 的矩形 x 区间
            List<long[]> seg = new ArrayList<>();
            for (int[] r : rectangles) {
                if (r[1] <= y1 && y2 <= r[3]) {
                    seg.add(new long[]{r[0], r[2]});
                }
            }
            if (seg.isEmpty()) {
                continue;
            }
            seg.sort((a, b) -> Long.compare(a[0], b[0]));
            long cover = 0;
            long start = 0, cur = 0;
            boolean in = false;
            for (long[] s : seg) {
                if (!in) {
                    start = s[0];
                    cur = s[1];
                    in = true;
                } else if (s[0] <= cur) {
                    cur = Math.max(cur, s[1]);
                } else {
                    cover += cur - start;
                    start = s[0];
                    cur = s[1];
                }
            }
            if (in) {
                cover += cur - start;
            }
            ans = (ans + ((y2 - y1) % MOD) * (cover % MOD)) % MOD;
        }
        return (int) ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;

        ++total; if (sol.rectangleArea(new int[][]{{0,0,2,2},{1,0,2,3},{1,0,3,1}}) == 6) ++pass; else System.out.println("FAIL 850-1");
        ++total; if (sol.rectangleArea(new int[][]{{0,0,1000000000,1000000000}}) == 49) ++pass; else System.out.println("FAIL 850-2");
        ++total; if (sol.rectangleArea(new int[][]{{0,0,1,1}}) == 1) ++pass; else System.out.println("FAIL 850-3");
        ++total; if (sol.rectangleArea(new int[][]{{0,0,2,2},{1,1,3,3}}) == 7) ++pass; else System.out.println("FAIL 850-4");
        ++total; if (sol.rectangleArea(new int[][]{{0,0,2,2},{2,0,4,2},{0,2,2,4},{2,2,4,4}}) == 16) ++pass; else System.out.println("FAIL 850-5");

        ++total; if (sol.rectangleArea2(new int[][]{{0,0,2,2},{1,0,2,3},{1,0,3,1}}) == 6) ++pass; else System.out.println("FAIL 850-6");
        ++total; if (sol.rectangleArea2(new int[][]{{0,0,2,2},{1,1,3,3}}) == 7) ++pass; else System.out.println("FAIL 850-7");
        ++total; if (sol.rectangleArea2(new int[][]{{0,0,1000000000,1000000000}}) == 49) ++pass; else System.out.println("FAIL 850-8");

        if (pass == total) {
            System.out.println("== 0850 PASS ==");
        } else {
            System.out.println("== 0850 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n^2)O(n)
解法二O(n^2)O(n)