暴力递归到动态规划(一):Robot to Target And Two Wise Pocker Men
建立并维护M个K维表格
Robot Goes to Target
题目一:
完全暴力递归
1 | int baoli(int cur, int rest, int target, int length) { |
傻缓存法
1 | {main}vector<vector<int>> dp(N + 1, vector<int>(R + 1, -1)); |
逆向直接dp表外挂法
1 | int zhengbiaoshengcheng(int cur, int rest, int target, int length) { |
先把第一列填好, 然后从左往右, 从上到下, 根据暴力方法的代码 所推导出格子之间的依赖, 把数值填好. 最后, 返回dp1[cur][rest];
入参判断&&分析bug来源
注意: 如果所有其他的数据都合理, 当N == 1, 且 R != 0, 答案应该为 0 .
如果不这么设置, 会出现”vector subscript out of range”, 即 数组下标 超出范围. 因为, 在”dp外挂” 这一种解法中, 会用到dp1[1][i] = dp1[2][i-1]. 如果 1 ==length , 那么 显然会产生越界.
我是怎么发现的呢? 用对数器反复实验, 总有那么几次(大概100次有一次
)会导致 报上面提到的错, 于是, 我以30为单位, 重复执行程序, 打印出每一次得到的四个随机整数, 停下来的那一次就是出错的数组. 于是, 我得到了三组数(从左到右依次为:初始位置,剩余步数,目标位置,活动范围):1 8 1 1
1 1 1 1
1 2 1 1
不难发现
, 第一个和最后一个数都是1的时候, 程序出错(当然这种观察方式过于原始且有赌博性质).进一步分析
, 由于初始位置一定小于活动范围, 即初始位置由活动范围所控制/界定, 所以, 问题就出在了活动范围!
怎么办? 向我之前那样, 全部都检查一下, 就好啦!
其实还有一种发现的方法:由于我一开始N的取值范围是1~100,所以N取到1的期望就是1/100.😄
用对数器看着Test Over. 跑完, 真爽!🤭
1 |
|
ME
我感到非常疑虑, 因为我真的感觉行和列的区别很难分清, 也很难界定. 因为, 好像任何二维数组的横纵坐标可以互换, 只要代码做出相应调整, 还是能实现同样的功能. 我想知道, 如果有两个变量, 我应该如何确定它们代表行(第一个参数)还是列(第二个参数)呢?
GPT
1 | 理解行和列,特别是在编程的上下文中,确实可能会引起困惑。但记住,行和列更多是一个习惯和传统的约定,而不是一个固定的规则。以下是一些建议,帮助你区分和使用它们: |
ME
像这个例子, 我真的没有一套熟练的思路和逻辑 , 应该是让横纵坐标代表rest 还是代表机器人的移动范围N,
两种分配方式各有什么优劣?
GPT
1 | 对于动态规划问题,如何选择行和列来代表你的状态是一个常见的问题。正确地组织你的dp数组可以使问题更加直观和易于理解,但可能的组织方式不止一种,并且每种方式都有其优势和劣势。 |
所以, 到这一步,我就解决了内心的一个长期疑惑, 二维数组的横纵坐标应该如何定义的问题. 于是, 我趁还有热度, 开始把zhengbiaoshengcheng(也就是
最后一种dp外挂解法
)重新用横纵坐标交换的方式写了一遍, 一气呵成:
1 |
|
结束!
二维数组的传递
数组指针_确定列数
1 |
|
数组指针_列数不确定
1 | void display(int** arr, int rows, int cols) { |
使用模板
1 |
|
vector容器
1 |
|
GPT_三种对比
使用指针到指针(例如 int** arr)的方法可以让你在创建函数时不知道数组的大小。但是,这种方法通常需要你手动管理内存,增加了出错的机会。
使用数组的指针(例如 int arr[][cols])的方法要求你知道至少一维的大小(通常是列数)。这意味着你不能完全在不知道任何维度的大小的情况下使用这种方法。
std::vector:
使用 std::vector<std::vector
模板:
使用模板(例如 int (&arr)[rows][cols])允许你在编译时获取数组的尺寸,但你仍然需要知道至少一维的大小(通常是列数)。
Two Wise Poker Men
题目:
完全暴力递归
f函数: 我是先手.我要考虑,拿左边或者右边以后, 我作为后手能拿到的分数, 然后选择较大的那个(所以取max).
g函数: 我是后手.我要在对方拿完以后,尝试拿到剩下的牌中的最多的分数,但是,对方只会让我被迫选择较小的那个(所以取min).
1 | // ways1 : 递归解法 |
傻缓存法
这里,dp表格必须要两个, 为什么? 子函数有 f 和 g , 所以每个子函数自己配备一个dp表.
1 |
|
严格表递推法
1 | // ways 3 严格表递推 |
1 |
|
整个过程中,
有时
出现过很多次三种ways差那么1~2分(上面完整代码里注释的部分).仔细查看原函数才发现我ways1调用f和g时, L 传的时0, 但是R 传的是 length. 所以其实读取范围超出了数组本身范围. 但是c++的尿性又不会像java一样RangeException之类的报错, 所以就出现能跑但是结果不对的情况.
不过呢, 由于可能length位置的数字是0, 导致有些情况结果是正确的.