leetcode
leetcode 1751 ~ 1800
划分数字的方案数

划分数字的方案数

难度:

标签:

题目描述

代码结果

运行时间: 2113 ms, 内存: 538.0 MB


/*
 * 思路:
 * 1. 通过递归和动态规划来计算字符串 num 的拆分方式。
 * 2. 使用 Java Stream API 优化部分操作。
 */
import java.util.*;
import java.util.stream.IntStream;

public class Solution {
    private static final int MOD = 1000000007;

    public int numDecodings(String num) {
        int n = num.length();
        int[] dp = new int[n + 1];
        dp[0] = 1;

        IntStream.rangeClosed(1, n).forEach(i ->
            IntStream.rangeClosed(1, i).mapToObj(j -> num.substring(j - 1, i))
                .filter(this::isValid)
                .forEach(sub -> dp[i] = (dp[i] + dp[i - sub.length()]) % MOD)
        );

        return dp[n];
    }

    private boolean isValid(String s) {
        if (s.charAt(0) == '0') return false;
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) < s.charAt(i - 1)) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        System.out.println(solution.numDecodings("327"));  // 输出:2
        System.out.println(solution.numDecodings("094"));  // 输出:0
        System.out.println(solution.numDecodings("0"));    // 输出:0
        System.out.println(solution.numDecodings("9999999999999"));  // 输出:101
    }
}

解释

方法:

此题使用动态规划的方法。首先进行特殊情况的判断:如果字符串的第一个字符为'0',由于不能有前导0,直接返回0。定义dp数组f[i][j],表示考虑num的子串num[0..j],且最后一个数字为num[i..j]时的方案数。初始化f[0][i]=1,即当第一个数字扩展到i时,只有一种划分方案。对于每个位置i到j,若num[i]=='0',则跳过,因为不能有前导0。为了保证非递减,需要比较当前数字与前一个数字的大小。这通过预处理一个最长公共前缀数组lcp来辅助,减少比较次数。动态规划递推时,若当前数字大于等于前一个数字,则累加前面的方案数。最后,将所有以n-1结尾的方案数相加,得到总方案数。

时间复杂度:

O(n^2)

空间复杂度:

O(n^2)

代码细节讲解

🦆
动态规划数组f[i][j]表示什么意义,为什么它的初始值f[0][i]设为1?
动态规划数组f[i][j]表示考虑数字字符串num的子串num[0..j],且最后一个数字为num[i..j]时的方案数。这意味着,f[i][j]计算的是所有以num[i..j]结尾的划分方案的数量。初始化f[0][i]=1是因为当只考虑从头开始到i位置的子串时,整个子串作为一个数字是唯一的划分方式,因此初始方案数为1。
🦆
如何理解和计算最长公共前缀数组lcp[i][j],它在算法中扮演什么角色?
最长公共前缀数组lcp[i][j]表示字符串num中从位置i开始和从位置j开始的子串的最长公共前缀的长度。计算lcp数组是通过动态规划完成的,从字符串的末尾向前计算,确保lcp[i][j]正确反映两个位置的子串前缀匹配的长度。在算法中,lcp数组用于快速判断两个子串的大小关系,这有助于减少重复的字符串比较,提高算法效率。
🦆
在dp递推过程中,为何需要判断`num[i] == '0'`然后跳过某些步骤?
在数字字符串的划分中,任何部分如果以'0'开头且长度大于1,则这样的划分是不合法的(例如'012'或'00123')。因此,在动态规划过程中,如果某个位置i的字符为'0',则跳过这一步是因为以i开始的任何长度大于1的划分都是无效的,从而保证所有划分都是合法的数字。
🦆
为什么在计算dp值时,需要比较`lcp[i - length][i]`与`length`,这个比较的逻辑是基于什么?
在计算dp值时,需要确保新形成的数字(子串num[i..j])与前一个数字(子串num[i-length..i-1])的大小关系符合非递减顺序。比较`lcp[i - length][i]`与`length`是为了快速判断这两个子串的大小关系。如果最长公共前缀长度小于子串长度,则可以直接通过公共前缀后的第一个不同字符判断两个子串的大小;如果等于或大于,表示这两个子串相等,满足非递减条件。这种比较方法避免了完整的字符串比较,提高了效率。

相关问题