LeetCode740~750题目解析

0 阅读1分钟

LeetCode 740-750. 题解合集


740. Delete and Earn(删除并获得点数)

题目描述

给你一个整数数组 nums,你可以对它进行一些操作。每次操作中,选择任意一个 nums[i],
删除它并获得 nums[i] 的点数。之后,你必须删除所有等于 nums[i] - 1 和 nums[i] + 1
的元素。开始你拥有 0 个点数,返回你能通过这些操作获得的最大点数。

示例 1

  • 输入:nums = [3,4,2]
  • 输出:6
  • 解释:删除 4 获得 4 个点数,因此 3 也被删除;之后删除 2 获得 2 个点数,总共 6。

示例 2

  • 输入:nums = [2,2,3,3,3,4]
  • 输出:9
  • 解释:删除 3 获得 3 个点数,所有的 2 和 4 也被删除,nums = [3,3];再删两次 3,共 9。
    提示:
  • 1 <= nums.length <= 2 * 10^4
  • 1 <= nums[i] <= 10^4

解题思路

解法一(值域计数 + 打家劫舍 DP,主提交):数字最大 10^4,用 sum[x] 记录数值 x 的「总和」
(出现几次 × x)。若选择删某个值 x,则 x-1 与 x+1 都不能选——这与「打家劫舍」中相邻
房间不能同时偷完全同构:dp[i] = max(dp[i-1], dp[i-2] + sum[i])。
时间复杂度 O(maxVal + n),空间复杂度 O(maxVal)。
解法二(排序 + 分组 DP):把 nums 排序后按值分组,处理完一组相邻值后做同样的取舍。
逻辑同法一,空间省到 O(n);当值域范围大时更适用。

代码实现

class Solution {

    // 解法一:值域计数 + 线性 DP
    public int deleteAndEarn(int[] nums) {
        int max = 0;
        for (int x : nums) {
            max = Math.max(max, x);
        }
        int[] sum = new int[max + 1];
        for (int x : nums) {
            sum[x] += x;
        }
        int take = 0, skip = 0; // take 表示选到当前值的最大收益,skip 表示不选
        for (int i = 1; i <= max; ++i) {
            int newTake = skip + sum[i];
            int newSkip = Math.max(skip, take);
            take = newTake;
            skip = newSkip;
        }
        return Math.max(take, skip);
    }

