【问题标题】:Memoizing a backtracking problem with multiple variables记忆具有多个变量的回溯问题
【发布时间】:2021-10-05 00:49:32
【问题描述】:

我正在尝试解决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;
}

另外,如果你能帮助我理解这里的时间复杂度,那就太好了!

【问题讨论】:

    标签: java algorithm memoization


    【解决方案1】:

    您可以返回给定索引的最便宜的成本。这可以很容易地缓存。像这样你也可以摆脱窗口和迄今为止的成本。否则它仍然是您的解决方案:

    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 在您的链接中提出的解决方案非常糟糕(不是性能,而是优雅、简单和编程风格,甚至没有像您那样循环执行 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)。

    【讨论】:

      【解决方案2】:

      你可以用这样的方式记住你的解决方案:

      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

      【讨论】:

      • 我知道这个 memoized dp 解决方案。但是,我正在尝试记住我提出的解决方案。感谢您的帮助!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-26
      • 2023-02-04
      • 2020-06-22
      • 2019-06-13
      • 1970-01-01
      相关资源
      最近更新 更多