跳转至

2466. 统计构造好字符串的方案数

来源第 91 场双周赛 Q2难度中等分数1694

题目描述

给你整数 zero ,one ,low 和 high ,我们从空字符串开始构造一个字符串,每一步执行下面操作中的一种:

  • 将 '0' 在字符串末尾添加 zero  次。
  • 将 '1' 在字符串末尾添加 one 次。

以上操作可以执行任意次。

如果通过以上过程得到一个 长度 在 low 和 high 之间(包含上下边界)的字符串,那么这个字符串我们称为 好 字符串。

请你返回满足以上要求的 不同 好字符串数目。由于答案可能很大,请将结果对 109 + 7 取余 后返回。

 

示例 1:

输入:low = 3, high = 3, zero = 1, one = 1
输出:8
解释:
一个可能的好字符串是 "011" 。
可以这样构造得到:"" -> "0" -> "01" -> "011" 。
从 "000" 到 "111" 之间所有的二进制字符串都是好字符串。

示例 2:

输入:low = 2, high = 3, zero = 1, one = 2
输出:5
解释:好字符串为 "00" ,"11" ,"000" ,"110" 和 "011" 。

 

提示:

  • 1 <= low <= high <= 105
  • 1 <= zero, one <= low

解法

方法一:记忆化搜索

思考

每次追加 \(zero\) 个 \(0\) 或 \(one\) 个 \(1\),长度落在 \([low,high]\) 即好串。 \(high \le 10^5\),令 \(dfs(i)\) 为已拼出长度 \(i\) 时的方案:若 \(i\) 已在区间内先计 \(1\),再分支 \(i+zero\) 与 \(i+one\)。越界为 \(0\)。

我们设计一个函数 \(dfs(i)\) 表示从第 \(i\) 位开始构造的好字符串的个数,答案即为 \(dfs(0)\)。

函数 \(dfs(i)\) 的计算过程如下:

  • 如果 \(i \gt high\),返回 \(0\);
  • 如果 $ low \leq i \leq high$,答案累加 \(1\),然后 \(i\) 之后既可以添加 zero 个 \(0\),也可以添加 one 个 \(1\),因此答案累加上 \(dfs(i + zero) + dfs(i + one)\)。

过程中,我们需要对答案取模,并且可以使用记忆化搜索减少重复计算。

