蓝桥杯练习题——dp

五部曲(代码随想录)

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

2.确定递推公式

3.确定 dp 数组初始化

4.确定遍历顺序

5.debug

入门题

1.斐波那契数

思路

1.fi:第 i 个数的值

2.fi = fi - 1 + fi - 2

3.f0 = 0, f1 = 1

4.顺序遍历

5.记得特判 n == 0 的时候,因为初始化了 f1

cpp 复制代码
class Solution {
public:
    int fib(int n) {
        if(n == 0) return n;
        vector<int> f(n + 1);
        f[0] = 0, f[1] = 1;
        for(int i = 2; i <= n; i++) f[i] = f[i - 1] + f[i - 2];
        return f[n];
    }
};

2.爬楼梯

思路

每次可以从下面一个台阶或者下面两个台阶处上来

1.fi:爬到第 i 阶楼梯有多少种方法

2.fi = fi - 1 + fi - 2

3.f0 = 1, f1 = 1

4.顺序遍历

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        vector<int> f(n + 1);
        f[0] = 1, f[1] = 1;
        for(int i = 2; i <= n; i++) f[i] = f[i - 1] + f[i - 2];
        return f[n];
    }
};

3.使用最小花费爬楼梯

思路

可以从 0 或 1 处开始爬楼梯,需要爬到第 n 阶楼梯

1.fi:爬到第 i 阶楼梯需要的最小花费

2.fi = min(fi - 1 + costi - 1, fi - 2 + cost[i - 2)

3.f0 = 0, f1 = 0

4.顺序遍历

cpp 复制代码
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> f(n + 1);
        f[0] = 0, f[1] = 0;
        for(int i = 2; i <= n; i++){
            f[i] = min(f[i - 1] + cost[i - 1], f[i - 2] + cost[i - 2]);
        }
        return f[n];
    }
};

4.不同路径

思路

1.fij: 走到 (i, j) 总共的路径

2.fij = fi - 1j + fij - 1

3.fi1 = 1, f1i = 1

4.顺序遍历

cpp 复制代码
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> f(n + 1, vector<int>(m + 1));
        for(int i = 0; i <= n; i++) f[i][1] = 1;
        for(int i = 0; i <= m; i++) f[1][i] = 1;
        for(int i = 2; i <= n; i++){
            for(int j = 2; j <= m; j++){
                f[i][j] = f[i - 1][j] + f[i][j - 1];
            }
        }
        return f[n][m];
    }
};

5.不同路径 II

思路

1.fij: 走到 (i, j) 总共的路径

2.fij = fi - 1j + fij - 1

3.fi0 = 1, f0i = 1, 其他 = 0

4.顺序遍历

cpp 复制代码
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int n = obstacleGrid.size();
        int m = obstacleGrid[0].size();
        vector<vector<int>> f(n, vector<int>(m, 0));
        for(int i = 0; i < n && !obstacleGrid[i][0]; i++) f[i][0] = 1;
        for(int i = 0; i < m && !obstacleGrid[0][i]; i++) f[0][i] = 1;
        for(int i = 1; i < n; i++){
            for(int j = 1; j < m; j++){
                if(!obstacleGrid[i][j]){
                    f[i][j] = f[i - 1][j] + f[i][j - 1];
                }
            }
        }
        return f[n - 1][m - 1];
    }
};

6.整数拆分

思路

1.fi: 拆数字 i 可得到的最大乘积

2.拆分成 j * (i - j) 或 j * fi - j,fi = max(fi, max(j * (i - j), j * i - j))

3.f0 = 0, f1 = 1

4.顺序遍历

cpp 复制代码
class Solution {
public:
    int integerBreak(int n) {
        vector<int> f(n + 1);
        f[0] = 0, f[1] = 1;
        for(int i = 2; i <= n; i++){
            for(int j = 0; j < i; j++){
                f[i] = max(f[i], max(j * (i - j), j * f[i - j]));
            }
        }
        return f[n];
    }
};

7.不同的二叉搜索树

思路

dp3,就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量

元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量

元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量

元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量

有2个元素的搜索树数量就是dp2

有1个元素的搜索树数量就是dp1

有0个元素的搜索树数量就是dp0

所以dp3 = dp2 * dp0 + dp1 * dp1 + dp0 * dp2

dpi += dp以j为头结点左子树节点数量 * dp以j为头结点右子树节点数量

1.fi: 由 1 到 i 个节点的二叉搜索树个数

2.fi += fj - 1 * fi - j

3.f0 = 1

4.顺序遍历

cpp 复制代码
class Solution {
public:
    int numTrees(int n) {
        vector<int> f(n + 1);
        f[0] = 1;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= i; j++){
                f[i] += f[j - 1] * f[i - j];
            }
        }
        return f[n];
    }
};

背包问题

1.01背包问题

思路

1.fij: 前 i 个物品在容量为 j 的情况下的最大价值

2.fij = max(fi - 1j, fi - 1j - v\[i] + wi)

3.全部 = 0

4.顺序遍历

cpp 复制代码
#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int f[N][N], v[N], w[N];
int n, m;

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= m; j++){
        	// 不选
            f[i][j] = f[i - 1][j];
            // 选
            if(v[i] <= j) f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }
    }
    printf("%d", f[n][m]);
    return 0;
}

