3454.分割正方形II

目标

给你一个二维整数数组 squares ,其中 squares[i] = [xi, yi, li] 表示一个与 x 轴平行的正方形的左下角坐标和正方形的边长。

找到一个最小的 y 坐标,它对应一条水平线,该线需要满足它以上正方形的总面积 等于 该线以下正方形的总面积。

答案如果与实际答案的误差在 10^-5 以内,将视为正确答案。

注意:正方形 可能会 重叠。重叠区域只 统计一次 。

示例 1:

输入: squares = [[0,0,1],[2,2,1]]
输出: 1.00000
解释:
任何在 y = 1 和 y = 2 之间的水平线都会有 1 平方单位的面积在其上方,1 平方单位的面积在其下方。最小的 y 坐标是 1。

示例 2:

输入: squares = [[0,0,2],[1,1,1]]
输出: 1.00000
解释:
由于蓝色正方形和红色正方形有重叠区域且重叠区域只统计一次。所以直线 y = 1 将正方形分割成两部分且面积相等。

说明:

  • 1 <= squares.length <= 5 * 10^4
  • squares[i] = [xi, yi, li]
  • squares[i].length == 3
  • 0 <= xi, yi <= 10^9
  • 1 <= li <= 10^
  • 所有正方形的总面积不超过 10^15。

思路

代码

性能

85.最大矩形

目标

给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

示例 1:

输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
输出:6
解释:最大矩形如上图所示。

示例 2:

输入:matrix = [["0"]]
输出:0

示例 3:

输入:matrix = [["1"]]
输出:1

说明:

  • rows == matrix.length
  • cols == matrix[0].length
  • 1 <= rows, cols <= 200
  • matrix[i][j] 为 '0' 或 '1'

思路

代码

性能

1458.两个子序列的最大点积

目标

给你两个数组 nums1 和 nums2 。

请你返回 nums1 和 nums2 中两个长度相同的 非空 子序列的最大点积。

数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[2,3,5] 是 [1,2,3,4,5] 的一个子序列而 [1,5,3] 不是。

示例 1:

输入:nums1 = [2,1,-2,5], nums2 = [3,0,-6]
输出:18
解释:从 nums1 中得到子序列 [2,-2] ,从 nums2 中得到子序列 [3,-6] 。
它们的点积为 (2*3 + (-2)*(-6)) = 18 。

示例 2:

输入:nums1 = [3,-2], nums2 = [2,-6,7]
输出:21
解释:从 nums1 中得到子序列 [3] ,从 nums2 中得到子序列 [7] 。
它们的点积为 (3*7) = 21 。

示例 3:

输入:nums1 = [-1,-1], nums2 = [1,1]
输出:-1
解释:从 nums1 中得到子序列 [-1] ,从 nums2 中得到子序列 [1] 。
它们的点积为 -1 。

说明:

  • 1 <= nums1.length, nums2.length <= 500
  • -1000 <= nums1[i], nums2[i] <= 1000

思路

有两个数组 nums1nums2,求这两个数组长度相等的子序列的点积的最大值。

定义 dp[i][j] 表示 nums1 的前 i 个元素的子序列与 nums2 的前 j 个元素的子序列的最大点积

  • 如果选择 nums[i] * nums[j],剩下的子问题变成 nums1 的前 i - 1 个元素的子序列 与 nums2 的前 j - 1 个元素的子序列的点积最大值,或者为 0,因为当前已经选择了,前面可以不选。即 Math.max(0, dp[i - 1][j - 1]) + nums[i] * nums[j]
  • 如果不选 nums[i] * nums[j],问题变成 nums1 的前 i - 1 个元素的子序列 与 nums2 的前 j 个元素的子序列的点积最大值 dp[i - 1][j],以及 dp[i][j - 1]dp[i - 1][j - 1]

代码


/**
 * @date 2026-01-08 9:05
 */
public class MaxDotProduct1458 {