时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n = high\)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Solution:
    def countGoodStrings(self, low: int, high: int, zero: int, one: int) -> int:
        @cache
        def dfs(i):
            if i > high:
                return 0
            ans = 0
            if low <= i <= high:
                ans += 1
            ans += dfs(i + zero) + dfs(i + one)
            return ans % mod

        mod = 10**9 + 7
        return dfs(0)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
    private static final int MOD = (int) 1e9 + 7;
    private int[] f;
    private int lo;
    private int hi;
    private int zero;
    private int one;

    public int countGoodStrings(int low, int high, int zero, int one) {
        f = new int[high + 1];
        Arrays.fill(f, -1);
        lo = low;
        hi = high;
        this.zero = zero;
        this.one = one;
        return dfs(0);
    }

    private int dfs(int i) {
        if (i > hi) {
            return 0;
        }
        if (f[i] != -1) {
            return f[i];
        }
        long ans = 0;
        if (i >= lo && i <= hi) {
            ++ans;
        }
        ans += dfs(i + zero) + dfs(i + one);
        ans %= MOD;
        f[i] = (int) ans;
        return f[i];
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Solution {
public:
    const int mod = 1e9 + 7;

    int countGoodStrings(int low, int high, int zero, int one) {
        vector<int> f(high + 1, -1);
        function<int(int)> dfs = [&](int i) -> int {
            if (i > high) return 0;
            if (f[i] != -1) return f[i];
            long ans = i >= low && i <= high;
            ans += dfs(i + zero) + dfs(i + one);
            ans %= mod;
            f[i] = ans;
            return ans;
        };
        return dfs(0);
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func countGoodStrings(low int, high int, zero int, one int) int {
    f := make([]int, high+1)
    for i := range f {
        f[i] = -1
    }
    const mod int = 1e9 + 7
    var dfs func(i int) int
    dfs = func(i int) int {
        if i > high {
            return 0
        }
        if f[i] != -1 {
            return f[i]
        }
        ans := 0
        if i >= low && i <= high {
            ans++
        }
        ans += dfs(i+zero) + dfs(i+one)
        ans %= mod
        f[i] = ans
        return ans
    }
    return dfs(0)
}

方法二:动态规划

思考

方法一从长度 \(high\) 往回填后缀表,\(f[i]\) 是当前长度已为 \(i\) 时还能得到的好串数。同一批字符串也可以按恰好拼到长度 \(i\) 来数:\(f[0]=1\),由 \(f[i-zero]\) 与 \(f[i-one]\) 转入,再把 \([low,high]\) 上的 \(f\) 求和。

自左向右填表,时间仍为 \(O(n)\)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function countGoodStrings(low: number, high: number, zero: number, one: number): number {
    const mod = 10 ** 9 + 7;
    const f: number[] = new Array(high + 1).fill(0);
    f[0] = 1;

    for (let i = 1; i <= high; i++) {
        if (i >= zero) f[i] += f[i - zero];
        if (i >= one) f[i] += f[i - one];
        f[i] %= mod;
    }

    const ans = f.slice(low, high + 1).reduce((acc, cur) => acc + cur, 0);

    return ans % mod;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * @param {number} low
 * @param {number} high
 * @param {number} zero
 * @param {number} one
 * @return {number}
 */
function countGoodStrings(low, high, zero, one) {
    const mod = 10 ** 9 + 7;
    const f = Array(high + 1).fill(0);
    f[0] = 1;

    for (let i = 1; i <= high; i++) {
        if (i >= zero) f[i] += f[i - zero];
        if (i >= one) f[i] += f[i - one];
        f[i] %= mod;
    }

    const ans = f.slice(low, high + 1).reduce((acc, cur) => acc + cur, 0);

    return ans % mod;
}

方法三:动态规划

思考

每次追加 \(zero\) 个 \(0\) 或 \(one\) 个 \(1\),长度落在 \([low,high]\) 才是好串。\(high \le 10^5\),按追加顺序展开会重复大量长度。

较短的那一档总是先递归。\(zero\) 与 \(one\) 都不小于 \(1\),这次调用一定走向更大的长度,最坏调用链长度为 \(high\),栈会溢出。

更长的长度在从 \(high\) 往 \(0\) 填时已经就绪。令 \(f[i]\) 为当前长度已是 \(i\) 时的好串数:区间内先计 \(1\),再加上 \(f[i+zero]\) 与 \(f[i+one]\),越过 \(high\) 的项视为 \(0\)。

令 \(f[i]\) 表示当前长度已经是 \(i\) 时,能够构造出的好字符串个数。答案为 \(f[0]\)。长度超过 \(high\) 的贡献为 \(0\)。

从 \(i = high\) 填到 \(0\)。若 \(low \le i \le high\),当前字符串本身就是好串,先计入 \(1\)。之后仍可追加 \(zero\) 个 \(0\) 或 \(one\) 个 \(1\),把 \(f[i + zero]\) 与 \(f[i + one]\) 加进来;下标超过 \(high\) 时该项为 \(0\)。每一步都对 \(10^9 + 7\) 取模。

时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n = high\)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Solution:
    def countGoodStrings(self, low: int, high: int, zero: int, one: int) -> int:
        mod = 10**9 + 7
        f = [0] * (high + 1)
        for i in range(high, -1, -1):
            ans = int(low <= i <= high)
            if i + zero <= high:
                ans += f[i + zero]
            if i + one <= high:
                ans += f[i + one]
            f[i] = ans % mod
        return f[0]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Solution {
    private static final int MOD = (int) 1e9 + 7;

    public int countGoodStrings(int low, int high, int zero, int one) {
        int[] f = new int[high + 1];
        for (int i = high; i >= 0; --i) {
            long ans = i >= low && i <= high ? 1 : 0;
            if (i + zero <= high) {
                ans += f[i + zero];
            }
            if (i + one <= high) {
                ans += f[i + one];
            }
            f[i] = (int) (ans % MOD);
        }
        return f[0];
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
    const int mod = 1e9 + 7;

    int countGoodStrings(int low, int high, int zero, int one) {
        vector<int> f(high + 1);
        for (int i = high; i >= 0; --i) {
            long ans = i >= low && i <= high;
            if (i + zero <= high) {
                ans += f[i + zero];
            }
            if (i + one <= high) {
                ans += f[i + one];
            }
            f[i] = ans % mod;
        }
        return f[0];
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func countGoodStrings(low int, high int, zero int, one int) int {
    const mod int = 1e9 + 7
    f := make([]int, high+1)
    for i := high; i >= 0; i-- {
        ans := 0
        if i >= low && i <= high {
            ans++
        }
        if i+zero <= high {
            ans += f[i+zero]
        }
        if i+one <= high {
            ans += f[i+one]
        }
        f[i] = ans % mod
    }
    return f[0]
}

评论