动态规划--[自用]代码随想录刷题记录【JAVA】

动态规划–代码随想录刷题记录【JAVA】

刷题小白先挖个坑!感兴趣欢迎关注收藏!
文章目的:

  1. 提取出精简的解题思路
  2. 进一步补充不清楚的逻辑分析
  3. 代码部分会多加注释
  4. 方便本人复盘

注:以下绝大多数思路来自代码随想录网站!

文章目录

  • 动态规划--代码随想录刷题记录【JAVA】
    • 41. 最长递增子序列
    • 674. 最长连续递增序列
    • 718.最长重复子数组
    • 1143.最长公共子序列
    • 最大子序和
    • 判断子序列
    • 不同的子序列
    • 两个字符串的删除操作
    • 编辑距离 Todo
    • 回文子串 Todo
    • 最长回文子序列Todo
    • 309.最佳买卖股票时机含冷冻期

41. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 :

  • 输入:nums = [10,9,2,5,3,7,101,18]
  • 输出:4
  • 解释:最长递增子序列是 [2,3,7,101],因此长度为 4
  1. dp[i]的定义

dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度

  1. 状态转移方程

if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1)

  1. dp[i]的初始化

每一个i,对应的dp[i](即最长递增子序列)起始大小至少都是1.

  1. 确定遍历顺序
  • dp[i] 是有0到i-1各个位置的最长递增子序列 推导而来,那么遍历i一定是从前向后遍历。

  • j其实就是遍历0到i-1,那么是从前到后,还是从后到前遍历都无所谓,只要吧 0 到 i-1 的元素都遍历了就行了。 所以默认习惯 从前向后遍历。

  1. 为何要进行 dp[i] = Math.max(dp[i], dp[j] + 1) 的比较?
  • 这个比较的目的就是要决定,是否可以通过把 nums[i] 加入到 nums[j] 结尾的递增子序列中,从而扩展这个子序列的长度。
  • 前提条件:nums[i] > nums[j],这意味着 nums[i] 可以接在 nums[j] 后面,形成一个新的递增子序列。
  • 因此,dp[i] 要么保持原值(没有发现能加上 nums[i] 的递增子序列),要么更新为新的最大值 dp[j] + 1。
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length <= 1) return nums.length;  //记得可以提前返回!
        int[] dp = new int[nums.length];
        int res = 1;
        Arrays.fill(dp, 1); //数组全部初始化为1
        for (int i = 1; i < dp.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

674. 最长连续递增序列

class Solution {
    public int findLengthOfLCIS(int[] nums) {
        if (nums.length <= 1) return nums.length;
        int[] dp = new int[nums.length]; 
        Arrays.fill(dp,1);
        int result = 0;
        for (int i = 1; i < nums.length; i++) {  //注意 i从1开始 不然数组会越界
            if(nums[i]>nums[i-1]) dp[i]=dp[i-1]+1;
            result=Math.max(result,dp[i]);
        }
        return result;
    }

}

718.最长重复子数组

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

  • 输入: A: [1,2,3,2,1] B: [3,2,1,4,7]
  • 输出:3
  • 解释:长度最长的公共子数组是 [3, 2, 1] 。

确定dp数组(dp table)以及下标的含义

  • dp[i][j] :以下标i - 1为结尾的A的子串,和以下标j - 1为结尾的B的子串,最长重复子数组长度为dp[i][j]。
  • 此时细心的同学应该发现,那dp[0][0]是什么含义呢?总不能是以下标-1为结尾的A数组吧。
  • 其实dp[i][j]的定义也就决定着,我们在遍历dp[i][j]的时候i 和 j都要从1开始。

确定递推公式

  • 即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;

  • 根据递推公式可以看出,遍历i 和 j 要从1开始!

dp数组如何初始化

  • 根据dp[i][j]的定义,dp[i][0] 和dp[0][j]其实都是没有意义的!

  • 但dp[i][0] 和dp[0][j]要初始值,所以dp[i][0] 和dp[0][j]初始化为0。

  • dp[1][1] = dp[0][0] +1,只有dp[0][0]初始为0,正好符合递推公式逐步累加起来。

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int result = 0;
        int[][] dp = new int[nums1.length + 1][nums2.length + 1];
        
        for (int i = 1; i < nums1.length + 1; i++) {
            for (int j = 1; j < nums2.length + 1; j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    result = Math.max(result, dp[i][j]); 
                }
            }
        }
        
        return result;
    }
}

