本文章主要讲解动态规划从入门到比较困难档次的题目,可以让小白实现从零到1的跨越。
这些题目掌握后你就可以做dp入门题如喝水。
动态规划(Dynamic Programming,简称DP)是一种解决复杂问题的算法设计技术,通常用于优化问题,特别是涉及最优化搜索路径、序列或决策的问题。DP 的核心思想是将原问题分解为子问题,然后通过解决子问题来解决原问题,同时避免多次解决相同的子问题,从而提高效率。
这类题型都有固定的步骤:
定义问题状态:将原问题分解成一系列子问题,通常以状态(state)来表示这些子问题。状态包括了问题的关键特征和参数,它们可以描述问题的不同变化和变化之间的联系。
找到状态转移方程:确定问题状态之间的转移关系,也就是如何从一个状态转移到另一个状态。这通常是问题的关键部分,因为它定义了如何递归地解决子问题。
初始化:设置初始状态的值,通常是将基本情况下的状态设定为已知值。
计算顺序:按照某种规定的顺序计算每个状态的值。通常是自底向上(bottom-up)或自顶向下(top-down)。
返回结果:根据状态转移方程计算出的值,得到原问题的最优解。
动态规划常用于解决许多问题,如最短路径问题、背包问题、编辑距离问题、最长公共子序列问题等。它在许多领域中都有广泛的应用,包括计算机科学、经济学、工程学和生物学等。这种方法通常需要合理地定义问题状态和状态转移方程,以便有效地解决问题。
最能体现动态规划找关系式的案例
详解:
1.确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
2.确定递推公式dp[i] = dp[i - 1] + dp[i - 2];
3.dp数组如何初始化:
dp[0] = 0; dp[1] = 1;
4.由递推公式判断为从前往后
5.举例推导dp数组
for循环运行,求i=n时对应的值。
能看出完全符合刚刚讲解的方法的,以后做题一定要想起来!!!
java代码:
class Solution {
public int fib(int n) {
if (n <= 1) return n;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int index = 2; index <= n; index++){
dp[index] = dp[index - 1] + dp[index - 2];
}
return dp[n];
}
}
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
class 爬楼梯 {//找规律:n=1,有1种,n=2,有两种,n=3,有三种
//分析当n>=3时,次数应该是n=n-2对应的情况再跨一大步
// 加上n=n-1对应的情况再跨一小步就是全部的情况
//注意:n=0时,是有一次的。
public int climbStairs(int n){
int []s=new int[n+1];
s[0]=1;
s[1]=1;
if(n>=2)
for (int i = 2;i
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
java:
class 是用最小花费爬楼梯746 {
public int minCostClimbingStairs(int[] cost) {
int []s=new int[cost.length+1];
//设置到达每层花费最少体力为s[i];
//又s[i]=s[i-2]跨一大步,或者s[i-1]跨一小步
//所以s[i]=min(s[i-1]+cost[i-1],s[i-2]+cost[i-2])两者中的较小值
//这下思路就清晰了,需要知道cost[0],cost[1],就能推出之后的所有值,直到s[cost.length]
s[0]=0;
s[1]=0;
for (int i = 2; i < cost.length+1; i++) {
s[i]=Math.min(s[i-1]+cost[i-1],s[i-2]+cost[i-2]);
}
return s[cost.length];
}
}
难点:得出的数较大但输出的数又需要int,需要进行转换,最后将结果转换成int
public class 不同路径62 {
public long uniquePaths(int m, int n) {
//组合
int s=m+n-2;
long sum=1;
int a=1;
if(s<=0)
return 1;
long min=Math.min(m-1,n-1);
for (int i = 0; i
//有点小难度了,因为没办法考虑绕过障碍物的法子,
// 所以可以先考虑全部,再去除不符合的
public class 不同路径II63 {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
//因为障碍物的存在,又给了障碍物的坐标,所以需要把
// 结果中经过障碍物的路径值全都写为0,就结束了,(不止一个障碍物,艹)
int m=obstacleGrid.length;
int n=obstacleGrid[0].length;
int [][]s=new int[m][n];
if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1)
return 0;
//因为题中没说条件,所以可能障碍物和终点或起点重合,
// 从0,0,0开始,因为给的数组是这样,不能自建
for (int i = 0; i
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
class 最长递增子序列 {
public int lengthOfLIS(int[] nums) {
//动态规划
//先给每个数设为1;
if(nums.length == 0)
return 0;
int []dp=new int[nums.length];//存放nums循环i位时最长的递增自序列
for (int i = 0; i < nums.length ; i++) {
dp[i]=1;
}
//if(nums[i]>nums[i+1])
//dp[i+1]=dp[i]+1;
//递推公式:
for (int i = 0; i nums[j]){
dp[i]=Math.max(dp[i],dp[j]+1);
//这行意思是求出dp[j]+1的最大值(j在循环,但小于i),仔细看看,对吧?
//太恶心人了,呜呜
//看图!!
//求出数组中每个num[i]结尾的数组所对应的最大长度
//记住!!
}
}
}
Arrays.sort(dp);
return dp[dp.length-1];
}
}
图演示:所以本题意思是,例如:当i=3时,dp[3]最大值只能来自dp[0~2]中
小于nums[3]的nums对应的dp的值加1,如果没有,那么dp[3]=1;比如dp[2]就是这样
当dp[3]时,dp[3]先后判断,1+1=2,2+1=3, 1+1=2,最后3最大,写3
: for (int j = 0; j nums[j]){
dp[i]=Math.max(dp[i],dp[j]+1);}
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6
so easy!!!
public class 最大子数组和53 {
public int maxSubArray(int[] nums) {
//动态规划:很简单,不要想太复杂了,!
//先建数组,不再赘述
int[] dp = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
dp[i]=nums[i];
}
//关系推导式:
dp[0]=nums[0];
int max=dp[0];
for (int i = 1; i < nums.length; i++) {
int m=0;
//必须包含nums[i]
// m=nums[j]+m;
dp[i] =Math.max(dp[i], dp[i-1]+nums[i]);
max=Math.max(max,dp[i]);
}
return max;
}
}
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
"ace"
是 "abcde"
的子序列,但 "aec"
不是 "abcde"
的子序列。两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
public int longestCommonSubsequence(String text1, String text2) {
// 动态规划:数组记录两个字符串中分别i,j长度时对应的最长自序列长度;
int [][]dp=new int[text1.length()+1][text2.length()+1];
//推导式,一定要写好推导式和定义的数组含义!!动态规划的关键!
for (int i = 1; i
给定一个正整数 n
,将其拆分为 k
个 正整数 的和( k >= 2
),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
题解:
public class 整数拆分343 {
public int integerBreak(int n) {
//动态规划,先定义数组:
int []dp=new int[n+1];//意思是分拆数字i
// 得到的最大乘积为dp[i]
dp[1]=1;
dp[2]=1;
//dp[3]=1*2=2;即dp[3]=j*(i-j);
//dp[4]=2*2=4;即j*(i-j)>j*dp[i-j];
//dp[5]=3*2=6;即j*(i-j)>j*dp[i-j];
//当i>=5时,dp[i]>i;
//dp[6]=3*3=9;
//dp[7]=3*4=12;
// dp[8]=3*2*3=18;可以发现,当分成3份或以上份数的乘积最大时:
// dp[i]=j*dp[i-j]>j*(i-j);
// dp[9]=3*3*3=27;
// dp[10]=3*3*4=36;
//得到规律,
for (int i = 2; i 中值,得到的最大值
}
}
return dp[n];
}
}
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
public class 不同的二叉搜索树96 {
public int numTrees(int n) {
//dp
int []dp=new int[n+1];
//每个i对应此时,二叉树的种类
//假设我们选择根节点的值为 j(1 <= j <= i),
// 则左子树中包含 j - 1 个节点,右子树中包含 i - j 个节点。
// 因此,以节点 j 为根的二叉搜索树的数量等于左子树和右子树的数量的乘积。
dp[0] =1;
for (int i = 1; i