今天遇到了一个问题,因为好久没刷题了,导致不那么熟悉。于是写下来记录一下解决过程。文末附可执行程序。
问题:
一个玩家从某一村庄出发,开始挖金币。已知,玩家只能向右或者向下走,不能向左走或者向右走。此外,还有一个数组,存储每个金币的深度。村庄的位置在数组中的第一个金币的上方。求玩家能获得的最大金币数。
例:存储金币深度的数组 depth = [ 2 , 3 , 1 , 4 , 4], 则玩家最多能获得4枚金币。如下图。
递归解法
首先我尝试了暴力解法,即每次要么选择当前位置的金币,更改深度,要么维持当前深度,继续前进。针对时间复杂度高的问题,采用了记忆化搜索,使用dp数组记录状态,避免重复访问。但是数据量大时,开辟的数组可能会造成溢出。
非递归解法
然后,我将暴力解法变成了循环实现。并且通过两个一维数组prev 和curv 交替记录状态,避免了使用二维数组。将n*m的空间复杂度 变成了 2*m,其中n是金币个数,m是金币所埋藏的最大深度 。注意下面这段程序中,第二个for循环的初始条件那里笔误,应该是int dep = max_d。v数组是存储金币深度的数组。
后来发现,通过选择从深度由大到小的赋值顺序,可以只用一个数组解决。至此,该方法的空间复杂度进一步降低到 o(m )。时间复杂度是o (n*m)。
完整的可执行程序如下
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> depth; //存储金币数组
int n; //输入金币数
cin >> n;
for (int i = 0; i < n; ++i) { //依次输入每个金币的深度
int tmp;
cin >> tmp;
depth.push_back(tmp);
}
//查找最大的深度
int max_depth = 0;
for (int i = 0; i < depth.size(); ++i) {
if (depth[i] > max_depth) max_depth = depth[i];
}
//边界条件是0
vector<int> curv(max_depth + 5, 0);
for (int p = n - 1; p >= 0; --p) {
for (int dep = max_depth; dep >= 0; --dep) {
if (depth[p] == dep) {
curv[dep] = 1 + curv[depth[p]];
}
else if (depth[p] > dep) {
curv[dep] = curv[depth[p]] > curv[dep] ? curv[depth[p]] : curv[dep];
}
else {
continue;
}
}
}
cout << curv[0] << endl;;
}
上述解法对于本题来讲实际上是冗余的,即题目只要求从村庄出发能拿到的金币数。但本方法给出了从任意位置出发的能拿到的最大金币数。优化的思路可以从这里开始。
进一步的改善方法我考虑了通过一些数据结构维持p+1位置的金币数,这个等待以后有时间再说吧。
不过我的思路大概是,通过只拿需要的数据来简化那个长度为m的数组。如下图。金币数组是 {2 3 1 4 4}, 绿色数字标出的是从该位置出发能获得的最大金币数,则根据图片所示,可以确定从村庄出发能获得的最大金币数就是 (1,1)坐标处的值,即 4 个。
我的打算是用滑动窗口来解决,不过暂时发现,这个方法比我想象的要复杂,至于能否找到线性时间复杂度的方法,等以后闲下来再说吧,先搞毕业论文了。