1143.最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

  • 输入:text1 = “abcde”, text2 = “ace”
  • 输出:3
  • 解释:最长公共子序列是 “ace”,它的长度为 3。

确定dp数组(dp table)以及下标的含义

dp[i][j]:长度为i - 1为结束下标的的字符串text1与长度为 j - 1为结束下标的字符串text2的最长公共子序列为dp[i][j]

确定递推公式

  • 如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1
  • 如果text1[i - 1] 与 text2[j - 1]不相同,dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

dp数组如何初始化

所以dp[i][0] = dp[0][j]=0

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
   
        int[][] dp = new int[text1.length() + 1][text2.length() + 1]; // 先对dp数组做初始化操作
        for (int i = 1 ; i <= text1.length() ; i++) {
            char char1 = text1.charAt(i - 1);
            for (int j = 1; j <= text2.length(); j++) {
                char char2 = text2.charAt(j - 1);
                if (char1 == char2) { // 开始列出状态转移方程
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[text1.length()][text2.length()];
    }
}

最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

  • 示例:
  • 输入: [-2,1,-3,4,-1,2,1,-5,4]
  • 输出: 6
  • 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6

确定dp数组(dp table)以及下标的含义

dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]

确定递推公式

  • dp[i]只有两个方向可以推出来:
  • dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和nums[i]
  • 所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);
 public static int maxSubArray(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }

        int res = nums[0]; // dp初始化
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
            res = res > dp[i] ? res : dp[i];
        }
        return res;
    }

判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

  • 示例 1:
    输入:s = “abc”, t = “ahbgdc” 输出:true
  • 示例 2:
    输入:s = “axc”, t = “ahbgdc” 输出:false

确定dp数组(dp table)以及下标的含义

  • dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]

确定递推公式

  • if (s[i - 1] == t[j - 1]),那么dp[i][j] = dp[i - 1][j - 1] + 1;,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1
  • if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1]

结果判断

如果dp[s.size()][t.size()] 与 字符串s的长度相同说明:s与t的最长相同子序列就是s,那么s 就是 t 的子序列

class Solution {
    public boolean isSubsequence(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;
        }
    }
}

不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

题目数据保证答案符合 32 位带符号整数范围。

  • 输入:s=“rabbbit”,t="rabbit 输出:3

两个字符串的删除操作

确定dp数组(dp table)以及下标的含义

dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]

确定递推公式

  • 在这一类问题中,我们需要分析两种情况

  • 1.s[i - 1] 与 t[j - 1] 相等
  • (1) 使用 s[i-1] 来匹配 t[j-1]:这意味着我们已经决定了将 s[i-1] 和 t[j-1] 配对上了,因此剩下的问题是:在 s[0…i-2] 中找到 t[0…j-2] 的子序列匹配,答案是 dp[i-1][j-1]。
  • (2) 不使用 s[i-1] 来匹配 t[j-1]:此时,我们相当于跳过了 s[i-1],只考虑 s[0…i-2] 和 t[0…j-1] 的匹配,答案是 dp[i-1][j]
  • 比如说: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,即用s[0]s[1]s[2]组成的bag或者s[0]s[1]s[3]组成的bag都可以

  • 2.s[i - 1] 与 t[j - 1] 不相等
  • 我们不能使用 s[i-1] 来匹配 t[j-1],因此问题转化为在 s[0…i-2] 中找到 t[0…j-1] 的匹配

数组初始化

  • dp[i][0]表示什么呢?
  • dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
  • 那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
  • 再来看dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
    那么dp[0][j]一定都是0,s如论如何也变成不了t。
  • dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t
class Solution {
    public int numDistinct(String s, String t) {
        int[][] dp = new int[s.length() + 1][t.length() + 1];
        for (int i = 0; i < s.length() + 1; i++) {
            dp[i][0] = 1;  //只需要初始化为1的部分
        }
        
        for (int i = 1; i < s.length() + 1; i++) {
            for (int j = 1; j < t.length() + 1; j++) {
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                }else{
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        
        return dp[s.length()][t.length()];
    }
}

编辑距离 Todo

回文子串 Todo

最长回文子序列Todo

309.最佳买卖股票时机含冷冻期

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:

  • 输入: [1,2,3,0,2]
  • 输出: 3
  • 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出

状态解读

状态一:持有股票状态(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)
不持有股票状态,这里就有两种卖出股票状态
状态二:保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)
状态三:今天卖出股票
状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天!

疑惑

问:「今天卖出股票」我是没有单独列出一个状态的归类为「不持有股票的状态」,而本题为什么要单独列出「今天卖出股票」 一个状态呢?

答: 因为本题我们有冷冻期,而冷冻期的前一天,只能是 「今天卖出股票」状态,如果是 「不持有股票状态」那么就很模糊,因为不一定是 卖出股票的操作。

最后的结果

取 状态二,状态三,和状态四的最大值,不少同学会把状态四忘了,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if (n == 0) return 0;

