算法基础-----【动态规划】

news2024/11/27 0:37:09

动态规划(待完善)

动规五部曲分别为:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式(状态转移公式)
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组、

动态规划的核心就是递归+剪枝(存储键值,下次不再访问,用空间换时间)

找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
这道题目我举例推导状态转移公式了么?
我打印dp数组的日志了么?
打印出来了dp数组和我想的一样么?
或者更清晰的知道自己究竟是哪一点不明白,是状态转移不明白,还是实现代码不知道该怎么写,还是不理解遍历dp数组的顺序。

动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的,例如:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

动态规划中dp[j]是由dp[j-weight[i]]推导出来的,然后取max(dp[j], dp[j - weight[i]] + value[i])。

但如果是贪心呢,每次拿物品选一个最大的或者最小的就完事了,和上一个状态没有关系。

所以贪心解决不了动态规划的问题。

动态规划的解题步骤:(1)确定dp数组以及下标含义 (2)确定递推公式 (3)dp数组如何初始化 (4)确定遍历顺序 (5)例举推导dp数组。

【基础题目】

【509】斐波那契数列

解题步骤:

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数组。

按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:

0 1 1 2 3 5 8 13 21 34 55

如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的。

//动态规划基础问题
/*递归的方法 :时间复杂度o(n^2) 空间复杂度o(1)*/
class Solution {
public:
    int fib(int n) {
        if(n<2)return n;
        return fib(n-1)+fib(n-2);
    }
};
/*动态规划的方法 时间复杂度o(n) 空间复杂度o(n)(经典做法)*/
class Solution {
public:
    int fib(int n) {
        if(n<2)return n;
        vector<int> dp(n+1);
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2;i<n+1;i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

/*动态规划简写 时间复杂度o(n) 空间复杂度o(1)*/
class Solution {
public:
    int fib(int n) {
        if(n<2)return n;
        vector<int> dp(2);
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2;i<n+1;i++){
            int sum = dp[0]+dp[1];
            dp[0] = dp[1]; 
            dp[1] = sum;
        }
        return dp[1];
    }
};

【70】爬楼梯

确定递归数列:找规律 f(n) = f(n-1)+f(n-2)
确定终值f(1) = 1 f(0) = 0
存储节点:定义数组存储节点
最标准的做法,要是还要优化空间复杂度就考虑上面的做法

class Solution {
public:
    int climbStairs(int n) {
        if(n<2)return n;//(f(1)= 1,f(2) =2)
        vector<int> f(n+1);
        f[1] =1;
        f[2] =2;
        for(int i =3;i<n+1;i++){
            f[i] =f[i-1]+f[i-2];
        }
        return f[n];
    }
};

【118】杨辉三角

注意申请数组具体那一行
注意改变数组的长度的函数resize(为了防止0出现)

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> ret(numRows);
        for (int i = 0; i < numRows; ++i) {
            ret[i].resize(i + 1);
            ret[i][0] = ret[i][i] = 1;
            for (int j = 1; j < i; ++j) {
                ret[i][j] = ret[i - 1][j] + ret[i - 1][j - 1];
            }
        }
        return ret;
    }
};

【198】打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

按照五部曲进行推导

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        //确定dp数组 dp[i]存放最高金额
        vector<int> dp(n);
        if(n == 0)return 0;
        if(n == 1)return nums[0];
        if( n == 2)return max(nums[0],nums[1]);
        dp[0] = nums[0];
        dp[1] = max(nums[0],nums[1]);

        for(int i = 2;i<n;i++){
            dp[i] = max(dp[i-1],nums[i]+dp[i-2]);
            cout<<dp[i]<<endl;
        }
        return dp[n-1];
    }
};

【背包问题】

【0-1背包】

对于面试,掌握01背包和完全背包,多重背包。
在这里插入图片描述

基础引用:对于0,1背包,就是m个物品,给定对应的重量和价值,最大容量为n,这些物品你只能选一个或者不选(01),求最大价值。
在这里插入图片描述

动态规划五部曲:

(1)确定dp数组以及下标的含义dp[i] [j ]:表示下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

(2)确定递推公式:

  • 放物品i:由dp[i - 1] [j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i] [j]就是dp[i - 1] [j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)
  • 放物品i:由dp[i - 1] [j - weight[i]]推出,dp[i - 1] [j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1] [j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

所以递归公式: dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i]);

(3)初始化dp数组

