不同的子序列问题I

作者:Grey

原文地址: 不同的子序列问题I

题目链接

LeetCode 115. 不同的子序列

暴力解法

定义递归函数

int process(char[] str, char[] t, int i, int j)

递归函数表示:stri一直到最后,生成的序列可以匹配多少个t从j往后生成的字符串

所以process(str,t,0,0)得到的结果就是答案。

接下来考虑递归函数的base case

        if (j == t.length) {
// 表示str已经把t整个都搞定了,返回1,说明得到了一种情况
return 1;
}
// 到了这里,说明t还没到头
if (i == str.length) {
// str已经没有字符串了,t又没到头,所以,无法匹配
return 0;
}

接下来是普遍位置,考虑str[i]是否参与匹配来决定下一步的操作,注:str[i]如果要参与匹配,则必须满足str[i] == t[j]

        // str[i]位置不参与匹配
int ans = process(str, t, i + 1, j);
if (str[i] == t[j]) {
// str[i]参与,必须满足str[i] == t[j]
ans += process(str, t, i + 1, j + 1);
}

完整代码如下

    public static int numDistinct(String s, String t) {
if (s.length() < t.length()) {
return 0;
}
return process(s.toCharArray(), t.toCharArray(), 0, 0);
} // str[0....结尾]搞定t[0....结尾]
public static int process(char[] str, char[] t, int i, int j) {
if (j == t.length) {
// 全部搞定了
return 1;
}
if (i == str.length) {
// 没有了,搞不定
return 0;
}
// 不用i位置的去搞定
int ans = process(str, t, i + 1, j);
if (str[i] == t[j]) {
ans += process(str, t, i + 1, j + 1);
}
return ans;
}

这个暴力解法在LeetCode上直接超时。

动态规划

二维数组

根据暴力方法,可以得到,递归函数只有两个可变参数,所以定义二维dpdp的含义和递归函数的含义保持一致。所以dp[0][0]就是答案。

        int m = str.length;
int n = target.length;
int[][] dp = new int[m + 1][n + 1];

根据暴力方法

        if (j == t.length) {
// 全部搞定了
return 1;
}
if (i == str.length) {
// 没有了,搞不定
return 0;
}

可以得到dp的最后一行都是1,即

        for (int i = 0; i < m + 1; i++) {
dp[i][n] = 1;
}

接下来考虑普遍的dp[i][j],根据暴力方法

        int ans = process(str, t, i + 1, j);
if (str[i] == t[j]) {
ans += process(str, t, i + 1, j + 1);
}

可以得到,dp[i][j]依赖dp[i+1][j]dp[i+1][j+1](需要满足str[i] == t[j])位置的值。

所以

        for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
dp[i][j] = dp[i + 1][j] + (str[i] == target[j] ? dp[i + 1][j + 1] : 0);
}
}

完整代码

    public static int numDistinct(String s, String t) {
if (s.length() < t.length()) {
return 0;
}
char[] str = s.toCharArray();
char[] target = t.toCharArray();
int m = str.length;
int n = target.length;
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < m + 1; i++) {
dp[i][n] = 1;
}
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
dp[i][j] = dp[i + 1][j] + (str[i] == target[j] ? dp[i + 1][j + 1] : 0);
}
}
return dp[0][0];
}

时间复杂度O(m*n),其中mn分别是st的长度。

空间复杂度O(m*n),其中mn分别是st的长度。

一维数组

通过分析上述动态规划的解法,我们可得到一个结论,二维dp的计算顺序是从最后一行到第一行,且当前行只依赖上一行有限几个位置的信息,所以,我们可以将上述二维表简化成一维表,定义

        int m = str.length;
int[] dp = new int[n + 1];

通过一维表的从最后一行到第一行的滚动更新,来得到第一行的值,完整代码如下

    public static int numDistinct(String s, String t) {
if (s.length() < t.length()) {
return 0;
}
char[] str = s.toCharArray();
char[] target = t.toCharArray();
int m = str.length;
int n = target.length;
int[] dp = new int[n + 1];
dp[n] = 1;
for (int i = m - 1; i >= 0; i--) {
// 这里要注意,从左往右
for (int j = 0; j <= n - 1; j++) {
dp[j] += (str[i] == target[j] ? dp[j + 1] : 0);
} }
return dp[0];
}

时间复杂度O(m*n),其中mn分别是st的长度。

空间复杂度O(n),其中nt的长度。

更多

算法和数据结构笔记

