浅析动态规划(Dynamic Programming,DP)

动态规划可以理解为递归,只不过递归是通过函数实现,动态规划通过循环实现!

一、前言

动态规划有多好用我就不过多介绍,写这篇文章的时候我也不是熟练掌握,只是单纯记录一下我的学习经历并分享一些我的心得体会,仅此而已。

推荐看一下这个视频,对你的理解应该会有所帮助。

二、基本思想

动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案(所谓的DP数组)。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式

三、解题步骤

这或许会包含各种各样的例题,我想从中进行对比以达成熟练掌握的目的,持续更新。。。

大致步骤如下:

  • 穷举分析
  • 确定边界
  • 找规律,确定最优子结构
  • 状态转移方程

例题一(最长递增子序列)

1、穷举分析

自顶向上的穷举

这里观察规律,显然是有关系的,我们还是遵循动态规划自底向上的原则,基于示例1的数据,从数组只有一个元素开始分析。

  • 当nums只有一个元素10时,最长递增子序列是[10],长度是1.
  • 当nums需要加入一个元素9时,最长递增子序列是[10]或者[9],长度是1。
  • 当nums再加入一个元素2时,最长递增子序列是[10]或者[9]或者[2],长度是1。
  • 当nums再加入一个元素5时,最长递增子序列是[2,5],长度是2。
  • 当nums再加入一个元素3时,最长递增子序列是[2,5]或者[2,3],长度是2。
  • 当nums再加入一个元素7时,,最长递增子序列是[2,5,7]或者[2,3,7],长度是3。
  • 当nums再加入一个元素101时,最长递增子序列是[2,5,7,101]或者[2,3,7,101],长度是4。
  • 当nums再加入一个元素18时,最长递增子序列是[2,5,7,101]或者[2,3,7,101]或者[2,5,7,18]或者[2,3,7,18],长度是4。
  • 当nums再加入一个元素7时,最长递增子序列是[2,5,7,101]或者[2,3,7,101]或者[2,5,7,18]或者[2,3,7,18],长度是4。

通过上面分析,我们可以发现一个规律

如果新加入一个元素nums[i], 最长递增子序列要么是以nums[i]结尾的递增子序列,要么就是nums[i-1]的最长递增子序列

 原问题数组nums[i]的最长递增子序列 = 子问题数组nums[i-1]的最长递增子序列/nums[i]结尾的最长递增子序列

所以原问题,我们转化成求出以数组nums每个元素结尾的最长子序列集合,再取最大值。

2、确定边界

当nums数组只有一个元素时,最长递增子序列的长度dp(1)=1,当nums数组有两个元素时,dp[2] =2或者1, 因此边界就是dp[1]=1。

3、最优子结构

从穷举分析,我们可以得出,以下的最优结构:

dp[i] = max(dp[j]) + 1,存在j属于区间[0,i-1],并且num[i] > num[j]。

max(dp[j]) 就是最优子结构。

4、状态转移方程

dp[i] = Math.max(dp[i], dp[j] + 1);

 5、代码实现

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n=nums.length;
        if(n==1) return 1;
        if(n==0) return 0;
        int[] dp=new int[n];
        int res=0;
        Arrays.fill(dp,1);
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j])
                {
                dp[i]=Math.max(dp[i],dp[j]+1);
                }
                res=Math.max(res,dp[i]);
            }
        }
        return res;
    }
}

6、复杂度

时间复杂度O(n^2),空间复杂度O(n)

例题二(过河卒)

1、分析

 卒只能向下和向右走,所以当前点等于上面的点加左边的点的可能数

2、确定边界

显然,dp[0][0] = 1

3、状态转移方程

if (d[i][j] != -1) {
	if (i) dp[i][j] += dp[i - 1][j];
	if (j) dp[i][j] += dp[i][j - 1];
}

4、AC代码

#include<bits/stdc++.h>

#define MAX 30

using namespace std;

int d[MAX][MAX] = { 0 };
long long dp[MAX][MAX] = { 0 };//开long long防止数值过大

//用于马的控制点赋值
const int dx[] = { 2,1,-1,-2,-2,-1,1,2 };
const int dy[] = { 1,2,2,1,-1,-2,-2,-1 };

