leetcode
leetcode 801 ~ 850
DI 序列的有效排列

DI 序列的有效排列

难度:

标签:

题目描述

代码结果

运行时间: 40 ms, 内存: 16.0 MB


/*
 * Problem: Given a string 's' of length n where 's[i]' is:
 * - 'D' means decreasing
 * - 'I' means increasing
 * A valid permutation is a permutation of numbers [0, 1, ..., n] that satisfies:
 * - If s[i] == 'D', then perm[i] > perm[i+1]
 * - If s[i] == 'I', then perm[i] < perm[i+1]
 * Return the number of such valid permutations modulo 10^9 + 7.
 */

import java.util.stream.IntStream;
import java.util.stream.Stream;

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

    public int numPermsDISequence(String s) {
        int n = s.length();
        int[][] dp = new int[n + 1][n + 1];
        IntStream.range(0, n + 1).forEach(i -> dp[0][i] = 1);

        IntStream.range(1, n + 1).forEach(i -> {
            if (s.charAt(i - 1) == 'D') {
                for (int j = i - 1; j >= 0; j--) {
                    dp[i][j] = (dp[i][j + 1] + dp[i - 1][j]) % MOD;
                }
            } else {
                for (int j = 0; j < i; j++) {
                    dp[i][j] = (dp[i][j - 1] + dp[i - 1][j]) % MOD;
                }
            }
        });

        return dp[n][0];
    }

    public static void main(String[] args) {
        ValidPermutationsStream vp = new ValidPermutationsStream();
        System.out.println(vp.numPermsDISequence("DID")); // Output: 5
        System.out.println(vp.numPermsDISequence("D"));   // Output: 1
    }
}

解释

方法:

这个题解采用动态规划的方法。定义 f[i][j] 表示前 i 个字符构成的子序列中,以 j 结尾的有效排列数量。为了简化空间复杂度,使用一维数组 f[j] 不断更新来表示当前的状态,并用辅助数组 g[j] 来计算更新。对于每一个字符,根据是 'D' 还是 'I',决定填充的方向和方式。如果是 'D',从后向前更新 g[j],累积前面的 f[j] 值确保递减;如果是 'I',从前向后更新 g[j],累积前面的 f[j] 值以保证递增。最后,将 g 赋值回 f 进行下一轮的计算。计算结束后,f 数组的累加和就是所求的答案。

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
在动态规划的解法中,一维数组f的初始设置为[1] + [0] * n的原因是什么?
在动态规划中,数组f用于记录各种排列的数量。初始设置为[1] + [0] * n的目的是为了表示在序列开始之前,存在一个虚拟的起点,且这个起点唯一确定无其他选择。因此,f[0]设置为1表示只有一个方式填充到序列的第一个位置,而其他位置f[1]到f[n]初始化为0,因为在没有任何输入字符处理前,这些位置无法形成任何有效的排列。
🦆
为什么在处理字符'D'和'I'时更新g数组的迭代方向不同?
在处理字符'D'时,要求序列递减,因此需要从后向前更新g数组,这样可以确保在填充时,每个位置的值都是基于之前所有可能的较大值的累加,从而满足递减的条件。相反,在处理字符'I'时,要求序列递增,因此需要从前向后更新g数组,这样可以确保在填充时,每个位置的值都是基于之前所有可能的较小值的累加,从而满足递增的条件。这种迭代方向的选择是为了正确地反映'D'和'I'对序列增长方向的要求。
🦆
在动态规划中,前缀和pre的作用具体是什么,它如何帮助计算g数组?
在动态规划中,前缀和pre用于累积f数组中的值,以便于计算g数组。对于字符'D',pre累积从当前位置往前的所有f[j]的值,确保当填充到任何一个位置j时,g[j]能反映所有可能的、比j大的位置的排列数之和。对于字符'I',则反之,pre累积到当前位置之前所有的f[j]的值,确保每个位置j的g[j]能包含所有可能的、比j小的位置的排列数之和。这样,pre帮助在保持状态转移的连续性和正确性的同时,高效地更新g数组。
🦆
在遍历完成所有字符后,为何可以通过返回f数组的元素之和模10^9+7得到最终答案?
在遍历完所有字符后,f数组中的每个元素f[j]代表以数字j结尾的所有有效排列的数量。因此,将f数组中所有元素求和,就得到了满足给定DI序列的所有可能排列的总数。由于结果可能非常大,为了防止溢出并满足题目要求,我们需要对结果取模10^9+7,这是一个常用的大素数,用于保持计算在安全的整数范围内。

相关问题