leecode更新
This commit is contained in:
parent
d951e56db7
commit
1412b12408
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue