LeetCode690~700题目解析

4 阅读1分钟

LeetCode 690-700. 题解合集


690. Employee Importance(员工的重要性)

题目描述

你有一个保存员工信息的数据结构,它包含了员工唯一的 id、重要度和直系下属的 id。
给定一个员工数组 employees,其中:

  • employees[i].id 是第 i 个员工的 ID;
  • employees[i].importance 是第 i 个员工的重要度;
  • employees[i].subordinates 是第 i 名员工的直接下属的 ID 列表。
    给定一个整数 id 表示一个员工的 ID,返回这个员工和他所有下属的重要度的总和。

示例 1

  • 输入:employees = [[1,5,[2,3]],[2,3,[]],[3,3,[]]], id = 1
  • 输出:11
  • 解释:员工 1 自身的重要度是 5,他有两个直系下属 2 和 3,而且 2 和 3 的重要度均为 3。
    因此员工 1 的总重要度是 5 + 3 + 3 = 11。

示例 2

  • 输入:employees = [[1,2,[5]],[5,-3,[]]], id = 5
  • 输出:-3
  • 解释:员工 5 的重要度为 -3 并且没有直接下属。因此,员工 5 的总重要度为 -3。
    提示:
  • 1 <= employees.length <= 2000
  • 1 <= employees[i].id <= 2000
  • 所有的 employees[i].id 互不相同
  • -100 <= employees[i].importance <= 100
  • 一名员工最多有一名直接领导,并可能有多名下属
  • employees[i].subordinates 中的 ID 都有效

解题思路

解法一(哈希表 + DFS,主提交):先把所有员工按 id 存入哈希表。从给定 id 出发深度优先
遍历:返回自身重要度加上所有下属递归结果之和。因为每个员工 id 唯一且下属关系构成树,
直接递归即可,不需要访问标记。时间复杂度 O(n),空间复杂度 O(n)。
解法二(哈希表 + BFS):同样用哈希表建立 id 到员工的映射,从给定 id 开始广度优先逐层
累加重要度。使用队列按层处理,避免深度递归,适合链很深的情况。时间复杂度 O(n),
空间复杂度 O(n)。

代码实现

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

class Solution {

    // 解法一:哈希表 + DFS
    private final Map<Integer, Employee> d = new HashMap<>();

    public int getImportance(List<Employee> employees, int id) {
        for (Employee e : employees) {
            d.put(e.id, e);
        }
        return dfs(id);
    }

    private int dfs(int id) {
        Employee e = d.get(id);
        int s = e.importance;
        for (int sub : e.subordinates) {
            s += dfs(sub);
        }
        return s;
    }

    // 解法二:哈希表 + BFS
    public int getImportance2(List<Employee> employees, int id) {
        Map<Integer, Employee> map = new HashMap<>();
        for (Employee e : employees) {
            map.put(e.id, e);
        }
        Deque<Integer> q = new ArrayDeque<>();
        q.offer(id);
        int s = 0;
        while (!q.isEmpty()) {
            Employee e = map.get(q.poll());
            s += e.importance;
            q.addAll(e.subordinates);
        }
        return s;
    }

    // 与 LeetCode 相同的数据结构
    public static class Employee {
        public int id;
        public int importance;
        public List<Integer> subordinates;

