leecode更新

This commit is contained in:
markilue 2022-12-16 14:52:30 +08:00
parent 013a1161d1
commit 600003ba7f
2 changed files with 505 additions and 0 deletions

View File

@ -0,0 +1,275 @@
package com.markilue.leecode.dynamic;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
/**
*@BelongsProject: Leecode
*@BelongsPackage: com.markilue.leecode.dynamic
*@Author: dingjiawen
*@CreateTime: 2022-12-16 09:51
*@Description:
* TODO 力扣718题 最长重复子数组:
* 给两个整数数组 nums1 nums2 返回 两个数组中 公共的 长度最长的子数组的长度
* 经过测试子数组必须连续,[3,2,1][3,1,2,1],其最长子数组为2而不是3
*@Version: 1.0
*/
public class T29_FindLength {
@Test
public void test() {
int[] nums1 = {1, 2, 3, 2, 1};
int[] nums2 = {3, 2, 1, 4, 7};
System.out.println(findLength1(nums1, nums2));
}
@Test
public void test1() {
int[] nums1 = {1,2,3,1,2,1};
int[] nums2 = {3,2,1,4,7};
System.out.println(findLength2(nums1, nums2));
}
/**
* 思路:最长子数组既然必须要求是连续的那么跟28其实是比较类似的就是需要找到对应的情况
* 比如[3,2,1][3,2,3,2,1],代码随想录的思路是直接全都遍历了就知道了所以时间复杂度O(N*M)
* TODO 代码随想录动态规划法:题目中说的子数组其实就是连续子序列这种问题动规最拿手
* (1)dp定义:dp[i][j] 表示以下标i - 1为结尾的A和以下标j - 1为结尾的B最长重复子数组长度为dp[i][j]
* (特别注意 以下标i - 1为结尾的A 标明一定是 以A[i-1]为结尾的字符串)
* 其实dp[i][j]的定义也就决定着我们在遍历dp[i][j]的时候i j都要从1开始
* (2)dp状态转移方程:即当A[i - 1] 和B[j - 1]相等的时候dp[i][j] = dp[i - 1][j - 1] + 1;
* (3)dp初始化:根据dp[i][j]的定义dp[i][0] 和dp[0][j]其实都是没有意义的
* 为了方便递归公式dp[i][j] = dp[i - 1][j - 1] + 1所以dp[i][0] 和dp[0][j]初始化为0
* (4)dp遍历顺序:外层for循环遍历A内层for循环遍历B
* (5)dp举例推导:A: [1,2,3,2,1]B: [3,2,1,4,7]为例
* B: 3 2 1 4 7
* A: 0 0 0 0 0 0
* 1 0 0 0 1 0 0
* 2 0 0 1 0 0 0
* 3 0 1 0 0 0 0
* 2 0 1 2 0 0 0
* 1 0 0 0 3 0 0
* 速度击败71.18%内存击败24.68%
* 时间复杂度为O(N*M)
* @param nums1
* @param nums2
* @return
*/
public int findLength(int[] nums1, int[] nums2) {
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
int result = 0;
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length; j++) {
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
if (result < dp[i][j]) result = dp[i][j];
}
}
return result;
}
/**
* 滚动数组优化思路:
*速度击败44.24%内存击败88.2%
* @param nums1
* @param nums2
* @return
*/
public int findLength1(int[] nums1, int[] nums2) {
int[] dp = new int[nums2.length + 1];
int result = 0;
for (int i = 1; i < nums1.length + 1; i++) {
for (int j = nums2.length + 1 - 1; j >= 1; j--) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[j] = dp[j - 1] + 1;
}else {
dp[j]=0; //如果不等需要重新赋值避免不覆盖的情况而二维的时候不用因为不用重复利用
}
if (result < dp[j]) result = dp[j];
}
}
return result;
}
/**
* 官方 滑动窗口法:从A来一遍又从B来一遍目的是防止[3,2,1][3,2,3,2,1]出现两次3,2的情况
* 时间复杂度O((N+M)*min(N,M)),空间复杂度O(1)
* 速度击败38.34%,内存击败84.78%
* @param nums1
* @param nums2
* @return
*/
public int findLength2(int[] nums1, int[] nums2) {
int n = nums1.length, m = nums2.length;
int ret = 0;
for (int i = 0; i < n; i++) {
int len = Math.min(m, n - i);
int maxlen = maxLength(nums1, nums2, i, 0, len);
ret = Math.max(ret, maxlen);
}
for (int i = 0; i < m; i++) {
int len = Math.min(n, m - i);
int maxlen = maxLength(nums1, nums2, 0, i, len);
ret = Math.max(ret, maxlen);
}
return ret;
}
//寻找A从addA开始和B从addB开始连续相同的最大值
public int maxLength(int[] A, int[] B, int addA, int addB, int len) {
int ret = 0, k = 0;
for (int i = 0; i < len; i++) {
if (A[addA + i] == B[addB + i]) {
k++;
} else {
k = 0;
}
ret = Math.max(ret, k);
}
return ret;
}
/**
* 官方二分查找+哈希表法不细研究
* 核心思想是如果数组 A B 有一个长度为 k 的公共子数组那么它们一定有长度为 j <= k 的公共子数组
* 这样我们可以通过二分查找的方法找到最大的 k
*/
int mod = 1000000009;
int base = 113;
public int findLength3(int[] A, int[] B) {
int left = 1, right = Math.min(A.length, B.length) + 1;
while (left < right) {
int mid = (left + right) >> 1;
if (check(A, B, mid)) {
left = mid + 1;
} else {
right = mid;
}
}
return left - 1;
}
public boolean check(int[] A, int[] B, int len) {
long hashA = 0;
for (int i = 0; i < len; i++) {
hashA = (hashA * base + A[i]) % mod;
}
Set<Long> bucketA = new HashSet<Long>();
bucketA.add(hashA);
long mult = qPow(base, len - 1);
for (int i = len; i < A.length; i++) {
hashA = ((hashA - A[i - len] * mult % mod + mod) % mod * base + A[i]) % mod;
bucketA.add(hashA);
}
long hashB = 0;
for (int i = 0; i < len; i++) {
hashB = (hashB * base + B[i]) % mod;
}
if (bucketA.contains(hashB)) {
return true;
}
for (int i = len; i < B.length; i++) {
hashB = ((hashB - B[i - len] * mult % mod + mod) % mod * base + B[i]) % mod;
if (bucketA.contains(hashB)) {
return true;
}
}
return false;
}
// 使用快速幂计算 x^n % mod 的值
public long qPow(long x, long n) {
long ret = 1;
while (n != 0) {
if ((n & 1) != 0) {
ret = ret * x % mod;
}
x = x * x % mod;
n >>= 1;
}
return ret;
}
/**
* 官方题解中最快的方法
* 似乎是二分查找+哈希表法
* 5ms
* 速度击败100%内存击败82.24%
*/
long a = 13131;//形象地说就是把 S 看成一个类似 base 进制的数左侧为高位右侧为低位它的十进制值就是这个它的哈希值
int N;
int M;
public int findLength4(int[] nums1, int[] nums2) {
N = nums1.length;
M = nums2.length;
int l = 0;
int r = Math.min(N, M);
while(l < r){
int mid = l + r + 1 >> 1;
//寻找有没有长度为k的公共子数组找到了就说明可能有更大的
if(findDup(nums1, nums2, mid)) l = mid;
else r = mid - 1;
}
return l;
}
public boolean findDup(int[] nums1, int[] nums2, int len){
//为了便于理解进行举例若nums1={1,2,3,1,2,1};nums2={3,2,1,4,7}
long h1 = 0;
long h2 = 0;
long al = 1;
Set<Long> set = new HashSet<>();
//计算[0-len]的nums1子数组哈希值
for(int i = 0; i < len; i++){
//a进制下的123
h1 = h1 * a + nums1[i];
//a进制下的1000
al = al * a;
}
set.add(h1);
//计算所有长度为len的子数组哈希值
for(int i = 1; i <= N - len; i++){
//以i=1为例
h1 = h1 * a;//1230
h1 = h1 - nums1[i - 1] * al;//1230-1000
h1 = h1 + nums1[i + len - 1];//231
set.add(h1);
}
//计算[0-len]的nums1子数组哈希值
for(int i = 0; i < len; i++) h2 = h2 * a + nums2[i];
if(set.contains(h2)) return true;
//计算所有len长的的nums2子数组哈希值
for(int i = 1; i <= M - len; i++){
h2 = h2 * a;
h2 = h2 - nums2[i - 1] * al;
h2 = h2 + nums2[i + len - 1];
if(set.contains(h2)) return true;//只要找到了就返回找到
}
return false;
}
}

