LeetCode 940. 不同的子序列 II(可能是东半球最详细的易懂的讲解)

事先声明,本文章个人纯手打,如果想要转载,请标明出处,谢谢合作。

本文的链接:LeetCode 940. 不同的子序列 II(可能是东半球最详细的易懂的讲解)_940算法题_程珝源的博客-CSDN博客

最近在刷算法题,在leetecode上遇到了这个问题,先看一下题干:

给定一个字符串 S,计算 S 的不同非空子序列的个数。
因为结果可能很大,所以返回答案模 10^9 + 7.
示例 1:
输入:"abc"
输出:7
解释:7 个不同的子序列分别是 "a", "b", "c", "ab", "ac", "bc", 以及 "abc"。
示例 2:
输入:"aba"
输出:6
解释:6 个不同的子序列分别是 "a", "b", "ab", "ba", "aa" 以及 "aba"。
示例 3:
输入:"aaa"
输出:3
解释:3 个不同的子序列分别是 "a", "aa" 以及 "aaa"。
提示:
S 只包含小写字母。
1 <= S.length <= 2000

看到题干就会想到这个可能是动态规划题,后面的情况由之前的状态决定。

首先,如果字符串中的所有的字符都是不相同的话,就会发现dp(i+1) = dp(i)*2+1,dp为字符串的非空子字符串的个数

解释如下:

那么题干中给的字符串可能包含多个重复的字符,如"aba","bcab","bccc"等。

那么在从左到右依次读取字符串到的第i个字符时,如果读取的字符在[0,i-1]区间内出现过的话那就减去距离第i个字符最近的那个相同字符的前一个字符时该阶段的非空子串个数再减去1(这里稍后说明,为什么还要减去1),这么说来可能有些难于理解,来让我举个栗子:

例如当字符串读取到"abcb"时,此时的char[3]='b',那么在[0,2]区间的字符串中出现过'b',即char[1]='b',那么此时字符串的非空子串个数为dp(4)-dp(2-1)-1=15-1-1=13;(这里说明一下char[]数组和dp[]数组之间差一位,即char[1]表示'b',而dp[2]表示读取到'b'字符时候字符串"ab"的非空字串个数)

但是,又是如何得到这个公式的,那么我们就再举几个栗子来说明:

比如上图中的"bcac"中,当字符串读取到最后一个'c'时候,此时的'c'在之前的"bca"字符串中出现过,我将"bcac"字符串的不去重的非空子串写了出来,然后将重复的用红色标识出来,此时重复的有两个,分别为"c"和"bc"。再又如字符串"bacc"中,不去重非空的子串中用红色标识出的重复有,"c","bc","ac","bac",有4个。此时同学们有没有发现第一个重复的那两个是由(b的子串)+c和c本身。而第二个重复的是由(ba的子串)+c和c组成的,这么说大家应该可以明白吧。现在就拿第二个继续,ba的子串的个数是怎么算出来的呢,正好不就是c的前一个字符a处时的dp么。还有之前不是提到过还要在减去一个1么,就是因为要不重复的那个字符减去,此处情况下就是字符c。

说了这么多,现在开始上java代码:

public static int distinctSubseqII(String s) {
		int mod = (int)1e9+7;//因为字符串会很长,子串个数很多,取模保存,不会越界
		char[] strTochar = s.toCharArray();
		int[] dp = new int[s.length()];//保存读取到该字符时候字符串的子串个数
		for(int i=0;i<s.length();i++) {
			if(i==0)
				dp[i] = 1;//如果字符串长度是1,直接返回1.
			else {
				dp[i] = (2*dp[i-1]+1)%mod;
				for(int j=i-1;j>=0;j--) {
					if(strTochar[j]==strTochar[i]) {//找到在[0,i-1]区间内距离此时字符相同最近的字符
						/*三元运算符,来判断是不是查找到起始位置了,
						 * 如果此时的字符与起始字符一致,那么就只需要
						 * 减去一个该字符即可。
				            */
						dp[i] = j==0?(dp[i]+mod-1)%mod:(dp[i]+mod-dp[j-1]-1)%mod;
						break;//找到直接跳出循环,因为我们只找最近的那个相同的字符。
					}
				}
			}
		}
		return dp[s.length()-1]%mod;//返回结果
	}

代码中有一段我要拿出来强调讲解一下

dp[i] = j==0?(dp[i]+mod-1)%mod:(dp[i]+mod-dp[j-1]-1)%mod;

为什么要加mod,因为这个涉及到进位,此处在举个栗子

比如:我们规定一个整数类型不超过10,那我们怎么存储13呢,此处就会使用取模,a=(b+c)%10 即(8+5)% 10 = 3,如果此时我们想要用a-b的话,会直接取得一个负数,-5,但是真实情况是应该是13-8 = 5,那怎么做呢,此时就需要(a+10-b),此处的mod即为10。那么回到上面这个代码dp[i]可能是取模后的而的dp[j-1]可能是取模前的,那么加一个mod就会解决出现负数的情况。

代码效率:

讲解就写到这里了,我的水平有限,如有错误,请大家及时指正。

微信公众号搜索:塞尔波星。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值