    public int maxDotProduct(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        int[][] dp = new int[n1 + 1][n2 + 1];
        Arrays.fill(dp[0], Integer.MIN_VALUE);
        for (int i = 0; i <= n1; i++) {
            dp[i][0] = Integer.MIN_VALUE;
        }
        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++) {
                dp[i][j] = Math.max(Math.max(0, dp[i - 1][j - 1]) + nums1[i - 1] * nums2[j - 1], Math.max(dp[i - 1][j], dp[i][j - 1]));
            }
        }
        return dp[n1][n2];
    }
}

性能

1411.给Nx3网格图涂色的方案数

目标

你有一个 n x 3 的网格图 grid ,你需要用 红,黄,绿 三种颜色之一给每一个格子上色,且确保相邻格子颜色不同(也就是有相同水平边或者垂直边的格子颜色不同)。

给你网格图的行数 n 。

请你返回给 grid 涂色的方案数。由于答案可能会非常大,请你返回答案对 10^9 + 7 取余的结果。

示例 1:

输入:n = 1
输出:12
解释:总共有 12 种可行的方法:

示例 2:

输入:n = 2
输出:54

示例 3:

输入:n = 3
输出:246

示例 4:

输入:n = 7
输出:106494

示例 5:

输入:n = 5000
输出:30228214

说明:

  • n == grid.length
  • grid[i].length == 3
  • 1 <= n <= 5000

思路

代码

性能

1970.你能穿过矩阵的最后一天

目标

给你一个下标从 1 开始的二进制矩阵,其中 0 表示陆地,1 表示水域。同时给你 row 和 col 分别表示矩阵中行和列的数目。

一开始在第 0 天,整个 矩阵都是 陆地 。但每一天都会有一块新陆地被 水 淹没变成水域。给你一个下标从 1 开始的二维数组 cells ,其中 cells[i] = [ri, ci] 表示在第 i 天,第 ri 行 ci 列(下标都是从 1 开始)的陆地会变成 水域 (也就是 0 变成 1 )。

你想知道从矩阵最 上面 一行走到最 下面 一行,且只经过陆地格子的 最后一天 是哪一天。你可以从最上面一行的 任意 格子出发,到达最下面一行的 任意 格子。你只能沿着 四个 基本方向移动(也就是上下左右)。

请返回只经过陆地格子能从最 上面 一行走到最 下面 一行的 最后一天 。

示例 1:

输入:row = 2, col = 2, cells = [[1,1],[2,1],[1,2],[2,2]]
输出:2
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 2 天。

示例 2:

输入:row = 2, col = 2, cells = [[1,1],[1,2],[2,1],[2,2]]
输出:1
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 1 天。

示例 3:

输入:row = 3, col = 3, cells = [[1,2],[2,1],[3,3],[2,2],[1,1],[1,3],[2,3],[3,2],[3,1]]
输出:3
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 3 天。

说明:

  • 2 <= row, col <= 2 * 10^4
  • 4 <= row col <= 2 10^4
  • cells.length == row * col
  • 1 <= ri <= row
  • 1 <= ci <= col
  • cells 中的所有格子坐标都是 唯一 的。

思路

有一个二进制矩阵,0 代表陆地,1 代表水域。第 0 天,整个矩阵都是陆地,之后的每一天都会有一块陆地变为水域,cells[i] = [rowi, coli] 表示第 i + 1 天,矩阵的 rowi 行,coli 列会变为水域,注意行列的下标从 1 开始。返回能从第一行走到最后一行的最后一天是哪天。

BFS 可用于判断路径是否存在,二分答案判断即可。

代码


/**
 * @date 2025-12-31 9:56
 */
public class LatestDayToCross1970 {

    public int latestDayToCross(int row, int col, int[][] cells) {
        int l = 0, r = cells.length - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (check(row, col, m, cells)) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return r;
    }