​ 后面的公式是根据前面来推导的,所以初始化正确了才能导致dp数组正确

​ 状态转移方程 dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

​ 要求出 dp[ 0 ] [ j]:也就是求种类0在不同重量下的最大价值:当j<weight[0]的时候肯定装不下,都为0.所以j从weight[0]开始初始化,都为value[0]:

在这里插入图片描述

(4)确定遍历顺序:先遍历物品,再遍历重量:

for(int i =1;i<m;i++){
    for(int j = 0;j<=m;j++){
        if(j<weight[i]){
            dp[i][j] = dp[i-1][j];//不放
        }else{
            dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);//放
        }
    }
}

在这里插入图片描述

(5)举例推导dp数组

image-20240429141403376
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
class Solution{
public:
   int maxSolution( vector<int>& weight,vector<int>& value,int m,int n){
       //确定dp数组
       vector<vector<int>> dp(m,vector<int>(n+1,0));//要包含一个0
       //初始化dp数组 dp[i-1][j] 初始化 dp[0][j]
        for(int j = weight[0];j<=n;j++){
            dp[0][j] = value[0];
        }

        for(int i =1;i<m;i++){//遍历背包种类 种类1已经初始化过了,要从2开始
            for(int j = weight[0];j<=n;j++){//遍历重量
                if(j<weight[i])dp[i][j] = dp[i-1][j];
                else dp[i][j]= max( dp[i-1][j-weight[i]]+value[i],dp[i-1][j]);

            }
        }
        cout<<dp[m-1][n]<<endl;
   }


};


int main()
{
    int m;//背包种类
    int n;//空间容量 bagweight
    vector<int> weight(m,0);
    vector<int> value(m,0);
//    cin >>m>>n;
//    for(int i =0;i<m;i++){
//        cin>> cap[i];
//    }
//    for(int i =0;i<m;i++){
//        cin>> value[i];
//    }
    m = 3;//背包种类
    n = 4;//最大容量是4
    weight = {1,3,4};//重量
    value = {15,20,20};//价值
    Solution s;
    int res = s.maxSolution(weight,value,m,n);
    return 0;
}

【416】分割等和子集

​ 0-1背包是可以用回溯的方式去做的,和【698】【473】都有相同的做法。

​ 给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

j:容量,dp[j]最大价值,可以看到都是倒叙取最大值,最后的dp数组是:

01234567891011
01111566661011

在这里插入图片描述

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum =0;
        for(auto num:nums){
            sum+=num;
        }
        if(sum%2 == 1)return false;//要是不能平分直接退出
        int n = sum/2;
        vector<int> dp(n+1,0);//初始化dp数组

        //dp遍历
        for(int i =0;i<nums.size();i++){
            for(int j=n;j>=nums[i];j--){//特别注意这个nums[i]
                dp[j] = max(dp[j],dp[j-nums[i]]+nums[i]);
                cout<<"i:"<<i<<" dp["<<j<<"]:"<<dp[j]<<endl;
            }
        }
        //判断
        if(dp[n] == n)return true;
        return false;
    }
};


【1049】最后一块石头的重量

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 xy,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int n = stones.size();
        int sum =0;
        for(auto item:stones){
            sum+=item;
        }
        int target = sum/2;
        vector<int> dp(target+1,0);
        for(int i =0;i<n;i++){
            for(int j = target;j>=stones[i];j--){
                dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum-2*dp[target];//注意最后返回的数值
    }
};

【494】目标和

【17】一和零

【完全背包】

完全背包内层循环从头开始

【322】零钱兑换

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //初始化dp数组
        vector<int> dp(amount+1,INT_MAX);
        dp[0] = 0;
        for(int i =0;i<coins.size();i++){
            for(int j = coins[i];j<=amount;j++){//遍历背包 注意初始化的位置
                if(dp[j-coins[i]] !=INT_MAX){// 如果dp[j - coins[i]]是初始值则跳过
                    dp[j] = min(dp[j],dp[j-coins[i]]+1);
                }

            }
        }
        if(dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

遍历的过程:

以coins = [1,2,5],amount = 11为例子:

01234567891011
初始化0MMMMMMMMMMM
1(只有1)01234567891011
2(1或2)011223344556
5(1或2或5)011221223323

【139】单词拆分

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //存储wordDict 背包
        unordered_set<string> wordMap(wordDict.begin(),wordDict.end());
        vector<int> dp(s.size()+1,false);//dp数组
        dp[0]= true;
        //求的是排列数,有顺序,背包在外层
        for(int i =1;i<=s.size();i++){//遍历背包
            for(int j =0;j<i;j++){//遍历物品
                string tmp = s.substr(j,i-j);
                if(wordMap.find(tmp)!= wordMap.end()&&dp[j] == true){
                    dp[i] = true;
                }
            }
        }
        return dp[s.size()];

    }
};

