首页 > 其他分享 >动态规划(dp数组)

动态规划(dp数组)

时间:2025-01-18 22:43:03浏览次数:1  
标签:偷窃 nums int 路径 数组 动态 dp

动态规划,是利用历史记录来避免重复计算的一种算法,是求解决策过程最优化的过程。一般用一维数组/二维数组来保存历史记录。
(将原问题拆解成若干子问题,同时保存子问题的答案,使每个子问题只求解一次,最终获得原问题的答案。)
一般动态规划有三个步骤:

1.定义数组元素的含义,一般求什么就定义成什么;
2.找出数组元素之间的关系式,类似归纳法;
3.找出初始值;

动态规划三要素:
1.最优子结构:原问题的最优解所包含的子问题的解也是最优的,通过子问题的最值得到原问题的最值。

2.存在重叠子问题:子问题间不独立(这是动态规划区别于分治的最大不同);如果暴力穷举的话效率会极其低下,所以需要「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。

3.无后效性:即后续的结果不会影响当前结果。
状态转移方程:一般思考过程,明确「状态」 -> 定义 dp 数组/函数的含义 -> 明确「选择」-> 明确 base case。方程本质是数学的递推式。

解题方法

DP数组的递推计算过程需要注意两点:

1、遍历的过程中,所需的状态必须是已经计算出来的。

2、遍历的终点必须是存储结果的那个位置

主要就是看 base case 和最终结果的存储位置。

例题:
斐波那契类型:

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

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

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

在第 i 号房子之前可偷窃的最高金额由两种情况确定:

1.第 i-2 号房子前可偷窃的最高金额加上第 i 号房子的存放金额;
2.第 i-1 号房子前可偷窃的最高金额。

两种情况取较大者即可。
转移方程为: dp[i] = max(dp[i-2] + nums[i], dp[i-1])
由转移方程可知,dp 数组的值取决于前两个 dp 数组的值,因此毫无疑问,
从前向后遍历。

int rob(int* nums, int numsSize) { if(numsSize==1) return *nums; int dp[numsSize]; dp[0]=nums[0]; dp[1]=fmax(nums[0],nums[1]); for(int i=2;i<numsSize;i++) { dp[i]=fmax(dp[i-2]+nums[i],dp[i-1]); } return fmax(dp[numsSize-1],dp[numsSize-2]); }
Leetcode——740.删除并获得点数

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:

输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:

输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

这一题可看作是打家劫舍的变种
可以定义一个数组用于记录数字的个数,然后按照数字排序的位序来进行打家劫舍问题解决。
int deleteAndEarn(int* nums, int numsSize) { int ma[10001]={0}; int dp[10001]={0}; for(int i=0;i<numsSize;i++) { ma[nums[i]]++; } dp[1]=ma[1]; for(int i=2;i<=10000;i++) { dp[i]=fmax(dp[i-1],dp[i-2]+ma[i]*i); } return dp[10000]; }

矩阵类型:
Leetcode——62.不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:
输入:m = 3, n = 7
输出:28

示例 2:
输入:m = 3, n = 2
输出:3

解释:
从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向下

示例 3:
输入:m = 7, n = 3
输出:28

示例 4:
输入:m = 3, n = 3
输出:6

我们将所有的 f(0,j) 以及 f(i,0) 都设置为边界条件,它们的值均为 1;
由于我们每一步只能从向下或者向右移动一步,因此要想走到 (i,j),如果向下走一步,那么会从 (i−1,j) 走过来;如果向右走一步,那么会从 (i,j−1) 走过来;
动态规划转移方程:f(i, j) = f(i-1, j) + f(i, j-1);
最终的答案即为 f(m-1,n-1).
复杂度分析
时间复杂度:O(mn)。
空间复杂度:O(mn),即为存储所有状态需要的空间。注意到 f(i,j) 仅与第 i 行和第 i−1 行的状态有关,因此我们可以使用滚动数组代替代码中的二维数组,使空间复杂度降低为 O(n);
此外,由于我们交换行列的值并不会对答案产生影响,因此我们总可以通过交换 m 和 n 使得 m≤n,这样空间复杂度降低至 O(min(m,n))。

int uniquePaths(int m, int n) { int l[m][n]; for(int i=0;i<m;++i) { l[i][0]=1; } for(int j=0;j<n;++j) { l[0][j]=1; } for(int i=1;i<m;++i) { for(int j=1;j<n;++j) { l[i][j]=l[i-1][j]+l[i][j-1]; } } return l[m-1][n-1]; }
Leetcode——64.最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。

对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。

创建二维数组 dp,与原始网格的大小相同,dp[i][j] 表示从左上角出发到 (i,j) 位置的最小路径和。显然,dp[0][0]=grid[0][0]。对于 dp 中的其余元素,通过以下状态转移方程计算元素值。

当 i>0 且 j=0 时,dp[i][0]=dp[i−1][0]+grid[i][0]。

当 i=0 且 j>0 时,dp[0][j]=dp[0][j−1]+grid[0][j]。