    public int[][] directions = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    public boolean check(int row, int col, int m, int[][] cells) {
        int[][] grid = new int[row + 1][col + 1];
        for (int i = 0; i < m; i++) {
            grid[cells[i][0]][cells[i][1]] = 1;
        }
        Deque<int[]> q = new ArrayDeque<>();
        for (int i = 1; i <= col; i++) {
            if (grid[1][i] == 0) {
                grid[1][i] = 1;
                q.offer(new int[]{1, i});
            }
        }
        while (!q.isEmpty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                int[] cur = q.poll();
                for (int[] direction : directions) {
                    int x = cur[0] + direction[0];
                    int y = cur[1] + direction[1];
                    if (x >= 1 && x <= row && y >= 1 && y <= col && grid[x][y] == 0) {
                        if (x == row) {
                            return true;
                        }
                        grid[x][y] = 1;
                        q.offer(new int[]{x, y});
                    }
                }
            }
        }
        return false;
    }

}

性能

2402.会议室III

目标

给你一个整数 n ,共有编号从 0n - 1n 个会议室。

给你一个二维整数数组 meetings ,其中 meetings[i] = [starti, endi] 表示一场会议将会在 半闭 时间区间 [starti, endi) 举办。所有 starti 的值 互不相同 。

会议将会按以下方式分配给会议室:

  1. 每场会议都会在未占用且编号 最小 的会议室举办。
  2. 如果没有可用的会议室,会议将会延期,直到存在空闲的会议室。延期会议的持续时间和原会议持续时间 相同 。
  3. 当会议室处于未占用状态时,将会优先提供给原 开始 时间更早的会议。

返回举办最多次会议的房间 编号 。如果存在多个房间满足此条件,则返回编号 最小 的房间。

半闭区间 [a, b)ab 之间的区间,包括 a 但 不包括 b

示例 1:

输入:n = 2, meetings = [[0,10],[1,5],[2,7],[3,4]]
输出:0
解释:
- 在时间 0 ,两个会议室都未占用,第一场会议在会议室 0 举办。
- 在时间 1 ,只有会议室 1 未占用,第二场会议在会议室 1 举办。
- 在时间 2 ,两个会议室都被占用,第三场会议延期举办。
- 在时间 3 ,两个会议室都被占用,第四场会议延期举办。
- 在时间 5 ,会议室 1 的会议结束。第三场会议在会议室 1 举办,时间周期为 [5,10) 。
- 在时间 10 ,两个会议室的会议都结束。第四场会议在会议室 0 举办,时间周期为 [10,11) 。
会议室 0 和会议室 1 都举办了 2 场会议,所以返回 0 。

示例 2:

输入:n = 3, meetings = [[1,20],[2,10],[3,5],[4,9],[6,8]]
输出:1
解释:
- 在时间 1 ,所有三个会议室都未占用,第一场会议在会议室 0 举办。
- 在时间 2 ,会议室 1 和 2 未占用,第二场会议在会议室 1 举办。
- 在时间 3 ,只有会议室 2 未占用,第三场会议在会议室 2 举办。
- 在时间 4 ,所有三个会议室都被占用,第四场会议延期举办。 
- 在时间 5 ,会议室 2 的会议结束。第四场会议在会议室 2 举办,时间周期为 [5,10) 。
- 在时间 6 ,所有三个会议室都被占用,第五场会议延期举办。 
- 在时间 10 ,会议室 1 和 2 的会议结束。第五场会议在会议室 1 举办,时间周期为 [10,12) 。 
会议室 1 和会议室 2 都举办了 2 场会议,所以返回 1 。

说明:

  • 1 <= n <= 100
  • 1 <= meetings.length <= 10^5
  • meetings[i].length == 2
  • 0 <= starti < endi <= 5 * 10^5
  • starti 的所有值 互不相同

思路

n 个会议室,编号为 0 ~ n - 1。有一个二维数组,meetings[i] 表示 [starti, endi) 范围内需要举办一场会议,会优先使用未被占用且编号最小的会议室。如果没有会议室可用,则需要延后举办,如果同一时间有多个会议需要举办,原开始时间最小的优先举办。

