动态规划算法基础及leetcode例题

01 基础理论

题型:动规基础(斐波那契数列or爬楼梯);背包问题;打家劫舍;股票问题;子序列问题

动规误区:只要看懂递推就ok(递推公式只是一部分)

解决动态规划应该要思考的几步:

  • 状态转移的DP数组以及下标的含义
  • 递推公式
  • DP数组为何初始化
  • 遍历顺序
  • 打印DP数组

02 例题

基础题目

509.斐波那契数列

思路:

确定dp[i]含义:第i个斐波那契数值
递推公式:dp[i]=dp[i-1]+dp[i-2]
dp数组如何初始化:dp[0]=1,dp[1]=1
遍历顺序:从前向后
打印dp数组:为了debug,如果出错,打印检查输出数组;

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];
}
}

70. 爬楼梯

思路:

1阶 1种
2阶 2种
3阶 3种(1+2)【1阶的方法+2步就是3阶;2阶的方法+1步就到3阶】
4阶 5种(2+3)【2阶的方法+2步就是3阶;3阶的方法+1步就到3阶】
.....
找到了递推关系:依赖与前两个状态

java代码

class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}

背包问题

01背包、完全背包、多重背包

01背包

二维dp实现01背包:

一维dp数组实现01背包:

416. 分割等和子集

思路:子集为所有元素之和的一半

容量为[所有元素之和的一半]的背包,抽象为01背包问题
dp[j]的含义:容量为j的背包,所背的最大价值
目标:dp[target]=target【每个元素的数组就是它的重量,也是它的价值】
递推公式:dp[j]=max(dp[j],dp(j-weight[i])+value[i]);
初始化:dp[0]=0;其他非零数组也等于0
遍历顺序:倒序【先物品,后背包】,因为每个元素只使用一次

java代码:

class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int n = nums.length;
int sum = 0;
for(int num : nums) {
sum += num;
}
//总和为奇数,不能平分
if(sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < n; i++) {
for(int j = target; j >= nums[i]; j--) {
//物品 i 的重量是 nums[i],其价值也是 nums[i]
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}

完全背包

与0-1背包区别:数据可以重复使用
遍历顺序:正序遍历

正序遍历:

private static void testCompletePack(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++){ // 遍历物品
for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
System.out.print(j + "-" +dp[j]);
System.out.print(" ");
}
System.out.println();
}
}
//输出
1-15 2-30 3-45 4-60
3-45 4-60
4-60

倒序遍历:

for (int i = 0; i < weight.length; i++){ // 遍历物品
for (int j = bagWeight; j >= weight[i]; j--){ // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
System.out.print(j + "-" +dp[j]);
System.out.print(" ");
}
System.out.println();
}
//输出
4-15 3-15 2-15 1-15
4-35 3-20
4-35

518.零钱兑换II

动规五部曲:

dp含义:dp[j] 装满容量为j的背包,有dp[j]种方法【最终要求的:dp[amount]】
递推公式:dp[j]+=dp[j-coins[i]]//装满有多少种方法的递推公式
【dp[j]方法数与前面方法数相关(类似爬楼)】
初始化:dp[0]=1【装满背包为0的方法有一种】,非零下标初始为0
遍历顺序:先物品后背包(组合数);先背包后物品(排列数)

java代码:

class Solution {
public int change(int amount, int[] coins) {
//递推表达式
int[] dp = new int[amount + 1];
//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}

377. 组合总和 Ⅳ

与上一次不同的是:

遍历顺序:先背包后物品(求全排列的个数)

java代码

class Solution {
public int combinationSum4(int[] nums, int target) {
if(nums == null || nums.length==0) return 0;
int[] dp = new int[target+1];
dp[0] = 1;
for(int j=0;j<=target;j++){
for(int i=0;i<nums.length;i++){
if(j>=nums[i]){
dp[j] += dp[j-nums[i]];
}
}
}
return dp[target];
}
}

70. 爬楼梯

核心:排列数

java代码:

class Solution {
public int climbStairs(int n) {
int[] dp = new int[n+1];
int[] nums = new int[]{1,2};
dp[0]=1;
for(int j = 0;j<=n;j++){
for(int i=0;i<nums.length;i++){
if(j>=nums[i]){
dp[j] += dp[j-nums[i]];
}
}
}
return dp[n];
}
}

打家劫舍

198.打家劫舍

思路:

dp含义:dp[i] 考虑下标i,包括i之前的能偷的最大金币的数量;【结果:dp[nums.size()-1]】
递推公式:两个状态:偷i和不偷i
     偷i:dp[i-2]+nums[i]
     不偷i: dp[i-1]
     要求的是最大的数量:dp[i]=max(dp[i-2]+nums[i],dp[i-1])
初始化:
   dp[0]=nums[0];
   dp[1]=max(nums[0],nums[1]);
   非零值=0;

遍历顺序:从小到大遍历(这样才可以用到前面的状态)

java代码:

class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(dp[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[nums.length - 1];
}
}

股票问题

121. 买卖股票的最佳时机

只能买卖一次

思路:

dp含义:dp[i][0]:持有股票的最大金额;dp[i][1]:不持有股票的最大金额
最后结果:max(dp[len-1][0],dp[len-1][1])
递推公式:dp[i][0]由dp[i-1]0 -prices[i](买入股票)的决定
     dp[i][0]=max(dp[i-1][0],-prices[i]);【股票只买卖一次,所以就直接-prices】
     dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
初始化:dp[0][0]=-prices[0];dp[0][1]=0;
遍历顺序:从前往后

java代码:

class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int length = prices.length;
// dp[i][0]代表第i天持有股票的最大收益
// dp[i][1]代表第i天不持有股票的最大收益
int[][] dp = new int[length][2];
int result = 0;
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);
}
return dp[length - 1][1];
}
}