当 i>0 且 j>0 时,dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j]。

最后得到 dp[m−1][n−1] 的值即为从网格左上角到网格右下角的最小路径和。

int minPathSum(int** grid, int gridSize, int* gridColSize) { int dp[gridSize][gridColSize[0]]; dp[0][0]=grid[0][0]; for(int i=1;i<gridSize;i++) { dp[i][0]=dp[i-1][0]+grid[i][0]; } for(int j=1;j<gridColSize[0];j++) { dp[0][j]=dp[0][j-1]+grid[0][j]; } for(int i=1;i<gridSize;i++) { for(int j=1;j<gridColSize[0];j++) { dp[i][j]=fmin(dp[i-1][j],dp[i][j-1])+grid[i][j]; } } return dp[gridSize-1][gridColSize[0]-1]; }

标签:偷窃,nums,int,路径,数组,动态,dp
From: https://www.cnblogs.com/Nakaliiiiiiii/p/18678897

相关文章

  • 4-08动态绑定_多态
    多继承无函数覆盖structBase1 { public: virtualvoidFn_1() { printf("Base1:Fn_1...\n"); } virtualvoidFn_2() { printf("Base1:Fn_2...\n"); } }; structB......
  • 关于动态规划
    主要问题大概是动规基础(斐波那契),背包,打家劫舍,股票,子序列。解决也主要是先分类,建立dp数组,明确dp数组的含义,dp数组的初始化,遍历顺序。动规基础斐波那契数列,爬楼梯,建立dp数组的时候,递推公式的推导就要参考建立dp数组并且明确dp数组的含义比如下面的最小花费爬楼梯,明确含义就可以在......
  • 动态代理IP池管理:避免爬虫被封禁的高效策略
    在进行大规模数据抓取时,反爬虫机制经常成为爬虫开发者的一大难题。许多网站通过监测请求频率、User-Agent、IP地址等信息来识别并封禁爬虫。为了防止这种情况,动态代理IP池的管理变得尤为重要。通过使用代理IP池,并定期更换IP,可以有效避开基于IP的封禁策略。本篇博客将深入......
  • MarsCode青训营打卡Day5(2025年1月18日)|稀土掘金-148.小A的子数组权值、304.计算特定条
    资源引用:148.小A的子数组权值304.计算特定条件下的四元组数量今日小记:148.题既可以如笔者给出的题解来遍历全部的子数组,也可以按照遍历权值的方式通过滑动窗口来实现,两种解题方法的时间复杂度相同,但前者的操作更为简单。304.题与Day4的三元组一题有相似之处,均通过将多元......
  • 嵌入式基础 C语言篇 数组.初阶
    一、基本概念逻辑:一次性定义多个相同类型的变量,并存储到一片连续的内存中语法释义:intbuf[5];buf是数组名,即这片连续内存的名称[5]代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素int代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组初......
  • 有一个包含开始日期和结束日期的数组,获取最小的日期和最大的日期,用java实现
    packagecom.cfb.oa.m;importjava.time.LocalDate;importjava.util.ArrayList;importjava.util.List;classDateRange{LocalDatestartDate;LocalDateendDate;publicDateRange(LocalDatestartDate,LocalDateendDate){this.startDate......
  • Profibus DP转Modbus TCP协议转换网关模块功能详解
    ProfibusDP和ModbusTCP是两种不同的工业现场总线协议,ProfibusDP常用于制造业自动化领域,而ModbusTCP则在工业自动化和楼宇自动化等领域广泛应用。实现ProfibusDP转ModbusTCP功能,通常需要特定的网关设备,以下为你详细介绍:捷米JM-DPM-TCP网关模块这......
  • 有一个包含开始时间和结束时间的数组,要求日期从早到晚有连贯性,不能出现重叠,用JAVA判断
    packagecom.cfb.oa.m;importjava.time.LocalDate;importjava.util.ArrayList;importjava.util.List;classDateRange{LocalDatestart;LocalDateend;publicDateRange(LocalDatestart,LocalDateend){this.start=start;th......
  • DP 蓝题思想精选
    P1099[NOIP2007提高组]树网的核如果有多条直径,对于任意一个直径的分差点,其在单调队列里的贡献就是另半条直径。而对于其其他分支,如果贡献大于那半条直径,那么这个贡献就会成为新的直径,与题设不符。因此,讨论任意一条直径即可。P1136迎接仪式容易注意到,一个点可以与前面、后面......
  • PowerShell 脚本调整鼠标指针的速度,虽然这不会直接影响鼠标的 DPI(硬件设置)。这个方法
    在PowerShell中,直接通过系统设置控制鼠标DPI或鼠标速度并不是一个简单的操作,因为这些设置通常依赖于硬件和驱动程序。大部分操作系统(包括Windows)本身并不提供简单的接口来直接控制DPI设置。通常,这些设置通过鼠标驱动程序或专门的鼠标软件来管理(例如:Logitech、Razer、Corsai......