        // 初始化dp数组
        int[][] dp = new int[n][4];
        dp[0][0] = -prices[0]; // 持有股票

       

        for (int i = 1; i < n; i++) {
            /*
                状态一: dp[i][0]  持有股票:
                1.维持状态,继续持有 dp[i - 1][0]
                2.前一天是冷冻期, dp[i - 1][3] - prices[i]
                3.前一天是保持卖出股票的状态 dp[i - 1][1] - prices[i]
            */
            dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i])); 
            /*
                状态二: dp[i][1]  保持卖出股票:
                1.保持前一天的状态
                2.前一天是冷冻期
            */
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]); 
              /*
                状态三: dp[i][2]  今天卖出股票:
                1.保持前一天持有股票
             */

            dp[i][2] = dp[i - 1][0] + prices[i]; 
            /*
                状态四: dp[i][3]  冷冻期:
                1.前一天卖出了股票
             */
             dp[i][3] = dp[i - 1][2]; 
        }

        
        return Math.max(dp[n - 1][3], Math.max(dp[n - 1][1], dp[n - 1][2]));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/913125.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DataFrame

目录 一、创建DataFrame二、Sql语法三、DSL语法四、RDD与DataFrame互相转换 一、创建DataFrame 在SparkSql中SparkSession是创建DataFrame和执行Sql的入口&#xff0c;创建DataFrame有三种方式&#xff1a; 通过Spark的数据源进行创建 从一个存在的RDD进行转换 从Hive Tabl…

C# 实现对指定句柄的窗口进行键盘输入的实现

在C#中实现对指定句柄的窗口进行键盘操作&#xff0c;可以通过多种方式来实现。以下是一篇详细的指南&#xff0c;介绍如何在C#中实现这一功能。 1. 使用Windows API函数 在C#中&#xff0c;我们可以通过P/Invoke调用Windows API来实现对指定窗口的键盘操作。以下是一些关键的…

GitHub个人主页美化

效果展示 展示为静态效果&#xff0c;动态效果请查看我的GitHub页面 创建GitHub仓库 创建与GitHub用户名相同的仓库&#xff0c;当仓库名与用户名相同时&#xff0c;此仓库会被视作特殊仓库&#xff0c;其README.md&#xff08;自述文件&#xff09;会展示在GitHub个人主页…

2024-09-01 - 分布式集群网关 - LoadBalancer - 阿里篇 - 流雨声

摘要 通过公有云部署创建类似 MateLB 的应用负载&#xff0c;可以更加方便的对系统资源进行合理规划。 应用实践 CCM提供Kubernetes与阿里云基础产品&#xff08;例如CLB、VPC等&#xff09;对接的能力&#xff0c;支持在同一个CLB后端挂载集群内节点和集群外服务器&#xf…

【销帮帮-注册_登录安全分析报告-试用页面存在安全隐患】

联通支付注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨…

初识Linux · 匿名管道

目录 前言&#xff1a; 匿名管道 理解为什么&#xff1f; 理解是什么&#xff1f; 理解怎么做&#xff1f; 前言&#xff1a; 引入管道之前&#xff0c;我们引入几个问题&#xff0c;进程通信的相关问题。 第一个是进程之间为什么要通信&#xff0c;对于进程间通信来说&…

Linux(CentOS)设置防火墙开放8080端口,运行jar包,接收请求

1、查看防火墙状态 systemctl status firewalld 防火墙开启状态 2、运行 jar 包&#xff0c;使用8080端口 程序正常启动 3、使用 postman 发送请求&#xff0c;失败 4、检查端口是否开放&#xff08;需更换到 root 用户&#xff09; firewall-cmd --zonepublic --query-por…

window11安装elasticsearch+Kibana

1、下载elasticsearch与elasticsearch 下载elasticsearch 查看elasticsearch对应的Kibana版本 下载elasticsearch解压后文件目录如下 可执行脚本文件,包括启动elasticsearch服务、插件管理、函数命令等 bin配置文件目录,如elasticsearch配置、角色配置、jvm配置等 conf 默认…

[单例模式]

