寻找最小楼梯成本的动态规划问题的错误答案

Wrong answer to dynamic programming problem of finding minimum stair cost

本文关键字:动态规划 问题 错误 答案 小楼 寻找      更新时间:2023-10-16

我正在尝试解决Leetcode上的以下问题:

在楼梯上,第 i 步长分配了一些非负成本cost[i](索引为 0(。

支付费用后,您可以爬上一两级台阶。 您需要找到到达地板顶部的最低成本,您可以从索引为 0 的步骤或索引为 1 的步骤开始。

这是我到目前为止的解决方案。我相信我没有正确考虑我可以从楼梯 0 或楼梯 1 开始的事实,而且我不确定该怎么做。

class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
return helper(cost, cost.size() - 1);
}
int helper(vector<int>& cost, int currStair) {
static vector<double> minCost(cost.size(), 0);
minCost[0] = cost[0];
minCost[1] = cost[1];
if (currStair < 0 || cost.size() <= 1) {
return 0;
}
if (minCost[currStair] > 0) {
return minCost[currStair];
}
return minCost[currStair] = min(helper(cost, currStair - 1), helper(cost, currStair - 2)) + cost[currStair];
}
};

这是非常正确的想法,但我认为这是自上而下和自下而上方法的融合。

由于问题告诉我们可以从步骤 0 或 1 开始,我认为从前到后处理cost数组更直观——您仍然可以像现在一样使用自上而下的递归 DP 方法。这样可以更轻松地区分是从第 0 步还是第 1 步开始。代码返回的最终解决方案始终是minCost[minCost.size()-1],它不考虑这一点。

使用static向量会使函数非幂等,因此它将在第二次运行时保留过时的值。就 Leetcode 而言,这不会影响正确性,因为它似乎为每个测试用例创建了一个类的新实例。尽管如此,这似乎与上述普遍误解有关;初始化 0 和 1 索引并不像您想象的那样设置正确的基本情况(这就是您以自下而上的方法设置基本情况的方式(。

考虑到这一点,从第一个楼梯开始解决问题,然后走到最后一个楼梯。非静态初始化缓存向量,然后从索引 0 递归填充缓存。禁止的 2n分支因子将由缓存处理,将复杂性降低到线性,最终结果将是从阶梯 0 或 1 开始的成本的最小值。该问题将输入cost向量限制为2 <= cost.size()这一事实是一个很大的提示;我们知道minCost[0]minCost[1]总是可以在没有先决条件的情况下进行选择。

另一个小点是,使用 0 作为空缓存标志可能会在填充为零的巨大向量上超时。由于我们需要区分未设置的索引和 0,因此我们应该使用 -1 作为标志来指示未设置的缓存索引。

class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> minCost(cost.size(), -1);
helper(cost, 0, minCost);
return min(minCost[0], minCost[1]);
}
int helper(vector<int>& cost, int currStair, vector<int>& minCost) {
if (currStair >= cost.size()) return 0;
else if (minCost[currStair] >= 0) {
return minCost[currStair];
}
return minCost[currStair] = cost[currStair] +
min(helper(cost, currStair + 1, minCost), 
helper(cost, currStair + 2, minCost));
}
};