View File

@ -0,0 +1,230 @@
package com.markilue.leecode.dynamic;
import org.junit.Test;
/**
*@BelongsProject: Leecode
*@BelongsPackage: com.markilue.leecode.dynamic
*@Author: dingjiawen
*@CreateTime: 2022-12-16 12:51
*@Description:
* TODO 力扣1143题 最长公共子序列:
* 给定两个字符串 text1 text2返回这两个字符串的最长 公共子序列 的长度如果不存在 公共子序列 返回 0
* 一个字符串的 子序列 是指这样一个新的字符串它是由原字符串在不改变字符的相对顺序的情况下删除某些字符也可以不删除任何字符后组成的新字符串
* 例如"ace" "abcde" 的子序列 "aec" 不是 "abcde" 的子序列
* 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列
*@Version: 1.0
*/
public class T30_LongestCommonSubsequence {
@Test
public void test(){
String text2 = "abcacde";
String text1 = "acfe";
System.out.println(longestCommonSubsequence1(text1,text2));
}
@Test
public void test1(){
String text2 = "abcba";
String text1 = "abcbcba";
System.out.println(longestCommonSubsequence1(text1,text2));
}
/**
* 思路:与T29类似只是将连续条件去掉了,变得可以不连续:
* TODO 动态规划五部曲:
* (1)dp定义:dp[i][j]表示以i-1结尾的text1和以j-1结尾的text2的最长公共子序列
* (2)dp状态转移方程: for j in (0,len-(i-1) ) if(num[i-1]==num[i-1+j:]) dp[i][j]=max(dp[i-1])+1
* (3)dp初始化:dp[0]=0
* (4)dp遍历顺序:两个for可以随意交换顺序
* (5)dp举例推导: text2 = "abcacde", text1 = "acfe" 为例
* text2: a b c a c d e
* text1: 0 0 0 0 0 0 0 0
* a: 0 1 1 1 1 1 1 1
* c: 0 1 1 2 2 2 2 2
* f: 0 1 1 2 2 2 2 2
* e: 0 1 1 2 2 2 2 3
* 速度击败39.62%内存击败47.86% 11ms
* @param text1
* @param text2
* @return
*/
public int longestCommonSubsequence(String text1, String text2) {
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
for (int i = 1; i < text1.length()+1; i++) {
char char1 = text1.charAt(i-1);
for (int j = 1; j < text2.length() + 1; j++) {
if(char1==text2.charAt(j-1)){
//不要text1[i-1],还是不要text2[j-1],还是都要大
dp[i][j]=Math.max(dp[i][j-1],Math.max(dp[i-1][j],dp[i-1][j-1]+1));
}else {
//不要text1[i-1],还是不要text2[j-1]
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[text1.length()][text2.length()];
}
/**
* 对于自己的浅浅优化一下不需要判断不要text2[j-1]
* 因为他事实上取决于它的[j-2][i-1][j-2]谁大所以他一定比[i-1]+1小
* 速度击败65.48%内存击败34.92% 10ms
* @param text1
* @param text2
* @return
*/
public int longestCommonSubsequence1(String text1, String text2) {
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
for (int i = 1; i < text1.length()+1; i++) {
char char1 = text1.charAt(i-1);
for (int j = 1; j < text2.length() + 1; j++) {
if(char1==text2.charAt(j-1)){
//不要text1[i-1],还是都要大
dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j-1]+1);
}else {
//不要text1[i-1],还是不要text2[j-1]
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[text1.length()][text2.length()];
}
/**
* 对于代码随想录进一步优化一下不需要判断dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j-1]+1);
* 因为他事实上dp[i-1][j-1]+1一定比dp[i][j-1]
* 速度击败77.38%内存击败34.92% 9ms
* @param text1
* @param text2
* @return
*/
public int longestCommonSubsequence2(String text1, String text2) {
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
for (int i = 1; i < text1.length()+1; i++) {
char char1 = text1.charAt(i-1);
for (int j = 1; j < text2.length() + 1; j++) {
if(char1==text2.charAt(j-1)){
//不要text1[i-1],还是都要大
dp[i][j]=dp[i-1][j-1]+1;
}else {
//不要text1[i-1],还是不要text2[j-1]
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[text1.length()][text2.length()];
}
/**
* 一维dp数组优化
* 速度击败77.38%内存击败97.17%
* @param text1
* @param text2
* @return
*/
public int longestCommonSubsequence3(String text1, String text2) {
int n1 = text1.length();
int n2 = text2.length();
// 多从二维dp数组过程分析
// 关键在于 如果记录 dp[i - 1][j - 1]
// 因为 dp[i - 1][j - 1] <!=> dp[j - 1] <=> dp[i][j - 1]
int [] dp = new int[n2 + 1];
for(int i = 1; i <= n1; i++){
// 这里pre相当于 dp[i - 1][j - 1]
int pre = dp[0];
for(int j = 1; j <= n2; j++){
//用于给pre赋值
int cur = dp[j];
if(text1.charAt(i - 1) == text2.charAt(j - 1)){
//这里pre相当于dp[i - 1][j - 1] 千万不能用dp[j - 1] !!
//或者倒序遍历也行
dp[j] = pre + 1;
} else{
// dp[j] 相当于 dp[i - 1][j]
// dp[j - 1] 相当于 dp[i][j - 1]
dp[j] = Math.max(dp[j], dp[j - 1]);
}
//更新dp[i - 1][j - 1], 为下次使用做准备
pre = cur;
}
}
return dp[n2];
}
/**
* 官方题解中合理且最快的方法本质上还是动态规划
* 但是优化在选取text长度最小的作为内层for,并将string变成数组后进行判断
* 速度击败99.96%内存击败98.16% 3ms
* @param text1
* @param text2
* @return
*/
public int longestCommonSubsequence4(String text1, String text2) {
//动态规划
//fn f(n-1)
//f(1) 递推
//确保text1最长
if(text1.length()<text2.length()){
return longestCommonSubsequence4(text2, text1);
}
char []chs1=text1.toCharArray();
char []chs2=text2.toCharArray();//转换字符串直接操作
int m=chs1.length;//记录长度1
int n=chs2.length;//记录长度2
int []dp=new int[n];//动态化,默认都是0
//abcde m
//axe n
//xab n
//abcde
//a a在text1里面dp[0]=1
//x x不在在text1里面dp[0]=0
//abcde m fn f(n-1)
//ax x不在在text1里面 dp[n]=dp[n-1]
//xa a在text1里面 dp[n]=dp[n-1]+1
int pre=0;//记录上一个
int cur=0;//当下一个
for(int i=0;i<m;i++){
char c=chs1[i];//取出第一个字母
if(c==chs2[0]){
dp[0]=1;
}
pre=dp[0];//记录上一个
for(int j=1;j<n;j++){//跳过第一个字符
cur=dp[j];//当前的数据默认0
if(c==chs2[j]){
dp[j]=pre+1;//dp[n]=dp[n-1]+1
}else{
dp[j]=Math.max(cur,dp[j-1]);//保存最大值
}
pre=cur;//记录上一个
}
}
return dp[n-1];//返回最后一个
}
}