        public Employee(int id, int importance, List<Integer> subordinates) {
            this.id = id;
            this.importance = importance;
            this.subordinates = subordinates;
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        List<Employee> emp1 = new ArrayList<>();
        emp1.add(new Employee(1, 5, Arrays.asList(2, 3)));
        emp1.add(new Employee(2, 3, Arrays.asList()));
        emp1.add(new Employee(3, 3, Arrays.asList()));
        ++total; if (sol.getImportance(emp1, 1) == 11) ++pass; else System.out.println("FAIL test1: " + sol.getImportance(emp1, 1));
        List<Employee> emp2 = new ArrayList<>();
        emp2.add(new Employee(1, 2, Arrays.asList(5)));
        emp2.add(new Employee(5, -3, Arrays.asList()));
        ++total; if (sol.getImportance(emp2, 5) == -3) ++pass; else System.out.println("FAIL test2: " + sol.getImportance(emp2, 5));
        List<Employee> emp3 = new ArrayList<>();
        emp3.add(new Employee(1, 5, Arrays.asList(2, 3)));
        emp3.add(new Employee(2, 3, Arrays.asList(4)));
        emp3.add(new Employee(3, 4, Arrays.asList()));
        emp3.add(new Employee(4, 2, Arrays.asList()));
        ++total; if (sol.getImportance(emp3, 1) == 14) ++pass; else System.out.println("FAIL test3: " + sol.getImportance(emp3, 1));
        // 解法二
        ++total; if (sol.getImportance2(emp1, 1) == 11) ++pass; else System.out.println("FAIL test4: " + sol.getImportance2(emp1, 1));
        ++total; if (sol.getImportance2(emp2, 5) == -3) ++pass; else System.out.println("FAIL test5: " + sol.getImportance2(emp2, 5));
        ++total; if (sol.getImportance2(emp3, 1) == 14) ++pass; else System.out.println("FAIL test6: " + sol.getImportance2(emp3, 1));
        if (pass == total) {
            System.out.println("== 0690 PASS ==");
        } else {
            System.out.println("== 0690 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

691. Stickers to Spell Word(贴纸拼词)

题目描述

我们有 n 种不同的贴纸。每个贴纸上都有一个小写的英文单词。
您想要拼写出给定的字符串 target,方法是从收集的贴纸中切割单个字母并重新排列它们。
如果你愿意,你可以多次使用每个贴纸,每个贴纸的数量是无限的。
返回你需要拼出 target 的最小贴纸数量。如果任务不可能,则返回 -1。
注意:在所有的测试用例中,所有的单词都是从 1000 个最常见的美国英语单词中随机选择的,
并且 target 被选择为两个随机单词的连接。

示例 1

  • 输入:stickers = ["with","example","science"], target = "thehat"
  • 输出:3
  • 解释:我们可以使用 2 个 "with" 贴纸,和 1 个 "example" 贴纸。把贴纸上的字母剪下来并
    重新排列后,就可以形成目标 "thehat" 了。此外,这是形成目标字符串所需的最小贴纸数量。

示例 2

  • 输入:stickers = ["notice","possible"], target = "basicbasic"
  • 输出:-1
  • 解释:我们不能通过剪切给定贴纸的字母来形成目标 "basicbasic"。
    提示:
  • n == stickers.length
  • 1 <= n <= 50
  • 1 <= stickers[i].length <= 10
  • 1 <= target.length <= 15
  • stickers[i] 和 target 由小写英文单词组成

解题思路

解法一(BFS + 状态压缩,主提交):target 长度不超过 15,用一个 15 位二进制数表示
target 各位是否已被拼出。从状态 0 开始做 BFS,每一层代表使用贴纸的数量:枚举每张贴纸,
对当前未覆盖的字符位,若贴纸中有该字母则覆盖到 nxt 状态。BFS 首次到达
(1 << n) - 1(全部拼出)的层数就是最少贴纸数。时间复杂度 O(2^n × m × l),
空间复杂度 O(2^n)×(2^n)。
解法二(DFS 记忆化 + 状态压缩):定义 dfs(state) 表示已覆盖集合为 state 时,
拼出剩余字母需要的最少贴纸数。对每张贴纸计算它能从剩余字母中消耗多少,递归求解并记忆化。
相比 BFS 可以跳过完全无贡献的贴纸。时间复杂度 O(2^n × m × l),空间复杂度 O(2^n)。

代码实现

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;

class Solution {

    // 解法一:BFS + 状态压缩
    public int minStickers(String[] stickers, String target) {
        int n = target.length();
        Deque<Integer> q = new ArrayDeque<>();
        q.offer(0);
        boolean[] vis = new boolean[1 << n];
        vis[0] = true;
        for (int ans = 0; !q.isEmpty(); ++ans) {
            for (int size = q.size(); size > 0; --size) {
                int cur = q.poll();
                if (cur == (1 << n) - 1) {
                    return ans;
                }
                for (String s : stickers) {
                    int[] cnt = new int[26];
                    for (char c : s.toCharArray()) {
                        ++cnt[c - 'a'];
                    }
                    int nxt = cur;
                    for (int i = 0; i < n; ++i) {
                        int j = target.charAt(i) - 'a';
                        if ((cur >> i & 1) == 0 && cnt[j] > 0) {
                            --cnt[j];
                            nxt |= 1 << i;
                        }
                    }
                    if (!vis[nxt]) {
                        vis[nxt] = true;
                        q.offer(nxt);
                    }
                }
            }
        }
        return -1;
    }

    // 解法二:DFS 记忆化 + 状态压缩
    private int[] memo;
    private int n2;
    private int[] stickerMask;

    public int minStickers2(String[] stickers, String target) {
        n2 = target.length();
        // 把每张贴纸转成字母计数数组
        int[][] cntArr = new int[stickers.length][26];
        for (int i = 0; i < stickers.length; ++i) {
            for (char c : stickers[i].toCharArray()) {
                ++cntArr[i][c - 'a'];
            }
        }
        // 预计算每张贴纸能覆盖的字符位集合
        stickerMask = new int[stickers.length];
        for (int i = 0; i < stickers.length; ++i) {
            int mask = 0;
            for (int j = 0; j < n2; ++j) {
                int idx = target.charAt(j) - 'a';
                if (cntArr[i][idx] > 0) {
                    mask |= 1 << j;
                }
            }
            stickerMask[i] = mask;
        }
        memo = new int[1 << n2];
        Arrays.fill(memo, -1);
        int res = dfs2(0, target, cntArr);
        return res >= Integer.MAX_VALUE / 2 ? -1 : res;
    }

    private int dfs2(int state, String target, int[][] cntArr) {
        if (state == (1 << n2) - 1) {
            return 0;
        }
        if (memo[state] != -1) {
            return memo[state];
        }
        int best = Integer.MAX_VALUE / 2;
        for (int i = 0; i < cntArr.length; ++i) {
            // 只有对当前 state 有贡献(覆盖了新的位)的贴纸才有意义
            if ((stickerMask[i] & ~state) == 0) {
                continue;
            }
            int[] cnt = cntArr[i].clone();
            int nxt = state;
            for (int j = 0; j < n2; ++j) {
                int idx = target.charAt(j) - 'a';
                if ((state >> j & 1) == 0 && cnt[idx] > 0) {
                    --cnt[idx];
                    nxt |= 1 << j;
                }
            }
            best = Math.min(best, 1 + dfs2(nxt, target, cntArr));
        }
        memo[state] = best;
        return best;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        ++total; if (sol.minStickers(new String[]{"with", "example", "science"}, "thehat") == 3) ++pass; else System.out.println("FAIL test1: " + sol.minStickers(new String[]{"with", "example", "science"}, "thehat"));
        ++total; if (sol.minStickers(new String[]{"notice", "possible"}, "basicbasic") == -1) ++pass; else System.out.println("FAIL test2: " + sol.minStickers(new String[]{"notice", "possible"}, "basicbasic"));
        ++total; if (sol.minStickers(new String[]{"ab", "bc", "ac"}, "abc") == 2) ++pass; else System.out.println("FAIL test3: " + sol.minStickers(new String[]{"ab", "bc", "ac"}, "abc"));
        // 解法二
        ++total; if (sol.minStickers2(new String[]{"with", "example", "science"}, "thehat") == 3) ++pass; else System.out.println("FAIL test4: " + sol.minStickers2(new String[]{"with", "example", "science"}, "thehat"));
        ++total; if (sol.minStickers2(new String[]{"notice", "possible"}, "basicbasic") == -1) ++pass; else System.out.println("FAIL test5: " + sol.minStickers2(new String[]{"notice", "possible"}, "basicbasic"));
        ++total; if (sol.minStickers2(new String[]{"ab", "bc", "ac"}, "abc") == 2) ++pass; else System.out.println("FAIL test6: " + sol.minStickers2(new String[]{"ab", "bc", "ac"}, "abc"));
        if (pass == total) {
            System.out.println("== 0691 PASS ==");
        } else {
            System.out.println("== 0691 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

692. Top K Frequent Words(前K个高频单词)

题目描述

给定一个单词列表 words 和一个整数 k,返回前 k 个出现次数最多的单词。 返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字典顺序排序。

示例 1

  • 输入:words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2
  • 输出: ["i", "love"] 解析: "i" 和 "love" 为出现次数最多的两个单词,均为 2 次。注意,按字母顺序 "i" 在 "love" 之前。

示例 2

  • 输入:["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
  • 输出: ["the", "is", "sunny", "day"] 解析: "the", "is", "sunny" 和 "day" 是出现次数最多的四个单词,出现次数依次为 4, 3, 2 和 1 次。 提示:
  • 1 <= words.length <= 500
  • 1 <= words[i].length <= 10
  • words[i] 由小写英文字母组成
  • k 的取值范围是 [1, 不同 words[i] 的数量]

解题思路

解法一(哈希表 + 排序,主提交):用哈希表统计每个单词出现次数,然后把单词按 (-次数, 字典序) 排序,取前 k 个。时间复杂度 O(n log n),空间复杂度 O(n)。 解法二(哈希表 + 堆):用大小为 k 的小根堆维护“目前最高的 k 个”。堆内按 (次数, 字典序) 排序,次数相同则字典序大的在堆顶(这样从堆中弹走的是字典序大的, 最终按倒序取出后再反转,保证同频词字典序升序)。时间复杂度 O(n log k),空间复杂度 O(n)。

代码实现

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;

class Solution {

    // 解法一:哈希表 + 排序
    public List<String> topKFrequent(String[] words, int k) {
        Map<String, Integer> cnt = new HashMap<>();
        for (String w : words) {
            cnt.put(w, cnt.getOrDefault(w, 0) + 1);
        }
        List<String> keys = new ArrayList<>(cnt.keySet());
        keys.sort((a, b) -> {
            int c1 = cnt.get(a), c2 = cnt.get(b);
            return c1 == c2 ? a.compareTo(b) : c2 - c1;
        });
        return keys.subList(0, k);
    }

    // 解法二:哈希表 + 堆
    public List<String> topKFrequent2(String[] words, int k) {
        Map<String, Integer> cnt = new HashMap<>();
        for (String w : words) {
            cnt.put(w, cnt.getOrDefault(w, 0) + 1);
        }
        // 小根堆:频率小的在顶;同频时字典序大的在顶(越“差”的越先被淘汰)
        PriorityQueue<String> pq = new PriorityQueue<>((a, b) -> {
            int c1 = cnt.get(a), c2 = cnt.get(b);
            return c1 == c2 ? b.compareTo(a) : c1 - c2;
        });
        for (String w : cnt.keySet()) {
            pq.offer(w);
            if (pq.size() > k) {
                pq.poll();
            }
        }
        List<String> ans = new ArrayList<>();
        while (!pq.isEmpty()) {
            ans.add(pq.poll());
        }
        // 堆顶最小,出堆顺序是频率升序;反转得到频率降序
        java.util.Collections.reverse(ans);
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        ++total; if (sol.topKFrequent(new String[]{"i", "love", "leetcode", "i", "love", "coding"}, 2).equals(Arrays.asList("i", "love"))) ++pass; else System.out.println("FAIL test1: " + sol.topKFrequent(new String[]{"i", "love", "leetcode", "i", "love", "coding"}, 2));
        ++total; if (sol.topKFrequent(new String[]{"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"}, 4).equals(Arrays.asList("the", "is", "sunny", "day"))) ++pass; else System.out.println("FAIL test2: " + sol.topKFrequent(new String[]{"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"}, 4));
        ++total; if (sol.topKFrequent(new String[]{"a", "aa", "aaa"}, 1).equals(Arrays.asList("a"))) ++pass; else System.out.println("FAIL test3: " + sol.topKFrequent(new String[]{"a", "aa", "aaa"}, 1));
        // 解法二
        ++total; if (sol.topKFrequent2(new String[]{"i", "love", "leetcode", "i", "love", "coding"}, 2).equals(Arrays.asList("i", "love"))) ++pass; else System.out.println("FAIL test4: " + sol.topKFrequent2(new String[]{"i", "love", "leetcode", "i", "love", "coding"}, 2));
        ++total; if (sol.topKFrequent2(new String[]{"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"}, 4).equals(Arrays.asList("the", "is", "sunny", "day"))) ++pass; else System.out.println("FAIL test5: " + sol.topKFrequent2(new String[]{"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"}, 4));
        ++total; if (sol.topKFrequent2(new String[]{"a", "aa", "aaa"}, 1).equals(Arrays.asList("a"))) ++pass; else System.out.println("FAIL test6: " + sol.topKFrequent2(new String[]{"a", "aa", "aaa"}, 1));
        if (pass == total) {
            System.out.println("== 0692 PASS ==");
        } else {
            System.out.println("== 0692 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

693. Binary Number with Alternating Bits(交替位二进制数)

题目描述

给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位 的数字永不相同。

示例 1

  • 输入:n = 5
  • 输出:true
  • 解释:5 的二进制表示是:101。

示例 2

  • 输入:n = 7
  • 输出:false
  • 解释:7 的二进制表示是:111。

示例 3

  • 输入:n = 11
  • 输出:false
  • 解释:11 的二进制表示是:1011。 提示:
  • 1 <= n <= 2^31 - 1

解题思路

解法一(逐位比较,主提交):反复取最低位 n & 1,然后 n >>= 1;与上一位比较,若相同则 说明出现相邻相同位,返回 false。时间复杂度 O(log n),空间复杂度 O(1)。 解法二(位运算技巧):n ^ (n >> 1) 得到的数其二进制应是全 1(相邻位都不同则异或结果全是 1), 即 x & (x + 1) == 0,其中 x = n ^ (n >> 1)。时间复杂度 O(1),空间复杂度 O(1)。

代码实现

class Solution {

    // 解法一:逐位比较
    public boolean hasAlternatingBits(int n) {
        int prev = n & 1;
        n >>= 1;
        while (n > 0) {
            int cur = n & 1;
            if (cur == prev) {
                return false;
            }
            prev = cur;
            n >>= 1;
        }
        return true;
    }

    // 解法二:异或后应为全 1
    public boolean hasAlternatingBits2(int n) {
        int x = n ^ (n >> 1);
        return (x & (x + 1)) == 0;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        ++total; if (sol.hasAlternatingBits(5)) ++pass; else System.out.println("FAIL test1: " + sol.hasAlternatingBits(5));
        ++total; if (!sol.hasAlternatingBits(7)) ++pass; else System.out.println("FAIL test2");
        ++total; if (!sol.hasAlternatingBits(11)) ++pass; else System.out.println("FAIL test3");
        ++total; if (sol.hasAlternatingBits(10)) ++pass; else System.out.println("FAIL test4: " + sol.hasAlternatingBits(10));
        ++total; if (sol.hasAlternatingBits(1)) ++pass; else System.out.println("FAIL test5: " + sol.hasAlternatingBits(1));
        ++total; if (!sol.hasAlternatingBits(4)) ++pass; else System.out.println("FAIL test6");
        ++total; if (sol.hasAlternatingBits2(5)) ++pass; else System.out.println("FAIL test7");
        ++total; if (!sol.hasAlternatingBits2(7)) ++pass; else System.out.println("FAIL test8");
        ++total; if (!sol.hasAlternatingBits2(11)) ++pass; else System.out.println("FAIL test9");
        ++total; if (sol.hasAlternatingBits2(1)) ++pass; else System.out.println("FAIL test10");
        if (pass == total) {
            System.out.println("== 0693 PASS ==");
        } else {
            System.out.println("== 0693 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

694. Number of Distinct Islands(不同岛屿的数量)

题目描述

给定一个非空 01 二维数组表示的网格,一个岛屿由四连通(上、下、左、右四个方向)的 1 组成, 你可以认为网格的四周被海水包围。 请你计算这个网格中共有多少个形状不同的岛屿。两个岛屿被认为是相同的,当且仅当一个岛屿可以通过 平移变换(不可以旋转、翻转)和另一个岛屿重合。

示例 1

  • 输入:grid = [[1,1,0,0,0],[1,1,0,0,0],[0,0,0,1,1],[0,0,0,1,1]]
  • 输出:1

示例 2

  • 输入:grid = [[1,1,0,1,1],[1,0,0,0,0],[0,0,0,0,1],[1,1,0,1,1]]
  • 输出:3 提示:
  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] 仅包含 0 或 1

解题思路

解法一(DFS + 相对坐标序列,主提交):遍历网格,遇到未访问的陆地开始 DFS;把每个陆地格子的 坐标减去起始格子的坐标,得到相对坐标,把整个岛屿的相对坐标集合作为该岛屿的“签名”。 因为旋转/翻转会得到不同签名,只有平移重合的岛屿签名相同。用哈希集合去重后计数。 时间复杂度 O(m·n),空间复杂度 O(m·n)。 解法二(DFS + 方向字符串):DFS 时记录「前进方向字符 + 回溯方向字符」形成的路径串 (如 DURDL...),不同形状的岛屿序列不同。用集合去重。时间复杂度 O(m·n),空间复杂度 O(m·n)。

代码实现

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

class Solution {

    private static final int[] DX = {-1, 1, 0, 0};
    private static final int[] DY = {0, 0, -1, 1};
    private int m, n;

    // 解法一:相对坐标签名
    public int numDistinctIslands(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        Set<String> islands = new HashSet<>();
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    List<int[]> cells = new ArrayList<>();
                    dfsRel(grid, i, j, i, j, cells);
                    // 转字符串 key:相对坐标排序后以 | 拼接
                    StringBuilder sb = new StringBuilder();
                    cells.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
                    for (int[] c : cells) {
                        sb.append(c[0]).append(',').append(c[1]).append('|');
                    }
                    islands.add(sb.toString());
                }
            }
        }
        return islands.size();
    }

    private void dfsRel(int[][] grid, int si, int sj, int i, int j, List<int[]> cells) {
        grid[i][j] = 0;
        cells.add(new int[] {i - si, j - sj});
        for (int d = 0; d < 4; ++d) {
            int ni = i + DX[d], nj = j + DY[d];
            if (ni >= 0 && ni < m && nj >= 0 && nj < n && grid[ni][nj] == 1) {
                dfsRel(grid, si, sj, ni, nj, cells);
            }
        }
    }

    // 解法二:方向路径字符串
    private StringBuilder path = new StringBuilder();

    public int numDistinctIslands2(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        Set<String> islands = new HashSet<>();
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    path.setLength(0);
                    dfsPath(grid, i, j, 'S'); // S = 起点
                    path.append('E');         // E = 结束标记
                    islands.add(path.toString());
                }
            }
        }
        return islands.size();
    }

    private void dfsPath(int[][] grid, int i, int j, char dir) {
        grid[i][j] = 0;
        path.append(dir);
        char[] dirs = {'D', 'U', 'R', 'L'};
        int[] ni0 = {i + 1, i - 1, i, i};
        int[] nj0 = {j, j, j + 1, j - 1};
        for (int d = 0; d < 4; ++d) {
            int ni = ni0[d], nj = nj0[d];
            if (ni >= 0 && ni < m && nj >= 0 && nj < n && grid[ni][nj] == 1) {
                dfsPath(grid, ni, nj, dirs[d]);
                path.append('B'); // 回溯标记
            }
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[][] g1 = {
            {1, 1, 0, 0, 0},
            {1, 1, 0, 0, 0},
            {0, 0, 0, 1, 1},
            {0, 0, 0, 1, 1}
        };
        ++total; if (sol.numDistinctIslands(g1) == 1) ++pass; else System.out.println("FAIL test1: " + sol.numDistinctIslands(g1));
        int[][] g2 = {
            {1, 1, 0, 1, 1},
            {1, 0, 0, 0, 0},
            {0, 0, 0, 0, 1},
            {1, 1, 0, 1, 1}
        };
        ++total; if (sol.numDistinctIslands(g2) == 3) ++pass; else System.out.println("FAIL test2: " + sol.numDistinctIslands(g2));
        int[][] g3 = {
            {1, 1, 0, 0},
            {1, 1, 0, 0}
        };
        ++total; if (sol.numDistinctIslands(g3) == 1) ++pass; else System.out.println("FAIL test3: " + sol.numDistinctIslands(g3));
        int[][] g4 = {{0}};
        ++total; if (sol.numDistinctIslands(g4) == 0) ++pass; else System.out.println("FAIL test4: " + sol.numDistinctIslands(g4));
        // 解法二
        int[][] g5 = {
            {1, 1, 0, 0, 0},
            {1, 1, 0, 0, 0},
            {0, 0, 0, 1, 1},
            {0, 0, 0, 1, 1}
        };
        ++total; if (sol.numDistinctIslands2(g5) == 1) ++pass; else System.out.println("FAIL test5: " + sol.numDistinctIslands2(g5));
        int[][] g6 = {
            {1, 1, 0, 1, 1},
            {1, 0, 0, 0, 0},
            {0, 0, 0, 0, 1},
            {1, 1, 0, 1, 1}
        };
        ++total; if (sol.numDistinctIslands2(g6) == 3) ++pass; else System.out.println("FAIL test6: " + sol.numDistinctIslands2(g6));
        int[][] g7 = {{0}};
        ++total; if (sol.numDistinctIslands2(g7) == 0) ++pass; else System.out.println("FAIL test7");
        if (pass == total) {
            System.out.println("== 0694 PASS ==");
        } else {
            System.out.println("== 0694 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

695. Max Area of Island(岛屿的最大面积)

题目描述

给你一个大小为 m x n 的二进制矩阵 grid。 岛屿是由一些相邻的 1(代表土地)构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直的四个 方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。 岛屿的面积是岛上值为 1 的单元格的数目。 计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0。

示例 1

  • 输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
  • 输出:6

示例 2

  • 输入:grid = [[0,0,0,0,0,0,0,0]]
  • 输出:0 提示:
  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] 为 0 或 1

解题思路

解法一(DFS 染色,主提交):遍历所有格子,遇到 1 即以该格为起点 DFS,把访问过的陆地改成 0 防止重复计数,累加四方向递归返回的面积。每次 DFS 返回岛屿面积,取全局最大。 时间复杂度 O(m·n),空间复杂度 O(m·n)(递归栈最坏)。 解法二(BFS/迭代栈):改用显式栈模拟 DFS,避免深递归;逻辑相同。 时间复杂度 O(m·n),空间复杂度 O(m·n)。

代码实现

class Solution {

    private static final int[] DX = {-1, 1, 0, 0};
    private static final int[] DY = {0, 0, -1, 1};
    private int m, n;

    // 解法一:递归 DFS
    public int maxAreaOfIsland(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        int best = 0;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    best = Math.max(best, dfs(grid, i, j));
                }
            }
        }
        return best;
    }

    private int dfs(int[][] grid, int i, int j) {
        grid[i][j] = 0;
        int area = 1;
        for (int d = 0; d < 4; ++d) {
            int ni = i + DX[d], nj = j + DY[d];
            if (ni >= 0 && ni < m && nj >= 0 && nj < n && grid[ni][nj] == 1) {
                area += dfs(grid, ni, nj);
            }
        }
        return area;
    }

    // 解法二:显式栈 DFS
    public int maxAreaOfIsland2(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        int best = 0;
        java.util.Deque<int[]> st = new java.util.ArrayDeque<>();
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] != 1) {
                    continue;
                }
                grid[i][j] = 0;
                int area = 0;
                st.push(new int[] {i, j});
                while (!st.isEmpty()) {
                    int[] cur = st.pop();
                    ++area;
                    for (int d = 0; d < 4; ++d) {
                        int ni = cur[0] + DX[d], nj = cur[1] + DY[d];
                        if (ni >= 0 && ni < m && nj >= 0 && nj < n && grid[ni][nj] == 1) {
                            grid[ni][nj] = 0;
                            st.push(new int[] {ni, nj});
                        }
                    }
                }
                best = Math.max(best, area);
            }
        }
        return best;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[][] g1 = {
            {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
            {0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0},
            {0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}
        };
        ++total; if (sol.maxAreaOfIsland(g1) == 6) ++pass; else System.out.println("FAIL test1: " + sol.maxAreaOfIsland(g1));
        int[][] g2 = {{0, 0, 0, 0, 0, 0, 0, 0}};
        ++total; if (sol.maxAreaOfIsland(g2) == 0) ++pass; else System.out.println("FAIL test2: " + sol.maxAreaOfIsland(g2));
        int[][] g3 = {{1}};
        ++total; if (sol.maxAreaOfIsland(g3) == 1) ++pass; else System.out.println("FAIL test3: " + sol.maxAreaOfIsland(g3));
        int[][] g4 = {{1, 1, 0, 1, 1}};
        ++total; if (sol.maxAreaOfIsland(g4) == 2) ++pass; else System.out.println("FAIL test4: " + sol.maxAreaOfIsland(g4));
        // 解法二
        int[][] g5 = {
            {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
            {0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0},
            {0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}
        };
        ++total; if (sol.maxAreaOfIsland2(g5) == 6) ++pass; else System.out.println("FAIL test5");
        int[][] g6 = {{1, 1, 0, 1, 1}};
        ++total; if (sol.maxAreaOfIsland2(g6) == 2) ++pass; else System.out.println("FAIL test6");
        if (pass == total) {
            System.out.println("== 0695 PASS ==");
        } else {
            System.out.println("== 0695 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

696. Count Binary Substrings(计数二进制子串)

题目描述

给定一个字符串 s,统计并返回具有相同数量 0 和 1 的非空(连续)子字符串的数量,并且这些子字符串 中的所有 0 和所有 1 都是成组连续的。 重复出现(不同位置)的子串也要统计它们出现的次数。

示例 1

  • 输入:s = "00110011"
  • 输出:6
  • 解释:6 个子串满足具有相同数量的连续 1 和 0 :"0011"、"01"、"1100"、"10"、"0011" 和 "01"。

示例 2

  • 输入:s = "10101"
  • 输出:4 提示:
  • 1 <= s.length <= 10^5
  • s[i] 为 '0' 或 '1'

解题思路

解法一(连续段扫描,主提交):把字符串按连续相同字符切分成段,如 00110011 → 段长 [2,2,2,2]。 相邻两段一个全是 0、一个全是 1,能贡献 min(前段长, 后段长) 个合法子串(如段 00 与 11 相邻 贡献 2 个:"01" 与 "0011")。累加所有相邻对即可。 时间复杂度 O(n),空间复杂度 O(1)。 解法二(动态统计 prev/cur):扫描时维护当前段长度 cur 与前一段长度 prev,每当字符变化时, 把 min(prev, cur) 累加进答案并令 prev = cur,重新累积 cur。省去切段数组。 时间复杂度 O(n),空间复杂度 O(1)。

代码实现

class Solution {

    // 解法一:切段 + 相邻贡献
    public int countBinarySubstrings(String s) {
        int n = s.length();
        int ans = 0;
        int i = 0;
        int prev = 0;
        while (i < n) {
            int j = i;
            while (j < n && s.charAt(j) == s.charAt(i)) {
                ++j;
            }
            int cur = j - i;
            if (prev > 0) {
                ans += Math.min(prev, cur);
            }
            prev = cur;
            i = j;
        }
        return ans;
    }

    // 解法二:滚动 prev/cur
    public int countBinarySubstrings2(String s) {
        int ans = 0, prev = 0, cur = 1;
        for (int i = 1; i < s.length(); ++i) {
            if (s.charAt(i) == s.charAt(i - 1)) {
                ++cur;
            } else {
                ans += Math.min(prev, cur);
                prev = cur;
                cur = 1;
            }
        }
        ans += Math.min(prev, cur);
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        ++total; if (sol.countBinarySubstrings("00110011") == 6) ++pass; else System.out.println("FAIL test1: " + sol.countBinarySubstrings("00110011"));
        ++total; if (sol.countBinarySubstrings("10101") == 4) ++pass; else System.out.println("FAIL test2: " + sol.countBinarySubstrings("10101"));
        ++total; if (sol.countBinarySubstrings("0011") == 2) ++pass; else System.out.println("FAIL test3: " + sol.countBinarySubstrings("0011"));
        ++total; if (sol.countBinarySubstrings("0") == 0) ++pass; else System.out.println("FAIL test4: " + sol.countBinarySubstrings("0"));
        ++total; if (sol.countBinarySubstrings("1100") == 2) ++pass; else System.out.println("FAIL test5: " + sol.countBinarySubstrings("1100"));
        ++total; if (sol.countBinarySubstrings2("00110011") == 6) ++pass; else System.out.println("FAIL test6");
        ++total; if (sol.countBinarySubstrings2("10101") == 4) ++pass; else System.out.println("FAIL test7");
        ++total; if (sol.countBinarySubstrings2("0011") == 2) ++pass; else System.out.println("FAIL test8");
        ++total; if (sol.countBinarySubstrings2("0") == 0) ++pass; else System.out.println("FAIL test9");
        if (pass == total) {
            System.out.println("== 0696 PASS ==");
        } else {
            System.out.println("== 0696 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

697. Degree of an Array(数组的度)

题目描述

给定一个非空且只包含非负数的整数数组 nums,数组的度的定义是指数组里任一元素出现频数的最大值。 你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1

  • 输入:nums = [1,2,2,3,1]
  • 输出:2
  • 解释:输入数组的度是 2,因为元素 1 和 2 的出现频数最大,均为 2。最短连续子数组 [2,2] 的长度为 2。

示例 2

  • 输入:nums = [1,2,2,3,1,4,2]
  • 输出:6
  • 解释:数组的度是 3,因为元素 2 重复出现 3 次。所以 [2,2,3,1,4,2] 是最短子数组,因此返回 6。 提示:
  • nums.length 在 1 到 50000 范围内
  • nums[i] 是一个在 0 到 49999 范围内的整数

解题思路

解法一(三个哈希表,主提交):cnt 记录每个元素出现次数,first 记录首次出现下标, last 记录末次出现下标。先扫一遍求出全数组的度 deg;再对所有出现次数等于 deg 的元素, 计算 last - first + 1 并取最小。 时间复杂度 O(n),空间复杂度 O(n)。 解法二(单遍 + 滚动答案):在单次遍历中同时维护每个元素的 cnt/first/last 与当前全局度; 当某个元素的 cnt 超过当前度时更新答案;等于当前度时用 last-first+1 更新最小值。 时间复杂度 O(n),空间复杂度 O(n)。

代码实现

import java.util.HashMap;
import java.util.Map;

class Solution {

    // 解法一:计数 + 首末位置
    public int findShortestSubArray(int[] nums) {
        Map<Integer, Integer> cnt = new HashMap<>();
        Map<Integer, Integer> first = new HashMap<>();
        Map<Integer, Integer> last = new HashMap<>();
        int deg = 0;
        for (int i = 0; i < nums.length; ++i) {
            int x = nums[i];
            cnt.put(x, cnt.getOrDefault(x, 0) + 1);
            if (!first.containsKey(x)) {
                first.put(x, i);
            }
            last.put(x, i);
            deg = Math.max(deg, cnt.get(x));
        }
        int ans = nums.length;
        for (Map.Entry<Integer, Integer> e : cnt.entrySet()) {
            if (e.getValue() == deg) {
                int x = e.getKey();
                ans = Math.min(ans, last.get(x) - first.get(x) + 1);
            }
        }
        return ans;
    }

    // 解法二:单遍滚动
    public int findShortestSubArray2(int[] nums) {
        Map<Integer, Integer> cnt = new HashMap<>();
        Map<Integer, Integer> first = new HashMap<>();
        Map<Integer, Integer> last = new HashMap<>();
        int deg = 0, ans = nums.length;
        for (int i = 0; i < nums.length; ++i) {
            int x = nums[i];
            if (!first.containsKey(x)) {
                first.put(x, i);
            }
            last.put(x, i);
            int c = cnt.getOrDefault(x, 0) + 1;
            cnt.put(x, c);
            if (c > deg) {
                deg = c;
                ans = i - first.get(x) + 1;
            } else if (c == deg) {
                ans = Math.min(ans, i - first.get(x) + 1);
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        ++total; if (sol.findShortestSubArray(new int[] {1, 2, 2, 3, 1}) == 2) ++pass; else System.out.println("FAIL test1: " + sol.findShortestSubArray(new int[] {1, 2, 2, 3, 1}));
        ++total; if (sol.findShortestSubArray(new int[] {1, 2, 2, 3, 1, 4, 2}) == 6) ++pass; else System.out.println("FAIL test2: " + sol.findShortestSubArray(new int[] {1, 2, 2, 3, 1, 4, 2}));
        ++total; if (sol.findShortestSubArray(new int[] {1}) == 1) ++pass; else System.out.println("FAIL test3: " + sol.findShortestSubArray(new int[] {1}));
        ++total; if (sol.findShortestSubArray(new int[] {1, 2, 3, 4}) == 1) ++pass; else System.out.println("FAIL test4: " + sol.findShortestSubArray(new int[] {1, 2, 3, 4}));
        ++total; if (sol.findShortestSubArray2(new int[] {1, 2, 2, 3, 1}) == 2) ++pass; else System.out.println("FAIL test5");
        ++total; if (sol.findShortestSubArray2(new int[] {1, 2, 2, 3, 1, 4, 2}) == 6) ++pass; else System.out.println("FAIL test6");
        ++total; if (sol.findShortestSubArray2(new int[] {1}) == 1) ++pass; else System.out.println("FAIL test7");
        ++total; if (sol.findShortestSubArray2(new int[] {1, 2, 3, 4}) == 1) ++pass; else System.out.println("FAIL test8");
        if (pass == total) {
            System.out.println("== 0697 PASS ==");
        } else {
            System.out.println("== 0697 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

698. Partition to K Equal Sum Subsets(划分为k个相等的子集)

题目描述

给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集, 其总和都相等。

示例 1

  • 输入:nums = [4, 3, 2, 3, 5, 2, 1], k = 4
  • 输出:True 说明:有可能将其分成 4 个子集(5)、(1,4)、(2,3)、(2,3)等于总和。

示例 2

  • 输入:nums = [1,2,3,4], k = 3
  • 输出: false 提示:
  • 1 <= k <= len(nums) <= 16
  • 0 < nums[i] < 10000
  • 每个元素的频率在 [1,4] 范围内

解题思路

解法一(DFS + 剪枝,主提交):先求总和,若不能被 k 整除直接返回 false;每个子集期望和为 s。把数组降序排序(先放大数,搜索更快),建立长度为 k 的桶数组 cur,逐个把数放入桶中: 若放入后桶和超过 s 则跳过;若相邻桶已有相同和则跳过(对称剪枝,避免重复搜索)。 所有数放完返回 true。时间复杂度 O(k^(n-k))(带剪枝通常远小于此),空间复杂度 O(n + k)。 解法二(状态压缩 + DFS 记忆化):用 state 的二进制位表示哪些元素已被使用,f[state] 记录该状态是否已被搜过。记当前子集和为 t(相对 s 取模),枚举未使用元素加入;因为数组 升序,若 t + nums[i] > s 则可直接剪掉后续更大的元素。时间复杂度 O(n × 2^n), 空间复杂度 O(2^n)。 解法三(状态压缩 DP):自底向上,f[mask] 表示状态 mask 能否合法填完若干桶, cur[mask] 记录该状态最后一个桶已装的和。枚举未用的元素转移。时间复杂度 O(n × 2^n), 空间复杂度 O(2^n)。

代码实现

import java.util.Arrays;

class Solution {

    // 解法一:DFS + 剪枝
    private int[] nums1;
    private int[] cur1;
    private int target1;

    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = 0;
        for (int v : nums) {
            sum += v;
        }
        if (sum % k != 0) {
            return false;
        }
        target1 = sum / k;
        Arrays.sort(nums);
        this.nums1 = nums;
        this.cur1 = new int[k];
        return dfs1(nums.length - 1);
    }

    private boolean dfs1(int i) {
        if (i < 0) {
            return true;
        }
        for (int j = 0; j < cur1.length; ++j) {
            if (j > 0 && cur1[j] == cur1[j - 1]) {
                continue;
            }
            cur1[j] += nums1[i];
            if (cur1[j] <= target1 && dfs1(i - 1)) {
                return true;
            }
            cur1[j] -= nums1[i];
        }
        return false;
    }

    // 解法二:状态压缩 + DFS 记忆化
    private int[] f;
    private int[] nums2;
    private int n2;
    private int s2;

    public boolean canPartitionKSubsets2(int[] nums, int k) {
        int sum = 0;
        for (int v : nums) {
            sum += v;
        }
        if (sum % k != 0) {
            return false;
        }
        s2 = sum / k;
        Arrays.sort(nums);
        this.nums2 = nums;
        n2 = nums.length;
        f = new int[1 << n2];
        return dfs2(0, 0);
    }

    private boolean dfs2(int state, int t) {
        if (state == (1 << n2) - 1) {
            return true;
        }
        if (f[state] != 0) {
            return f[state] == 1;
        }
        for (int i = 0; i < n2; ++i) {
            if (((state >> i) & 1) == 1) {
                continue;
            }
            if (t + nums2[i] > s2) {
                break;
            }
            if (dfs2(state | (1 << i), (t + nums2[i]) % s2)) {
                f[state] = 1;
                return true;
            }
        }
        f[state] = -1;
        return false;
    }

    // 解法三:状态压缩 DP
    public boolean canPartitionKSubsets3(int[] nums, int k) {
        int sum = 0;
        for (int v : nums) {
            sum += v;
        }
        if (sum % k != 0) {
            return false;
        }
        int s = sum / k;
        Arrays.sort(nums);
        int n = nums.length;
        boolean[] dp = new boolean[1 << n];
        int[] cur = new int[1 << n];
        dp[0] = true;
        for (int mask = 0; mask < (1 << n); ++mask) {
            if (!dp[mask]) {
                continue;
            }
            for (int j = 0; j < n; ++j) {
                if (cur[mask] + nums[j] > s) {
                    break;
                }
                if (((mask >> j) & 1) == 0) {
                    int next = mask | (1 << j);
                    dp[next] = true;
                    cur[next] = (cur[mask] + nums[j]) % s;
                }
            }
        }
        return dp[(1 << n) - 1];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        ++total; if (sol.canPartitionKSubsets(new int[]{4, 3, 2, 3, 5, 2, 1}, 4)) ++pass; else System.out.println("FAIL test1");
        ++total; if (!sol.canPartitionKSubsets(new int[]{1, 2, 3, 4}, 3)) ++pass; else System.out.println("FAIL test2");
        ++total; if (sol.canPartitionKSubsets(new int[]{1, 1, 1, 1, 2, 2, 2, 2}, 2)) ++pass; else System.out.println("FAIL test3");
        // 解法二
        ++total; if (sol.canPartitionKSubsets2(new int[]{4, 3, 2, 3, 5, 2, 1}, 4)) ++pass; else System.out.println("FAIL test4");
        ++total; if (!sol.canPartitionKSubsets2(new int[]{1, 2, 3, 4}, 3)) ++pass; else System.out.println("FAIL test5");
        ++total; if (sol.canPartitionKSubsets2(new int[]{1, 1, 1, 1, 2, 2, 2, 2}, 2)) ++pass; else System.out.println("FAIL test6");
        // 解法三
        ++total; if (sol.canPartitionKSubsets3(new int[]{4, 3, 2, 3, 5, 2, 1}, 4)) ++pass; else System.out.println("FAIL test7");
        ++total; if (!sol.canPartitionKSubsets3(new int[]{1, 2, 3, 4}, 3)) ++pass; else System.out.println("FAIL test8");
        ++total; if (sol.canPartitionKSubsets3(new int[]{1, 1, 1, 1, 2, 2, 2, 2}, 2)) ++pass; else System.out.println("FAIL test9");
        if (pass == total) {
            System.out.println("== 0698 PASS ==");
        } else {
            System.out.println("== 0698 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(k^(n/k))(最坏指数级)O(n + k)
解法二O(n × 2^n)O(2^n)
解法三O(n × 2^n)O(2^n)

699. Falling Squares(掉落的方块)

题目描述

在二维平面上的 x 轴上,放置着一些方块。 给你一个二维整数数组 positions,其中 positions[i] = [left_i, sideLength_i] 表示: 第 i 个方块边长为 sideLength_i,其左侧边与 x 轴上坐标点 left_i 对齐。 每个方块都从一个比目前所有的落地方块更高的高度掉落而下。方块沿 y 轴负方向下落,直到 着陆到另一个正方形的顶边或者是 x 轴上。一个方块仅仅是擦过另一个方块的左侧边或右侧边 不算着陆。一旦着陆,它就会固定在原地,无法移动。 在每个方块掉落后,你必须记录目前所有已经落稳的方块堆叠的最高高度。 返回一个整数数组 ans,其中 ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。

示例 1

  • 输入:positions = [[1,2],[2,3],[6,1]]
  • 输出:[2,5,5]
  • 解释:第 1 个方块掉落后最高高度为 2;第 2 个方块(左边界 2)与第 1 个方块([1,3))有 重叠,落在其顶边上,堆叠高度为 5;第 3 个方块([6,7))不与任何方块重叠,高度 1。 因此返回 [2, 5, 5]。

示例 2

  • 输入:positions = [[100,100],[200,100]]
  • 输出:[100,100]
  • 解释:方块 2 擦过方块 1 的右侧边,但不会算作在方块 1 上着陆。 提示:
  • 1 <= positions.length <= 1000
  • 1 <= left_i <= 10^8
  • 1 <= sideLength_i <= 10^6

解题思路

解法一(暴力枚举,主提交):positions.length <= 1000,暴力是可行的。方块 i 的底边 高度 = 它下方所有与它 x 区间重叠的方块顶边高度的最大值。用数组 heights[i] 记录每个方块 落地后的顶边高度;对第 i 个方块,扫描 0..i-1 中与它区间重叠的方块,取最大顶边高度 加上自身边长,再更新全局最高。判断重叠:left_i < left_j + w_j && left_j < left_i + w_i (严格重叠,擦边不算)。时间复杂度 O(n²),空间复杂度 O(n)。 解法二(动态开点线段树):x 坐标可达 1e8 不能开满数组,用动态开点线段树维护区间最大高度, modify(l, r, v) 做区间整体赋值、query(l, r) 查区间最大。每次方块落地高度 h = query(l, r) + w,更新全局最大后把 [l, r] 区间赋值为 h。 时间复杂度 O(n log n),空间复杂度 O(n)(动态开点)。

代码实现

import java.util.ArrayList;
import java.util.List;

class Solution {

    // 解法一:暴力枚举相邻块重叠
    public List<Integer> fallingSquares(int[][] positions) {
        int n = positions.length;
        int[] heights = new int[n];
        List<Integer> ans = new ArrayList<>();
        int mx = 0;
        for (int i = 0; i < n; ++i) {
            int left = positions[i][0], w = positions[i][1];
            int right = left + w;
            int base = 0;
            for (int j = 0; j < i; ++j) {
                int lj = positions[j][0], wj = positions[j][1];
                int rj = lj + wj;
                if (left < rj && lj < right) { // 严格重叠(擦边不算)
                    base = Math.max(base, heights[j]);
                }
            }
            heights[i] = base + w;
            mx = Math.max(mx, heights[i]);
            ans.add(mx);
        }
        return ans;
    }

    // ===== 解法二:动态开点线段树 =====

    private static class Node {
        Node left, right;
        int l, r, mid;
        int v;   // 区间最大高度
        int add; // 懒标记(区间赋值)

        Node(int l, int r) {
            this.l = l;
            this.r = r;
            this.mid = (l + r) >> 1;
        }
    }

    private static class SegmentTree {
        private final Node root = new Node(1, 1000000000);

        void modify(int l, int r, int v) {
            modify(l, r, v, root);
        }

        private void modify(int l, int r, int v, Node node) {
            if (l > r) {
                return;
            }
            if (node.l >= l && node.r <= r) {
                node.v = v;
                node.add = v;
                return;
            }
            pushdown(node);
            if (l <= node.mid) {
                modify(l, r, v, node.left);
            }
            if (r > node.mid) {
                modify(l, r, v, node.right);
            }
            pushup(node);
        }

        int query(int l, int r) {
            return query(l, r, root);
        }

        private int query(int l, int r, Node node) {
            if (l > r) {
                return 0;
            }
            if (node.l >= l && node.r <= r) {
                return node.v;
            }
            pushdown(node);
            int res = 0;
            if (l <= node.mid) {
                res = Math.max(res, query(l, r, node.left));
            }
            if (r > node.mid) {
                res = Math.max(res, query(l, r, node.right));
            }
            return res;
        }

        private void pushup(Node node) {
            node.v = Math.max(node.left.v, node.right.v);
        }

        private void pushdown(Node node) {
            if (node.left == null) {
                node.left = new Node(node.l, node.mid);
            }
            if (node.right == null) {
                node.right = new Node(node.mid + 1, node.r);
            }
            if (node.add != 0) {
                node.left.add = node.add;
                node.right.add = node.add;
                node.left.v = node.add;
                node.right.v = node.add;
                node.add = 0;
            }
        }
    }

    public List<Integer> fallingSquares2(int[][] positions) {
        SegmentTree tree = new SegmentTree();
        List<Integer> ans = new ArrayList<>();
        int mx = 0;
        for (int[] p : positions) {
            int l = p[0], w = p[1], r = l + w - 1;
            int h = tree.query(l, r) + w;
            mx = Math.max(mx, h);
            ans.add(mx);
            tree.modify(l, r, h);
        }
        return ans;
    }

    private static void check(List<Integer> got, int[] exp, String tag) {
        StringBuilder sb = new StringBuilder();
        for (int x : got) {
            sb.append(x).append(',');
        }
        String gs = sb.toString();
        StringBuilder es = new StringBuilder();
        for (int x : exp) {
            es.append(x).append(',');
        }
        if (gs.equals(es.toString())) {
            System.out.println("  " + tag + " OK");
        } else {
            System.out.println("  " + tag + " FAIL got=" + gs + " exp=" + es);
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        check(sol.fallingSquares(new int[][]{{1, 2}, {2, 3}, {6, 1}}), new int[]{2, 5, 5}, "test1");
        check(sol.fallingSquares(new int[][]{{100, 100}, {200, 100}}), new int[]{100, 100}, "test2");
        check(sol.fallingSquares(new int[][]{{1, 1}, {2, 1}, {3, 1}}), new int[]{1, 1, 1}, "test3");
        check(sol.fallingSquares2(new int[][]{{1, 2}, {2, 3}, {6, 1}}), new int[]{2, 5, 5}, "test4");
        check(sol.fallingSquares2(new int[][]{{100, 100}, {200, 100}}), new int[]{100, 100}, "test5");
        check(sol.fallingSquares2(new int[][]{{1, 1}, {2, 1}, {3, 1}}), new int[]{1, 1, 1}, "test6");
        check(sol.fallingSquares2(new int[][]{{1, 2}, {3, 2}, {5, 2}}), new int[]{2, 2, 2}, "test7");
        check(sol.fallingSquares2(new int[][]{{4, 6}, {6, 4}, {2, 3}}), new int[]{6, 10, 10}, "test8");
        System.out.println("== 0699 PASS ==");
    }
}

复杂度分析

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

700. Search in a Binary Search Tree(二叉搜索树中的搜索)

题目描述

给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。返回以该节点为根的子树。如果节点不存在,则返回 null。

示例 1

  • 输入:root = [4,2,7,1,3], val = 2
  • 输出:[2,1,3]

示例 2

  • 输入:root = [4,2,7,1,3], val = 5
  • 输出:[] 提示:
  • 树中节点数在 [1, 5000] 范围内
  • 1 <= Node.val <= 10^7
  • root 是二叉搜索树
  • 1 <= val <= 10^7

解题思路

解法一(递归按大小分支,主提交):BST 有序:若当前节点值等于 val 直接返回;小于 val 递归右子树;大于 val 递归左子树。时间复杂度 O(height),空间复杂度 O(height)。 解法二(迭代):用 while 循环沿分支下降,直到找到目标或空。时间复杂度 O(height),空间复杂度 O(1)。

代码实现

class Solution {

    private static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }

    // 解法一:递归
    public TreeNode searchBST(TreeNode root, int val) {
        if (root == null || root.val == val) {
            return root;
        }
        return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
    }

    // 解法二:迭代
    public TreeNode searchBST2(TreeNode root, int val) {
        TreeNode cur = root;
        while (cur != null && cur.val != val) {
            cur = val < cur.val ? cur.left : cur.right;
        }
        return cur;
    }

    // 层序构建(测试辅助)
    private static TreeNode build(Integer[] arr) {
        if (arr == null || arr.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(arr[0]);
        java.util.Queue<TreeNode> q = new java.util.LinkedList<>();
        q.offer(root);
        int i = 1;
        while (!q.isEmpty() && i < arr.length) {
            TreeNode cur = q.poll();
            if (arr[i] != null) {
                cur.left = new TreeNode(arr[i]);
                q.offer(cur.left);
            }
            ++i;
            if (i < arr.length && arr[i] != null) {
                cur.right = new TreeNode(arr[i]);
                q.offer(cur.right);
            }
            ++i;
        }
        return root;
    }

    // 先序打印(测试辅助)
    private static String preorder(TreeNode root) {
        if (root == null) {
            return "null";
        }
        return root.val + "," + preorder(root.left) + "," + preorder(root.right);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        TreeNode t1 = build(new Integer[] {4, 2, 7, 1, 3});
        TreeNode r1 = sol.searchBST(t1, 2);
        ++total; if (r1 != null && preorder(r1).equals("2,1,null,null,3,null,null")) ++pass; else System.out.println("FAIL test1: " + (r1 == null ? "null" : preorder(r1)));
        TreeNode r2 = sol.searchBST(t1, 5);
        ++total; if (r2 == null) ++pass; else System.out.println("FAIL test2");
        TreeNode r3 = sol.searchBST(t1, 7);
        ++total; if (r3 != null && preorder(r3).equals("7,null,null")) ++pass; else System.out.println("FAIL test3");
        // 迭代
        TreeNode r4 = sol.searchBST2(t1, 2);
        ++total; if (r4 != null && preorder(r4).equals("2,1,null,null,3,null,null")) ++pass; else System.out.println("FAIL test4");
        TreeNode r5 = sol.searchBST2(t1, 5);
        ++total; if (r5 == null) ++pass; else System.out.println("FAIL test5");
        TreeNode r6 = sol.searchBST2(t1, 4);
        ++total; if (r6 != null && preorder(r6).equals("4,2,1,null,null,3,null,null,7,null,null")) ++pass; else System.out.println("FAIL test6");
        if (pass == total) {
            System.out.println("== 0700 PASS ==");
        } else {
            System.out.println("== 0700 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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