【子序列问题】

【300】最长子序列

(1)根据返回值确定dp[i]:以nums[i]结尾的最长子序列的数组长度。

(2)状态转移方程: if(nums[i]>nums[j])dp[i] = max(dp[i],dp[j]+1);(往前对比)

(3)dp数组初始化,dp[i] = 1

(4) 确定遍历顺序,dp[i+1] = dp[i]

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int res =1;
        if(nums.size() == 1)return 1;
        if(nums.size() == 0)return 0;

        vector<int> dp(nums.size(),1);
        
        for(int i =1;i<nums.size();i++){
            for(int j = 0;j<i;j++){
                if(nums[i]>nums[j])dp[i] = max(dp[i],dp[j]+1);
            }
            if(dp[i]>res)res = dp[i];//不一定是最后一个元素,取最长子序列
        }
        return res;
    }
};

【301】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1875067.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TS_开发一个项目

目录 一、编译一个TS文件 1.安装TypeScript 2.创建TS文件 3.编译文件 4.用Webpack打包TS ①下载依赖 ②创建文件 ③启动项目 TypeScript是微软开发的一个开源的编程语言&#xff0c;通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或…

simulink开发stm32,使用中断模块,无法产生中断,其中包括使用timer模块,以及ADC都无法产生中断,需要注意的地方

1&#xff0c;其中包括使用timer模块&#xff0c;以及ADC都无法产生中断&#xff0c;需要注意的地方 原来是需要在配置文件里开启一下timer的中断&#xff0c;其他模块自动加载ioc就可以了&#xff0c;这个timer需要注意力&#xff0c;需要自己勾选一下 如下图&#xff1a; 看…

Android简介-历史、API等级与体系结构

1. Android简介 Android是一种基于Linux内核的自由及开放源代码的操作系统。最初是由安迪鲁宾(Andy Rubin)开发的一款相机操作系统。2005年8月被Google收购。2007年11月&#xff0c;Google与84家硬件制造商、软件开发商及电信营运商组建开放手机联盟共同研发改良Android系统。…

【漏洞复现】I doc view——任意文件读取

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 I doc view 在线文档预览是一个用于查看、编辑、管理文档的工具…

Why is Kafka fast?(Kafka性能基石)

Kafka概述 Why is kafka fast&#xff1f; 思考一下&#xff0c;当我们在讨论Kafka快的时候我们是在谈论什么呢&#xff1f;What does it even mean that Kafka is fast? 我们是在谈论kafka的低延迟&#xff08;low latency&#xff09;还是在讨论吞吐量&#xff08;through…

CTF-PWN-LLVM-【红帽杯-2021 simpleVM】

文章目录 参考检查逆向漏洞思路调试定位到PASS名exp 参考 https://bbs.kanxue.com/thread-274259.htm#msg_header_h2_6 http://www.blackbird.wang/2022/08/30/LLVM-PASS%E7%B1%BBpwn%E9%A2%98%E6%80%BB%E7%BB%93/ 检查 因为是用opt运行&#xff0c;加载动态库VMPASS.so的P…

【ACM_2023】3D Gaussian Splatting for Real-Time Radiance Field Rendering

【ACM_2023】3D Gaussian Splatting for Real-Time Radiance Field Rendering 一、前言Abstract1 INTRODUCTION2 RELATED WORK2.1 Traditional Scene Reconstruction and Rendering2.2 Neural Rendering and Radiance Fields2.3 Point-Based Rendering and Radiance Fields 3 O…

15秒下雨短视频:成都柏煜文化传媒有限公司

15秒下雨短视频&#xff1a;瞬间的诗意与情感共鸣 在数字时代的浪潮中&#xff0c;短视频以其独特的魅力&#xff0c;成为了人们生活中不可或缺的一部分。其中&#xff0c;一段仅15秒的下雨短视频&#xff0c;成都柏煜文化传媒有限公司 或许在时间长河中只是一瞬间&#xff0c…

pdf已加密如何解除?解密密码的两个方法【可加密】

