方向一 题目解析 小C选点+小R的糖果选择 | 豆包MarsCode AI刷题

141 阅读4分钟

小C选点

问题描述

小C在坐标轴上有 n 个点,她想从中选出 m 个点,使得这些点之间的两两距离都不超过 k。你的任务是帮助小C计算出有多少种选点的方案。由于答案可能非常大,请将答案对 10^9 + 7 取模。

例如:给定点的坐标为 [1, 2, 3, 4],并且 k=3k=3,你可以任选两个点,它们的距离都不会超过 kk,因此总共有6种选点方案。


MOD = 10**9 + 7

# 预处理阶乘和逆元
def precompute_factorials(n, mod):
    factorial = [1] * (n + 1)
    inv_factorial = [1] * (n + 1)
    
    for i in range(2, n + 1):
        factorial[i] = factorial[i - 1] * i % mod
    
    # 费马小定理求逆元
    inv_factorial[n] = pow(factorial[n], mod - 2, mod)
    for i in range(n - 1, 0, -1):
        inv_factorial[i] = inv_factorial[i + 1] * (i + 1) % mod
    
    return factorial, inv_factorial

# 计算组合数 C(n, r)
def comb(n, r, factorial, inv_factorial, mod):
    if n < r or r < 0:
        return 0
    return factorial[n] * inv_factorial[r] % mod * inv_factorial[n - r] % mod

# 主函数
def solution(n, m, k, x):
    x.sort()                             # 将点排序,便于使用滑动窗口
    factorial, inv_factorial = precompute_factorials(n, MOD)
    total_ways = 0                       # 记录总的方案数
    left = 0                             # 滑动窗口的左边界
    
    # 使用滑动窗口法
    for right in range(n):               # 枚举每个点作为窗口的右边界
        # 维持窗口大小:若窗口的最大距离超过 k,则缩小窗口左边界
        while x[right] - x[left] > k:
            left += 1
        
        # 当前窗口大小为 (right - left + 1)
        if right - left + 1 >= m:
            # 从窗口中选择 m - 1 个点(左端包含 right)
            total_ways += comb(right - left, m - 1, factorial, inv_factorial, MOD)
            total_ways %= MOD            # 对结果取模,防止溢出
    
    return total_ways

这个问题涉及从给定的点集合中选出符合条件的组合数。我们可以采用以下步骤来分析和解决该问题:

分析

  1. 首先,将点按从小到大的顺序排列。这样可以通过双指针或滑动窗口的方法快速找到满足两两距离不超过 k 的点集合。
  2. 对每个点x_i,通过寻找以x_i为起点、到x_j的距离不超过 k 的点,形成有效的窗口。
  3. 使用组合公式C(n,m)计算在每个有效窗口内选择m个点的方案数。注意大数情况,对 10^9 + 7 取模。

实现步骤

  1. 使用预处理方法计算组合数并对模数取模。
  2. 使用滑动窗口逐一计算每个起点形成的有效窗口。
  3. 累加每个窗口内的符合条件的组合数,得到最终结果。

小R的糖果选择

问题描述

小R来到糖果店,想要购买一根长长的糖果。糖果店允许顾客选择从中间的某个位置切断,切断后的前半段会被出售。每段糖果有着不同的口味,小R希望她购买的糖果段中包含尽可能多不同的口味。你能帮助小R计算她能购买的最长不重复口味的糖果段吗?

糖果的每段长度为1,小R只对连续的一段感兴趣,并且希望这段糖果的口味不重复。 ``

def solution(n: int, a: list) -> int:
    res = 0
    temp = 0
    l = 0
    r = 0
    table = set()
    while r < n:
        if a[r] not in table:
            table.add(a[r])
            temp += 1
            r += 1
            res = max(temp, res)
        else:
            break
    return res

代码流程

整体思路,求从第一个数字开始的最长不重复子数组的长度

  1. 初始化变量

    • res:用于存储当前最长无重复子数组的长度。
    • temp:用于记录当前窗口内无重复子数组的长度。
    • lr:分别代表当前子数组的左右边界索引。
    • table:一个集合,用于存储当前子数组内的元素,方便检查重复项。
  2. 主循环

    • 当右边界 r 小于数组长度 n 时,进入循环。

    • 检查当前元素 a[r] 是否在 table 中:

      • 如果 a[r] 不在 table 中:

        • a[r] 加入 table,表示当前窗口扩展包含 a[r]
        • 增加 temp,表示当前无重复子数组的长度增加。
        • 移动右边界 r,尝试扩大窗口。
        • 更新 res 为当前 tempres 中的最大值,确保 res 记录最长无重复子数组的长度。
      • 如果 a[r] table 中:

        • 遇到重复元素,跳出循环,因为当前窗口的无重复子数组到此为止。
  3. 返回结果

    • 返回 res,即最长无重复子数组的长度。

代码逻辑的关键点

  • 通过双指针(左右边界)和 table 集合来维护一个无重复元素的窗口,并在窗口扩展时检查是否出现重复元素。
  • 每次遇到重复元素时,退出循环,避免重复元素进入子数组,从而实现无重复的效果。

时间复杂度

由于每个元素最多只会进入和离开 table 一次,时间复杂度为 O(n)。