int main() {
	int bx, by, mx, my;
	cin >> bx >> by >> mx >> my;
	/*for (int i = 1; i < bx; i++) {
		dp[i][0] = 1;
	}
	for (int i = 1; i < by; i++) {
		dp[0][i] = 1;
	}*/
	d[mx][my] = -1;
	for (int i = 0; i < 8; i++) {//马控制点赋值
		if (mx + dx[i] >= 0 && mx + dx[i] <= bx && my + dy[i] >= 0 && my + dy[i] <= by) {
			d[mx + dx[i]][my + dy[i]] = -1;
		}
	}
	dp[0][0] = 1;
	for (int i = 0; i <= bx; i++) {//卒只能向下和向右走,所以当前点等于上面的点加左边的点的可能数
		for (int j = 0; j <= by; j++) {
			if (d[i][j] != -1) {
				if (i) dp[i][j] += dp[i - 1][j];
				if (j) dp[i][j] += dp[i][j - 1];
			}
		}
	}
	//打印地图和dp
	/*for (int i = 0; i <= by; i++) {
		for (int j = 0; j <= bx; j++) {
			cout << d[i][j]<< " ";
			if (j == bx) {
				cout << endl;
			}
		}
	}
	cout << endl;
	for (int i = 0; i <= by; i++) {
		for (int j = 0; j <= bx; j++) {
			cout << dp[i][j] << " ";
			if (j == bx) {
				cout << endl;
			}
		}
	}*/
	cout << dp[bx][by] << endl;
	return 0;
}

例题三(摆花)

1、分析

定义状态:f(i,j) 表示前 i 个数总和为 j 的方案数。

2、状态转移方程

f(i,j)=f(i−1,j−k) (k从0到a[i])

其中, k是枚举当前第 i 个数的取值。

3、复杂度

时间复杂度:O(nmai​),稳得一批。

空间复杂度:O(nm)

4、代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105, mod = 1000007;
int n, m, a[maxn], f[maxn][maxn];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    f[0][0] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= m; j++)
            for (int k = 0; k <= min(j, a[i]); k++)
                f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
    cout << f[n][m] << endl;
    return 0;
}

例题四(李白打酒加强版)

1、分析

设 f[i][j][k] 为当前走到第 i 个位置,看了 j 次花,有 k 斗酒的方案数。

题目中说了初始两斗酒,那么 f[0][0][2]=1。

重点是怎么前推后,依次循环 i,j,k,

如果 f[i][j][k] 不是 0,那么我们在第 i+1 个位置看花或者经过酒店。

看花:走到第 i+1 个位置,看了 j+1 次花,酒的数量变为 k−1。

酒馆:走到第 i+1 个位置,看了 j 次花,酒的数量变为 k×2。

2、状态转移方程

if (f[i][j][k] != 0)//这里也可以简写成 if(f[i][j][k])
{
	f[i + 1][j + 1][k - 1] += f[i][j][k];
	f[i + 1][j][k * 2] += f[i][j][k];
}

j 只需要循环到 m−1,不需要循环到 m,如果循环到了 m:

首先,再看花变成 m+1 已经对答案没有任何的贡献了。

其次,这样还会多算。如果不看花只能是没有酒,一直经过酒店,

但是题目要求最后一次只能经过花,所以不行。

还有一个问题:酒的斗数上限是什么?

显然不会超过 m,不然最后的酒就喝不完了,所以也循环到 m。

3、代码 

#include <iostream>
using namespace std;
int n, m;
int f[205][105][105];
int main() {
	cin >> n >> m;
	f[0][0][2] = 1;
	for (int i = 0; i < n + m; i ++)
		for (int j = 0; j < m; j ++)
			for (int k = 0; k <= m; k ++)
				if (f[i][j][k]) {
					if (k > 0) 
                        f[i + 1][j + 1][k - 1] 
                        = (f[i + 1][j + 1][k - 1] + f[i][j][k]) % 1000000007;
					if (k <= 50) 
                        f[i + 1][j][k * 2] 
                        = (f[i + 1][j][k * 2] + f[i][j][k]) % 1000000007;
				}
	cout << f[n + m][m][0];
	return 0;
}

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于多车辆低速无人驾驶动态规划,需要考虑到车辆的实际行驶场景需求,如跟车、定速巡航、停车、汇流等。在纵向轨迹规划中,不同的行驶情景有不同的期望配置。然而,目前关于低速无人驾驶车辆的适用范围和实验方法仍然缺乏国内外统一的标准。借鉴了ISO22737标准可作为参考。低速无人驾驶车辆相比传统机动车辆具有应用范围广、高效和维护费用低、机动灵活性强的特点。利用其AI优势建立全方位的信息化作业模式,不仅能够保障周围人身安全,还可以提高专用工作执行效力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [无人驾驶路径规划(三)局部路径规划-Frenet坐标系下的动态轨迹规划](https://blog.csdn.net/m0_55205668/article/details/124884393)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [陆晓科:低速无人驾驶车辆试验鉴定方法浅析](https://blog.csdn.net/weixin_55366265/article/details/121804967)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hiOoo.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值