不同的子序列问题I的更多相关文章

  1. 用python实现最长公共子序列算法(找到所有最长公共子串)

    软件安全的一个小实验,正好复习一下LCS的写法. 实现LCS的算法和算法导论上的方式基本一致,都是先建好两个表,一个存储在(i,j)处当前最长公共子序列长度,另一个存储在(i,j)处的回溯方向. 相对 ...

  2. codevs 1576 最长上升子序列的线段树优化

    题目:codevs 1576 最长严格上升子序列 链接:http://codevs.cn/problem/1576/ 优化的地方是 1到i-1 中最大的 f[j]值,并且A[j]<A[i] .根 ...

  3. [LeetCode] Arithmetic Slices II - Subsequence 算数切片之二 - 子序列

    A sequence of numbers is called arithmetic if it consists of at least three elements and if the diff ...

  4. [LeetCode] Is Subsequence 是子序列

    Given a string s and a string t, check if s is subsequence of t. You may assume that there is only l ...

  5. [LeetCode] Wiggle Subsequence 摆动子序列

    A sequence of numbers is called a wiggle sequence if the differences between successive numbers stri ...

  6. [LeetCode] Increasing Triplet Subsequence 递增的三元子序列

    Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the ar ...

  7. [LeetCode] Distinct Subsequences 不同的子序列

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

  8. 动态规划之最长公共子序列(LCS)

    转自:http://segmentfault.com/blog/exploring/ LCS 问题描述 定义: 一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则 ...

  9. [Data Structure] LCSs——最长公共子序列和最长公共子串

    1. 什么是 LCSs? 什么是 LCSs? 好多博友看到这几个字母可能比较困惑,因为这是我自己对两个常见问题的统称,它们分别为最长公共子序列问题(Longest-Common-Subsequence ...

  10. 51nod1134(最长递增子序列)

    题目链接: https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1134 题意: 中文题诶~ 思路: 直接暴力的话时间复杂度为 ...

随机推荐

  1. Java之万年历

    @(文章目录) 二.Java之万年历 2.1 要求 输入年份: 输入月份: 输出某年某月的日历. 2.2 思路 实现从控制台接收年和月,判断是否是闰年(判断是否是闰年:能被4整除但不能被100整除:或 ...

  2. Java学习day27

    今天跟着做了一个模拟龟兔赛跑的程序 只有一条赛道,乌龟和兔子在同一条赛道上比赛,使用了多线程 为了实现兔子睡觉,在run方法内增加了当前奔跑者是否是兔子的判断且当前奔跑步数是否是10的整数倍的判断,如 ...

  3. input清楚阴影 number属性

    IOS,input出现阴影: input[type="text"],input[type="email"],input[type="search&qu ...

  4. 【面试普通人VS高手系列】HashMap是怎么解决哈希冲突的?

    常用数据结构基本上是面试必问的问题,比如HashMap.LinkList.ConcurrentHashMap等. 关于HashMap,有个学员私信了我一个面试题说: "HashMap是怎么解 ...

  5. 爬虫篇-如何下载selenium及其适配谷歌浏览器插件chromedriver(含chrome各版本及下载地址)

    最近换了电脑,练习爬虫时用到selenium,结果在重新安装chromedriver插件的时候发现原网址不能使用,找了好久终于找到了了新网址,顺便更一篇详细使用的文章,希望可以对屏幕前的你有所帮助.本 ...

  6. GitStats - 统计Git所有提交记录工具

    如果你是研发效能组的一员或者在从事 CI/CD 或 DevOps,除了提供基础设施,指标和数据是也是一个很重要的一环,比如需要分析下某个 Git 仓库代码提交情况: 该仓库的代码谁提交的代码最多 该仓 ...

  7. HashMap中红黑树插入节点的调整过程

    如果有对红黑树的定义及调整过程有过研究,其实很容易理解HashMap中的红黑树插入节点的调整过程. "红黑树定义及调整过程"的参考文章:<红黑树原理.查找效率.插入及变化规则 ...

  8. Linux下的计划任务—crontab

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 来自:Linux就该这么学 crond 是linux下用 ...

  9. 二、深入学习c++需要掌握的基础知识

    一.掌握形参带默认值的函数 给定默认值的时候是从右向左给,因为函数在内存中的压栈顺序是按照形参列表的元素从右向左依次向内存中压栈 形参是否有默认值对调用效率的问题:如果有一个默认值,在函数调用的过程中 ...

  10. 斯坦福NLP课程 | 第12讲 - NLP子词模型

    作者:韩信子@ShowMeAI,路遥@ShowMeAI,奇异果@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www. ...