这个题目的关键点是,当有多个满足条件的会议室时,选择编号最小的,而不是最早可用的。需要使用两个优先队列,一个追踪会议室的最早空闲时间,另一个则追踪空闲会议室中编号最小的。将会议按照开始时间排序,按顺序遍历,将可选的会议室加入空闲编号堆,如果这个堆非空,从中取一个会议室,并将结束时间(可用时间)放入空闲时间堆中;否则,从空闲时间堆中取一个会议室,将其空闲时间加上会议持续时间后再放入堆中。

代码


/**
 * @date 2025-12-30 14:01
 */
public class MostBooked2402 {

    public int mostBooked(int n, int[][] meetings) {
        int[] cnt = new int[n];
        PriorityQueue<int[]> rooms = new PriorityQueue<>((a, b) -> {
            int compare = a[0] - b[0];
            if (compare != 0) {
                return compare;
            }
            return a[1] - b[1];
        });
        PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> a[1] - b[1]);
        for (int i = 0; i < n; i++) {
            rooms.offer(new int[]{0, i});
        }
        Arrays.sort(meetings, (a, b) -> a[0] - b[0]);
        for (int[] meeting : meetings) {
            while (!rooms.isEmpty() && rooms.peek()[0] <= meeting[0]) {
                q.offer(rooms.poll());
            }
            int[] room;
            if (!q.isEmpty()) {
                room = q.poll();
                rooms.offer(new int[]{meeting[1], room[1]});
            } else {
                room = rooms.poll();
                rooms.offer(new int[]{room[0] + meeting[1] - meeting[0], room[1]});
            }
            cnt[room[1]]++;
        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (cnt[i] > cnt[res]) {
                res = i;
            }
        }
        return res;
    }

}

性能

960.删列造序III

目标

给定由 n 个小写字母字符串组成的数组 strs ,其中每个字符串长度相等。

选取一个删除索引序列,对于 strs 中的每个字符串,删除对应每个索引处的字符。

比如,有 strs = ["abcdef","uvwxyz"] ,删除索引序列 {0, 2, 3} ,删除后为 ["bef", "vyz"] 。

假设,我们选择了一组删除索引 answer ,那么在执行删除操作之后,最终得到的数组的行中的 每个元素 都是按字典序排列的(即 (strs[0][0] <= strs[0][1] <= ... <= strs[0][strs[0].length - 1]) 和 (strs[1][0] <= strs[1][1] <= ... <= strs[1][strs[1].length - 1]) ,依此类推)。

请返回 answer.length 的最小可能值 。

示例 1:

输入:strs = ["babca","bbazb"]
输出:3
解释:
删除 0、1 和 4 这三列后,最终得到的数组是 strs = ["bc", "az"]。
这两行是分别按字典序排列的(即,strs[0][0] <= strs[0][1] 且 strs[1][0] <= strs[1][1])。
注意,strs[0] > strs[1] —— 数组 strs 不一定是按字典序排列的。

示例 2:

输入:strs = ["edcba"]
输出:4
解释:如果删除的列少于 4 列,则剩下的行都不会按字典序排列。

示例 3:

输入:strs = ["ghi","def","abc"]
输出:0
解释:所有行都已按字典序排列。

说明:

  • n == strs.length
  • 1 <= n <= 100
  • 1 <= strs[i].length <= 100
  • strs[i] 由小写英文字母组成

思路

有一个元素长度相同的字符串数组 strs,通过删除列使得每个元素内部的字母是非严格递增的,返回删除的最少列数。

定义 dp[i] 表示以 i 列结尾的最长子序列长度,注意每一行都应该是非严格递增的。对于所有 j < i,如果 j 列均小于等于 i 列,dp[i] = Math.max(dp[i], dp[j] + 1)

代码


/**
 * @date 2025-12-22 9:09
 */
public class MinDeletionSize960 {

