普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月11日LeetCode 每日一题题解

每日一题-统计被覆盖的建筑🟡

2025年12月11日 00:00

给你一个正整数 n,表示一个 n x n 的城市,同时给定一个二维数组 buildings,其中 buildings[i] = [x, y] 表示位于坐标 [x, y] 的一个 唯一 建筑。

如果一个建筑在四个方向(左、右、上、下)中每个方向上都至少存在一个建筑,则称该建筑 被覆盖 

返回 被覆盖 的建筑数量。

 

示例 1:

输入: n = 3, buildings = [[1,2],[2,2],[3,2],[2,1],[2,3]]

输出: 1

解释:

  • 只有建筑 [2,2] 被覆盖,因为它在每个方向上都至少存在一个建筑:
    • 上方 ([1,2])
    • 下方 ([3,2])
    • 左方 ([2,1])
    • 右方 ([2,3])
  • 因此,被覆盖的建筑数量是 1。

示例 2:

输入: n = 3, buildings = [[1,1],[1,2],[2,1],[2,2]]

输出: 0

解释:

  • 没有任何一个建筑在每个方向上都有至少一个建筑。

示例 3:

输入: n = 5, buildings = [[1,3],[3,2],[3,3],[3,5],[5,3]]

输出: 1

解释:

  • 只有建筑 [3,3] 被覆盖,因为它在每个方向上至少存在一个建筑:
    • 上方 ([1,3])
    • 下方 ([5,3])
    • 左方 ([3,2])
    • 右方 ([3,5])
  • 因此,被覆盖的建筑数量是 1。

 

提示:

  • 2 <= n <= 105
  • 1 <= buildings.length <= 105
  • buildings[i] = [x, y]
  • 1 <= x, y <= n
  • buildings 中所有坐标均 唯一 

3531. 统计被覆盖的建筑

作者 stormsunshine
2025年4月29日 20:13

解法一

思路和算法

根据建筑被覆盖的定义,当一个建筑在四个方向都至少存在一个其他建筑时,该建筑被覆盖。为了计算被覆盖的建筑数量,需要分别判断每个建筑是否被覆盖,因此需要分别统计每个 $x$ 坐标和每个 $y$ 坐标的所有建筑的列表。

使用两个哈希表分别记录每个 $x$ 坐标的所有建筑列表和每个 $y$ 坐标的所有建筑列表,两个哈希表分别为 $x$ 分组哈希表和 $y$ 分组哈希表。遍历数组 $\textit{buildings}$ 将所有建筑加入两个哈希表,然后将两个哈希表中的每个 $x$ 坐标和 $y$ 坐标对应的建筑列表排序,排序方法如下:在 $x$ 分组哈希表中,将每个 $x$ 坐标的所有建筑列表按 $y$ 坐标升序排序;在 $y$ 分组哈希表中,将每个 $y$ 坐标的所有建筑列表按 $x$ 坐标升序排序。

排序结束之后,即可判断每个建筑在 $x$ 坐标方向和 $y$ 坐标方向是否被覆盖。遍历所有建筑,对于位于坐标 $(x, y)$ 的建筑,判断方法如下。

  • 从 $y$ 分组哈希表中得到该建筑的相同 $y$ 坐标的所有建筑的列表,列表已经按 $x$ 坐标升序排序,判断当前 $x$ 坐标是否为列表中的最小值或最大值,如果既不是最小值也不是最大值,则该建筑在 $x$ 坐标方向被覆盖。

  • 从 $x$ 分组哈希表中得到该建筑的相同 $x$ 坐标的所有建筑的列表,列表已经按 $y$ 坐标升序排序,判断当前 $y$ 坐标是否为列表中的最小值或最大值,如果既不是最小值也不是最大值,则该建筑在 $y$ 坐标方向被覆盖。

当一个建筑同时在 $x$ 坐标方向和 $y$ 坐标方向被覆盖时,该建筑被覆盖。

遍历结束之后,即可得到被覆盖的建筑数量。

代码