电脑文件加密的目的就是保护重要信息&#xff0c;防止数据泄露。如果需要解除密码&#xff0c;应该如何操作呢&#xff1f;pdf已加密如何解除&#xff1f;本文整理了以下两种解除文件方法&#xff0c;希望能够帮到有需要的朋友们&#xff01; 方法一、使用金舟文件夹加密大师解…

【环境配置】ubuntu中 Docker的安装

前言 Docker的使用&#xff0c;一直没有系统的梳理和记录&#xff0c;刚好最近看视频又再次的全面介绍到docker。于是记录下方便后面自己的使用&#xff0c;毕竟安装过程中还是会存在小的不丝滑。 1 Docker的简单介绍 这里的介绍来自于大模型的问答&#xff08;LLM时代的到来&a…

Python酷库之旅-第三方库openpyxl(15)

目录 一、 openpyxl库的由来 1、背景 2、起源 3、发展 4、特点 4-1、支持.xlsx格式 4-2、读写Excel文件 4-3、操作单元格 4-4、创建和修改工作表 4-5、样式设置 4-6、图表和公式 4-7、支持数字和日期格式 二、openpyxl库的优缺点 1、优点 1-1、支持现代Excel格式…

OpenAI推出自我改进AI- CriticGPT

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【Java】字节数组 pcm 与 wav 格式互转 (附原理概述)

前言 最近实现了一个文字转语音的功能&#xff0c;语音引擎返回的是pcm格式的数据。需要转化成wav格式前端才能播放。本文首先会给出解决方案&#xff0c;后续会讲背后的原理。 场景 git 仓库 https://github.com/ChenghanY/pcm-wav-converter 1. pcm wav 转化工具类 入参和…

将CSV、Excel、XML文件转换为MySQL数据库

在平时的工作中&#xff0c;经常会遇到需要将文件数据导入到数据库中的情况。有些客户之前可能只使用Excel表格作为记录工具&#xff0c;但当数据量达到一定程度或者需要将数据导入到其他系统中时&#xff0c;就会很emo,因为Excel表格虽然方便&#xff0c;但在数据处理和管理方…

ORB-SLAM2同OpenMVS实现三维重建

ORB-SLAM2 位姿导出 Note: 为与OpenMVS进行对接本次进对ORB-SLAM2进行部分修改&#xff0c;使之可以为 OpenMVS提供稀疏点云、关键帧的位姿、内参&#xff0c;以及稀疏点云在各个View 中的可见性。 主要更改如下 . 在Map文件下增添如下函数 public: void Save(const string &a…

面包板的使用

概要 当你学习怎样去建立一个电路时&#xff0c;面包板是最基本、最简单的元件之一。由于板子上有很多小插孔&#xff0c;各种电子元器件可根据需要任意插入或拔出&#xff0c;免去了焊接的工序&#xff0c;节省了电路的组装时间&#xff0c;且元件可以重复使用&#xff0c;非…

论文学习:基于知识图谱的RAG进行客服问答

1.简介 文章名称&#xff1a; Retrieval-Augmented Generation with Knowledge Graphs for Customer Service Question Answering&#xff08;基于知识图谱的RAG进行客服问答&#xff09; 2.摘要ABSTRACT 在客户服务技术支持中&#xff0c;迅速准确地检索相关的过往问题对于有…

爬虫笔记17——selenium框架的使用

selenium框架的使用 1、python程序安装selenium框架2、下载Chrome谷歌驱动3、selenium的基本使用4、多个标签页切换顺序混乱的问题 1、python程序安装selenium框架 # 在安装过程中最好限定框架版本为4.9.1 # pip install selenium 没有制定版本&#xff0c;非镜像下载也会比较…

node.js环境安装和VUE-cli脚手架搭建

简介 node.js Node.js 是一个免费、开源、跨平台的 JavaScript 运行时环境&#xff0c;它让开发人员能够创建服务器、Web 应用、命令行工具和脚本。 VUE-cli Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统&#xff0c;提供&#xff1a; 通过 vue/cli 实现的交互式的项…

「C++系列」C++简介、应用领域

文章目录 一、C简介C 的主要特点&#xff1a;C 的学习建议&#xff1a; 二、C 应用领域三、C 有哪些优势四、C 初学案例1. Hello, World!2. 两数求和3. 判断奇偶4. 判断闰年5. 判断质数6. 求阶乘7. 斐波那契数列8. 延时程序&#xff08;简单示例&#xff09; 五、相关链接 一、…