记忆具有多个变量的回溯问题
Memoizing a backtracking problem with multiple variables
我正在尝试解决 this problem,并且我能够使用回溯解决它。但是我想不出正确的方法来记住它,因为有多个变量(天数数组的索引不断变化,每天我们尝试不同的成本),因此需要一些帮助。这是我的代码,它似乎工作正常,但它显然正在进行重复计算
private int[] durations = {1, 7, 30};
public int mincostTickets(int[] days, int[] costs) {
if (days.length == 0) return 0;
return backtrack(days, costs, 0, 0, 0);
}
private int backtrack(int[] days, int[] costs, int index, int costSoFar, int window) {
if (index >= days.length ) {
return costSoFar;
}
int cost = Integer.MAX_VALUE;
for (int j = 0; j < costs.length; j++) {
int currCost = 0;
if (days[index] >= window ) {
currCost = backtrack(days, costs, index + 1, costSoFar + costs[j], days[index] + durations[j]);
} else {
currCost = backtrack(days, costs, index + 1, costSoFar, window);
}
cost = Math.min(cost, currCost);
}
return cost;
}
另外,如果你能帮助我理解这里的时间复杂度,那就太好了!
您可以通过以下方式记住您的解决方案:
class Solution {
public int mincostTickets(int[] days, int[] costs) {
int[] dp = new int[days.length];
Arrays.fill(dp, -1);
return rec(days, costs, 0, dp);
}
int rec(int[] days, int[] costs, int idx, int[] dp) {
if (idx >= days.length)
return 0;
if (dp[idx] != -1)
return dp[idx];
// 1 day pass
int min = Integer.MAX_VALUE;
min = Math.min(min, costs[0] + rec(days, costs, idx + 1, dp));
// 7 day pass
int tarDay = days[idx] + costs[1];
int i = idx;
while (i < days.length && days[i] < tarDay) i++;
min = Math.min(min, costs[1] + rec(days, costs, i, dp));
// 30 day pass
tarDay = days[idx] + costs[2];
while (i < days.length && days[i] < tarDay) i++;
min = Math.min(min, costs[2] + rec(days, costs, i, dp));
return dp[idx] = min;
}
}
递归后代码的复杂性将是 O(n)
,因为有多个保存的状态和预计算。否则,由于您将访问所有州,因此它将是 n^2
.
您可以 return 给定索引的最低费用。这可以很容易地缓存。这样你也可以摆脱 window 和到目前为止的成本。否则它仍然是你的解决方案:
private int[] durations = {1, 7, 30};
public int mincostTickets(int[] days, int[] costs) {
if (days.length == 0) return 0;
for (int i = 0; i < costs.length; i++) if (costs[i] == 0) return 0;
return backtrack(days, costs, 0, new int[days.length]);
}
private int backtrack(int[] days, int[] costs, int index, int[] memory) {
if (memory[index] > 0) return memory[index];
int cost = Integer.MAX_VALUE;
for (int j = 0; j < costs.length; j++) {
int currCost = costs[j];
int next = index + 1;
while (next < days.length && days[next] - days[index] < durations[j])
next++;
if (next < days.length)
currCost += backtrack(days, costs, next, memory);
cost = Math.min(cost, currCost);
}
memory[index] = cost;
return cost;
}
我没有看到任何其他好的解决方案。因为到目前为止,同一索引的成本会发生变化,因此完全使用您的方式是行不通的。我也不认为这是 dp 解决方案,因为那将建立在我们已有的基础上。所以我们必须从最后一天开始,然后倒退。嗯,最后差不多了。
这将是动态规划解决方案。它不需要递归。这将是我的首选解决方案,在我看来,Poby 在您的 link 中提出的解决方案非常糟糕(不是性能,而是优雅、简单和编程风格,甚至没有在循环中执行 1、7、30 天就像你一样):
private static final int[] durations = {1, 7, 30};
public int mincostTickets(int[] days, int[] costs) {
if (days.length == 0) return 0;
int[] dp = new int[days.length];
for (int i = days.length - 1; i >= 0; i--) {
int best = Integer.MAX_VALUE;
for (int j = 0; j < costs.length; j++) {
int cost = costs[j];
int next = i + 1;
while (next < days.length && days[next] - days[i] < durations[j])
next++;
if (next < days.length)
cost += dp[next];
best = Math.min(cost, best);
}
dp[i] = best;
}
return dp[0];
}
两种解决方案的时间复杂度均为 O(n)。如果我们说成本和持续时间是动态的,那么 m 是您可以购买的不同门票的数量,那么您可以使用二分法找到第二天。这将为广义解决方案提供 O(nmlogn)。
我正在尝试解决 this problem,并且我能够使用回溯解决它。但是我想不出正确的方法来记住它,因为有多个变量(天数数组的索引不断变化,每天我们尝试不同的成本),因此需要一些帮助。这是我的代码,它似乎工作正常,但它显然正在进行重复计算
private int[] durations = {1, 7, 30};
public int mincostTickets(int[] days, int[] costs) {
if (days.length == 0) return 0;
return backtrack(days, costs, 0, 0, 0);
}
private int backtrack(int[] days, int[] costs, int index, int costSoFar, int window) {
if (index >= days.length ) {
return costSoFar;
}
int cost = Integer.MAX_VALUE;
for (int j = 0; j < costs.length; j++) {
int currCost = 0;
if (days[index] >= window ) {
currCost = backtrack(days, costs, index + 1, costSoFar + costs[j], days[index] + durations[j]);
} else {
currCost = backtrack(days, costs, index + 1, costSoFar, window);
}
cost = Math.min(cost, currCost);
}
return cost;
}
另外,如果你能帮助我理解这里的时间复杂度,那就太好了!
您可以通过以下方式记住您的解决方案:
class Solution {
public int mincostTickets(int[] days, int[] costs) {
int[] dp = new int[days.length];
Arrays.fill(dp, -1);
return rec(days, costs, 0, dp);
}
int rec(int[] days, int[] costs, int idx, int[] dp) {
if (idx >= days.length)
return 0;
if (dp[idx] != -1)
return dp[idx];
// 1 day pass
int min = Integer.MAX_VALUE;
min = Math.min(min, costs[0] + rec(days, costs, idx + 1, dp));
// 7 day pass
int tarDay = days[idx] + costs[1];
int i = idx;
while (i < days.length && days[i] < tarDay) i++;
min = Math.min(min, costs[1] + rec(days, costs, i, dp));
// 30 day pass
tarDay = days[idx] + costs[2];
while (i < days.length && days[i] < tarDay) i++;
min = Math.min(min, costs[2] + rec(days, costs, i, dp));
return dp[idx] = min;
}
}
递归后代码的复杂性将是 O(n)
,因为有多个保存的状态和预计算。否则,由于您将访问所有州,因此它将是 n^2
.
您可以 return 给定索引的最低费用。这可以很容易地缓存。这样你也可以摆脱 window 和到目前为止的成本。否则它仍然是你的解决方案:
private int[] durations = {1, 7, 30};
public int mincostTickets(int[] days, int[] costs) {
if (days.length == 0) return 0;
for (int i = 0; i < costs.length; i++) if (costs[i] == 0) return 0;
return backtrack(days, costs, 0, new int[days.length]);
}
private int backtrack(int[] days, int[] costs, int index, int[] memory) {
if (memory[index] > 0) return memory[index];
int cost = Integer.MAX_VALUE;
for (int j = 0; j < costs.length; j++) {
int currCost = costs[j];
int next = index + 1;
while (next < days.length && days[next] - days[index] < durations[j])
next++;
if (next < days.length)
currCost += backtrack(days, costs, next, memory);
cost = Math.min(cost, currCost);
}
memory[index] = cost;
return cost;
}
我没有看到任何其他好的解决方案。因为到目前为止,同一索引的成本会发生变化,因此完全使用您的方式是行不通的。我也不认为这是 dp 解决方案,因为那将建立在我们已有的基础上。所以我们必须从最后一天开始,然后倒退。嗯,最后差不多了。
这将是动态规划解决方案。它不需要递归。这将是我的首选解决方案,在我看来,Poby 在您的 link 中提出的解决方案非常糟糕(不是性能,而是优雅、简单和编程风格,甚至没有在循环中执行 1、7、30 天就像你一样):
private static final int[] durations = {1, 7, 30};
public int mincostTickets(int[] days, int[] costs) {
if (days.length == 0) return 0;
int[] dp = new int[days.length];
for (int i = days.length - 1; i >= 0; i--) {
int best = Integer.MAX_VALUE;
for (int j = 0; j < costs.length; j++) {
int cost = costs[j];
int next = i + 1;
while (next < days.length && days[next] - days[i] < durations[j])
next++;
if (next < days.length)
cost += dp[next];
best = Math.min(cost, best);
}
dp[i] = best;
}
return dp[0];
}
两种解决方案的时间复杂度均为 O(n)。如果我们说成本和持续时间是动态的,那么 m 是您可以购买的不同门票的数量,那么您可以使用二分法找到第二天。这将为广义解决方案提供 O(nmlogn)。