LeetCode 每日一题 2026/6/22-2026/6/28

记录了初步解题思路 以及本地实现代码;并不一定为最优 也希望大家能一起探讨 一起进步


目录

      • [6/22 1189. "气球" 的最大数量](#6/22 1189. “气球” 的最大数量)
      • [6/23 3699. 锯齿形数组的总数 I](#6/23 3699. 锯齿形数组的总数 I)
      • [6/24 3700. 锯齿形数组的总数 II](#6/24 3700. 锯齿形数组的总数 II)
      • [6/25 3737. 统计主要元素子数组数目 I](#6/25 3737. 统计主要元素子数组数目 I)
      • [6/26 3739. 统计主要元素子数组数目 II](#6/26 3739. 统计主要元素子数组数目 II)
      • 6/27
      • 6/28

6/22 1189. "气球" 的最大数量

统计b,a,l,o,n,e出现的次数

取b,a,n以及l/2,o/2的最小值

python 复制代码
def maxNumberOfBalloons(text):
    """
    :type text: str
    :rtype: int
    """
    cnt = [0]*6
    for c in text:
        if c == 'b':
            cnt[0] += 1
        elif c == 'a':
            cnt[1] += 1
        elif c == 'n':
            cnt[2] += 1
        elif c == 'l':
            cnt[3] += 1
        elif c == 'o':
            cnt[4] += 1
        elif c == 'e':
            cnt[5] += 1
    return min(cnt[0], cnt[1], cnt[2], cnt[3]//2, cnt[4]//2)

6/23 3699. 锯齿形数组的总数 I

动态规划 + 原地前后缀累加(优化常数)

"相邻不相等" + "任意 3 个连续元素不能严格单调"

等价于:相邻比较结果必须交替(<, >, <, >... 或 >, <, >, <...)。

只统计"第一步上升(<)"这一类。

设 dpv 为当前长度下,末尾取值索引 v(0...m-1) 的方案数。

第 step 次比较时:

若需要 "<":newy = sum(oldx), x < y

可左到右原地做前缀累加得到;

若需要 ">":newy = sum(oldx), x > y

可右到左原地做后缀累加得到。

利用对称性:映射 x -> (l + r - x) 会把 "<" 与 ">" 全部互换,

因而"第一步上升"和"第一步下降"方案数相同,最终答案 = 2 * count_first_up。

python 复制代码
def zigZagArrays(n, l, r):
    """
    :type n: int
    :type l: int
    :type r: int
    :rtype: int
    """
    MOD = 10**9 + 7
    m = r - l + 1

    # 对称性:把任意值 x 映射成 (l + r - x),会把 "<" 与 ">" 全部互换,
    # 因而"第一步上升"和"第一步下降"的方案数相同,只需算一边再乘 2。
    # dp[v]:当前长度下,末尾取索引 v(0..m-1) 的方案数(仅统计"第一步上升")。
    dp = [1] * m

    # step 表示第 step 次比较(从 1 到 n-1)
    for step in range(1, n):
        if step & 1:
            # 需要 "<":new[y] = sum(old[x], x < y)
            pre = 0
            for y in range(m):
                old = dp[y]
                dp[y] = pre
                pre += old
                if pre >= MOD:
                    pre -= MOD
        else:
            # 需要 ">":new[y] = sum(old[x], x > y)
            suf = 0
            for y in range(m - 1, -1, -1):
                old = dp[y]
                dp[y] = suf
                suf += old
                if suf >= MOD:
                    suf -= MOD

    return (sum(dp) * 2) % MOD

6/24 3700. 锯齿形数组的总数 II

矩阵快速幂

设 m = r - l + 1,把取值映射为 0...m-1。

条件"相邻不等 + 任意 3 个连续元素不严格单调"

等价于:相邻比较符号必须交替(<,>,<,>... 或 >,<,>,<...)。

先只统计"第一步是上升(<)"的方案数,最后利用对称性乘 2:

映射 x -> (l + r - x) 会把所有 < 与 > 互换,因此两类数量相同。

定义长度为 m 的列向量 v,vy 表示当前末尾值为 y 的方案数。

U 变换(一步要求上升):newy = sum(vx), x < y

D 变换(一步要求下降):newy = sum(vx), x > y

对"第一步上升"来说,步骤序列是 U, D, U, D, ...

把两步合并:

B = D * U。若总比较次数 s = n - 1,

s = 2t: v = B^t * v0

s = 2t+1: v = U(B^t * v0)

其中 v0 = 1,1,...,1(长度 1 时末尾任意)。

所以只需计算 B^t 对向量的作用,用快速幂 O(log n)。

python 复制代码
def zigZagArrays(n, l, r):
    """
    :type n: int
    :type l: int
    :type r: int
    :rtype: int
    """
    MOD = 10**9 + 7
    m = r - l + 1

    def apply_up(vec):
        """U(vec): new[y] = sum(vec[x], x < y)"""
        out = [0] * m
        pre = 0
        for y in range(m):
            out[y] = pre
            pre += vec[y]
            if pre >= MOD:
                pre -= MOD
        return out

    def mat_mul(A, B):
        """m*m 矩阵乘法"""
        C = [[0] * m for _ in range(m)]
        for i in range(m):
            Ai = A[i]
            Ci = C[i]
            for k in range(m):
                aik = Ai[k]
                if aik == 0:
                    continue
                Bk = B[k]
                for j in range(m):
                    Ci[j] = (Ci[j] + aik * Bk[j]) % MOD
        return C

    def mat_vec_mul(A, vec):
        """m*m 矩阵乘 m 向量"""
        out = [0] * m
        for i in range(m):
            row = A[i]
            s = 0
            for j in range(m):
                s = (s + row[j] * vec[j]) % MOD
            out[i] = s
        return out

    # B = D * U,且可直接写成闭式:
    # B[y][x] = |{z | z>x 且 z>y}| = (m - 1 - max(x, y))
    B = [[0] * m for _ in range(m)]
    for y in range(m):
        By = B[y]
        for x in range(m):
            By[x] = m - 1 - (x if x > y else y)

    s = n - 1
    t = s // 2
    vec = [1] * m

    # 快速幂:计算 vec <- B^t * vec
    base = B
    while t:
        if t & 1:
            vec = mat_vec_mul(base, vec)
        base = mat_mul(base, base)
        t >>= 1

    # 若比较次数为奇数,还需额外做一次 U
    if s & 1:
        vec = apply_up(vec)

    # 乘 2 补上"第一步下降"的对称方案
    return (2 * (sum(vec) % MOD)) % MOD

6/25 3737. 统计主要元素子数组数目 I

枚举子数组

枚举每个子数组起点 i,再向右枚举终点 j。

在固定 i 的内层循环里,维护当前子数组 numsi...j 中 target 的出现次数 cnt。

当前子数组长度为 k = j - i + 1。

若 cnt > k // 2,则 target 是该子数组的主要元素,答案加 1。

由于只关心 target 的次数,不需要统计其他元素频次。

python 复制代码
def countMajoritySubarrays(nums, target):
    """
    :type nums: List[int]
    :type target: int
    :rtype: int
    """
    n = len(nums)
    ans = 0

    for i in range(n):
        cnt = 0
        for j in range(i, n):
            if nums[j] == target:
                cnt += 1
            if cnt > (j - i + 1) // 2:
                ans += 1

    return ans

6/26 3739. 统计主要元素子数组数目 II

前缀和 + 树状数组

把数组按 target 转换成:

numsi == target 记为 +1

numsi != target 记为 -1

那么一个子数组中 target 为主要元素(出现次数 > 一半)

等价于该子数组和 > 0。

设前缀和为 pre。对每个位置作为右端点时,

需要统计有多少历史前缀和 old_pre 满足 old_pre < pre,

这些都对应和 > 0 的子数组。

前缀和范围在 -n, n,做一个偏移 offset = n + 1,

把它映射到 1, 2n+1,用树状数组维护"每个前缀和值出现次数":

query(idx): 前缀频次和

update(idx, +1): 插入当前前缀和

流程:

先插入空前缀和 0。

每遍历一个数更新 pre,再把 query(pre-1) 累加到答案,

最后把当前 pre 插入树状数组。

python 复制代码
def countMajoritySubarrays(nums, target):
    """
    :type nums: List[int]
    :type target: int
    :rtype: int
    """
    n = len(nums)

    class Fenwick:
        def __init__(self, size):
            self.n = size
            self.bit = [0] * (size + 1)

        def update(self, i, delta):
            while i <= self.n:
                self.bit[i] += delta
                i += i & -i

        def query(self, i):
            s = 0
            while i > 0:
                s += self.bit[i]
                i -= i & -i
            return s

    size = 2 * n + 1
    offset = n + 1
    tree = Fenwick(size)

    pre = 0
    ans = 0

    # 空前缀和 pre=0 先入树
    tree.update(offset, 1)

    for x in nums:
        pre += 1 if x == target else -1
        idx = pre + offset
        ans += tree.query(idx - 1)  # 统计历史前缀和 < 当前前缀和
        tree.update(idx, 1)

    return ans

6/27

python 复制代码

6/28

python 复制代码