From 1412b12408f05ea9aae4e17c9f52ee09f42d8a07 Mon Sep 17 00:00:00 2001 From: markilue <745518019@qq.com> Date: Wed, 21 Dec 2022 13:26:56 +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/T37_CountSubstrings.java | 240 ++++++++++++++++++ .../dynamic/T38_LongestPalindromeSubseq.java | 135 ++++++++++ 2 files changed, 375 insertions(+) create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T37_CountSubstrings.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T38_LongestPalindromeSubseq.java diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T37_CountSubstrings.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T37_CountSubstrings.java new file mode 100644 index 0000000..ea57737 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T37_CountSubstrings.java @@ -0,0 +1,240 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-21 09:33 + *@Description: + * TODO 力扣647题 回文子串: + * 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 + * 回文字符串 是正着读和倒过来读一样的字符串。 + * 子字符串 是字符串中的由连续字符组成的一个序列。 + * 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 + *@Version: 1.0 + */ +public class T37_CountSubstrings { + + @Test + public void test() { + String s = "abc"; + System.out.println(countSubstrings(s)); + } + + @Test + public void test1() { + String s = "aaa"; + System.out.println(countSubstrings1(s)); + } + + + @Test + public void test2() { + String s = "fdsklf"; + System.out.println(countSubstrings2(s)); + } + + + /** + * 思路:求回文子字符串个数,可以通过逐个增加的方式求得 + * TODO 动态规划法: + * 1.dp定义:dp[i][j]表示s[i]结尾,s[j]开头的字符串有几个回文子串;很明显,j<=i + * 2.dp状态转移方程: + * dp[i][j]有两种情况,需要新加了s[i],和不需要新加的s[i];以aa加上最后一个a为例 + * 1.不需要新加的s[i] 如aa + * dp[i][j]=dp[i-1][j] + * 2.需要新加的s[j] + * if s[i-1]=s[j-1], 如aaa和后两个aa + * dp[i][j]=dp[i][j+1]+1 + * else, 如只有后两个aa + * dp[i][j]=dp[i][j+1] + * dp[i][j]=1.+2.-dp[i-1][j+1] //1和2两者有一个重复的地方 + * 3.dp初始化:dp[0][0]=1;dp[i][i]=1 + * 4.dp遍历顺序:第一层for正序,第二层for倒序 + * 5.dp距离推导:以aaa为例 + * [0 a a a] + * i=0: 1 0 0 0 + * i=a: 1 1 0 0 + * i=a: 1 3 1 0 + * i=a: 1 6 3 1 + * 暂时存在错误,无法知道当前子字符串是不是回文 + * @param s + * @return + */ + public int countSubstrings(String s) { + + int[][] dp = new int[s.length() + 1][s.length() + 1]; + + for (int i = 0; i < dp.length; i++) { + dp[i][i] = 1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i - 1; j >= 1; j--) { + if (s.charAt(i - 1) == s.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j] + dp[i][j + 1] + 1 - dp[i - 1][j + 1]; + } else { + dp[i][j] = dp[i - 1][j] + dp[i][j + 1] - dp[i - 1][j + 1]; + } + } + } + + return dp[s.length()][1]; + + } + + + /** + * 另一种思路:dp定义为当前子字符串是不是回文 + * dp[i][j]=s[i]==s[j]&&dp[i-1][j+1] + * 速度击败19.97%,内存击败42.14% 12ms + * @param s + * @return + */ + public int countSubstrings1(String s) { + + boolean[][] dp = new boolean[s.length()][s.length()]; + + int result = 0; + + for (int i = 0; i < dp.length; i++) { + dp[i][i] = true; + result += 1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i - 1; j >= 0; j--) { + if (i - 1 > j + 1) { + dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i - 1][j + 1]; + } else { + dp[i][j] = s.charAt(i) == s.charAt(j); + } + + if (dp[i][j]) result += 1; + } + } + + return result; + + } + + + /** + * 另一种思路:dp定义为当前子字符串是不是回文 + * dp[i][j]=s[i]==s[j]&&dp[i-1][j+1] + * 速度击败12.56%,内存击败23.72% + * @param s + * @return + */ + public int countSubstrings2(String s) { + + boolean[][] dp = new boolean[s.length()][s.length()]; + + int result = 0; + + for (int i = 0; i < dp.length; i++) { + dp[i][i] = true; + if (i + 1 < dp.length) { + dp[i][i + 1] = true; + } + result += 1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i - 1; j >= 0; j--) { + dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i - 1][j + 1]; + if (dp[i][j]) result += 1; + } + } + + return result; + + } + + + + /** + * 代码随想录思路:与本人方法类似,只不过将相等的情况分为了三种 + * TODO + * 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串 + * 情况二:下标i 与 j相差为1,例如aa,也是回文子串 + * 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了, + * 我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间 + * ,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。 + * 速度击败62.5%,内存击败22.99% 4ms + * @param s + * @return + */ + public int countSubstrings3(String s) { + + boolean[][] dp = new boolean[s.length()][s.length()]; + int result = 0; + char[] chars = s.toCharArray(); + +// for (int i = 1; i < dp.length; i++) { +// for (int j = i ; j >= 0; j--) { +// if(chars[i]==chars[j]){ +// if(i-j<=1){//情况一 情况二 +// result++; +// dp[i][j]=true; +// }else if(dp[i-1][j+1]){//情况三 +// result++; +// dp[i][j]=true; +// } +// } +// +// } +// } + + + for (int i = 0; i < dp.length; i++) { + for (int j = i ; j >= 0; j--) { + if(chars[i]==chars[j]&&((i-j<=1)||dp[i-1][j+1])){ + //三种情况简化后的版本 + result++; + dp[i][j]=true; + } + + } + } + return result; + + } + + + /** + * 双指针法:判断一个字符串是不是回文,可以从中心向两边扩散: + * 中心的情况可以分为两种:一个中心,或者两个中心 + * 如aaa,开始遍历 + * i=0:判断第一个a是不是+第一个aa是不是 + * i=1:判断第二个a是不是,判断扩散后aaa是不是+判断第二个aa是不是 + * i=2:判断第三个a是不是 + * 时间复杂度O(N^2),但空间复杂度降至O(1) + * 速度击败99.97%,内存击败58.49% 1ms + * @param s + * @return + */ + public int countSubstrings4(String s) { + + int result = 0; + char[] chars = s.toCharArray(); + for (int i = 0; i < s.length(); i++) { + result += extend(chars, i, i, s.length()); // 以i为中心 + result += extend(chars, i, i + 1, s.length()); // 以i和i+1为中心 + } + return result; + + } + + public int extend(char[] s, int i, int j, int n) { + int res = 0; + while (i >= 0 && j < n && s[i] == s[j]) { + i--; + j++; + res++; + } + return res; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T38_LongestPalindromeSubseq.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T38_LongestPalindromeSubseq.java new file mode 100644 index 0000000..f55f9e5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T38_LongestPalindromeSubseq.java @@ -0,0 +1,135 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; +import org.omg.CORBA.PUBLIC_MEMBER; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic + *@Author: dingjiawen + *@CreateTime: 2022-12-21 11:57 + *@Description: + * TODO 力扣516题 最长回文子序列: + * 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。 + * 子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 + *@Version: 1.0 + */ +public class T38_LongestPalindromeSubseq { + + @Test + public void test(){ + String s ="bbbab"; + System.out.println(longestPalindromeSubseq1(s)); + } + + @Test + public void test1(){ + String s ="cbbd"; + System.out.println(longestPalindromeSubseq(s)); + } + + /** + * 思路:最长回文子序列,则可以删除 + * TODO 动态规划法: + * (1)dp定义:dp[i][j]表示以s[j]开始,以s[i]结束的最长回文子序列长度 + * (2)dp状态转移方程:可以根据当前s[i]等不等于s[j]来划分状态 + * 1.若s[i]!=s[j] 最长是和把他去掉一样 + * dp[i][j]=Math.max(dp[i-1][j],dp[i][j+1]);//掐头大还是去尾大 + * 2.若s[i]==s[j] 掐头去尾后+这两个字符 + * dp[i][j]=dp[i-1][j+1]+2 + * (3)dp初始化: + * (4)dp遍历顺序: + * (5)dp举例推导:以bbbab举例: + * [b b b a b] + * i=b: 1 0 0 0 0 + * i=b: 2 1 0 0 0 + * i=b: 3 2 1 0 0 + * i=a: 3 2 1 1 0 + * i=b: 4 3 3 1 1 + * 速度击败99.61%,内存击败15.44% + * @param s + * @return + */ + public int longestPalindromeSubseq(String s) { + + char[] chars = s.toCharArray(); + int[][] dp = new int[s.length()][s.length()]; + for (int i = 0; i < dp.length; i++) { + dp[i][i]=1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = i-1; j >=0 ; j--) { + if(chars[i]==chars[j]){ + dp[i][j]=dp[i-1][j+1]+2; + }else { + dp[i][j]=Math.max(dp[i-1][j],dp[i][j+1]);//掐头大还是去尾大 + } + } + } + + return dp[s.length()-1][0]; + + } + + + /** + * 题解中最快的方法:本质上是一维dp优化 + * 速度击败100%,内存击败98.53% + * @param s + * @return + */ + public int longestPalindromeSubseq1(String s) { + char[] c = s.toCharArray(); + int[] dp = new int[c.length]; + int max = 0; + for (int i = 0; i < dp.length; i++ ) { + dp[i] = 1; + int curMax = 0; + for (int j = i - 1; j >= 0; j--) { + int prev = dp[j];//prev=dp[i-1][j];curmax理论上记录的是dp[i-1][j+1] + if (c[i] == c[j]) + dp[j] = curMax + 2;//本质上说的是如果不等则dp[i][j]=dp[i][j-1] + curMax = Math.max(prev, curMax);//之所以可以这样是因为dp[i-1][j]一定>=dp[i-1][j+1],但不能改成下式 +// curMax = prev;//之所以可以这样是因为dp[i-1][j]一定>=dp[i-1][j+1] + } + } + for (int n : dp) { + max = Math.max(max, n); + } + return max; + } + + + /** + * 正常的一维dp优化: + * 10ms + * @param s + * @return + */ + public int longestPalindromeSubseq2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] chars = s.toCharArray(); + int[] dp = new int[chars.length]; + int old; + int temp; + + for (int start = chars.length-1; start >= 0; start--) { + dp[start] = 1; + old = 0; + for (int end = start+1; end < chars.length; end++) { + temp = dp[end]; + if (chars[start] == chars[end]){ + dp[end] = 2 + old; + }else { + dp[end] = Math.max(dp[end], dp[end-1]); + } + old = temp; + } + } + + return dp[chars.length-1]; + } +}