// 滚动数组优化
#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int f[N], v[N], w[N];
int n, m;

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);
    for(int i = 1; i <= n; i++){
        for(int j = m; j >= v[i]; j--){
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    printf("%d", f[m]);
    return 0;
}

2.分割等和子集

思路

分割成两个等和子集,即找到是否存在和为 sum / 2 的子集,转化为 01 背包,背包容量为 sum / 2

1.fj: 背包容量为 i,放入物品后的最大重量

2.fj = max(fj, fj - nums\[i] + numsi)

3.全部 = 0

4.倒序遍历

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size(), sum = 0;
        for(int i = 0; i < n; i++) sum += nums[i];
        if(sum % 2) return false;
        vector<int> f(10001, 0);
        for(int i = 0; i < n; i++){
            for(int j = sum / 2; j >= nums[i]; j--){
                f[j] = max(f[j], f[j - nums[i]] + nums[i]);
                if(f[j] == sum / 2) return true;
            }
        }
        return false;
    }
};

3.最后一块石头的重量 II

思路

尽可能分成两堆重量相同,使得相撞后重量最小

1.fj: 容量为 j 的背包,最大价值

2.fj = max(fj, fj - stones\[i] + stonesi)

3.全部 = 0

4.倒序遍历

cpp 复制代码
class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int n = stones.size(), sum = 0;
        for(int i = 0; i < n; i++) sum += stones[i];
        vector<int> f(1501, 0);
        for(int i = 0; i < n; i++){
            for(int j = sum / 2; j >= stones[i]; j--){
                f[j] = max(f[j], f[j - stones[i]] + stones[i]);
            }
        }
        return (sum - f[sum / 2]) - f[sum / 2];
    }
};

4.目标和

思路

pos - neg = tar 得 pos - (sum - pos) = tar 得 pos = (sum + tar) / 2

转换为背包容量为 pos,有多少种情况装满

无解的情况:pos 为奇数,tar 的绝对值大于 sum

只要搞到 numsi,凑成 fj 就有 fj - nums\[i] 种方法。

例如:fj,j 为5,

已经有一个1(numsi)的话,有 f4种方法 凑成 容量为5的背包。

已经有一个2(numsi)的话,有 f3种方法 凑成 容量为5的背包。

已经有一个3(numsi)的话,有 f2中方法 凑成 容量为5的背包

已经有一个4(numsi)的话,有 f1中方法 凑成 容量为5的背包

已经有一个5(numsi)的话,有 f0中方法 凑成 容量为5的背包

那么凑整 f5 有多少方法呢,也就是把 所有的 fj - nums\[i] 累加起来。

1.fj:填满 j 背包有多少种情况

2.fj += fj - nums\[i]

3.f0 = 1,其他 = 0

4.倒序遍历

cpp 复制代码
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n = nums.size(), sum = 0;
        for(int i = 0; i < n; i++) sum += nums[i];
        if((sum + target) % 2 || abs(target) > sum) return 0;
        int pos = (sum + target) / 2;
        vector<int> f(pos + 1, 0);
        f[0] = 1;
        for(int i = 0; i < n; i++){
            for(int j = pos; j >= nums[i]; j--){
                f[j] += f[j - nums[i]];
            }
        }
        return f[pos];
    }
};

5.一和零

思路

可以等价为两个 01 背包,一个装 0,一个装 1

1.fij: 最多有 i 个 0 和 j 个 1 的最长子集大小

2.fij = max(fij, fi - zeroj - one + 1)

3.全部 = 0

4.倒序遍历

cpp 复制代码
class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> f(m + 1, vector<int>(n + 1, 0));
        for(auto str : strs){
            int zero = 0, one = 0;
            for(int i = 0; i < str.size(); i++){
                if(str[i] == '0') zero++;
                else one++;  
            }
            for(int i = m; i >= zero; i--){
                for(int j = n; j >= one; j--){
                    f[i][j] = max(f[i][j], f[i - zero][j - one] + 1);
                }
            }
        }
        return f[m][n];
    }
};
相关推荐
Raink老师1 小时前
【AI面试临阵磨枪-79】实时数据 RAG:订单、商家、物流、天气、动态库存
人工智能·面试·职场和发展
Cosolar1 小时前
Chroma向量库面试学习指南
数据库·人工智能·面试·职场和发展·数据库架构
kkeeper~3 小时前
0基础C语言积跬步之数据在内存中的存储
c语言·数据结构·算法
wabs6664 小时前
关于贪心算法的一些自我总结【力扣45.跳跃游戏II】【灵感来源:代码随想录】
算法·贪心算法·复盘
2401_876964134 小时前
【湖北专升本】2026湖北专升本真题PDF+备考资料汇总
数据结构·人工智能·经验分享·深度学习·算法·计算机视觉
嗝o゚5 小时前
CANN GE 算子融合——融合算法与调度策略
算法·昇腾·cann·ge
小江的记录本5 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试
Ulyanov6 小时前
用声明式语法重新定义Python桌面UI:QML+PySide6现代开发入门(一)
开发语言·python·算法·ui·系统仿真·雷达电子对抗仿真
数据科学小丫6 小时前
特征工程处理
人工智能·算法·机器学习
秦明月137 小时前
电芯装配测试线安全回路设计实战
经验分享·其他·职场和发展·创业创新·学习方法