Medium

题目描述

给定三个整数 nposk

n 个人站成一排,索引从 0 到 n - 1。每个人独立选择一个方向:

  • ‘L’:只对右侧的人可见
  • ‘R’:只对左侧的人可见

索引为 pos 的人看到其他人的规则如下:

  • 对于索引 i < pos 的人,当且仅当他们选择 ‘L’ 时才可见
  • 对于索引 i > pos 的人,当且仅当他们选择 ‘R’ 时才可见

返回使得索引为 pos 的人恰好能看到 k 个人的可能方向分配数量。

由于答案可能很大,返回结果对 10^9 + 7 取模。

示例 1:

输入:n = 3, pos = 1, k = 0
输出:2
解释:
索引 0 在 pos = 1 的左侧,索引 2 在 pos = 1 的右侧。
要看到 k = 0 个人,索引 0 必须选择 'R',索引 2 必须选择 'L',保持都不可见。
索引 1 的人可以选择 'L' 或 'R',因为这不影响计数。因此答案是 2。

示例 2:

输入:n = 3, pos = 2, k = 1
输出:4
解释:
索引 0 和索引 1 在 pos = 2 的左侧,右侧没有索引。
要看到 k = 1 个人,索引 0 或索引 1 中恰好有一个必须选择 'L',另一个必须选择 'R'。
有 2 种方法选择左侧哪个索引可见。
索引 2 的人可以选择 'L' 或 'R',因为这不影响计数。因此答案是 2 + 2 = 4。

示例 3:

输入:n = 1, pos = 0, k = 0
输出:2
解释:
pos = 0 的左侧或右侧都没有索引。
要看到 k = 0 个人,不需要额外条件。
索引 0 的人可以选择 'L' 或 'R'。因此答案是 2。

约束条件:

  • 1 <= n <= 10^5
  • 0 <= pos, k <= n - 1

解题思路

这是一道组合数学问题。我们需要分析在位置 pos 的人能看到恰好 k 个人的所有可能方案数。

核心思路:

  1. 分析可见性规则

    • 左侧(索引 < pos)的人选择 ‘L’ 才对 pos 可见
    • 右侧(索引 > pos)的人选择 ‘R’ 才对 pos 可见
  2. 分割问题

    • 设左侧有 left = pos 个人,右侧有 right = n - pos - 1 个人
    • 要看到恰好 k 个人,可以从左侧选择 i 个可见,从右侧选择 k-i 个可见(0 ≤ i ≤ k)
  3. 计算每种分配方案

    • 从左侧 left 个人中选择 i 个可见:C(left, i)
    • 从右侧 right 个人中选择 k-i 个可见:C(right, k-i)
    • 左侧选中的 i 个人必须选 ‘L’(1种选择)
    • 左侧未选中的 left-i 个人可以选 ‘L’ 或 ‘R’(2^(left-i) 种选择)
    • 右侧选中的 k-i 个人必须选 ‘R’(1种选择)
    • 右侧未选中的 right-(k-i) 个人可以选 ‘L’ 或 ‘R’(2^(right-k+i) 种选择)
    • pos 位置的人可以选 ‘L’ 或 ‘R’(2种选择)
  4. 最终公式: 对所有有效的 i 求和:∑ C(left,i) × C(right,k-i) × 2^(left-i) × 2^(right-k+i) × 2

推荐解法:使用预计算组合数和快速幂的方法,时间复杂度 O(k)。

代码实现

