【LeetCode每日一题】——115.不同的子序列

一【题目类别】

  • 字符串

二【题目难度】

  • 困难

三【题目编号】

  • 115.不同的子序列

四【题目描述】

  • 给定一个字符串s和一个字符串 t ,计算在s的子序列中t出现的个数。
  • 字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
  • 题目数据保证答案符合32位带符号整数范围。

五【题目示例】

  • 示例 1:
    输入:s = “rabbbit”, t = “rabbit”
    输出:3
    解释:
    如下图所示, 有 3 种可以从 s 中得到 “rabbit” 的方案。
    (上箭头符号 ^ 表示选取的字母)
    rabbbit
    ^^^^ ^^
    rabbbit
    ^^ ^^^^
    rabbbit
    ^^^ ^^^
  • 示例 2:
    输入:s = “babgbag”, t = “bag”
    输出:5
    解释:
    如下图所示, 有 5 种可以从 s 中得到 “bag” 的方案。
    (上箭头符号 ^ 表示选取的字母)
    babgbag
    ^^   ^
    babgbag
    ^^         ^
    babgbag
    ^         ^^
    babgbag
        ^     ^^
    babgbag
             ^^^

六【题目提示】

  • 0 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成

七【解题思路】

  • 深度优先搜索+记忆化递归/回溯
  • 因为这两天做了回溯的题,首先想到的就是回溯,类似记忆化递归,需要一个二维数组记忆s的某个位置和t的某个位置开始的匹配个数,递归时只要匹配当前值是否相等,如果当前位置的元素匹配,继续匹配下一个,如果当前位置元素不匹配,s跳过当前位置元素,用s的下一个元素和t的当前元素继续匹配,如果t的指针到了结尾说明匹配到了一个,如果s指针已经指向了结尾还没匹配到,说明从当前位置开始不存在匹配的子串,直接返回,如果s的某个位置和t的某个位置开始已经匹配到了,就直接返回,可以看作回溯

八【时间频度】

  • 时间复杂度:时间复杂度一般为常数,递归树的高度 O ( H ) O(H) O(H)

九【代码实现】

  1. Java语言版
package String;

public class p115_DistinctSubsequences {

    public static void main(String[] args) {
        String s = "babgbag";
        String t = "bag";
        int res = numDistinct(s, t);
        System.out.println("res = " + res);
    }

    public static int numDistinct(String s, String t) {
        // 求出s和t的长度
        int s_len = s.length();
        int t_len = t.length();
        // 创建对应的二维数组
        int dp[][] = new int[s_len + 1][t_len + 1]; // 这里+1的作用是考虑到t和s为0的情况
        // 将每一行的最后一位置为1,因为当t为空时,只能在s中选中一个,而当s为空时,一个也选不中,按理说应该把每列的最后一个置为 0,但是二维数组默认就是0,这里只需要修改每一行的最后一位
        for (int i = 0; i <= s_len; i++) {
            dp[i][t_len] = 1;
        }
        // 从这里开始,倒着进行循环,从t字符串的后面开始,t每次增加一个字母,因为是从s里面找t,所以t的循环在外面
        for (int t_i = t_len - 1; t_i >= 0; t_i--) {
            // 倒着进行循环,从s字符串的后面开始,s每次增加一个字母
            for (int s_i = s_len - 1; s_i >= 0; s_i--) {
                // 如果两个字母相等
                if (t.charAt(t_i) == s.charAt(s_i)) {
                    // 在相等的情况下,可以选择s中的字母,跳过s中的,也跳过t中的;也可以不选择s中的字母,跳过s中的,不跳过t中的
                    dp[s_i][t_i] = dp[s_i + 1][t_i + 1] + dp[s_i + 1][t_i];
                }
                // 如果两个字母不相等
                else {
                    // 不选择s中的字母,跳过s中的,不跳过t中的
                    dp[s_i][t_i] = dp[s_i + 1][t_i];
                }
            }
        }
        // 因为是从后向前循环的,所以返回第一行第一列的值
        return dp[0][0];
    }

}
  1. C语言版
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int numDistinct(char * s, char * t)
{
	/*求出s和t的长度*/
	int s_len = strlen(s);
	int t_len = strlen(t);
	/*创建对应的w二维数组,并开辟空间,这里要用long,要不输入z字符串太长会出错,这里+1的作用是考虑到t和s为0的情况*/
	long dp[0 + 1][0 + 1]; /*因为c语言不允许使用没定义的参数,所以这里写成固定的,正常应该是long dp[s_len + 1][t_len + 1];*/
	memset(dp, 0, sizeof(dp));
	/*将每一行的最后一位置为1,因为当t为空时,只能在s中选中一个,而当s为空时,一个也选不中,按理说应该把每列的最后一个置为 0,但是二维数组默认就是0,这里只需要修改每一行的最后一位*/
	for (int i = 0; i <= s_len; i++)
	{
		dp[i][t_len] = 1;
	}
	/*从这里开始,倒着进行循环,从t字符串的后面开始,t每次增加一个字母,因为是从s里面找t,所以t的循环在外面*/
	for (int t_i = t_len - 1; t_i >= 0; t_i--)
	{
		/*倒着进行循环,从s字符串的后面开始,s每次增加一个字母*/
		for (int s_i = s_len - 1; s_i >= 0; s_i--)
		{
			/*如果两个字母相等*/
			if (t[t_i] == s[s_i])
			{
				/*在相等的情况下,可以选择s中的字母,跳过s中的,也跳过t中的;也可以不选择s中的字母,跳过s中的,不跳过t中的*/
				dp[s_i][t_i] = dp[s_i + 1][t_i + 1] + dp[s_i + 1][t_i];
			}
			/*如果两个字母不相等*/
			else
			{
				/*不选择s中的字母,跳过s中的,不跳过t中的*/
				dp[s_i][t_i] = dp[s_i + 1][t_i];
			}
		}
	}
	/*因为是从后向前循环的,所以返回第一行第一列的值*/
	return dp[0][0];
}

/*主函数省略*/

十【提交结果】

  1. Java语言版
    在这里插入图片描述

  2. C语言版
    在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IronmanJay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值