    public int minDeletionSize(String[] strs) {
        int n = strs[0].length();
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int res = 1;
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (isIncrease(i, j, strs)) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            res = Math.max(res, dp[i]);
        }
        return n - res;
    }

    public boolean isIncrease(int i, int j, String[] strs) {
        for (String str : strs) {
            if (str.charAt(j) > str.charAt(i)) {
                return false;
            }
        }
        return true;
    }

}

性能

2092.找出知晓秘密的所有专家

目标

给你一个整数 n ,表示有 n 个专家从 0 到 n - 1 编号。另外给你一个下标从 0 开始的二维整数数组 meetings ,其中 meetings[i] = [xi, yi, timei] 表示专家 xi 和专家 yi 在时间 timei 要开一场会。一个专家可以同时参加 多场会议 。最后,给你一个整数 firstPerson 。

专家 0 有一个 秘密 ,最初,他在时间 0 将这个秘密分享给了专家 firstPerson 。接着,这个秘密会在每次有知晓这个秘密的专家参加会议时进行传播。更正式的表达是,每次会议,如果专家 xi 在时间 timei 时知晓这个秘密,那么他将会与专家 yi 分享这个秘密,反之亦然。

秘密共享是 瞬时发生 的。也就是说,在同一时间,一个专家不光可以接收到秘密,还能在其他会议上与其他专家分享。

在所有会议都结束之后,返回所有知晓这个秘密的专家列表。你可以按 任何顺序 返回答案。

示例 1:

输入:n = 6, meetings = [[1,2,5],[2,3,8],[1,5,10]], firstPerson = 1
输出:[0,1,2,3,5]
解释:
时间 0 ,专家 0 将秘密与专家 1 共享。
时间 5 ,专家 1 将秘密与专家 2 共享。
时间 8 ,专家 2 将秘密与专家 3 共享。
时间 10 ,专家 1 将秘密与专家 5 共享。
因此,在所有会议结束后,专家 0、1、2、3 和 5 都将知晓这个秘密。

示例 2:

输入:n = 4, meetings = [[3,1,3],[1,2,2],[0,3,3]], firstPerson = 3
输出:[0,1,3]
解释:
时间 0 ,专家 0 将秘密与专家 3 共享。
时间 2 ,专家 1 与专家 2 都不知晓这个秘密。
时间 3 ,专家 3 将秘密与专家 0 和专家 1 共享。
因此,在所有会议结束后,专家 0、1 和 3 都将知晓这个秘密。

示例 3:

输入:n = 5, meetings = [[3,4,2],[1,2,1],[2,3,1]], firstPerson = 1
输出:[0,1,2,3,4]
解释:
时间 0 ,专家 0 将秘密与专家 1 共享。
时间 1 ,专家 1 将秘密与专家 2 共享,专家 2 将秘密与专家 3 共享。
注意,专家 2 可以在收到秘密的同一时间分享此秘密。
时间 2 ,专家 3 将秘密与专家 4 共享。
因此,在所有会议结束后,专家 0、1、2、3 和 4 都将知晓这个秘密。

说明:

  • 2 <= n <= 10^5
  • 1 <= meetings.length <= 10^5
  • meetings[i].length == 3
  • 0 <= xi, yi <= n - 1
  • xi != yi
  • 1 <= timei <= 10^5
  • 1 <= firstPerson <= n - 1

思路

按照时间将会议分组,按时间顺序遍历,使用并查集合并当天同一会议的专家,当所有会议遍历完成后,需要重置不知道秘密的连通块。

代码


/**
 * @date 2025-12-19 9:04
 */
public class FindAllPeople2092 {

    class UnionFind {
        private int[] fa;

        public UnionFind(int n) {
            this.fa = new int[n];
            Arrays.setAll(fa, i -> i);
        }

        public void reset(int a){
            fa[a] = a;
        }

        public int find(int a) {
            if (fa[a] != a) {
                fa[a] = find(fa[a]);
            }
            return fa[a];
        }

