From 79c867e3ade44e3d12753e0e38f187b41d551332 Mon Sep 17 00:00:00 2001 From: markilue <745518019@qq.com> Date: Mon, 19 Dec 2022 13:03:08 +0800 Subject: [PATCH] =?UTF-8?q?leecode=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leecode/dynamic/T33_IsSubsequence.java | 186 ++++++++++++++++++ .../leecode/dynamic/T34_NumDistinct.java | 179 +++++++++++++++++ 2 files changed, 365 insertions(+) create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T33_IsSubsequence.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T34_NumDistinct.java diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T33_IsSubsequence.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T33_IsSubsequence.java new file mode 100644 index 0000000..43e01cd --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T33_IsSubsequence.java @@ -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; + } + + + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T34_NumDistinct.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T34_NumDistinct.java new file mode 100644 index 0000000..75d6425 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T34_NumDistinct.java @@ -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; + } +}