LeetCode 115 Distinct Subsequences 不同子串

LeetCode 115 Distinct Subsequences 不同字串

题目

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

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).

It’s guaranteed the answer fits on a 32-bit signed integer.

题目给定两个字符串S, 和T,要求返回S有多少不同子串等于T。这里由于定义子串是从原始字符串中删除任意(包括0个)字符的结果,所以当S中有重复字符时,删掉不同位置的相同字符算作不同的子串。
比如:
Input: S = “rabbbit”, T = “rabbit”
Output: 3
Explanation:
As shown below, there are 3 ways you can generate “rabbit” from S.
(The caret symbol ^ means the chosen letters)

rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
S中的3个‘b’删掉任意一个都等于T,所以结果为3.

解析

本题涉及到两个字符串相关的关系,一般会思考是不是能用二维动态规划解决。比如编辑距离等题目,就会设计二维数组dp,其中 d p [ i ] [ j ] dp[i][j] dp[i][j]代表S从0到i, T从0到j两个子串得到的结果。本题仍然可以采用这一策略进行:
d p [ i ] [ j ] dp[i][j] dp[i][j]代表S从0到i的子串有多少不同子串等于 T从0到j这部分。
那么 d p [ i ] [ j ] dp[i][j] dp[i][j]怎么从前面推断出来呢?
首先如果 S [ i ] = = T [ j ] S[i]==T[j] S[i]==T[j],也就是两个字符串最后一个字符是一样的,那就有两种情况:
第一 用S[i]对位T[j],二者一样,那就保留S[i]不删掉用它来表示T[j],用S从0到i-1这部分表示出T从0到j-1这部分,双方后面都加上一个相同字符,数量自然不变,此时符合题意的子串数量就是 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]
第二,不采用S[i],直接删掉他,那么此时就要用S传从0到i-1这部分来表示T从0到j这部分,则符合题意的数量就是 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]
所以 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j ] , S [ i ] = = T [ j ] dp[i][j] = dp[i-1][j-1]+dp[i-1][j],S[i]==T[j] dp[i][j]=dp[i1][j1]+dp[i1][j]S[i]==T[j]
而当最后一个字符不相等时,显然只能删掉S[i],符合题意的数量就是 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]
也即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] , S [ i ] ! = T [ j ] dp[i][j] = dp[i-1][j],S[i]!=T[j] dp[i][j]=dp[i1][j]S[i]=T[j]
另外上述讨论一定满足 j ≤ i j \le i ji,如果 j > i j>i j>i,要得到的字符串比原始字符串还要长,显然不可能,数量只能为0.
基于上述的状态表示与转移策略,C++代码如下:

int solution::numDistinct(string s, string t)
{
	if (t.empty()) return 1;
	else if (s.empty()) return 0;
	int m = s.size(), n = t.size();
	vector<vector<uint64_t>>dp(m + 1, vector<uint64_t>(n + 1, 0));
	for (int i = 0; i <= m; ++i) dp[i][0] = 1;
	for (int j = 1; j <= n; ++j)dp[0][j] = 0;
	for (int i = 1; i <= m; ++i) {
		for (int j = 1; j <= n; ++j) {
			if (j > i)dp[i][j] = 0;
			else {
				if (s[i-1] == t[j-1])dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
				else dp[i][j] = dp[i - 1][j];
			}
		}
	}
	return dp[m][n];
}

这里我们为dp数组行列各加一,用来存放空串时的情况。显然S为空串时,除非T也为空子串数量是1,否则都是0;而当T是空串时,无论S是什么,数量都是1.
这里 d p [ i ] [ j ] dp[i][j] dp[i][j]就表示输入S[0,i-1] T[0,j-1]的结果。
实现上跟前面解析一致,对于 j > i j>i j>i直接取0;否则分别讨论s[i-1]和t[j-1]是否相等的情况。

优化

观察可以发现,无论何种情况, d p [ i ] [ j ] dp[i][j] dp[i][j]都只和 d p [ i − 1 ] dp[i-1] dp[i1]那部分有关,因此只需要1行存放数据就够了。由于用到的是左边和左上角的信息,我们采取两个变量left和cur分别记录前一个值和当前值,cur计算完成,其左上角点就不再需要了,更新为left即可,然后left更新为cur,再去计算下一个点。节省了存储空间也一定程度上提高了速度。
代码如下:

int solution::numDistinct(string s, string t)
{
	if (t.empty()) return 1;
	else if (s.empty()) return 0;
	int m = s.size(), n = t.size();
	uint64_t left = 0, cur = 0,last=0;
	vector<uint64_t>dp(m + 1, 1);
	for (int j = 1; j <= n; ++j) {
		for (int i = j; i <= m; ++i) {
			if (s[i - 1] == t[j - 1]) cur = dp[i - 1] + left;
			else cur = left;
			dp[i - 1] = left;
			left = cur;
		}
		dp[m] = left;
		left = 0;
	}
	return dp[m];
}

这里dp就是记录当前j值一定,每个i对应的情况,每次i从i=j开始讨论即可。left初始化为0,通过dp[i-1](相当于二维动规中的dp[i-1][j-1])和left(相当于二维动规中的dp[i-1][j])计算cur值。cur计算完成,dp[i-1]就不需要了,将它赋值为left,相当于此时dp[i-1] 更新成了二维动规中的dp[i-1][j],cur变成了此时的left,继续计算下一个i的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,有三种方法可以解决LeetCode上的最长回文子串问题。 方法一是使用扩展中心法优化,即从左向右遍历字符串,找到连续相同字符组成的子串作为扩展中心,然后从该中心向左右扩展,找到最长的回文子串。这个方法的时间复杂度为O(n²)。\[1\] 方法二是直接循环字符串,判断子串是否是回文子串,然后得到最长回文子串。这个方法的时间复杂度为O(n³),效率较低。\[2\] 方法三是双层for循环遍历所有子串可能,然后再对比是否反向和正向是一样的。这个方法的时间复杂度也为O(n³),效率较低。\[3\] 综上所述,方法一是解决LeetCode最长回文子串问题的最优解法。 #### 引用[.reference_title] - *1* [LeetCode_5_最长回文子串](https://blog.csdn.net/qq_38975553/article/details/109222153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode-最长回文子串](https://blog.csdn.net/duffon_ze/article/details/86691293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [LeetCode 第5题:最长回文子串(Python3解法)](https://blog.csdn.net/weixin_43490422/article/details/126479629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值