        public void merge(int a, int b) {
            int x = find(a);
            int y = find(b);
            if (x < y) {
                fa[y] = x;
            } else if (x > y) {
                fa[x] = y;
            }
        }

        public List<Integer> getConnected(int a) {
            int root = find(a);
            List<Integer> res = new ArrayList<>();
            for (int i = 0; i < fa.length; i++) {
                if (find(i) == root) {
                    res.add(i);
                }
            }
            return res;
        }
    }

    public List<Integer> findAllPeople(int n, int[][] meetings, int firstPerson) {
        UnionFind uf = new UnionFind(n);
        uf.merge(0, firstPerson);
        TreeMap<Integer, List<int[]>> map = new TreeMap<>();
        for (int[] meeting : meetings) {
            map.putIfAbsent(meeting[2], new ArrayList<>());
            List<int[]> list = map.get(meeting[2]);
            list.add(meeting);
        }
        for (List<int[]> value : map.values()) {
            for (int[] meeting : value) {
                uf.merge(meeting[0], meeting[1]);
            }
            for (int[] meeting : value) {
                if (uf.find(meeting[0]) != 0){
                    uf.reset(meeting[0]);
                    uf.reset(meeting[1]);
                }
            }
        }
        return uf.getConnected(0);
    }

}

性能

3562.折扣价交易股票的最大利润

目标

给你一个整数 n,表示公司中员工的数量。每位员工都分配了一个从 1 到 n 的唯一 ID ,其中员工 1 是 CEO。另给你两个下标从 1 开始的整数数组 present 和 future,两个数组的长度均为 n,具体定义如下:

  • present[i] 表示第 i 位员工今天可以购买股票的 当前价格 。
  • future[i] 表示第 i 位员工明天可以卖出股票的 预期价格 。

公司的层级关系由二维整数数组 hierarchy 表示,其中 hierarchy[i] = [ui, vi] 表示员工 ui 是员工 vi 的直属上司。

此外,再给你一个整数 budget,表示可用于投资的总预算。

公司有一项折扣政策:如果某位员工的直属上司购买了自己的股票,那么该员工可以以 半价 购买自己的股票(即 floor(present[v] / 2))。

请返回在不超过给定预算的情况下可以获得的 最大利润 。

注意:

  • 每只股票最多只能购买一次。
  • 不能使用股票未来的收益来增加投资预算,购买只能依赖于 budget。

示例 1:

输入: n = 2, present = [1,2], future = [4,3], hierarchy = [[1,2]], budget = 3
输出: 5
解释:
员工 1 以价格 1 购买股票,获得利润 4 - 1 = 3。
由于员工 1 是员工 2 的直属上司,员工 2 可以以折扣价 floor(2 / 2) = 1 购买股票。
员工 2 以价格 1 购买股票,获得利润 3 - 1 = 2。
总购买成本为 1 + 1 = 2 <= budget,因此最大总利润为 3 + 2 = 5。

示例 2:

输入: n = 2, present = [3,4], future = [5,8], hierarchy = [[1,2]], budget = 4
输出: 4
解释:
员工 2 以价格 4 购买股票,获得利润 8 - 4 = 4。
由于两位员工无法同时购买,最大利润为 4。

示例 3:

输入: n = 3, present = [4,6,8], future = [7,9,11], hierarchy = [[1,2],[1,3]], budget = 10
输出: 10
解释:
员工 1 以价格 4 购买股票,获得利润 7 - 4 = 3。
员工 3 可获得折扣价 floor(8 / 2) = 4,获得利润 11 - 4 = 7。
员工 1 和员工 3 的总购买成本为 4 + 4 = 8 <= budget,因此最大总利润为 3 + 7 = 10。

示例 4:

