动态规划总结

动态规划刷题总结

个人心得

问题本质

动态规划名字看起来高大上,感觉是种很复杂的算法,令人“望文生畏”,其实一句话概括,就是 数学归纳法 。动态规划问题的每一个状态是由上一个状态通过 状态转移方程 推导得出(对于存在很多状态的问题,需要画状态图辅助推导出正确的状态转移方程,类似《编译原理》的自动状态机)。动态规划是一种 “聪明的穷举” ,所谓具备 “最优子结构” 和存在 “重叠子问题” 。本质上来说,动态规划是通过引入dp数组这种 “空间换时间” 的方法来降低暴力算法的时间复杂度。

解题步骤

参考Carl的方法,牢牢把握“五部曲”:

  1. 确定dp数组以及下标的含义

  2. 确定递推公式

  3. dp数组如何初始化

  4. 确定遍历顺序

  5. 举例推导dp数组

解释说明:

  • 根据题目选择二维数组还是滚动数组(一维),状态数决定二维dp数组的列数
  • 2先于3是因为不同的状态转移方程需要不同的初始化方法
  • 深刻理解1,才能初始化好3
  • 遍历顺序主要集中于背包问题:用滚动数组解决01背包时,注意先物品再背包且背包倒序遍历(防止重复)。完全背包正序、倒序均可,若求排列问题只能先遍历背包,若求组合问题只能先遍历物品
  • 如果存在错误,打印dp数组日志来验证每一步过程,下附“灵魂三问”,解决好这三个问题debug将不再困难:
    • 这道题目我举例推导状态转移公式了么?
    • 我打印dp数组的日志了么?
    • 打印出来了dp数组和我想的一样么?

一、基础题目(就不贴外链了)

509.斐波那契数

70.爬楼梯

746.使用最小花费爬楼梯

62.不同路径

62.不同路径II

343.整数拆分(注意理解)

96.不同的二叉搜索树(注意理解)

二、背包问题(难点是遍历顺序,其次是递推公式)

引用自:代码随想录-背包总结篇

递推公式

问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); ,对应题目如下:

问装满背包有几种方法:dp[j] += dp[j - nums[i]] ,对应题目如下:

问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); ,对应题目如下:

问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]); ,对应题目如下:

遍历顺序

01背包

二维dp数组01背包先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历

一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历,防止重复!

完全背包

纯完全背包的一维dp数组实现,先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

相关题目如下:

如果求最小数,那么两层for循环的先后顺序就无所谓了,相关题目如下:

三、打家劫舍

198. 打家劫舍,从一维数组上打劫

213. 打家劫舍 II ,从环形一维数组上打劫

337. 打家劫舍 III ,从树的根节点上打劫

四、买卖股票

根据题目中所包含的状态数确定二维dp数组的列数,如309.,包含了4种状态和7种转移方程,因此二维dp数组有4列

五、子序列

引用自:代码随想录-动态规划总结篇

子序列(不连续)

子序列(连续)

编辑距离(由三道基础题目引出)

这里贴一个2022建行Fintech大赛第二道编程题:

// 文本相似度 = 1 - 最小编辑距离 / 最长的字符串长度
// 每次插入、删除、增加操作使得最小编辑距离 + 1
// 输出保留两位小数
#include<bits/stdc++.h>
using namespace std;

int editDistance(string s1, string s2) {
    int len1 = s1.size(), len2 = s2.size();
    vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
    for (int i = 1; i <= len1; i++) {
        dp[i][0] = i;
    }
    for (int j = 1; j <= len2; j++) {
        dp[0][j] = j;
    }
    for (int i = 1; i <= len1; i++) {
        for (int j = 1; j <= len2; j++) {
            if (s1[i - 1] == s2[j - 1])
                dp[i][j] = dp[i - 1][j - 1];
            else
                dp[i][j] = min(dp[i - 1][j] + 1, min(dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1));
        }
    }
    return dp[len1 - 1][len2 - 1];
}

int main(){
    string s1 = "abcde", s2 = "ace";  
    int maxLen = max(s1.size(), s2.size());
    // 两数相除,只有分子本身是double结果才能显示小数,否则强制转换都没用
    double similarity = 1 - (double)editDistance(s1, s2) / maxLen;
    // 使用fixed(不用则只保留有效数字)配合setprecision(2)来确保cout输出保留两位小数
    cout << fixed << setprecision(2) <<similarity << endl;
    return 0;
}

回文串

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值