leecode更新
This commit is contained in:
parent
29fe083026
commit
79c867e3ad
|
|
@ -0,0 +1,186 @@
|
||||||
|
package com.markilue.leecode.dynamic;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic
|
||||||
|
*@Author: dingjiawen
|
||||||
|
*@CreateTime: 2022-12-19 10:05
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣392题 判断子序列:
|
||||||
|
* 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
|
||||||
|
* 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。
|
||||||
|
* (例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
|
||||||
|
* 进阶:
|
||||||
|
* 如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T33_IsSubsequence {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = "abc";
|
||||||
|
String t = "ahbgdc";
|
||||||
|
System.out.println(isSubsequence1(s, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
String s = "axc";
|
||||||
|
String t = "ahbgdc";
|
||||||
|
System.out.println(isSubsequence1(s, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
String s = "";
|
||||||
|
String t = "ahbgdc";
|
||||||
|
System.out.println(isSubsequence1(s, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本质上还是一个子序列的判断问题,与之前的子序列类似:
|
||||||
|
* TODO 动态规划五部曲:本质上还是一个暴力解法 时间复杂度O(MN)
|
||||||
|
* (1)dp定义:dp[i][j]表示使用s[0-i]是否是t[0-j]的子序列
|
||||||
|
* (2)dp状态转移方程:
|
||||||
|
* 1.以前就是||现在才是 注意以前就是必须是t多,s多不是;最后一个是现在才是
|
||||||
|
* dp[i][j]=dp[i-1][j]||(t[i]==s[j] and dp[i-1][j-1])
|
||||||
|
* 为了初始化方便,这里改为使用s[0-(i-1)]是否是t[0-(j-1)]
|
||||||
|
* (3)dp初始化:dp[i][0]=true 没有的时候一定是子序列
|
||||||
|
* (4)dp遍历顺序:从前往后
|
||||||
|
* (5)dp举例推导:以s = "abc", t = "ahbgdc"为例
|
||||||
|
* [0 a b c]
|
||||||
|
* i=0: t f f f
|
||||||
|
* i=1: t t f f
|
||||||
|
* i=2: t t f f
|
||||||
|
* i=3: t t t f
|
||||||
|
* i=4: t t t f
|
||||||
|
* i=5: t t t f
|
||||||
|
* i=6: t t t t
|
||||||
|
* 速度击败29.74%,内存击败8.12% 3ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence(String s, String t) {
|
||||||
|
|
||||||
|
char[] sArray = s.toCharArray();
|
||||||
|
char[] tArray = t.toCharArray();
|
||||||
|
|
||||||
|
boolean[][] dp = new boolean[tArray.length + 1][sArray.length + 1];
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
dp[i][0] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < dp.length; i++) {
|
||||||
|
for (int j = 1; j < dp[0].length; j++) {
|
||||||
|
dp[i][j] = dp[i - 1][j] || tArray[i - 1] == sArray[j - 1] && dp[i - 1][j - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[tArray.length][sArray.length];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败27.55%,内存击败88.7% 4ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence1(String s, String t) {
|
||||||
|
|
||||||
|
boolean[] dp = new boolean[s.length() + 1];
|
||||||
|
dp[0] = true;
|
||||||
|
|
||||||
|
for (int i = 1; i < t.length()+1; i++) {
|
||||||
|
for (int j = dp.length-1; j >=1 ; j--) {
|
||||||
|
//倒序,因为dp[j]依赖于dp[j-1],因此在dp[j]改变之前不能先改变dp[j-1]
|
||||||
|
dp[j] = dp[j] || (t.charAt(i-1) == s.charAt(j-1) && dp[j - 1]);//dp[j - 1]相当于上面的dp[i-1][j-1]
|
||||||
|
if(dp[s.length()])return true;//如果最后是true了那么s不用遍历完
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[s.length()];//这里不能直接返回false因为可能没进for
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码随想录动态规划:这道题目算是编辑距离的入门题目(毕竟这里只是涉及到减法),也是动态规划解决的经典题型。
|
||||||
|
* 速度击败27.55%,内存击败17.96% 4ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence2(String s, String t) {
|
||||||
|
|
||||||
|
int length1 = s.length(); int length2 = t.length();
|
||||||
|
int[][] dp = new int[length1+1][length2+1];
|
||||||
|
for(int i = 1; i <= length1; i++){
|
||||||
|
for(int j = 1; j <= length2; j++){
|
||||||
|
if(s.charAt(i-1) == t.charAt(j-1)){
|
||||||
|
dp[i][j] = dp[i-1][j-1] + 1;
|
||||||
|
}else{
|
||||||
|
dp[i][j] = dp[i][j-1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(dp[length1][length2] == length1){
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方题解:
|
||||||
|
* 双指针法:由于是子序列即可,所以匹配不上的时候就移动t,匹配上了就都移动
|
||||||
|
* 时间复杂度O(M+N)
|
||||||
|
* 速度击败87.41%,内存击败46.86% 1ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence3(String s, String t) {
|
||||||
|
int n = s.length(), m = t.length();
|
||||||
|
int i = 0, j = 0;
|
||||||
|
while (i < n && j < m) {
|
||||||
|
if (s.charAt(i) == t.charAt(j)) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
return i == n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题解中最快的:
|
||||||
|
* 时间复杂度O(N)
|
||||||
|
* 速度击败100% 内存击败57.34% 0ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence4(String s, String t) {
|
||||||
|
int flag = 0;
|
||||||
|
for(int i = 0; i < s.length(); i++){
|
||||||
|
int temp = t.indexOf(s.charAt(i));
|
||||||
|
//没找到直接返回false
|
||||||
|
if(-1 == temp){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
flag = temp;
|
||||||
|
//继续寻找他的子数组
|
||||||
|
t = t.substring(temp + 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
package com.markilue.leecode.dynamic;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic
|
||||||
|
*@Author: dingjiawen
|
||||||
|
*@CreateTime: 2022-12-19 11:11
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣115题 不同的子序列:
|
||||||
|
* 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
|
||||||
|
* 字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。
|
||||||
|
* (例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)
|
||||||
|
* 题目数据保证答案符合 32 位带符号整数范围。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T34_NumDistinct {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = "rabbbit";
|
||||||
|
String t = "rabbit";
|
||||||
|
System.out.println(numDistinct3(s, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
String s = "babgbag";
|
||||||
|
String t = "bag";
|
||||||
|
System.out.println(numDistinct2(s, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:本质上就是要求s的子序列能凑出t的个数
|
||||||
|
* TODO 动态规划法:
|
||||||
|
* (1)dp定义:dp[i][j]表示是s[0-i]中能抽出t[0-j]的子序列个数
|
||||||
|
* (2)dp状态转移方程:
|
||||||
|
* 1.dp[i][j] 有两种情况:s[i]!=t[j];s[i]==t[j]
|
||||||
|
* if s[i]!=t[j]:
|
||||||
|
* dp[i][j]=dp[i-1][j]
|
||||||
|
* else
|
||||||
|
* dp[i-1][j]==0: //证明之前没匹配上过,这是第一次匹配,那么与之前刚好匹配上的保持一致
|
||||||
|
* dp[i][j]=dp[i-1][j-1]
|
||||||
|
* else: //证明之前匹配上过,那么在之前匹配上的基础上+后来匹配上的次数
|
||||||
|
* dp[i][j]=dp[i-1][j-1]+dp[i-1][j]
|
||||||
|
* (3)dp初始化:为了初始化方便,改为s[i-1]!=t[j-1],,则dp[0][j]=0;dp[i][0]=1;
|
||||||
|
* (4)dp遍历顺序:s的for在外边
|
||||||
|
* (5)dp举例推导: 以s = "rabbbit", t = "rabbit"为例:
|
||||||
|
* [0 r a b b i t]
|
||||||
|
* s=0 1 0 0 0 0 0 0
|
||||||
|
* s=r 1 1 0 0 0 0 0
|
||||||
|
* s=a 1 1 1 0 0 0 0
|
||||||
|
* s=b 1 1 1 1 0 0 0
|
||||||
|
* s=b 1 1 1 2 1 0 0 c21 c22
|
||||||
|
* s=b 1 1 1 3 3 0 0 c31 c32
|
||||||
|
* s=i 1 1 1 3 3 3 0
|
||||||
|
* s=t 1 1 1 3 3 3 3
|
||||||
|
* 速度击败40.32%,内存击败32.75% 15ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numDistinct(String s, String t) {
|
||||||
|
|
||||||
|
int[][] dp = new int[s.length() + 1][t.length() + 1];
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
dp[i][0] = 1;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < dp.length; i++) {
|
||||||
|
for (int j = 1; j < dp[0].length; j++) {
|
||||||
|
if (s.charAt(i - 1) != t.charAt(j - 1)) {
|
||||||
|
dp[i][j] = dp[i - 1][j];
|
||||||
|
} else {
|
||||||
|
if (dp[i - 1][j] == 0) {
|
||||||
|
//证明之前没匹配上过,这是第一次匹配,那么与之前刚好匹配上的保持一致
|
||||||
|
dp[i][j] = dp[i - 1][j - 1];
|
||||||
|
} else {
|
||||||
|
//证明之前匹配上过,那么在之前匹配上的基础上+后来匹配上的次数
|
||||||
|
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[s.length()][t.length()];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浅优化,本质上是说 当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
|
||||||
|
* 1.一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。
|
||||||
|
* 2.一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
|
||||||
|
* 即如果s为bagg且t为bag,那么遍历到bagg时,
|
||||||
|
* 1.可以用ba加上最后的g来凑bag
|
||||||
|
* 2.可以就只用bag不用最后一个g来凑bag
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numDistinct1(String s, String t) {
|
||||||
|
|
||||||
|
int[][] dp = new int[s.length() + 1][t.length() + 1];
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
dp[i][0] = 1;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < dp.length; i++) {
|
||||||
|
for (int j = 1; j < dp[0].length; j++) {
|
||||||
|
if (s.charAt(i - 1) != t.charAt(j - 1)) {
|
||||||
|
dp[i][j] = dp[i - 1][j];
|
||||||
|
} else {
|
||||||
|
//证明之前匹配上过,那么在之前匹配上的基础上+后来匹配上的次数
|
||||||
|
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[s.length()][t.length()];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败73.2%,内存击败93.11% 11ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numDistinct2(String s, String t) {
|
||||||
|
|
||||||
|
int[] dp = new int[t.length() + 1];
|
||||||
|
dp[0] = 1;
|
||||||
|
for (int i = 1; i < s.length()+1; i++) {
|
||||||
|
for (int j = dp.length-1; j >=1; j--) {
|
||||||
|
if (s.charAt(i - 1) == t.charAt(j - 1)) {
|
||||||
|
//倒序遍历,因为需要用到之前的dp[j-1]
|
||||||
|
dp[j] = dp[j - 1] + dp[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[t.length()];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方题解中合理且最快的方法:本质上还是动态规划法
|
||||||
|
* 速度击败99.84%,内存击败23.18%
|
||||||
|
*/
|
||||||
|
Integer[][] memo;
|
||||||
|
public int numDistinct3(String s, String t) {
|
||||||
|
int sLen = s.length(), tLen = t.length();
|
||||||
|
memo = new Integer[tLen][sLen];
|
||||||
|
|
||||||
|
return dfs(s, t, s.length() - 1, t.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int dfs(String s, String t, int sIndex, int tIndex) {//递归作用,缩小规划,好分析边界,解决问题
|
||||||
|
if (tIndex < 0) return 1; //t的索引小于零了,那么就找到了
|
||||||
|
if (sIndex < 0) return 0;
|
||||||
|
|
||||||
|
if (sIndex < tIndex) return 0; // <3> 优化3, 12ms --> 2ms 这里表示s的长度都小于t了,那么就是没找到
|
||||||
|
|
||||||
|
if (memo[tIndex][sIndex] != null) return memo[tIndex][sIndex];//以前算过了,直接返回
|
||||||
|
//没算过就去算
|
||||||
|
int ans = 0;
|
||||||
|
//这个递推公式可以参照那个二维数组法;本质上也是反向遍历
|
||||||
|
if (s.charAt(sIndex) == t.charAt(tIndex)) ans += dfs(s, t, sIndex - 1, tIndex - 1);
|
||||||
|
ans += dfs(s, t, sIndex - 1, tIndex);//匹配时,可选,可不选。, //不匹配时,只能不选,
|
||||||
|
|
||||||
|
memo[tIndex][sIndex] = ans; //memo记录动态规划的dp数组
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue