[LeetCode#115]Distinct Subsequences

本文探讨了如何使用动态规划解决字符串子序列计数问题,通过详细的步骤解析和避免常见陷阱,提供了一种高效的方法来计算一个字符串在另一个字符串中的不同子序列数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem:

Given a string S and a string T, count the number of distinct subsequences of T in S.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not).

Here is an example:
S = "rabbbit", T = "rabbit"

Return 3.

Analysis:

This problem is another type of dynamic programming problem.
It's hard to understand, but it is really really powerful!!! You must master it!
The simple solution also includes many awesome programming skills. You must learn it.

Basic idea:
We use a two dimensional matrix checkboard, checkboard[i][j] means how many sequences t[0, j] in s[0, i].
Thus we could have following transitional equation.
1. if s[i] != t[j], checkboard[i][j] = checkboard[i-1][j].
Reason: the introduced s[i] means nothing for the number of sequences(which could be deleted!). Thus the subquences' number are the same for checkboard[i][j] and checkboard[i-1][j].

2. if s[i] == t[j], checkboard[i][j] = checkboard[i-1][j] + checkboard[i-1][j-1].
Reason:
benefit 1 (not remove character): the s[i] and t[j] would not affect s[0, i-1] and t[0, j-1], the extra character at both string, would not affect subquence number checkboard[i-1][j-1].
benefit 2 (remove charcter): Since we were allowed to remove characters from string s, the s[0, i] and t[0, j] still hold the subsequence from checkboard[i-1][j].

Note: 
1. checkboard[i-1] [j-1] has nothing to do with checkboard[i-1][j]
case:
s: aaab
t: ab
i = 3
j = 1
a. checkboard[i-1] [j-1] = 3
s[0, i-1] = aaa
t[0, 0] = a

b. checkboard[i-1][j] = 0
s[0, i-1] = aaa
t[0, 1] = ab

************
For this question, we care about how to delete characters from String s, thus to get String t.
Therefore, the String t is the target we care about.
1. checkboard[i][j] must not less than checkboard[i-1][j] ... checkboard[0][0], since we have more choice in deleting word, without comprising the probability to reach t.
2. Only when s[i] == t[j], we could change ingore those two characters, and think over alone over s[0, i-1] and t[0, j-1]. 

The additional two new characters do not reduce the subquences number from checkboard[i-1][j-1] (when the target still maintains).


Even you have fully understood above realtionships. It is still hard to write the program right one time.
wrong solution 1:
int[] check_board = new int[t.length()];
check_board[0] = (s.charAt(0) != t.charAt(0)) ? 0 : 1;
for (int i = 1; i < s.length(); i++) {
    for (int j = 1; j < t.length(); j++) {
        check_board[j] = ((s.charAt(i) == t.charAt(j)) ? 0 : check_board[j-1]) + check_board[j]; 
    }
}

Big error:(wrong direction)
Use one dimenional array to save space, but calculate through the wrong direction.
When you try to do check_board[i-1][j-1] + check_board[i-1][j], the check_board[j-1] actually store check_board[i][j-1]
It was rewritten by preivous step!!!!
[i-1, j-1] [i-1][j]
            [i][j]
Next time, when you encounter the situation of caculating current state through previous state, you must be very very careful.
Even you figure out the problem, you may come into folowing solution(do calculation through backward)


wrong solution 2:
int[] check_board = new int[t.length()];
check_board[0] = (s.charAt(0) != t.charAt(0)) ? 0 : 1;
for (int i = 1; i < s.length(); i++) {
    for (int j = t.length() - 1; j > 0; j++) {
        check_board[j] = ((s.charAt(i) == t.charAt(j)) ? 0 : check_board[j-1]) + check_board[j]; 
    }
}
error 1: naively thinking checkboard[0] is the same for all checkborad[*][0]. Thus start from i = 1 and ignore checkboard[*][0]
s: aaa
t: a
check board:
1
2
3
<checkboard[*][0] are different>
Thus we must also update checkboard[*][0]

However, if j = 0, the checkboard[j-i] would across the matrix's left boarder.
A skill:
Add a column on the left of the matrix, means no character in target string t. ""
Thus the matrix becomes:
1 0 0 0 0 0 0 0 0 0 0 0
1 original matrix part
1
1
1
*********************************************************************************
The whole column is '1', thus it requires no updates, we could stop at "column 1"
**********************************************************************************

This method is very tricky, but it holds the invarance for our problem, that checkboard[i][j] always indicate the subsequences s[0,i] , t[0,j] at here t is null.

To implement it, first 
int[] check_board = new int[t.length()+1]; (think it as a matrix of "t.length() + 1" columns)

Then adjust the loop:
for (int i = 0; i < s.length(); i++) {
    for (int j = t.length() - 1; j >= 0; j--) {
        check_board[j+1] = ((s.charAt(i) == t.charAt(j)) ? check_board[j] : 0) + check_board[j+1]; 
    }
}
return check_board[t.length()];

Note: we just add new column before checkboard matrix.
Since the the loop parameter is highly interwined with accessing each char in a String, it is better for us not to change the index for loop. But remember to place them into the right check_board position.

For j in the loop, it actually means "j+1" in the index system of upgraded matrix.


Pitfall, can we change 
for (int i = 0; i < s.length(); i++) {}
into 
for (int i = 1; i < s.length(); i++) {}

Absolutely no!!!
We actually add a virtual column "" for t, and virtual row "" for s. 
the first row for the matrix is (at beginning):
1 0 0 0 0 ...

If s.chartAt(0) = t.charAt(0), after the first iteration, we have
1 0 0 0 0 0 ...
1 1 0 0 0 0 ...

Apparently the checkboard was updated, which could not be used for refering row = 0, then we start from row = 1 would incure problem.

Solution:

public class Solution {
    public int numDistinct(String s, String t) {
        if (s == null || s.length() < t.length())
            return 0;
        if (t == null || t.length() == 0)
            return 1;
        //must be careful with the index
        int[] check_board = new int[t.length()+1];
        check_board[0] = 1;
        for (int i = 0; i < s.length(); i++) {
            for (int j = t.length() - 1; j >= 0; j--) {
                check_board[j+1] = ((s.charAt(i) == t.charAt(j)) ? check_board[j] : 0) + check_board[j+1]; 
            }
        }
        return check_board[t.length()];
    }
}

 

转载于:https://www.cnblogs.com/airwindow/p/4753742.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值