动态规划 字符串
题目描述 给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。
测试用例保证结果在 32 位有符号整数范围内。
示例 1:
输入: s = "rabbbit", t = "rabbit"
输出 : 3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
rabb bit
ra bbbit
rab bbit 示例 2:
输入: s = "babgbag", t = "bag"
输出 : 5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。
ba bg bag
ba bgbag
b abgbag
bab gbag
babgbag
提示:
1 <= s.length, t.length <= 1000 s 和 t 由英文字母组成 解法 方法一:动态规划 思考
统计 \(s\) 中有多少子序列等于 \(t\) 。对每个位置选或不选,\(s\) 与 \(t\) 长度可达 \(1000\) ,朴素递归会重复计算同一后缀对。
子问题只依赖「\(s\) 用了多长、\(t\) 匹配了多长」。\(f[i][j]\) 表示 \(s\) 前 \(i\) 个字符形成 \(t\) 前 \(j\) 个字符的方案数;\(s\) 当前字符与 \(t\) 对齐时可以多一条「用掉这对字符」的转移。空 \(t\) 有且仅有一种空子序列。
我们定义 \(f[i][j]\) 表示字符串 \(s\) 的前 \(i\) 个字符中,子序列构成字符串 \(t\) 的前 \(j\) 个字符的方案数。初始时 \(f[i][0]=1\) ,其中 \(i \in [0,m]\) 。
当 \(i \gt 0\) 时,考虑 \(f[i][j]\) 的计算:
当 \(s[i-1] \ne t[j-1]\) 时,不能选取 \(s[i-1]\) ,因此 \(f[i][j]=f[i-1][j]\) ; 否则,可以选取 \(s[i-1]\) ,此时 \(f[i][j]=f[i-1][j-1]\) 。 因此我们有如下的状态转移方程:
\[ f[i][j]=\left\{ \begin{aligned} &f[i-1][j], &s[i-1] \ne t[j-1] \\ &f[i-1][j-1]+f[i-1][j], &s[i-1]=t[j-1] \end{aligned} \right. \]
最终的答案即为 \(f[m][n]\) ,其中 \(m\) 和 \(n\) 分别是字符串 \(s\) 和 \(t\) 的长度。
时间复杂度 \(O(m \times n)\) ,空间复杂度 \(O(m \times n)\) 。
Python3 Java C++ Go TypeScript Rust
1
2
3
4
5
6
7
8
9
10
11
12 class Solution :
def numDistinct ( self , s : str , t : str ) -> int :
m , n = len ( s ), len ( t )
f = [[ 0 ] * ( n + 1 ) for _ in range ( m + 1 )]
for i in range ( m + 1 ):
f [ i ][ 0 ] = 1
for i , a in enumerate ( s , 1 ):
for j , b in enumerate ( t , 1 ):
f [ i ][ j ] = f [ i - 1 ][ j ]
if a == b :
f [ i ][ j ] += f [ i - 1 ][ j - 1 ]
return f [ m ][ n ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 class Solution {
public int numDistinct ( String s , String t ) {
int m = s . length (), n = t . length ();
int [][] f = new int [ m + 1 ][ n + 1 ] ;
for ( int i = 0 ; i < m + 1 ; ++ i ) {
f [ i ][ 0 ] = 1 ;
}
for ( int i = 1 ; i < m + 1 ; ++ i ) {
for ( int j = 1 ; j < n + 1 ; ++ j ) {
f [ i ][ j ] = f [ i - 1 ][ j ] ;
if ( s . charAt ( i - 1 ) == t . charAt ( j - 1 )) {
f [ i ][ j ] += f [ i - 1 ][ j - 1 ] ;
}
}
}
return f [ m ][ n ] ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 class Solution {
public :
int numDistinct ( string s , string t ) {
int m = s . size (), n = t . size ();
unsigned long long f [ m + 1 ][ n + 1 ];
memset ( f , 0 , sizeof ( f ));
for ( int i = 0 ; i < m + 1 ; ++ i ) {
f [ i ][ 0 ] = 1 ;
}
for ( int i = 1 ; i < m + 1 ; ++ i ) {
for ( int j = 1 ; j < n + 1 ; ++ j ) {
f [ i ][ j ] = f [ i - 1 ][ j ];
if ( s [ i - 1 ] == t [ j - 1 ]) {
f [ i ][ j ] += f [ i - 1 ][ j - 1 ];
}
}
}
return f [ m ][ n ];
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 func numDistinct ( s string , t string ) int {
m , n := len ( s ), len ( t )
f := make ([][] int , m + 1 )
for i := range f {
f [ i ] = make ([] int , n + 1 )
}
for i := 0 ; i <= m ; i ++ {
f [ i ][ 0 ] = 1
}
for i := 1 ; i <= m ; i ++ {
for j := 1 ; j <= n ; j ++ {
f [ i ][ j ] = f [ i - 1 ][ j ]
if s [ i - 1 ] == t [ j - 1 ] {
f [ i ][ j ] += f [ i - 1 ][ j - 1 ]
}
}
}
return f [ m ][ n ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 function numDistinct ( s : string , t : string ) : number {
const m = s . length ;
const n = t . length ;
const f : number [][] = new Array ( m + 1 ). fill ( 0 ). map (() => new Array ( n + 1 ). fill ( 0 ));
for ( let i = 0 ; i <= m ; ++ i ) {
f [ i ][ 0 ] = 1 ;
}
for ( let i = 1 ; i <= m ; ++ i ) {
for ( let j = 1 ; j <= n ; ++ j ) {
f [ i ][ j ] = f [ i - 1 ][ j ];
if ( s [ i - 1 ] === t [ j - 1 ]) {
f [ i ][ j ] += f [ i - 1 ][ j - 1 ];
}
}
}
return f [ m ][ n ];
}
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 impl Solution {
#[allow(dead_code)]
pub fn num_distinct ( s : String , t : String ) -> i32 {
let n = s . len ();
let m = t . len ();
let mut dp : Vec < Vec < u64 >> = vec! [ vec! [ 0 ; m + 1 ]; n + 1 ];
// Initialize the dp vector
for i in 0 ..= n {
dp [ i ][ 0 ] = 1 ;
}
// Begin the actual dp process
for i in 1 ..= n {
for j in 1 ..= m {
dp [ i ][ j ] = if s . as_bytes ()[ i - 1 ] == t . as_bytes ()[ j - 1 ] {
dp [ i - 1 ][ j ] + dp [ i - 1 ][ j - 1 ]
} else {
dp [ i - 1 ][ j ]
};
}
}
dp [ n ][ m ] as i32
}
}
方法二:动态规划优化 思考
方法一的 \(f[i][j]\) 只依赖上一行。压缩成一维后,内层必须从大到小更新 \(j\) ,以免覆盖尚未使用的 \(f[j-1]\) 。空间降为 \(O(|t|)\) 。
\(f[i][j]\) 只与上一行有关,因此可以去掉第一维。内层对 \(j\) 从大到小更新,避免覆盖还未使用的 \(f[j-1]\) ,空间复杂度降为 \(O(n)\) 。
Python3 Java C++ Go TypeScript
class Solution :
def numDistinct ( self , s : str , t : str ) -> int :
n = len ( t )
f = [ 1 ] + [ 0 ] * n
for a in s :
for j in range ( n , 0 , - 1 ):
if a == t [ j - 1 ]:
f [ j ] += f [ j - 1 ]
return f [ n ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 class Solution {
public int numDistinct ( String s , String t ) {
int n = t . length ();
int [] f = new int [ n + 1 ] ;
f [ 0 ] = 1 ;
for ( char a : s . toCharArray ()) {
for ( int j = n ; j > 0 ; -- j ) {
char b = t . charAt ( j - 1 );
if ( a == b ) {
f [ j ] += f [ j - 1 ] ;
}
}
}
return f [ n ] ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 class Solution {
public :
int numDistinct ( string s , string t ) {
int n = t . size ();
unsigned long long f [ n + 1 ];
memset ( f , 0 , sizeof ( f ));
f [ 0 ] = 1 ;
for ( char & a : s ) {
for ( int j = n ; j ; -- j ) {
char b = t [ j - 1 ];
if ( a == b ) {
f [ j ] += f [ j - 1 ];
}
}
}
return f [ n ];
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13 func numDistinct ( s string , t string ) int {
n := len ( t )
f := make ([] int , n + 1 )
f [ 0 ] = 1
for _ , a := range s {
for j := n ; j > 0 ; j -- {
if b := t [ j - 1 ]; byte ( a ) == b {
f [ j ] += f [ j - 1 ]
}
}
}
return f [ n ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14 function numDistinct ( s : string , t : string ) : number {
const n = t . length ;
const f : number [] = new Array ( n + 1 ). fill ( 0 );
f [ 0 ] = 1 ;
for ( const a of s ) {
for ( let j = n ; j ; -- j ) {
const b = t [ j - 1 ];
if ( a === b ) {
f [ j ] += f [ j - 1 ];
}
}
}
return f [ n ];
}