class Solution {
public:
    int countVisiblePeople(int n, int pos, int k) {
        const int MOD = 1e9 + 7;
        
        int left = pos;
        int right = n - pos - 1;
        
        if (k > left + right) return 0;
        
        // 预计算阶乘和逆元
        vector<long long> fact(n + 1), inv_fact(n + 1);
        fact[0] = 1;
        for (int i = 1; i <= n; i++) {
            fact[i] = fact[i-1] * i % MOD;
        }
        
        auto power = [&](long long base, long long exp) -> long long {
            long long result = 1;
            while (exp > 0) {
                if (exp & 1) result = result * base % MOD;
                base = base * base % MOD;
                exp >>= 1;
            }
            return result;
        };
        
        inv_fact[n] = power(fact[n], MOD - 2);
        for (int i = n - 1; i >= 0; i--) {
            inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD;
        }
        
        auto C = [&](int n, int k) -> long long {
            if (k < 0 || k > n) return 0;
            return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD;
        };
        
        long long result = 0;
        for (int i = 0; i <= k; i++) {
            if (i > left || k - i > right) continue;
            
            long long ways = C(left, i) * C(right, k - i) % MOD;
            ways = ways * power(2, left - i + right - k + i + 1) % MOD;
            result = (result + ways) % MOD;
        }
        
        return result;
    }
};
class Solution:
    def countVisiblePeople(self, n: int, pos: int, k: int) -> int:
        MOD = 10**9 + 7
        
        left = pos
        right = n - pos - 1
        
        if k > left + right:
            return 0
        
        # 预计算阶乘和逆元
        fact = [1] * (n + 1)
        for i in range(1, n + 1):
            fact[i] = fact[i-1] * i % MOD
        
        def power(base, exp):
            result = 1
            while exp > 0:
                if exp & 1:
                    result = result * base % MOD
                base = base * base % MOD
                exp >>= 1
            return result
        
        inv_fact = [1] * (n + 1)
        inv_fact[n] = power(fact[n], MOD - 2)
        for i in range(n - 1, -1, -1):
            inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD
        
        def C(n, k):
            if k < 0 or k > n:
                return 0
            return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD
        
        result = 0
        for i in range(k + 1):
            if i > left or k - i > right:
                continue
            
            ways = C(left, i) * C(right, k - i) % MOD
            ways = ways * power(2, left - i + right - k + i + 1) % MOD
            result = (result + ways) % MOD
        
        return result
public class Solution {
    public int CountVisiblePeople(int n, int pos, int k) {
        const int MOD = 1000000007;
        
        int left = pos;
        int right = n - pos - 1;
        
        if (k > left + right) return 0;
        
        // 预计算阶乘和逆元
        long[] fact = new long[n + 1];
        fact[0] = 1;
        for (int i = 1; i <= n; i++) {
            fact[i] = fact[i-1] * i % MOD;
        }
        
        long Power(long baseNum, long exp) {
            long result = 1;
            while (exp > 0) {
                if ((exp & 1) == 1) result = result * baseNum % MOD;
                baseNum = baseNum * baseNum % MOD;
                exp >>= 1;
            }
            return result;
        }
        
        long[] invFact = new long[n + 1];
        invFact[n] = Power(fact[n], MOD - 2);
        for (int i = n - 1; i >= 0; i--) {
            invFact[i] = invFact[i + 1] * (i + 1) % MOD;
        }
        
        long C(int n, int k) {
            if (k < 0 || k > n) return 0;
            return fact[n] * invFact[k] % MOD * invFact[n - k] % MOD;
        }
        
        long result = 0;
        for (int i = 0; i <= k; i++) {
            if (i > left || k - i > right) continue;
            
            long ways = C(left, i) * C(right, k - i) % MOD;
            ways = ways * Power(2, left - i + right - k + i + 1) % MOD;
            result = (result + ways) % MOD;
        }
        
        return (int)result;
    }
}
var countVisiblePeople = function(n, pos, k) {
    const MOD = 1e9 + 7;
    
    const left = pos;
    const right = n - pos - 1;
    
    if (k > left + right) return 0;
    
    // 预计算阶乘和逆元
    const fact = new Array(n + 1);
    fact[0] = 1;
    for (let i = 1; i <= n; i++) {
        fact[i] = (fact[i-1] * i) % MOD;
    }
    
    const power = (base, exp) => {
        let result = 1;
        while (exp > 0) {
            if (exp & 1) result = (result * base) % MOD;
            base = (base * base) % MOD;
            exp >>= 1;
        }
        return result;
    };
    
    const invFact = new Array(n + 1);
    invFact[n] = power(fact[n], MOD - 2);
    for (let i = n - 1; i >= 0; i--) {
        invFact[i] = (invFact[i + 1] * (i + 1)) % MOD;
    }
    
    const C = (n, k) => {
        if (k < 0 || k > n) return 0;
        return (fact[n] * invFact[k] % MOD * invFact[n - k]) % MOD;
    };
    
    let result = 0;
    for (let i = 0; i <= k; i++) {
        if (i > left || k - i > right) continue;
        
        let ways = (C(left, i) * C(right, k - i)) % MOD;
        ways = (ways * power(2, left - i + right - k + i + 1)) % MOD;
        result = (result + ways) % MOD;
    }
    
    return result;
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(n + k)预计算阶乘需要O(n),主循环需要O(k),组合数计算和快速幂都是O(1)
空间复杂度O(n)存储阶乘和逆元数组需要O(n)空间