[设计模式] 设计模式是软件工程中的一种常见做法, 它可以理解为"模板", 是针对一些常见的特定场景, 给出的一些比较好的固定的解决方案. 不同语言适用的设计模式是不一样的. 这里我们接下来要谈到的是java中典型的设计模式. 而且由于设计模式比较适合有一定编程经…

MethodChannel插件的用法

文章目录 1 知识回顾2 示例代码3 经验总结我们在上一章回中介绍了通道相关的内容,本章回中将介绍其中的一种通道:MethodChannnel.闲话休提,让我们一起Talk Flutter吧。 1 知识回顾 我们在上一章回中介绍了通道的概念和作用,并且提到了通道有不同的类型,本章回将其中一种通…

Golang | Leetcode Golang题解之第554题砖墙

题目&#xff1a; 题解&#xff1a; func leastBricks(wall [][]int) int {cnt : map[int]int{}for _, widths : range wall {sum : 0for _, width : range widths[:len(widths)-1] {sum widthcnt[sum]}}maxCnt : 0for _, c : range cnt {if c > maxCnt {maxCnt c}}retur…

通讯录(C 语言)

目录 一、通讯录设计思路1. 伪代码设计思路2. 代码设计思路 二、代码实现三、程序运行演示四、整体分析 一、通讯录设计思路 1. 伪代码设计思路 通讯录可以用来存储 100 个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址。 提供方法&#x…

深入解析四种核心网络设备:集线器、桥接器、路由器和交换机

计算机网络系列课程《网络核心设备》 在现代网络技术中&#xff0c;集线器、桥接器、路由器和交换机扮演着至关重要的角色。本文&#xff0c;将深入探讨这四种设备的功能、工作原理及其在网络架构中的重要性。 集线器&#xff1a;基础网络连接设备 集线器&#xff08;Hub&…

01 Oracle 数据库存储结构深度解析:从数据文件到性能优化的全链路探究

文章目录 Oracle 数据库存储结构深度解析&#xff1a;从数据文件到性能优化的全链路探究一、Oracle存储结构的物理层次1.1 控制文件&#xff08;Control File&#xff09;1.2 联机重做日志文件&#xff08;Online Redo Log File&#xff09;1.3 数据文件&#xff08;Data File&…

Type-C转DP线方案

在现代数字化生活中&#xff0c;高清视频传输已成为日常需求的重要组成部分。无论是工作中的多屏协作&#xff0c;还是娱乐中的沉浸式体验&#xff0c;高清显示器都扮演着不可或缺的角色。然而&#xff0c;随着设备接口的多样化&#xff0c;如何高效地将Type-C设备连接至Displa…

【c++篇】:栈、队列、优先队列:容器世界里的秩序魔法 - stack,queue与priority_queue探秘

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;c篇–CSDN博客 文章目录 前言一.容器stack1.介绍2.成员函数3.模拟实现4.注意事项 二.容器qu…

Java基础——循环switch大数值更改器访问器深浅拷贝

目录 1.循环 2.switch多分支选择结构 3.大数值 4.浅拷贝&深拷贝 5.Arrays.sort排序 6.面向对象 7.更改器&访问器 1.循环 for-each循环 在 Java 中&#xff0c;for-each 循环&#xff08;也称为增强型 for 循环&#xff09;是一种用于遍历数组或集合&#xff08…

引入 axios,根据 api 文档生成调用接口

起步 | Axios Docs 安装 axios npm install axios 生成 api 调用接口【可选】 https://github.com/ferdikoomen/openapi-typescript-codegen 安装 npm install openapi-typescript-codegen --save-dev 然后执行生成代码 # http://localhost:8805/api/user/v3/api-docs&a…

wsl2+Ubuntu安装图形化界面(VNC方式)

本章教程,记录在wsl2+Ubuntu操作系统中安装可视化界面步骤。 一、安装桌面环境 尽量以root用户进行安装,避免因权限不足导致部分依赖无法安装。 sudo apt update sudo apt upgrade -y sudo apt install ubuntu-desktop由于可视化桌面程序安装包比较大,网速慢的小伙伴,需要耐…

Linux应用——线程池

1. 线程池要求 我们创建线程池的目的本质上是用空间换取时间&#xff0c;而我们选择于 C 的类内包装原生线程库的形式来创建&#xff0c;其具体实行逻辑如图 可以看到&#xff0c;整个线程池其实就是一个大型的 CP 模型&#xff0c;接下来我们来完成它 2. 整体模板 #pragma …