列表

详情


1977. 划分数字的方案数

你写下了若干 正整数 ,并将它们连接成了一个字符串 num 。但是你忘记给这些数字之间加逗号了。你只记得这一列数字是 非递减 的且 没有 任何数字有前导 0 。

请你返回有多少种可能的 正整数数组 可以得到字符串 num 。由于答案可能很大,将结果对 109 + 7 取余 后返回。

 

示例 1:

输入:num = "327"
输出:2
解释:以下为可能的方案:
3, 27
327

示例 2:

输入:num = "094"
输出:0
解释:不能有数字有前导 0 ,且所有数字均为正数。

示例 3:

输入:num = "0"
输出:0
解释:不能有数字有前导 0 ,且所有数字均为正数。

示例 4:

输入:num = "9999999999999"
输出:101

 

提示:

原站题解

去查看

上次编辑到这里,代码来自缓存 点击恢复默认模板
class Solution { public: int numberOfCombinations(string num) { } };

cpp 解法, 执行用时: 268 ms, 内存消耗: 121 MB, 提交时间: 2023-07-12 09:22:40

/**
 * 动态规划
 **/
class Solution {
private:
    static constexpr int mod = 1000000007;

public:
    int numberOfCombinations(string num) {
        if (num[0] == '0') {
            return 0;
        }

        int n = num.size();

        // 预处理 lcp
        vector<vector<int>> lcp(n, vector<int>(n));
        for (int i = n - 1; i >= 0; --i) {
            lcp[i][n - 1] = (num[i] == num[n - 1]);
            for (int j = i + 1; j < n - 1; ++j) {
                lcp[i][j] = (num[i] == num[j] ? lcp[i + 1][j + 1] + 1 : 0);
            }
        }

        // 辅助函数,计算 x = (x + y) % mod
        auto update = [&](int& x, int y) {
            x += y;
            if (x >= mod) {
                x -= mod;
            }
        };

        // 动态规划
        vector<vector<int>> f(n, vector<int>(n));
        // 边界 f[0][..] = 1
        for (int i = 0; i < n; ++i) {
            f[0][i] = 1;
        }
        for (int i = 1; i < n; ++i) {
            // 有前导零,无需转移
            if (num[i] == '0') {
                continue;
            }
            // 前缀和
            int presum = 0;
            for (int j = i; j < n; ++j) {
                int length = j - i + 1;
                f[i][j] = presum;
                if (i - length >= 0) {
                    // 使用 lcp 比较 num(2i-j-1,i-1) 与 num(i,j) 的大小关系
                    if (lcp[i - length][i] >= length || num[i - length + lcp[i - length][i]] < num[i + lcp[i - length][i]]) {
                        update(f[i][j], f[i - length][i - 1]);
                    }
                    // 更新前缀和
                    update(presum, f[i - length][i - 1]);
                }
            }
        }

        // 最终答案即为所有 f[..][n-1] 的和
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            update(ans, f[i][n - 1]);
        }
        return ans;
    }
};

python3 解法, 执行用时: 3272 ms, 内存消耗: 551 MB, 提交时间: 2023-07-12 09:21:07

'''
'''
class Solution:
    def numberOfCombinations(self, num: str) -> int:
        mod = 10**9 + 7

        if num[0] == "0":
            return 0

        n = len(num)

        # 预处理 lcp
        # lcp[i][j] 表示在字符串 num 中,以 i 开始的后缀与以 j 开始的后缀的「最长公共前缀」的长度
        lcp = [[0] * n for _ in range(n)]
        for i in range(n - 1, -1, -1):
            lcp[i][n - 1] = int(num[i] == num[n - 1])
            for j in range(i + 1, n - 1):
                lcp[i][j] = (lcp[i + 1][j + 1] + 1 if num[i] == num[j] else 0)

        # 动态规划
        # f[i][j] 表示对于字符串 num 的第 0,1,⋯,j 个字符进行划分,并且最后一个数使用了第 
        # i,i+1,⋯j 个字符的方案数。为了叙述方便,用num(i,j) 表示该数。
        f = [[0] * n for _ in range(n)]
        # 边界 f[0][..] = 1
        for i in range(n):
            f[0][i] = 1
        
        for i in range(1, n):
            # 有前导零,无需转移
            if num[i] == "0":
                continue
            
            # 前缀和
            presum = 0
            for j in range(i, n):
                length = j - i + 1
                f[i][j] = presum % mod
                if i - length >= 0:
                    # 使用 lcp 比较 num(2i-j-1,i-1) 与 num(i,j) 的大小关系
                    if lcp[i - length][i] >= length or num[i - length + lcp[i - length][i]] < num[i + lcp[i - length][i]]:
                        f[i][j] = (f[i][j] + f[i - length][i - 1]) % mod
                    # 更新前缀和
                    presum += f[i - length][i - 1]

        # 最终答案即为所有 f[..][n-1] 的和
        ans = sum(f[i][n - 1] for i in range(n)) % mod
        return ans

golang 解法, 执行用时: 132 ms, 内存消耗: 161 MB, 提交时间: 2023-07-12 09:17:36

const mod int = 1e9 + 7

func numberOfCombinations(s string) (ans int) {
	if s[0] == '0' {
		return
	}

	n := len(s)
	// lcp[i][j] 后缀s[i:] 和 后缀 s[j:] 的最长公共前缀的长度
	lcp := make([][]int, n+1)
	for i := range lcp {
		lcp[i] = make([]int, n+1)
	}
	for i := n - 1; i >= 0; i-- {
		for j := n - 1; j >= 0; j-- {
			if s[i] == s[j] {
				lcp[i][j] = lcp[i+1][j+1] + 1
			}
		}
	}
	// 返回 s[l1:l2] <= s[l2:r2]
	lessEq := func(l1, l2, r2 int) bool {
		l := lcp[l1][l2]
		return l >= r2-l2 || s[l1+l] < s[l2+l]
	}

    // f[i][j] s的前j个字符串划分出的最后一个整数的起始位置为i时的方案数
	f := make([][]int, n)
	for i := range f {
		f[i] = make([]int, n)
	}
	for j := 0; j < n; j++ {
		f[0][j] = 1
	}
	for i := 1; i < n; i++ {
		if s[i] == '0' {
			continue
		}
		// k 和 j 同时向左向右扩展
		for j, k, sum := i, i-1, 0; j < n; j++ {
			f[i][j] = sum // 对应上面所说的长度小于最后一个划分出的整数
			if k < 0 {
				continue
			}
			if s[k] > '0' && lessEq(k, i, j+1) {
				f[i][j] = (f[i][j] + f[k][i-1]) % mod // 对应上面所说的长度等于最后一个划分出的整数
			}
			sum = (sum + f[k][i-1]) % mod
			k--
		}
	}
	for _, row := range f {
		ans = (ans + row[n-1]) % mod
	}
	return
}

上一题