前言
今日练习目的:从一维动态规划入手,理解"子问题是更小规模的父问题与子问题最优解"两大核心特征,建立"用历史结果推导当前解"的思维。
重要思维突破:理解动态规划本质是用数组记录子问题答案,吧自顶向下的递归,转化为自底向上的递推,但要注意,并非所有递归都可以转为动态规划。
509:斐波那契数列
题目要求:给定一个整数n,求第n个斐波那契数
核心思路(一维动态规划)
用数组dp[i]存储第i个斐波那契数
遍历从2到n
dp[i]=dp[i-1]+dp[i-2]
代码实现
java
class Solution {
public int fib(int n) {
if(n==0) return 0;
if(n==1) return 1;
int[] dp=new int[n+1];//dp[i]表示第i个斐波那契数
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
总结
作为入门的一维动态规划。核心需要理解:一维DP就是沿着线性状态从前往后累积计算,每个状态只依赖前两个状态
53:最大子数组和
题目要求:给定一个整数数组nums,找出一个拦蓄子数组,使得他们的和最大
要求:返回这个最大和
核心思路
假设dp[i]表示以nums[i]结尾的最大子数组和:dp[i] = max(dp[i-1] + nums[i], nums[i])
如果把前面的子数组加上nums[i]比nums[i]小-就从nums[i]重新开始
每一步更新全局最大值maxSum
代码实现
java
int n=nums.length;
int[] dp=new int[n]
dp[0]=nums[0];
int maxSum=dp[0];
for(int i=1;i<n;i++){
dp[i]=Math.max(dp[i-1]+nums[i];nums[i]);//状态转移
maxSum=Math.max(maxSum,dp[i]);
}
return maxSum;
总结
本题重点只有一个,就是理解dp[i]的递增实现
理解dp[i] = max(dp[i-1] + nums[i], nums[i])就能掌握本题
想要讲的重点是关于递归和一维动态规划的区别
递归的特点是:
自己调用自己,没有记录已经计算过的结果
DP的特点是:
讲递归的重复计算结果记住
用一个数组dp[i]存储每个状态的结果
自底向上(从小状态开始计算到大状态)
198:打家劫舍
题目要求:
有一个整数数组nums,表示每个房子里的钱,你不能抢相邻的房子,求你能抢到的最大金额。
核心思路
对于每个房子i,有两个选择:
- 抢当前房子-就不能抢上一个房子,只能加上dp[i-2];
- 不抢当前房子-最大金额=dp[i-1]
状态转移方程:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
代码实现
java
if(nums.length==0||nums==null) return 0;
if(nums.length==1) return nums[0];
int n=nums.length;
int[] dp=new int[n];
dp[0]=nums[0];
dp[1]=nums[1];
for(int i=2;i<n;i++){
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[i-1];
总结
理解一维动态规划的本质是:用数组记录子问题的答案。通过记录下来 的答案来获得父问题。
也就是说难点在于找出特征方程。
对于本题来说:每一遍历到一个房子i,都面临两个选项;1.抢(代表前一个房子不能抢)2.不抢(抢前一个房子)
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
300:最长递增子序列
题目要求:
给定一个整数数组nums,找到其中最长严格递增子序列长度
核心思路
先定义dp[i]=以nums[i]结尾的最长递增子序列长度
状态转移方程:dp[i]=max(dp[j]+1) for all j < i and nums[j] < nums[i]
对于每个nums[i],找前面所有比它小的nums[j]
初始化dp[i]=1
代码实现
java
if(nums.length==0||nums==null) return 0;
int n=nums.length;
int[] dp=new int[n];
int maxLen=1;
Arrays.fill(dp,1)
for(int i=1;i<n;i++){
for(int j=1;j<n;j++){
if(nums[i]>nums[j]{
dp[i]=Math.max(dp[i],dp[j+1]);
}
}
mxaLen=Math.max(maxLen,dp[i]);
}
总结
本题最具有难度的是想到通过i和j两个变量,一个前一个后来判断递增数组是否增加,命dp[i]为以nums[i]为结尾的最长递增子序列长度
状态转移方程:dp[i] = max(dp[j] + 1) for all j < i and nums[j] < nums[i]
139:单词拆分
题目要求:给定一个字符串s和一个单词字典wordDict
要求:判断是否可以用字典里的单词完全拼接成s。
核心思路
动态规划定义状态:dp[i]=true/false表示s[0...i-1]是否可以被字典拆分
状态转移方程:dp[i]=dp[j]&&wordDict.contains(s[j...i-1])
代码实现
java
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordSet=new HashSet<>(wordDict);//查找O(1)
int n=s.length();
boolean[] dp=new boolean[n+1];
dp[0]=true;//空字符串可拆分
for(int i=1;i<=n;i++){//子串长度
for(int j=0;j<i;j++){
if(dp[j]&&wordSet.contains(s.substring(j,i))){
dp[i]=true;
break;//找到一个拆分即可
}
}
}
return dp[n];
}
}