122.买卖股票的最佳时机II

思路:
修改地方:dp[i][0]=max(dp[i-1][0],dp[i - 1][1]-prices[i]);
java代码:

class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int len = prices.length;
int[][] dp = new int[len][2];
dp[0][0]=-prices[0];
dp[0][1]=0;
for(int i =1;i<len;i++){
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]-prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);
}
return dp[len - 1][1];
}
}

子序列问题

子序列不连续

300.最长递增子序列

思路:

dp[i]: i之前包括i的以nums[i]结尾的最长递增子序列的长度【结果:dp[i]的最大值】
递推公式:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1)
初始化:dp[i]=1
遍历顺序:从小到大

java代码:

class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
for (int i = 0; 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);
}
}
}
int res = 0;
for (int i = 0; i < dp.length; i++) {
res = Math.max(res, dp[i]);
}
return res;
}
}

子序列连续

674. 最长连续递增序列

java代码:

class Solution {
public int findLengthOfLCIS(int[] nums) {
int[] dp = new int[nums.length];
for (int i = 0; i < dp.length; i++) {
dp[i] = 1;
}
int res = 1;
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i + 1] > nums[i]) {
dp[i + 1] = dp[i] + 1;
}
res = res > dp[i + 1] ? res : dp[i + 1];
}
return res;
}
}

编辑距离

392.判断子序列

思路:

二维数组来表示两个字符串相同的情况
如果s与t的公共子序列的长度==s的长度,代表s是t的子序列

  1. dp[i][j] :以i-1为尾的字符串s和以j-1为尾的字符串t的相同子序列长度
  2. 递推公式:判断元素是否相同
    if(s[i-1]==t[j-1]) dp[i][j]=dp[i-1][j-1]+1
    else dp[i][j]=dp[i][j-1]
  3. 初始化: dp[i][0]=0;dp[0][j]=0;
  4. for左到右
  5. dp[s.size()][t.size()]==s.size()【true】
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;
}
}
}

72. 编辑距离

思路:

  1. dp[i][j] :以i-1为尾的字符串s和以j-1为尾的字符串t的最小的操作次数
  2. 递推公式:判断元素是否相同
    if(s[i-1]==t[j-1]) dp[i][j]=dp[i-1][j-1]
    else dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1)
  1. 初始化: dp[i][0]=i;dp[0][j]=j;
  2. for左到右
  3. 结果:dp[s.size()][t.size()]
class Solution {
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m + 1][n + 1];
// 初始化
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 1; j <= n; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 因为dp数组有效位从1开始
// 所以当前遍历到的字符串的位置为i-1 | j-1
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
}
}
}
return dp[m][n];
}
}

回文

647. 回文子串

思路:

  1. dp[i][j]:[i-j]字串是否为回文子串
  2. 递推公式:if(s[i]s[j]) {
    if(j-i<=1) {dp[i][j]=true;result++;}
    else if(dp[i+1][j-1]
    true){dp[i][j]=true;result++;}
    }
    //if(s[i]s[j]) dp[i][j]false;
  3. 初始化:dp[i][j]==false
  4. 遍历:从底往上遍历,从左往右;
class Solution {
public int countSubstrings(String s) {
int len = s.length();
int result=0;
boolean[][] dp=new boolean[len][len];
for(int i=len-1;i>=0;i--){
for(int j=i;j<len;j++){
if(s.charAt(i)==s.charAt(j)){
if(j-i<=1){
dp[i][j]=true;
result++;
}else if(dp[i+1][j-1]==true) {
dp[i][j]=true;
result++;
}
}
}
}
return result;
}
}

本文作者:Lee_ing

本文链接:https://www.cnblogs.com/yunshalee/p/17402952.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   lee_ing  阅读(24)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起