###Java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        Map<Integer, List<Integer>> xToBuildings = new HashMap<Integer, List<Integer>>();
        Map<Integer, List<Integer>> yToBuildings = new HashMap<Integer, List<Integer>>();
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            xToBuildings.putIfAbsent(x, new ArrayList<Integer>());
            xToBuildings.get(x).add(y);
            yToBuildings.putIfAbsent(y, new ArrayList<Integer>());
            yToBuildings.get(y).add(x);
        }
        Set<Map.Entry<Integer, List<Integer>>> xEntries = xToBuildings.entrySet();
        Set<Map.Entry<Integer, List<Integer>>> yEntries = yToBuildings.entrySet();
        for (Map.Entry<Integer, List<Integer>> entry : xEntries) {
            Collections.sort(entry.getValue());
        }
        for (Map.Entry<Integer, List<Integer>> entry : yEntries) {
            Collections.sort(entry.getValue());
        }
        int count = 0;
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            List<Integer> xList = yToBuildings.get(y);
            List<Integer> yList = xToBuildings.get(x);
            int xMin = xList.get(0), xMax = xList.get(xList.size() - 1);
            int yMin = yList.get(0), yMax = yList.get(yList.size() - 1);
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

###C#

public class Solution {
    public int CountCoveredBuildings(int n, int[][] buildings) {
        IDictionary<int, IList<int>> xToBuildings = new Dictionary<int, IList<int>>();
        IDictionary<int, IList<int>> yToBuildings = new Dictionary<int, IList<int>>();
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            xToBuildings.TryAdd(x, new List<int>());
            xToBuildings[x].Add(y);
            yToBuildings.TryAdd(y, new List<int>());
            yToBuildings[y].Add(x);
        }
        foreach (KeyValuePair<int, IList<int>> pair in xToBuildings) {
            ((List<int>) pair.Value).Sort();
        }
        foreach (KeyValuePair<int, IList<int>> pair in yToBuildings) {
            ((List<int>) pair.Value).Sort();
        }
        int count = 0;
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            IList<int> xIList = yToBuildings[y];
            IList<int> yIList = xToBuildings[x];
            int xMin = xIList[0], xMax = xIList[xIList.Count - 1];
            int yMin = yIList[0], yMax = yIList[yIList.Count - 1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

复杂度分析

  • 时间复杂度:$O(m \log m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。将所有建筑存入两个哈希表的时间是 $O(m)$,排序的时间是 $O(m \log m)$,排序之后计算被覆盖的建筑数量的时间是 $O(m)$,因此时间复杂度是 $O(m \log m)$。

  • 空间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。哈希表的空间是 $O(m)$。

解法二

思路和算法

判断一个建筑是否被覆盖时,需要知道如下信息。

  • 在相同 $y$ 坐标的所有建筑的列表中,该建筑的 $x$ 坐标是否为列表中的最小值或最大值。

  • 在相同 $x$ 坐标的所有建筑的列表中,该建筑的 $y$ 坐标是否为列表中的最小值或最大值。

因此,不需要维护每个 $x$ 坐标和每个 $y$ 坐标的所有建筑,只需要维护每个 $x$ 坐标的最小 $y$ 坐标和最大 $y$ 坐标,以及每个 $y$ 坐标的最小 $x$ 坐标和最大 $x$ 坐标。遍历数组 $\textit{buildings}$ 之后,将最小坐标与最大坐标的信息存入哈希表。再次遍历数组,即可根据哈希表中的最小坐标与最大坐标的信息判断每个建筑是否被覆盖,计算被覆盖的建筑数量。

代码

###Java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        Map<Integer, int[]> xToMinMax = new HashMap<Integer, int[]>();
        Map<Integer, int[]> yToMinMax = new HashMap<Integer, int[]>();
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            xToMinMax.putIfAbsent(x, new int[]{Integer.MAX_VALUE, Integer.MIN_VALUE});
            int[] yMinMax = xToMinMax.get(x);
            yMinMax[0] = Math.min(yMinMax[0], y);
            yMinMax[1] = Math.max(yMinMax[1], y);
            yToMinMax.putIfAbsent(y, new int[]{Integer.MAX_VALUE, Integer.MIN_VALUE});
            int[] xMinMax = yToMinMax.get(y);
            xMinMax[0] = Math.min(xMinMax[0], x);
            xMinMax[1] = Math.max(xMinMax[1], x);
        }
        int count = 0;
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            int[] xMinMax = yToMinMax.get(y);
            int[] yMinMax = xToMinMax.get(x);
            int xMin = xMinMax[0], xMax = xMinMax[1];
            int yMin = yMinMax[0], yMax = yMinMax[1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

###C#

public class Solution {
    public int CountCoveredBuildings(int n, int[][] buildings) {
        IDictionary<int, int[]> xToMinMax = new Dictionary<int, int[]>();
        IDictionary<int, int[]> yToMinMax = new Dictionary<int, int[]>();
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            xToMinMax.TryAdd(x, new int[]{int.MaxValue, int.MinValue});
            int[] yMinMax = xToMinMax[x];
            yMinMax[0] = Math.Min(yMinMax[0], y);
            yMinMax[1] = Math.Max(yMinMax[1], y);
            yToMinMax.TryAdd(y, new int[]{int.MaxValue, int.MinValue});
            int[] xMinMax = yToMinMax[y];
            xMinMax[0] = Math.Min(xMinMax[0], x);
            xMinMax[1] = Math.Max(xMinMax[1], x);
        }
        int count = 0;
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            int[] xMinMax = yToMinMax[y];
            int[] yMinMax = xToMinMax[x];
            int xMin = xMinMax[0], xMax = xMinMax[1];
            int yMin = yMinMax[0], yMax = yMinMax[1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

复杂度分析

  • 时间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。遍历所有建筑维护两个哈希表的时间是 $O(m)$,计算被覆盖的建筑数量的时间是 $O(m)$,因此时间复杂度是 $O(m)$。

  • 空间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。哈希表的空间是 $O(m)$。

统计行列的最小值和最大值(Python/Java/C++/Go)

作者 endlesscheng
2025年4月27日 12:46

分析

如果一个点在同一行的最左边,那么它左边没有点;如果一个点在同一行的最右边,那么它右边没有点。

如果一个点在同一列的最上边,那么它上边没有点;如果一个点在同一列的最下边,那么它下边没有点。

反之,如果一个点不在同一行的最左边也不在最右边,那么这个点左右都有点;如果一个点不在同一列的最上边也不在最下边,那么这个点上下都有点。

算法

记录同一行的最小横坐标和最大横坐标,同一列的最小纵坐标和最大纵坐标。

对于每个建筑 $(x,y)$,如果 $x$ 在这一行的最小值和最大值之间(不能相等),$y$ 在这一列的最小值和最大值之间(不能相等),那么答案加一。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def countCoveredBuildings(self, n: int, buildings: List[List[int]]) -> int:
        row_min = [n + 1] * (n + 1)
        row_max = [0] * (n + 1)
        col_min = [n + 1] * (n + 1)
        col_max = [0] * (n + 1)

        for x, y in buildings:
            # 手写 min max 更快
            if x < row_min[y]: row_min[y] = x
            if x > row_max[y]: row_max[y] = x
            if y < col_min[x]: col_min[x] = y
            if y > col_max[x]: col_max[x] = y

        ans = 0
        for x, y in buildings:
            if row_min[y] < x < row_max[y] and col_min[x] < y < col_max[x]:
                ans += 1
        return ans

###java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        int[] rowMin = new int[n + 1];
        int[] rowMax = new int[n + 1];
        int[] colMin = new int[n + 1];
        int[] colMax = new int[n + 1];
        Arrays.fill(rowMin, n + 1);
        Arrays.fill(colMin, n + 1);

        for (int[] p : buildings) {
            int x = p[0], y = p[1];
            rowMin[y] = Math.min(rowMin[y], x);
            rowMax[y] = Math.max(rowMax[y], x);
            colMin[x] = Math.min(colMin[x], y);
            colMax[x] = Math.max(colMax[x], y);
        }

        int ans = 0;
        for (int[] p : buildings) {
            int x = p[0], y = p[1];
            if (rowMin[y] < x && x < rowMax[y] && colMin[x] < y && y < colMax[x]) {
                ans++;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int countCoveredBuildings(int n, vector<vector<int>>& buildings) {
        vector<int> row_min(n + 1, INT_MAX), row_max(n + 1);
        vector<int> col_min(n + 1, INT_MAX), col_max(n + 1);
        for (auto& p : buildings) {
            int x = p[0], y = p[1];
            row_min[y] = min(row_min[y], x);
            row_max[y] = max(row_max[y], x);
            col_min[x] = min(col_min[x], y);
            col_max[x] = max(col_max[x], y);
        }

        int ans = 0;
        for (auto& p : buildings) {
            int x = p[0], y = p[1];
            if (row_min[y] < x && x < row_max[y] && col_min[x] < y && y < col_max[x]) {
                ans++;
            }
        }
        return ans;
    }
};

###go

func countCoveredBuildings(n int, buildings [][]int) (ans int) {
type pair struct{ min, max int }
row := make([]pair, n+1)
col := make([]pair, n+1)
for i := 1; i <= n; i++ {
row[i].min = math.MaxInt
col[i].min = math.MaxInt
}

add := func(m []pair, x, y int) {
m[y].min = min(m[y].min, x)
m[y].max = max(m[y].max, x)
}
isInner := func(m []pair, x, y int) bool {
return m[y].min < x && x < m[y].max
}

for _, p := range buildings {
x, y := p[0], p[1]
add(row, x, y) // x 加到 row[y] 中
add(col, y, x) // y 加到 col[x] 中
}

for _, p := range buildings {
x, y := p[0], p[1]
if isInner(row, x, y) && isInner(col, y, x) {
ans++
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n+m)$,其中 $m$ 是 $\textit{buildings}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

模拟

作者 tsreaper
2025年4月27日 12:15

解法:模拟

把 $x$ 值相同的建筑放在一个 vector 里,对它们的 $y$ 值排序,就能知道每个建筑上下有没有其它建筑。左右的处理类似。

复杂度 $\mathcal{O}(n\log n)$。

参考代码(c++)

class Solution {
public:
    int countCoveredBuildings(int n, vector<vector<int>>& buildings) {
        int m = buildings.size();
        int cnt[m];
        memset(cnt, 0, sizeof(cnt));

        typedef pair<int, int> pii;
        // 把 x 值相同的建筑放在同一个 vector 里,对它们的 y 值排序
        unordered_map<int, vector<pii>> X;
        // 把 y 值相同的建筑放在同一个 vector 里,对它们的 x 值排序
        unordered_map<int, vector<pii>> Y;
        for (int i = 0; i < m; i++) {
            X[buildings[i][0]].push_back({buildings[i][1], i});
            Y[buildings[i][1]].push_back({buildings[i][0], i});
        }

        for (auto &p : X) {
            auto &vec = p.second;
            sort(vec.begin(), vec.end());
            // 标记下面有其它建筑的房子
            for (int i = 1; i < vec.size(); i++) cnt[vec[i].second]++;
            // 标记上面有其它建筑的房子
            for (int i = 0; i + 1 < vec.size(); i++) cnt[vec[i].second]++;
        }
        for (auto &p : Y) {
            auto &vec = p.second;
            sort(vec.begin(), vec.end());
            // 标记左面有其它建筑的房子
            for (int i = 1; i < vec.size(); i++) cnt[vec[i].second]++;
            // 标记右面有其它建筑的房子
            for (int i = 0; i + 1 < vec.size(); i++) cnt[vec[i].second]++;
        }

        int ans = 0;
        // 需要有 4 个标记
        for (int i = 0; i < m; i++) if (cnt[i] == 4) ans++;
        return ans;
    }
};
昨天 — 2025年12月10日LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:脑筋急转弯(清晰题解)

作者 lcbin
2025年12月10日 07:19

方法一:脑筋急转弯

由于编号为 $0$ 的计算机密码已经被解锁,那么对于其他计算机 $i$,如果存在 $\text{complexity}[i] \leq \text{complexity}[0]$,则无法解锁计算机 $i$,因此返回 $0$。否则,排列可以是任意的,一共有 $(n - 1)!$ 种排列方式。

###python

class Solution:
    def countPermutations(self, complexity: List[int]) -> int:
        mod = 10**9 + 7
        ans = 1
        for i in range(1, len(complexity)):
            if complexity[i] <= complexity[0]:
                return 0
            ans = ans * i % mod
        return ans

###java

class Solution {
    public int countPermutations(int[] complexity) {
        final int mod = (int) 1e9 + 7;
        long ans = 1;
        for (int i = 1; i < complexity.length; ++i) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            ans = ans * i % mod;
        }
        return (int) ans;
    }
}

###cpp

class Solution {
public:
    int countPermutations(vector<int>& complexity) {
        const int mod = 1e9 + 7;
        long long ans = 1;
        for (int i = 1; i < complexity.size(); ++i) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            ans = ans * i % mod;
        }
        return ans;
    }
};

###go

func countPermutations(complexity []int) int {
mod := int64(1e9 + 7)
ans := int64(1)
for i := 1; i < len(complexity); i++ {
if complexity[i] <= complexity[0] {
return 0
}
ans = ans * int64(i) % mod
}
return int(ans)
}

###ts

function countPermutations(complexity: number[]): number {
    const mod = 1e9 + 7;
    let ans = 1;
    for (let i = 1; i < complexity.length; i++) {
        if (complexity[i] <= complexity[0]) {
            return 0;
        }
        ans = (ans * i) % mod;
    }
    return ans;
}

###rust

impl Solution {
    pub fn count_permutations(complexity: Vec<i32>) -> i32 {
        const MOD: i64 = 1_000_000_007;
        let mut ans = 1i64;
        for i in 1..complexity.len() {
            if complexity[i] <= complexity[0] {
                return 0;
            }
            ans = ans * i as i64 % MOD;
        }
        ans as i32
    }
}

时间复杂度 $O(n)$,其中 $n$ 是数组 $\text{complexity}$ 的长度。空间复杂度 $O(1)$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈 😄~

每日一题-统计计算机解锁顺序排列数🟡

2025年12月10日 00:00

给你一个长度为 n 的数组 complexity

在房间里有 n 台 上锁的 计算机,这些计算机的编号为 0 到 n - 1,每台计算机都有一个 唯一 的密码。编号为 i 的计算机的密码复杂度为 complexity[i]

编号为 0 的计算机密码已经 解锁 ,并作为根节点。其他所有计算机必须通过它或其他已经解锁的计算机来解锁,具体规则如下:

  • 可以使用编号为 j 的计算机的密码解锁编号为 i 的计算机,其中 j 是任何小于 i 的整数,且满足 complexity[j] < complexity[i](即 j < i 并且 complexity[j] < complexity[i])。
  • 要解锁编号为 i 的计算机,你需要事先解锁一个编号为 j 的计算机,满足 j < i 并且 complexity[j] < complexity[i]

求共有多少种 [0, 1, 2, ..., (n - 1)] 的排列方式,能够表示从编号为 0 的计算机(唯一初始解锁的计算机)开始解锁所有计算机的有效顺序。

由于答案可能很大,返回结果需要对 109 + 7 取余数。

注意:编号为 0 的计算机的密码已解锁,而 不是 排列中第一个位置的计算机密码已解锁。

排列 是一个数组中所有元素的重新排列。

 

示例 1:

输入: complexity = [1,2,3]

输出: 2

解释:

有效的排列有:

  • [0, 1, 2]
    • 首先使用根密码解锁计算机 0。
    • 使用计算机 0 的密码解锁计算机 1,因为 complexity[0] < complexity[1]
    • 使用计算机 1 的密码解锁计算机 2,因为 complexity[1] < complexity[2]
  • [0, 2, 1]
    • 首先使用根密码解锁计算机 0。
    • 使用计算机 0 的密码解锁计算机 2,因为 complexity[0] < complexity[2]
    • 使用计算机 0 的密码解锁计算机 1,因为 complexity[0] < complexity[1]

示例 2:

输入: complexity = [3,3,3,4,4,4]

输出: 0

解释:

没有任何排列能够解锁所有计算机。

 

提示:

  • 2 <= complexity.length <= 105
  • 1 <= complexity[i] <= 109

3577. 统计计算机解锁顺序排列数

作者 stormsunshine
2025年6月9日 06:15

解法

思路和算法

使用已解锁的计算机密码将未解锁的计算机解锁的条件是:已解锁的计算机的编号和密码复杂度分别小于未解锁的计算机的编号和密码复杂度。由于初始时只有编号为 $0$ 的计算机密码已解锁,编号 $0$ 是最小编号,因此对于其他任意编号 $i$,是否可以使用编号为 $0$ 的计算机密码解锁编号为 $i$ 的计算机的情况如下。

  • 当 $\textit{complexity}[i] > \textit{complexity}[0]$ 时,可以使用编号为 $0$ 的计算机密码解锁编号为 $i$ 的计算机。

  • 当 $\textit{complexity}[i] \le \textit{complexity}[0]$ 时,不能使用编号为 $0$ 的计算机密码解锁编号为 $i$ 的计算机。

如果存在 $1 \le i < n$ 的整数 $i$ 满足 $\textit{complexity}[i] \le \textit{complexity}[0]$,则对于任意可以被编号为 $0$ 的计算机密码解锁的计算机的编号 $j$,必有 $\textit{complexity}[j] > \textit{complexity}[0]$,因此 $\textit{complexity}[j] > \textit{complexity}[i]$,即编号为 $j$ 的计算机密码不能解锁编号为 $i$ 的计算机。因此,编号为 $i$ 的计算机无法被解锁,此时无法解锁所有计算机。

如果 $1 \le i < n$ 的所有整数 $i$ 都满足 $\textit{complexity}[i] > \textit{complexity}[0]$,则所有计算机都能被编号为 $0$ 的计算机密码解锁。由于初始时编号为 $0$ 的计算机密码已解锁,因此其余 $n - 1$ 台计算机可以按任意顺序解锁,排列数是 $(n - 1)!$。

代码

###Java

class Solution {
    static final int MODULO = 1000000007;

    public int countPermutations(int[] complexity) {
        long permutations = 1;
        int n = complexity.length;
        for (int i = 1; i < n; i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            permutations = permutations * i % MODULO;
        }
        return (int) permutations;
    }
}

###C#

public class Solution {
    const int MODULO = 1000000007;

    public int CountPermutations(int[] complexity) {
        long permutations = 1;
        int n = complexity.Length;
        for (int i = 1; i < n; i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            permutations = permutations * i % MODULO;
        }
        return (int) permutations;
    }
}

###C++

const int MODULO = 1000000007;

class Solution {
public:
    int countPermutations(vector<int>& complexity) {
        long long permutations = 1;
        int n = complexity.size();
        for (int i = 1; i < n; i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            permutations = permutations * i % MODULO;
        }
        return permutations;
    }
};

###Python

MODULO = 1000000007

class Solution:
    def countPermutations(self, complexity: List[int]) -> int:
        permutations = 1
        n = len(complexity)
        for i in range(1, n):
            if complexity[i] <= complexity[0]:
                return 0
            permutations = permutations * i % MODULO
        return permutations

###C

const int MODULO = 1000000007;

int countPermutations(int* complexity, int complexitySize) {
    long long permutations = 1;
    for (int i = 1; i < complexitySize; i++) {
        if (complexity[i] <= complexity[0]) {
            return 0;
        }
        permutations = permutations * i % MODULO;
    }
    return permutations;
}

###Go

const MODULO = 1000000007

func countPermutations(complexity []int) int {
    permutations := 1
    n := len(complexity)
    for i := 1; i < n; i++ {
        if complexity[i] <= complexity[0] {
            return 0
        }
        permutations = permutations * i % MODULO
    }
    return permutations
}

###JavaScript

const MODULO = 1000000007;

var countPermutations = function(complexity) {
    let permutations = 1;
    let n = complexity.length;
    for (let i = 1; i < n; i++) {
        if (complexity[i] <= complexity[0]) {
            return 0;
        }
        permutations = permutations * i % MODULO;
    }
    return permutations;
};

###TypeScript

const MODULO = 1000000007;

function countPermutations(complexity: number[]): number {
    let permutations = 1;
    let n = complexity.length;
    for (let i = 1; i < n; i++) {
        if (complexity[i] <= complexity[0]) {
            return 0;
        }
        permutations = permutations * i % MODULO;
    }
    return permutations;
};

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{complexity}$ 的长度。需要遍历数组一次。

  • 空间复杂度:$O(1)$。

脑筋急转弯(Python/Java/C++/Go)

作者 endlesscheng
2025年6月8日 12:06

题目说:用计算机 $j$ 解锁计算机 $i$ 的前提是 $j<i$ 且 $\textit{complexity}[j] < \textit{complexity}[i]$。

观察

  • 一开始就解锁的只有计算机 $0$。
  • 第一轮,被 $0$ 解锁的计算机(记作集合 $A$),密码复杂度比 $\textit{complexity}[0]$ 大。
  • 第二轮,被集合 $A$ 中的计算机解锁的计算机(记作集合 $B$),密码复杂度更大,所以也比 $\textit{complexity}[0]$ 大。
  • 第三轮,被集合 $B$ 中的计算机解锁的计算机(记作集合 $C$),密码复杂度更大,所以也比 $\textit{complexity}[0]$ 大。
  • 依此类推,所有被解锁的计算机的密码复杂度都要比 $\textit{complexity}[0]$ 大。

定理:当且仅当计算机 $0$ 右边的所有计算机的密码复杂度都比 $\textit{complexity}[0]$ 大,才能解锁所有计算机。

充分性:如果计算机 $0$ 右边的所有计算机的密码复杂度都比 $\textit{complexity}[0]$ 大,根据题意,仅用计算机 $0$ 便可解锁所有计算机。

必要性:如果可以解锁所有的计算机,那么计算机 $0$ 右边的所有计算机的密码复杂度都比 $\textit{complexity}[0]$ 大。考虑其逆否命题,即如果在计算机 $0$ 的右边存在计算机 $A$,满足 $\textit{complexity}[i] \le \textit{complexity}[0]$,那么不可能解锁计算机 $A$,更不可能解锁所有计算机。为了解锁计算机 $A$,我们需要在其左边找比 $\textit{complexity}[i]$ 更小的计算机。不断往左找,计算机的密码复杂度只会更小,直到找到一台被计算机 $0$ 解锁的计算机 $B$。$B$ 的密码复杂度必须比 $\textit{complexity}[0]$ 大,但为了能解锁计算机 $A$,$B$ 的密码复杂度又要 $< \textit{complexity}[i] \le \textit{complexity}[0]$,矛盾,所以不可能解锁计算机 $A$。

根据定理,如果计算机 $0$ 右边的所有计算机的密码复杂度都比 $\textit{complexity}[0]$ 大,那么我们可以按照任意顺序解锁这 $n-1$ 台计算机,方案数为 $n-1$ 个不同物品的全排列个数,即

$$
(n-1)!
$$

注意取模。关于模运算的知识点,见 模运算的世界:当加减乘除遇上取模

###py

class Solution:
    def countPermutations(self, complexity: List[int]) -> int:
        MOD = 1_000_000_007
        ans = 1
        for i in range(1, len(complexity)):
            if complexity[i] <= complexity[0]:
                return 0
            ans = ans * i % MOD
        return ans

###java

class Solution {
    public int countPermutations(int[] complexity) {
        final int MOD = 1_000_000_007;
        long ans = 1;
        for (int i = 1; i < complexity.length; i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            ans = ans * i % MOD;
        }
        return (int) ans;
    }
}

###cpp

class Solution {
public:
    int countPermutations(vector<int>& complexity) {
        const int MOD = 1'000'000'007;
        long long ans = 1;
        for (int i = 1; i < complexity.size(); i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            ans = ans * i % MOD;
        }
        return ans;
    }
};

###go

func countPermutations(complexity []int) int {
const mod = 1_000_000_007
ans := 1
for i := 1; i < len(complexity); i++ {
if complexity[i] <= complexity[0] {
return 0
}
ans = ans * i % mod
}
return ans
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{complexity}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

更多相似题目,见下面思维题单的「§5.2 脑筋急转弯」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

分类讨论

作者 tsreaper
2025年6月8日 12:05

解法:分类讨论

因为只能用小 complexity 解锁大 complexity,所以后续解锁的电脑一定满足 complexity[i] > complexity[0]

换句话说,如果存在 i > 0,使得 complexity[i] <= complexity[0],那就无法解锁该电脑,答案为 $0$。

否则,所有电脑都能通过电脑 $0$ 解锁,因此后续编号可以任意排列,答案为 $(n - 1)!$。

复杂度 $\mathcal{O}(n)$。

参考代码(c++)

class Solution {
public:
    int countPermutations(vector<int>& complexity) {
        int n = complexity.size();

        // 检查是否有无法解锁的电脑
        for (int i = 1; i < n; i++) if (complexity[i] <= complexity[0]) return 0;

        // 计算 (n - 1)!
        const int MOD = 1e9 + 7;
        long long ans = 1;
        for (int i = 1; i < n; i++) ans = ans * i % MOD;
        return ans;
    }
};
❌
❌