leecode更新

This commit is contained in:
markilue 2022-12-19 13:03:08 +08:00
parent 29fe083026
commit 79c867e3ad
2 changed files with 365 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}