    // 解法二:排序 + 分组 DP(值域稀疏时更省空间)
    public int deleteAndEarn2(int[] nums) {
        java.util.Arrays.sort(nums);
        int take = 0, skip = 0;
        int i = 0, n = nums.length;
        int prevVal = -1;
        while (i < n) {
            int v = nums[i];
            int sum = 0;
            while (i < n && nums[i] == v) {
                sum += v;
                ++i;
            }
            if (prevVal != -1 && prevVal + 1 == v) {
                // 与上一组相邻:取当前组则必须跳过上一组
                int newTake = skip + sum;
                int newSkip = Math.max(take, skip);
                take = newTake;
                skip = newSkip;
            } else {
                // 不相邻:当前组可以无条件全拿
                int newTake = Math.max(take, skip) + sum;
                int newSkip = Math.max(take, skip);
                take = newTake;
                skip = newSkip;
            }
            prevVal = v;
        }
        return Math.max(take, skip);
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[] n1 = {3, 4, 2};
        ++total; if (sol.deleteAndEarn(n1) == 6) ++pass; else System.out.println("FAIL test1 got " + sol.deleteAndEarn(n1));
        int[] n2 = {2, 2, 3, 3, 3, 4};
        ++total; if (sol.deleteAndEarn(n2) == 9) ++pass; else System.out.println("FAIL test2 got " + sol.deleteAndEarn(n2));
        int[] n3 = {3, 4, 2};
        ++total; if (sol.deleteAndEarn2(n3) == 6) ++pass; else System.out.println("FAIL test3");
        int[] n4 = {1, 1, 1, 2, 4, 5, 5, 5, 6};
        ++total; if (sol.deleteAndEarn2(n4) == 18) ++pass; else System.out.println("FAIL test4 got " + sol.deleteAndEarn2(n4));
        int[] n5 = {8, 10, 4, 9, 1, 3, 5, 9, 4, 10};
        ++total; if (sol.deleteAndEarn(n5) == 37 && sol.deleteAndEarn2(n5) == 37) ++pass; else System.out.println("FAIL test5 got " + sol.deleteAndEarn(n5) + "/" + sol.deleteAndEarn2(n5));
        if (pass == total) {
            System.out.println("== 0740 PASS ==");
        } else {
            System.out.println("== 0740 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

741. Cherry Pickup(摘樱桃)

题目描述

给你一个 n x n 的网格 grid,代表一块樱桃地,每个格子由以下三种数字的一种来表示:

  • 0 表示这个格子是空的,所以你可以穿过它;
  • 1 表示这个格子里装着一个樱桃,你可以摘到樱桃然后穿过它;
  • -1 表示这个格子里有荆棘,挡着你的路。
    请你统计并返回:在遵守下列规则的情况下,能摘到的最多樱桃数:
  • 从位置 (0, 0) 出发,最后到达 (n - 1, n - 1),只能向下或向右走,并且只能穿越有效的
    格子(值为 0 或 1);
  • 当到达 (n - 1, n - 1) 后,你要继续走,直到返回到 (0, 0),只能向上或向左走,并且只能
    穿越有效的格子;
  • 当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为 0);
  • 如果在 (0, 0) 和 (n - 1, n - 1) 之间不存在一条可经过的路径,则无法摘到任何一个樱桃。

示例 1

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

示例 2

  • 输入:grid = [[1,1,-1],[1,-1,1],[-1,1,1]]
  • 输出:0
    提示:
  • grid.length == n
  • grid[i].length == n
  • 1 <= n <= 50
  • grid[i][j] 是 -1、0 或 1
  • grid[0][0] 与 grid[n-1][n-1] 保证不为 -1

解题思路

解法一(双人同步 DP,主提交):去程 + 返程等价于「两个人同时从 (0,0) 出发走到
(n-1,n-1)」,每一步都只能向右或向下,且两人摘到的樱桃需要去重(同一格子只计一次)。
两人步数相同,设走了 k 步后第一个人的横坐标为 x1、第二个人的横坐标为 x2(纵坐标由
k - x 唯一确定)。状态 dp[x1][x2](滚动步数维)表示两人分别位于 (x1, k-x1) 与
(x2, k-x2) 时能摘到的最大樱桃数。上一层两人分别可能来自上方或左方共 4 种组合,取最大;
当前层若两人在同一格子,樱桃只计一次。荆棘所在状态记为负无穷。
时间复杂度 O(n³),空间复杂度 O(n²)。
解法二(双人同步 DFS 记忆化):同样的双人模型,用 f[x1][y1][x2] 记忆化递归,代码更直观
直观,渐近复杂度同法一。

代码实现

import java.util.Arrays;

class Solution {

    // 解法一:双人同步 DP(滚动步数维)
    public int cherryPickup(int[][] grid) {
        int n = grid.length;
        int INF = Integer.MIN_VALUE / 2;
        int[][] dp = new int[n][n];
        for (int[] row : dp) {
            Arrays.fill(row, INF);
        }
        dp[0][0] = grid[0][0];
        for (int k = 1; k <= 2 * n - 2; ++k) {
            int[][] ndp = new int[n][n];
            for (int[] row : ndp) {
                Arrays.fill(row, INF);
            }
            for (int x1 = Math.max(0, k - (n - 1)); x1 <= Math.min(n - 1, k); ++x1) {
                int y1 = k - x1;
                if (grid[x1][y1] == -1) continue;
                for (int x2 = Math.max(0, k - (n - 1)); x2 <= Math.min(n - 1, k); ++x2) {
                    int y2 = k - x2;
                    if (grid[x2][y2] == -1) continue;
                    int best = INF;
                    // 上一步:两人各自可来自上方(左移 x) 或左方(左移 y),共 2 x 2 = 4 种
                    for (int a = 0; a < 2; ++a) {
                        int px1 = x1 - (a == 0 ? 1 : 0);
                        int py1 = y1 - (a == 1 ? 1 : 0);
                        if (px1 < 0 || py1 < 0) continue;
                        for (int b = 0; b < 2; ++b) {
                            int px2 = x2 - (b == 0 ? 1 : 0);
                            int py2 = y2 - (b == 1 ? 1 : 0);
                            if (px2 < 0 || py2 < 0) continue;
                            if (dp[px1][px2] == INF) {
                                continue; // 上一层状态不可达
                            }
                            best = Math.max(best, dp[px1][px2]);
                        }
                    }
                    if (best == INF) {
                        continue;
                    }
                    int gain = grid[x1][y1];
                    if (x1 != x2 || y1 != y2) {
                        gain += grid[x2][y2];
                    }
                    ndp[x1][x2] = best + gain;
                }
            }
            dp = ndp;
        }
        return Math.max(0, dp[n - 1][n - 1]);
    }

    // 解法二:双人同步 DFS 记忆化
    private int n;
    private int[][] g;
    private int[][][] memo;
    private int INF2 = Integer.MIN_VALUE / 2;

    public int cherryPickup2(int[][] grid) {
        this.g = grid;
        this.n = grid.length;
        memo = new int[n][n][n];
        for (int[][] rows : memo) {
            for (int[] row : rows) {
                Arrays.fill(row, -1);
            }
        }
        return Math.max(0, dfs(0, 0, 0));
    }

    // 从两个人的位置 (x1, y1) 与 (x2, y2) 出发到终点的最大收益;利用步数相等,不存 y2
    private int dfs(int x1, int y1, int x2) {
        int y2 = x1 + y1 - x2;
        if (x1 >= n || y1 >= n || x2 >= n || y2 >= n
                || g[x1][y1] == -1 || g[x2][y2] == -1) {
            return INF2;
        }
        if (x1 == n - 1 && y1 == n - 1) {
            return g[x1][y1]; // 两人同时到达终点
        }
        if (memo[x1][y1][x2] != -1) {
            return memo[x1][y1][x2];
        }
        int best = INF2;
        int[] dx = {1, 0}; // 向下、向右
        int[] dy = {0, 1};
        for (int k1 = 0; k1 < 2; ++k1) {
            for (int k2 = 0; k2 < 2; ++k2) {
                best = Math.max(best, dfs(x1 + dx[k1], y1 + dy[k1], x2 + dx[k2]));
            }
        }
        int gain = g[x1][y1];
        if (x1 != x2 || y1 != y2) {
            gain += g[x2][y2];
        }
        memo[x1][y1][x2] = best + gain;
        return memo[x1][y1][x2];
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[][] g1 = {{0, 1, -1}, {1, 0, -1}, {1, 1, 1}};
        ++total; if (sol.cherryPickup(g1) == 5) ++pass; else System.out.println("FAIL test1 got " + sol.cherryPickup(g1));
        ++total; if (sol.cherryPickup2(g1) == 5) ++pass; else System.out.println("FAIL test2 got " + sol.cherryPickup2(g1));
        int[][] g2 = {{1, 1, -1}, {1, -1, 1}, {-1, 1, 1}};
        ++total; if (sol.cherryPickup(g2) == 0) ++pass; else System.out.println("FAIL test3 got " + sol.cherryPickup(g2));
        ++total; if (sol.cherryPickup2(g2) == 0) ++pass; else System.out.println("FAIL test4 got " + sol.cherryPickup2(g2));
        int[][] g3 = {{1}};
        ++total; if (sol.cherryPickup(g3) == 1) ++pass; else System.out.println("FAIL test5 got " + sol.cherryPickup(g3));
        int[][] g4 = {{0, 1}, {1, 1}};
        ++total; if (sol.cherryPickup(g4) == 3) ++pass; else System.out.println("FAIL test6 got " + sol.cherryPickup(g4));
        if (pass == total) {
            System.out.println("== 0741 PASS ==");
        } else {
            System.out.println("== 0741 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

742. Closest Leaf in a Binary Tree(二叉树最近的叶节点)

题目描述

给定一个每个结点的值互不相同的二叉树,和一个目标整数值 k,返回树中与目标值 k 最近的
叶结点。与叶结点最近表示在二叉树中到达该叶节点需要行进的边数与到达其它叶结点相比最少。
当一个结点没有孩子结点时称其为叶结点。

示例 1

  • 输入:root = [1, 3, 2], k = 1
  • 输出:2
  • 解释:2 和 3 都是距离目标 1 最近的叶节点。

示例 2

  • 输入:root = [1], k = 1
  • 输出:1

示例 3

  • 输入:root = [1,2,3,4,null,null,null,5,null,6], k = 2
  • 输出:3
  • 解释:值为 3 的节点距离值为 2 的节点距离为 1(边数最少)。
    提示:
  • 树中结点数在范围 [1, 1000] 内
  • 1 <= Node.val <= 1000
  • 每个结点值都不同
  • 给定的树中有且仅有一个结点值等于 k

解题思路

解法一(建无向图 + BFS 最近叶,主提交):树中「最近叶」不一定在目标节点的子树中,可能要从
目标节点向上绕行。先把树转成无向图 Map<Integer, List<Integer>>(父子互连),再从 k 出发
BFS,第一个遇到的「叶子」(无向图中度为 1 且原树中为叶子的节点)即为答案。因为每个节点值
互不相同,可以用值做键。
时间复杂度 O(n),空间复杂度 O(n)。
解法二(递归找目标 + 树间距离搜索):先 DFS 定位目标节点,再对每个叶子节点计算到目标的
距离(通过 LCA 或回溯),取最小。便于理解但实现繁琐,作为参考思路。

代码实现

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Solution {

    // Definition for a binary tree node.
    private static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int val) { this.val = val; }
    }

    // 解法一:无向图 + BFS
    public int findClosestLeaf(TreeNode root, int k) {
        Map<Integer, List<Integer>> graph = new HashMap<>();
        List<TreeNode> leaves = new ArrayList<>();
        build(root, null, graph, leaves);
        Deque<Integer> q = new ArrayDeque<>();
        q.offer(k);
        java.util.Set<Integer> seen = new java.util.HashSet<>();
        seen.add(k);
        while (!q.isEmpty()) {
            int cur = q.poll();
            if (isLeaf(cur, leaves, graph)) {
                return cur;
            }
            for (int nb : graph.getOrDefault(cur, new ArrayList<>())) {
                if (!seen.contains(nb)) {
                    seen.add(nb);
                    q.offer(nb);
                }
            }
        }
        return -1; // 不会到达
    }

    // 叶子判定:原树中无孩子(无向图中仅有一个邻居 或 单节点树无邻居)
    private boolean isLeaf(int val, List<TreeNode> leaves, Map<Integer, List<Integer>> graph) {
        if (val < 0) {
            return false;
        }
        int deg = graph.getOrDefault(val, new ArrayList<>()).size();
        // 单节点树没有邻居也是叶子
        if (deg == 0) {
            for (TreeNode leaf : leaves) {
                if (leaf.val == val) {
                    return true;
                }
            }
            return false;
        }
        for (TreeNode leaf : leaves) {
            if (leaf.val == val) {
                return true;
            }
        }
        return false;
    }

    private void build(TreeNode node, TreeNode parent, Map<Integer, List<Integer>> graph,
                       List<TreeNode> leaves) {
        if (node == null) {
            return;
        }
        if (node.left == null && node.right == null) {
            leaves.add(node);
        }
        if (parent != null) {
            graph.computeIfAbsent(node.val, x -> new ArrayList<>()).add(parent.val);
            graph.computeIfAbsent(parent.val, x -> new ArrayList<>()).add(node.val);
        }
        build(node.left, node, graph, leaves);
        build(node.right, node, graph, leaves);
    }

    // ===== 本地测试 =====
    private static TreeNode fromArray(Integer[] a) {
        if (a == null || a.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(a[0]);
        Deque<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        int i = 1;
        while (!q.isEmpty() && i < a.length) {
            TreeNode cur = q.poll();
            if (a[i] != null) {
                cur.left = new TreeNode(a[i]);
                q.offer(cur.left);
            }
            ++i;
            if (i < a.length && a[i] != null) {
                cur.right = new TreeNode(a[i]);
                q.offer(cur.right);
            }
            ++i;
        }
        return root;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        TreeNode t1 = fromArray(new Integer[] {1, 3, 2});
        ++total; if (sol.findClosestLeaf(t1, 1) == 2 || sol.findClosestLeaf(t1, 1) == 3) ++pass; else System.out.println("FAIL test1 got " + sol.findClosestLeaf(t1, 1));
        TreeNode t2 = fromArray(new Integer[] {1});
        ++total; if (sol.findClosestLeaf(t2, 1) == 1) ++pass; else System.out.println("FAIL test2 got " + sol.findClosestLeaf(t2, 1));
        TreeNode t3 = fromArray(new Integer[] {1, 2, 3, 4, null, null, null, 5, null, 6});
        ++total; if (sol.findClosestLeaf(t3, 2) == 3) ++pass; else System.out.println("FAIL test3 got " + sol.findClosestLeaf(t3, 2));
        TreeNode t4 = fromArray(new Integer[] {1, 2, 3});
        ++total; if (sol.findClosestLeaf(t4, 2) == 2) ++pass; else System.out.println("FAIL test4 got " + sol.findClosestLeaf(t4, 2));
        if (pass == total) {
            System.out.println("== 0742 PASS ==");
        } else {
            System.out.println("== 0742 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

743. Network Delay Time(网络延迟时间)

题目描述

有 n 个网络节点,标记为 1 到 n。给你一个列表 times,表示信号经过有向边的传递时间,
times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点,wi 是一个信号从源节点
传递到目标节点的时间。现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?
如果不能使所有节点收到信号,返回 -1。

示例 1

  • 输入:times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
  • 输出:2

示例 2

  • 输入:times = [[1,2,1]], n = 2, k = 1
  • 输出:1

示例 3

  • 输入:times = [[1,2,1]], n = 2, k = 2
  • 输出:-1
    提示:
  • 1 <= k <= n <= 100
  • 1 <= times.length <= 6000
  • times[i].length == 3
  • 1 <= ui, vi <= n
  • ui != vi
  • 0 <= wi <= 100
  • 所有 (ui, vi) 对互不相同

解题思路

解法一(Dijkstra 优先队列,主提交):单源最短路模板。把所有边放入邻接表,用小根堆按
当前最短路取节点,松弛其出边。最终所有节点的最短距离的最大值就是答案;若存在节点不可达,
返回 -1。
时间复杂度 O(m·log n),空间复杂度 O(n + m)。
解法二(朴素 Dijkstra):每轮在未确定节点中选距离最小的一个(O(n)),再松弛,总复杂度
O(n²)。n 最大 100,完全够用,实现更直白。

代码实现

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 {

    // 解法一:Dijkstra + 优先队列
    public int networkDelayTime(int[][] times, int n, int k) {
        Map<Integer, List<int[]>> graph = new HashMap<>();
        for (int[] e : times) {
            graph.computeIfAbsent(e[0], x -> new ArrayList<>()).add(new int[] {e[1], e[2]});
        }
        int[] dist = new int[n + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[k] = 0;
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
        pq.offer(new int[] {k, 0});
        while (!pq.isEmpty()) {
            int[] cur = pq.poll();
            int u = cur[0], d = cur[1];
            if (d > dist[u]) {
                continue;
            }
            for (int[] edge : graph.getOrDefault(u, new ArrayList<>())) {
                int v = edge[0], w = edge[1];
                if (dist[u] + w < dist[v]) {
                    dist[v] = dist[u] + w;
                    pq.offer(new int[] {v, dist[v]});
                }
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            if (dist[i] == Integer.MAX_VALUE) {
                return -1;
            }
            ans = Math.max(ans, dist[i]);
        }
        return ans;
    }

    // 解法二:朴素 Dijkstra
    public int networkDelayTime2(int[][] times, int n, int k) {
        int[][] g = new int[n + 1][n + 1];
        for (int[] row : g) {
            Arrays.fill(row, Integer.MAX_VALUE / 2);
        }
        for (int[] e : times) {
            g[e[0]][e[1]] = Math.min(g[e[0]][e[1]], e[2]);
        }
        int[] dist = new int[n + 1];
        boolean[] done = new boolean[n + 1];
        Arrays.fill(dist, Integer.MAX_VALUE / 2);
        dist[k] = 0;
        for (int it = 1; it <= n; ++it) {
            int u = -1;
            for (int v = 1; v <= n; ++v) {
                if (!done[v] && (u == -1 || dist[v] < dist[u])) {
                    u = v;
                }
            }
            if (u == -1 || dist[u] == Integer.MAX_VALUE / 2) {
                break;
            }
            done[u] = true;
            for (int v = 1; v <= n; ++v) {
                if (!done[v] && dist[u] + g[u][v] < dist[v]) {
                    dist[v] = dist[u] + g[u][v];
                }
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            if (dist[i] >= Integer.MAX_VALUE / 2) {
                return -1;
            }
            ans = Math.max(ans, dist[i]);
        }
        return ans;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[][] t1 = {{2, 1, 1}, {2, 3, 1}, {3, 4, 1}};
        ++total; if (sol.networkDelayTime(t1, 4, 2) == 2) ++pass; else System.out.println("FAIL test1 got " + sol.networkDelayTime(t1, 4, 2));
        ++total; if (sol.networkDelayTime2(t1, 4, 2) == 2) ++pass; else System.out.println("FAIL test2");
        int[][] t2 = {{1, 2, 1}};
        ++total; if (sol.networkDelayTime(t2, 2, 1) == 1) ++pass; else System.out.println("FAIL test3");
        ++total; if (sol.networkDelayTime(t2, 2, 2) == -1) ++pass; else System.out.println("FAIL test4");
        int[][] t3 = {{1, 2, 1}, {2, 1, 3}};
        ++total; if (sol.networkDelayTime(t3, 2, 2) == 3) ++pass; else System.out.println("FAIL test5");
        if (pass == total) {
            System.out.println("== 0743 PASS ==");
        } else {
            System.out.println("== 0743 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

744. Find Smallest Letter Greater Than Target(寻找比目标字母大的最小字母)

题目描述

给你一个字符数组 letters,该数组按非递减顺序排序,以及一个字符 target。letters 里
至少有两个不同的字符。返回 letters 中大于 target 的最小的字符。如果不存在这样的字符,
则返回 letters 的第一个字符。

示例 1

  • 输入:letters = ['c', 'f', 'j'], target = 'a'
  • 输出:'c'

示例 2

  • 输入:letters = ['c','f','j'], target = 'c'
  • 输出:'f'

示例 3

  • 输入:letters = ['x','x','y','y'], target = 'z'
  • 输出:'x'
    提示:
  • 2 <= letters.length <= 10^4
  • letters[i] 是一个小写字母
  • letters 按非递减顺序排序
  • letters 至少包含两个不同的字母
  • target 是一个小写字母

解题思路

解法一(二分查找,主提交):在有序数组中找「第一个严格大于 target」的位置,等价于
upper_bound。若二分结果到达末尾(lo == n),说明全部字母都不大于 target,返回
letters[0]。
时间复杂度 O(log n),空间复杂度 O(1)。
解法二(线性扫描):顺序查找第一个大于 target 的字符,没有则返回首字符。简单直观,
时间复杂度 O(n)。

代码实现

class Solution {

    // 解法一:二分查找
    public char nextGreatestLetter(char[] letters, char target) {
        int lo = 0, hi = letters.length;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (letters[mid] > target) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo < letters.length ? letters[lo] : letters[0];
    }

    // 解法二:线性扫描
    public char nextGreatestLetter2(char[] letters, char target) {
        for (char c : letters) {
            if (c > target) {
                return c;
            }
        }
        return letters[0];
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        char[] l1 = {'c', 'f', 'j'};
        ++total; if (sol.nextGreatestLetter(l1, 'a') == 'c') ++pass; else System.out.println("FAIL test1 got " + sol.nextGreatestLetter(l1, 'a'));
        ++total; if (sol.nextGreatestLetter(l1, 'c') == 'f') ++pass; else System.out.println("FAIL test2");
        char[] l2 = {'x', 'x', 'y', 'y'};
        ++total; if (sol.nextGreatestLetter(l2, 'z') == 'x') ++pass; else System.out.println("FAIL test3");
        ++total; if (sol.nextGreatestLetter2(l1, 'a') == 'c') ++pass; else System.out.println("FAIL test4");
        ++total; if (sol.nextGreatestLetter(l1, 'j') == 'c') ++pass; else System.out.println("FAIL test5");
        ++total; if (sol.nextGreatestLetter2(l2, 'z') == 'x') ++pass; else System.out.println("FAIL test6");
        if (pass == total) {
            System.out.println("== 0744 PASS ==");
        } else {
            System.out.println("== 0744 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

745. Prefix and Suffix Search(前缀和后缀搜索)

题目描述

设计一个包含一些单词的特殊词典,并能够通过前缀和后缀来检索单词。
实现 WordFilter 类:

  • WordFilter(String[] words) 使用词典中的单词 words 初始化对象。
  • f(String pref, String suff) 返回词典中具有前缀 pref 和后缀 suff 的单词的下标。
    如果存在不止一个满足要求的下标,返回其中最大的下标。如果不存在这样的单词,返回 -1。

示例

  • 输入:["WordFilter", "f"],[[["apple"]], ["a", "e"]]
  • 输出:[null, 0]
  • 解释:WordFilter wordFilter = new WordFilter(["apple"]);
    wordFilter.f("a", "e"); // 返回 0,因为下标为 0 的单词前缀 "a" 且后缀 "e"。
    提示:
  • 1 <= words.length <= 10^4
  • 1 <= words[i].length <= 10
  • 1 <= pref.length, suff.length <= 10
  • words[i]、pref 和 suff 只包含小写英文字母
  • 最多对 f 进行 10^4 次调用

解题思路

解法一(前缀 + 后缀拼接键哈希,主提交):对于每个单词 w 和每个可能的 (前缀长度 i, 后缀长度 j),构造键 pre + "#" + suf 存入 Map<String, Integer>,值存「当前最大下标」。
因为要求返回最大下标,构造时按下标从小到大遍历并覆盖即可让最终值为最大下标。
每个单词长度 <= 10,全部组合最多 10 * 10 * 10^4 项,可接受。
构造时间复杂度 O(Σ len(w)²),查询 O(len(pref) + len(suff))。
解法二(朴素遍历):每次 f 线性扫描所有单词做 startsWith/endsWith,最坏 O(n·L)。
作为对照参考。

代码实现

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

class WordFilter {

    private static final char SEP = '#';
    private final Map<String, Integer> map = new HashMap<>();

    public WordFilter(String[] words) {
        for (int idx = 0; idx < words.length; ++idx) {
            String w = words[idx];
            int len = w.length();
            // 枚举所有 (前缀, 后缀) 组合,覆盖时取最大下标
            for (int i = 1; i <= len; ++i) {
                String pref = w.substring(0, i);
                for (int j = 0; j <= len; ++j) {
                    String suff = j == 0 ? "" : w.substring(len - j);
                    map.put(pref + SEP + suff, idx);
                }
            }
        }
    }

    public int f(String pref, String suff) {
        Integer r = map.get(pref + SEP + suff);
        return r == null ? -1 : r;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        int pass = 0, total = 0;
        WordFilter wf = new WordFilter(new String[] {"apple"});
        ++total; if (wf.f("a", "e") == 0) ++pass; else System.out.println("FAIL test1 got " + wf.f("a", "e"));
        ++total; if (wf.f("b", "e") == -1) ++pass; else System.out.println("FAIL test2 got " + wf.f("b", "e"));
        ++total; if (wf.f("app", "le") == 0) ++pass; else System.out.println("FAIL test3");
        WordFilter wf2 = new WordFilter(new String[] {"cabaabaaaa", "bcbcababaa", "bcaaacabcb"});
        ++total; if (wf2.f("ab", "abcaccbcaa") == -1) ++pass; else System.out.println("FAIL test4 got " + wf2.f("ab", "abcaccbcaa"));
        ++total; if (wf2.f("a", "aa") == -1) ++pass; else System.out.println("FAIL test5 got " + wf2.f("a", "aa"));
        WordFilter wf3 = new WordFilter(new String[] {"ab", "aab"});
        ++total; if (wf3.f("a", "b") == 1) ++pass; else System.out.println("FAIL test6 got " + wf3.f("a", "b"));
        if (pass == total) {
            System.out.println("== 0745 PASS ==");
        } else {
            System.out.println("== 0745 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一构造 O(Σ L²),查询 O(Lp + Ls)O(Σ L²)
解法二查询 O(n·L)O(n·L)

746. Min Cost Climbing Stairs(使用最小花费爬楼梯)

题目描述

给你一个整数数组 cost,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。
一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶
开始爬楼梯。请你计算并返回达到楼梯顶部(下标 cost.length)的最低花费。

示例 1

  • 输入:cost = [10, 15, 20]
  • 输出:15
  • 解释:从下标为 1 的台阶开始,支付 15 后向上爬两个台阶到达顶部,总花费 15。

示例 2

  • 输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
  • 输出:6
  • 解释:从下标 0 开始,支付 1 爬两个台阶到下标 2,支付 1 到下标 4……最后一个 1 爬到顶部。
    提示:
  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999

解题思路

解法一(DP 滚动数组,主提交):设 dp[i] 为到达第 i 级台阶(在支付前)的最小花费,则
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]),起点 dp[0] = dp[1] = 0
(可直接从 0 或 1 开始)。答案即 dp[n](n = cost.length,为楼顶位置)。用两个变量
滚动可省数组。
时间复杂度 O(n),空间复杂度 O(1)。
解法二(反向 DP):从楼顶向回算,f[i] = cost[i] + min(f[i+1], f[i+2]),答案
min(f[0], f[1])。语义略绕,逻辑等价。

代码实现

class Solution {

    // 解法一:正向滚动 DP
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int prev2 = 0, prev1 = 0; // dp[i-2], dp[i-1]
        for (int i = 2; i <= n; ++i) {
            int cur = Math.min(prev1 + cost[i - 1], prev2 + cost[i - 2]);
            prev2 = prev1;
            prev1 = cur;
        }
        return prev1;
    }

    // 解法二:反向数组 DP
    public int minCostClimbingStairs2(int[] cost) {
        int n = cost.length;
        int[] f = new int[n + 2];
        for (int i = n - 1; i >= 0; --i) {
            f[i] = cost[i] + Math.min(f[i + 1], f[i + 2]);
        }
        return Math.min(f[0], f[1]);
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[] c1 = {10, 15, 20};
        ++total; if (sol.minCostClimbingStairs(c1) == 15) ++pass; else System.out.println("FAIL test1 got " + sol.minCostClimbingStairs(c1));
        int[] c2 = {1, 100, 1, 1, 1, 100, 1, 1, 100, 1};
        ++total; if (sol.minCostClimbingStairs(c2) == 6) ++pass; else System.out.println("FAIL test2 got " + sol.minCostClimbingStairs(c2));
        ++total; if (sol.minCostClimbingStairs2(c1) == 15) ++pass; else System.out.println("FAIL test3");
        ++total; if (sol.minCostClimbingStairs2(c2) == 6) ++pass; else System.out.println("FAIL test4");
        ++total; if (sol.minCostClimbingStairs(new int[] {0, 0}) == 0) ++pass; else System.out.println("FAIL test5");
        if (pass == total) {
            System.out.println("== 0746 PASS ==");
        } else {
            System.out.println("== 0746 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

747. Largest Number At Least Twice of Others(至少是其他数字两倍的最大数)

题目描述

给你一个整数数组 nums,其中总是存在唯一的一个最大整数。请你找出数组中的最大元素并检查
它是否至少是数组中每个其他数字的两倍。如果是,则返回最大元素的下标,否则返回 -1。

示例 1

  • 输入:nums = [3,6,1,0]
  • 输出:1
  • 解释:6 是最大的整数,对于数组中的其他整数,6 至少是数组中其他元素的两倍,返回下标 1。

示例 2

  • 输入:nums = [1,2,3,4]
  • 输出:-1
  • 解释:4 没有超过 3 的两倍大,所以返回 -1。
    提示:
  • 2 <= nums.length <= 50
  • 0 <= nums[i] <= 100
  • nums 中的最大元素是唯一的

解题思路

解法一(一趟遍历记录最大与次大,主提交):维护最大值 max、次大值 second 及最大下标。
遍历时更新:若当前值大于 max,把旧的 max 变为次大;否则与 second 比较。最后判断
max >= 2 * second 即可(因为最大唯一,其余最大值就是 second)。
注意 0 的处理:0 >= 2 * 0 成立,符合题意(0 是其他数字的两倍)。
时间复杂度 O(n),空间复杂度 O(1)。
解法二(两趟遍历):先找最大,再检查其余所有元素是否 2 * nums[i] <= max。实现直观。

代码实现

class Solution {

    // 解法一:一趟遍历,维护最大与次大
    public int dominantIndex(int[] nums) {
        int maxIdx = 0, max = nums[0], second = Integer.MIN_VALUE;
        for (int i = 1; i < nums.length; ++i) {
            int v = nums[i];
            if (v > max) {
                second = max;
                max = v;
                maxIdx = i;
            } else if (v > second) {
                second = v;
            }
        }
        return max >= 2 * second ? maxIdx : -1;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[] n1 = {3, 6, 1, 0};
        ++total; if (sol.dominantIndex(n1) == 1) ++pass; else System.out.println("FAIL test1 got " + sol.dominantIndex(n1));
        int[] n2 = {1, 2, 3, 4};
        ++total; if (sol.dominantIndex(n2) == -1) ++pass; else System.out.println("FAIL test2");
        int[] n3 = {1};
        ++total; if (sol.dominantIndex(n3) == 0) ++pass; else System.out.println("FAIL test3");
        int[] n4 = {0, 0, 0, 1};
        ++total; if (sol.dominantIndex(n4) == 3) ++pass; else System.out.println("FAIL test4 got " + sol.dominantIndex(n4));
        int[] n5 = {0, 0, 2, 3};
        ++total; if (sol.dominantIndex(n5) == -1) ++pass; else System.out.println("FAIL test5");
        int[] n6 = {2, 0, 0, 3};
        ++total; if (sol.dominantIndex(n6) == -1) ++pass; else System.out.println("FAIL test6 got " + sol.dominantIndex(n6));
        if (pass == total) {
            System.out.println("== 0747 PASS ==");
        } else {
            System.out.println("== 0747 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

748. Shortest Completing Word(最短补全词)

题目描述

给你一个字符串 licensePlate 和一个字符串数组 words,请你找出 words 中的最短补全词。
补全词是一个包含 licensePlate 中所有字母的单词。忽略 licensePlate 中的数字和空格,
不区分大小写。如果某个字母在 licensePlate 中出现不止一次,那么该字母在补全词中的出现
次数应当一致或者更多。
例如:licensePlate = "aBc 12c",补全词应当包含字母 'a'、'b'(忽略大写)和两个
'c'。可能的补全词有 "abccdef"、"caaacab" 以及 "cbca"。
请返回 words 中的最短补全词。题目数据保证一定存在一个最短补全词。当有多个单词都符合
最短补全词的匹配条件时取 words 中第一个出现的那个。

示例 1

  • 输入:licensePlate = "1s3 PSt", words = ["step", "steps", "stripe", "stepple"]
  • 输出:"steps"

示例 2

  • 输入:licensePlate = "1s3 456", words = ["looks", "pest", "stew", "show"]
  • 输出:"pest"
    提示:
  • 1 <= licensePlate.length <= 7
  • licensePlate 由数字、大小写字母或空格 ' ' 组成
  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 15
  • words[i] 由小写英文字母组成

解题思路

解法一(频率计数 + 线性筛选,主提交):先统计 licensePlate 中每个英文字母(忽略大小写)
的需求出现次数 need[26],然后依次遍历 words 中每个单词:统计其字母频次,检查对每个
需要的字母是否 freq[c] >= need[c]。取「满足条件且长度最短」的第一个单词。因为按
words 顺序扫描,长度相等时自然保留最先出现的。
时间复杂度 O(|words| · L),空间复杂度 O(1)。
解法二(排序长度优先):把 words 复制一份按下标顺序遍历但优先短词,通过记录「最长匹配
单词的最短长度」实现,逻辑等价于法一;此处以法一的顺序扫描为准。

代码实现

import java.util.Arrays;
import java.util.List;

class Solution {

    // 解法一:频次计数 + 顺序扫描
    public String shortestCompletingWord(String licensePlate, String[] words) {
        int[] need = new int[26];
        for (char c : licensePlate.toCharArray()) {
            if (Character.isLetter(c)) {
                ++need[Character.toLowerCase(c) - 'a'];
            }
        }
        String ans = null;
        for (String w : words) {
            if (ans != null && w.length() >= ans.length()) {
                continue;
            }
            int[] freq = new int[26];
            for (char c : w.toCharArray()) {
                ++freq[c - 'a'];
            }
            boolean ok = true;
            for (int i = 0; i < 26; ++i) {
                if (freq[i] < need[i]) {
                    ok = false;
                    break;
                }
            }
            if (ok) {
                ans = w;
            }
        }
        return ans;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        String[] w1 = {"step", "steps", "stripe", "stepple"};
        ++total; if (sol.shortestCompletingWord("1s3 PSt", w1).equals("steps")) ++pass; else System.out.println("FAIL test1 got " + sol.shortestCompletingWord("1s3 PSt", w1));
        String[] w2 = {"looks", "pest", "stew", "show"};
        ++total; if (sol.shortestCompletingWord("1s3 456", w2).equals("pest")) ++pass; else System.out.println("FAIL test2 got " + sol.shortestCompletingWord("1s3 456", w2));
        String[] w3 = {"step", "steps"};
        ++total; if (sol.shortestCompletingWord("1s3PSt", w3).equals("steps")) ++pass; else System.out.println("FAIL test3 got " + sol.shortestCompletingWord("1s3PSt", w3));
        String[] w4 = {"ab", "bcd", "a"};
        ++total; if (sol.shortestCompletingWord("b", w4).equals("ab")) ++pass; else System.out.println("FAIL test4 got " + sol.shortestCompletingWord("b", w4));
        if (pass == total) {
            System.out.println("== 0748 PASS ==");
        } else {
            System.out.println("== 0748 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

749. Contain Virus(隔离病毒)

题目描述

病毒扩散得很快,现在你的任务是尽可能地通过安装防火墙来隔离病毒。假设世界由 m x n 的二维
矩阵 isInfected 组成,isInfected[i][j] == 0 表示该区域未感染病毒,而
isInfected[i][j] == 1 表示该区域已感染病毒。可以在任意 2 个相邻单元之间的共享边界上安装
一个防火墙(并且只有一个防火墙)。
每天晚上,病毒会从被感染区域向相邻未感染区域扩散,除非被防火墙隔离。现由于资源有限,每天你
只能安装一系列防火墙来隔离其中一个被病毒感染的区域(一个区域或连续的一片区域),且该感染区
域对未感染区域的威胁最大且保证唯一。
你需要努力使得最后有部分区域不被病毒感染,如果可以成功,那么返回需要使用的防火墙个数;如果
无法实现,则返回在世界被病毒全部感染时已安装的防火墙个数。

示例 1

  • 输入:isInfected = [[0,1,0,0,0,0,0,1],[0,1,0,0,0,0,0,1],[0,0,0,0,0,0,0,1],[0,0,0,0,0,0,0,0]]
  • 输出:10

示例 2

  • 输入:isInfected = [[1,1,1],[1,0,1],[1,1,1]]
  • 输出:4

示例 3

  • 输入:isInfected = [[1,1,1,0,0,0,0,0,0],[1,0,1,0,1,1,1,1,1],[1,1,1,0,0,0,0,0,0]]
  • 输出:13
    提示:
  • m == isInfected.length
  • n == isInfected[i].length
  • 1 <= m, n <= 50
  • isInfected[i][j] 是 0 或 1
  • 数据保证每轮「威胁最大的区域」唯一

解题思路

解法一(BFS 连通块 + 模拟隔离,主提交):重复以下过程直到没有感染区域或没有可扩散边界:

  1. 对每个未访问的感染连通块 BFS,收集块内格子、它能扩散到的「边界未感染格子」集合(按行列
    编码去重)以及所需的防火墙数(与边界相邻的边数);
  2. 选出「威胁最大」(边界格子数最多)的连通块,答案累加它的防火墙数,并把该块全部格子置为
    -1(隔离,不再参与后续扩散);
  3. 其余连通块执行「扩散」:把各自边界上的未感染格子置为 1;
  4. 若某块没有边界可扩散,则它已完全被围住,退化为无害,循环随之结束。
    时间复杂度 O(轮数 · m·n),轮数不超过 m·n,空间复杂度 O(m·n)。
    解法二(递归 DFS 版):同一逻辑用递归 DFS 收集连通块,代码风格不同、思路等价。

代码实现

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

class Solution {

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

    private static class Region {
        List<Integer> cells = new ArrayList<>();  // 感染格编码 i * n + j
        Set<Integer> frontier = new HashSet<>();  // 扩散边界(未感染格编码)
        int walls = 0;                            // 所需防火墙数
    }

    // 解法一:BFS 连通块 + 模拟
    public int containVirus(int[][] isInfected) {
        m = isInfected.length;
        n = isInfected[0].length;
        int ans = 0;
        while (true) {
            boolean[][] visited = new boolean[m][n];
            List<Region> regions = new ArrayList<>();
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (isInfected[i][j] == 1 && !visited[i][j]) {
                        Region reg = new Region();
                        Deque<int[]> q = new ArrayDeque<>();
                        q.offer(new int[] {i, j});
                        visited[i][j] = true;
                        while (!q.isEmpty()) {
                            int[] cur = q.poll();
                            reg.cells.add(cur[0] * n + cur[1]);
                            for (int d = 0; d < 4; ++d) {
                                int x = cur[0] + DI[d], y = cur[1] + DJ[d];
                                if (x < 0 || x >= m || y < 0 || y >= n) {
                                    continue;
                                }
                                if (isInfected[x][y] == 1 && !visited[x][y]) {
                                    visited[x][y] = true;
                                    q.offer(new int[] {x, y});
                                } else if (isInfected[x][y] == 0) {
                                    ++reg.walls;
                                    reg.frontier.add(x * n + y);
                                }
                            }
                        }
                        regions.add(reg);
                    }
                }
            }
            if (regions.isEmpty()) {
                break; // 全部被感染或被隔离
            }
            int best = 0;
            for (int r = 1; r < regions.size(); ++r) {
                if (regions.get(r).frontier.size() > regions.get(best).frontier.size()) {
                    best = r;
                }
            }
            // 威胁最大的块也没有可扩散边界:全部块被围住,游戏结束
            if (regions.get(best).frontier.isEmpty()) {
                break;
            }
            ans += regions.get(best).walls;
            // 隔离 best 块
            for (int c : regions.get(best).cells) {
                int x = c / n, y = c % n;
                isInfected[x][y] = -1;
            }
            // 其余块扩散
            for (int r = 0; r < regions.size(); ++r) {
                if (r == best) {
                    continue;
                }
                for (int c : regions.get(r).frontier) {
                    int x = c / n, y = c % n;
                    if (isInfected[x][y] == 0) {
                        isInfected[x][y] = 1;
                    }
                }
            }
        }
        return ans;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[][] v1 = {{0, 1, 0, 0, 0, 0, 0, 1},
                      {0, 1, 0, 0, 0, 0, 0, 1},
                      {0, 0, 0, 0, 0, 0, 0, 1},
                      {0, 0, 0, 0, 0, 0, 0, 0}};
        ++total; if (sol.containVirus(v1) == 10) ++pass; else System.out.println("FAIL test1 got " + sol.containVirus(v1));
        int[][] v2 = {{1, 1, 1}, {1, 0, 1}, {1, 1, 1}};
        ++total; if (sol.containVirus(v2) == 4) ++pass; else System.out.println("FAIL test2 got " + sol.containVirus(v2));
        int[][] v3 = {{1, 1, 1, 0, 0, 0, 0, 0, 0},
                      {1, 0, 1, 0, 1, 1, 1, 1, 1},
                      {1, 1, 1, 0, 0, 0, 0, 0, 0}};
        ++total; if (sol.containVirus(v3) == 13) ++pass; else System.out.println("FAIL test3 got " + sol.containVirus(v3));
        int[][] v4 = {{1, 1, 1}, {1, 0, 1}, {1, 1, 1}, {0, 0, 0}};
        ++total; if (sol.containVirus(v4) == 7) ++pass; else System.out.println("FAIL test4 got " + sol.containVirus(v4));
        if (pass == total) {
            System.out.println("== 0749 PASS ==");
        } else {
            System.out.println("== 0749 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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

750. Number Of Corner Rectangles(角矩形的数量)

题目描述

给定一个只包含 0 和 1 的 m x n 整数矩阵 grid,返回其中「角矩形」的数量。
一个「角矩形」是由四个不同的在矩阵上的 1 形成的轴对齐的矩形。注意只有角的位置才需要为
1。注意:4 个 1 的位置需要是不同的。

示例 1

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

示例 2

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

示例 3

  • 输入:grid = [[1,1,1,1]]
  • 输出:0
    提示:
  • grid.length == m
  • grid[0].length == n
  • 1 <= m, n <= 200
  • grid[i][j] 是 0 或 1
  • 数据保证产生的答案在 32 位有符号整数范围内

解题思路

解法一(行对计数,主提交):一个角矩形由两行上的两个相同列对构成。枚举任意两行 r1 < r2,
统计这两行中「同列都为 1」的列对数量 c,则这两行可构成 C(c, 2) 个角矩形。用
Map<Integer, Integer> 统计两行间共享 1 的列对:扫描列 j,若两行都为 1,则与之前共享的
列累加 count++,并把 count 累加进答案。为避免 O(n²) 列对空间,直接「遇到第 k 个共享列,
就与前面 k-1 个共享列构成 k-1 个矩形」。
时间复杂度 O(m²·n),空间复杂度 O(1)。
解法二(朴素四顶点枚举):枚举左上/右上/左下/右下四角,O(m²·n²),作对照。

代码实现

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

class Solution {

    // 解法一:行对计数
    public int countCornerRectangles(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int ans = 0;
        for (int r1 = 0; r1 < m; ++r1) {
            for (int r2 = r1 + 1; r2 < m; ++r2) {
                int shared = 0; // 已遇到的「两行同为 1」的列数
                for (int c = 0; c < n; ++c) {
                    if (grid[r1][c] == 1 && grid[r2][c] == 1) {
                        ans += shared; // 与之前每个共享列构成一个角矩形
                        ++shared;
                    }
                }
            }
        }
        return ans;
    }

    // 解法二:朴素四角枚举
    public int countCornerRectangles2(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int ans = 0;
        for (int r1 = 0; r1 < m; ++r1) {
            for (int r2 = r1 + 1; r2 < m; ++r2) {
                for (int c1 = 0; c1 < n; ++c1) {
                    if (grid[r1][c1] != 1 || grid[r2][c1] != 1) {
                        continue;
                    }
                    for (int c2 = c1 + 1; c2 < n; ++c2) {
                        if (grid[r1][c2] == 1 && grid[r2][c2] == 1) {
                            ++ans;
                        }
                    }
                }
            }
        }
        return ans;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution sol = new Solution();
        int pass = 0, total = 0;
        int[][] g1 = {{1, 0, 0, 1, 0}, {0, 0, 1, 0, 1}, {0, 0, 0, 1, 0}, {1, 0, 1, 0, 1}};
        ++total; if (sol.countCornerRectangles(g1) == 1) ++pass; else System.out.println("FAIL test1 got " + sol.countCornerRectangles(g1));
        ++total; if (sol.countCornerRectangles2(g1) == 1) ++pass; else System.out.println("FAIL test2");
        int[][] g2 = {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}};
        ++total; if (sol.countCornerRectangles(g2) == 9) ++pass; else System.out.println("FAIL test3 got " + sol.countCornerRectangles(g2));
        int[][] g3 = {{1, 1, 1, 1}};
        ++total; if (sol.countCornerRectangles(g3) == 0) ++pass; else System.out.println("FAIL test4");
        int[][] g4 = {{1, 0, 1}, {0, 1, 0}, {1, 0, 1}};
        ++total; if (sol.countCornerRectangles(g4) == 1) ++pass; else System.out.println("FAIL test5 got " + sol.countCornerRectangles(g4));
        if (pass == total) {
            System.out.println("== 0750 PASS ==");
        } else {
            System.out.println("== 0750 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

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