输入: n = 3, present = [5,2,3], future = [8,5,6], hierarchy = [[1,2],[2,3]], budget = 7
输出: 12
解释:
员工 1 以价格 5 购买股票,获得利润 8 - 5 = 3。
员工 2 可获得折扣价 floor(2 / 2) = 1,获得利润 5 - 1 = 4。
员工 3 可获得折扣价 floor(3 / 2) = 1,获得利润 6 - 1 = 5。
总成本为 5 + 1 + 1 = 7 <= budget,因此最大总利润为 3 + 4 + 5 = 12。

说明:

  • 1 <= n <= 160
  • present.length, future.length == n
  • 1 <= present[i], future[i] <= 50
  • hierarchy.length == n - 1
  • hierarchy[i] == [ui, vi]
  • 1 <= ui, vi <= n
  • ui != vi
  • 1 <= budget <= 160
  • 没有重复的边。
  • 员工 1 是所有员工的直接或间接上司。
  • 输入的图 hierarchy 保证 无环 。

思路

代码

性能

2147.分隔长廊的方案数

目标

在一个图书馆的长廊里,有一些座位和装饰植物排成一列。给你一个下标从 0 开始,长度为 n 的字符串 corridor ,它包含字母 'S' 和 'P' ,其中每个 'S' 表示一个座位,每个 'P' 表示一株植物。

在下标 0 的左边和下标 n - 1 的右边 已经 分别各放了一个屏风。你还需要额外放置一些屏风。每一个位置 i - 1 和 i 之间(1 <= i <= n - 1),至多能放一个屏风。

请你将走廊用屏风划分为若干段,且每一段内都 恰好有两个座位 ,而每一段内植物的数目没有要求。可能有多种划分方案,如果两个方案中有任何一个屏风的位置不同,那么它们被视为 不同 方案。

请你返回划分走廊的方案数。由于答案可能很大,请你返回它对 10^9 + 7 取余 的结果。如果没有任何方案,请返回 0 。

示例 1:

输入:corridor = "SSPPSPS"
输出:3
解释:总共有 3 种不同分隔走廊的方案。
上图中黑色的竖线表示已经放置好的屏风。
上图每种方案中,每一段都恰好有 两个 座位。

示例 2:

输入:corridor = "PPSPSP"
输出:1
解释:只有 1 种分隔走廊的方案,就是不放置任何屏风。
放置任何的屏风都会导致有一段无法恰好有 2 个座位。

示例 3:

输入:corridor = "S"
输出:0
解释:没有任何方案,因为总是有一段无法恰好有 2 个座位。

说明:

  • n == corridor.length
  • 1 <= n <= 10^5
  • corridor[i] 要么是 'S' ,要么是 'P' 。

思路

有一个仅由 SP 组成的长廊布局示意图 corridor,其中 S 表示座位,P 表示植物, 位置 0 的左边与 n - 1 的右边已经放置了一个屏风。现在需要使用屏风将走廊进行划分,要求屏风之间只能有两个座位,求可行的划分方案数。

首先找到第二个座位的下标,再找到下一个两个座位的第一个座位的下标,它们之间的植物数量 + 1 就是可插入的屏风方案,使用乘法原理计算总方案数。

代码


/**
 * @date 2025-12-22 16:28
 */
public class NumberOfWays2147 {

    public int numberOfWays(String corridor) {
        int mod = 1000000007;
        int[] prev = new int[]{-1, 0};
        int i = 0;
        char[] chars = corridor.toCharArray();
        int n = chars.length;
        while (i < n && chars[i] == 'P') {
            i++;
        }
        prev[1] = i - 1;
        long res = 0L;
        long prevCnt = 1L;
        while (i < n) {
            int[] cur = new int[2];
            int cnt = 0;
            while (i < n && cnt < 2) {
                if (chars[i] == 'S') {
                    cur[cnt++] = i;
                }
                i++;
            }
            if (cnt == 2) {
                prevCnt = (prevCnt * (cur[0] - prev[1])) % mod;
                res = prevCnt;
                prev = cur;
            } else if (cnt == 1) {
                res = 0;
            }
        }
        return (int) res;
    }

}

性能