【问题标题】:Overlapping subproblem in a dynamic problem question (Coin change problem)动态问题中的重叠子问题(硬币找零问题)
【发布时间】:2019-12-05 12:01:52
【问题描述】:

我正在努力培养对动态编程问题的良好直觉,但我无法理解问题的特定方面。

我将以 leetcode https://leetcode.com/problems/coin-change/ 上提供的 Coin Change 问题为例

在许多教程中,都提到了一种自下而上的方法,例如这个 - https://www.topcoder.com/community/competitive-programming/tutorials/dynamic-programming-from-novice-to-advanced/

在这种方法中,我们从最佳解决方案开始,然后针对我们的解决方案构建阵列。示例 - 我们找到求和 2 然后 3 的最优解,依此类推。最后,我们将有我们的解决方案。这是我理解的方法。

我无法理解另一种带有记忆的递归方法。我已经为该问题编写了回溯方法,但不确定如何对其应用记忆。

public int changeCoins_BT(int[] coins, int target, int min, int num_curr) {

    if(target == 0) {
        return min < num_curr ? min : num_curr;
    } else if(target < 0) return min;
    else if(num_curr > min) return min;

    for(int i=0;i<coins.length;i++) {
        min = changeCoins_BT(coins,target-coins[i],min,num_curr+1);
    }

    return min;
}

【问题讨论】:

  • 它通常非常简单:记住函数调用以及可以更改的参数。在这种情况下,您需要 mem[target][min][num_curr]。所以在 return min 之前,你存储了值,并且在函数的开头,如果你已经存储了值,你只需返回它
  • 感谢您的回复。我们用可以改变的参数来记忆是经验法则吗?
  • 如果你有足够的内存,它总是最好的,以防止重新计算是的

标签: algorithm recursion logic dynamic-programming backtracking


【解决方案1】:

在为 DP 找到递归解决方案之前,请尝试确定相关问题的子问题。因为,每个子问题都与父问题相同,并且将应用相同的算法。

让我们以硬币兑换为例,其中给定面额列表 d[] 和总和 S,我们需要找到最小面额数,计数(面额)以求和 S。如果我们想定义一个解决方案(方法)来查找计数 int findMinDenom(int[] d, int S)。在这一点上,我们不知道它会是什么实现,但我们知道问题需要哪些参数,即 d 和 S。

请记住,子问题也有相同的解决方案,但总和较低。因此,我们尝试以 findMinDenom 解决每个子问题的方式来实现。这将导致我们找到一个递归解决方案,在该解决方案中,我们调用具有较低总和 s 的相同方法。

int findMinDenom(int[] d, int S) {
  if (S == 0) {
    // If Sum is zero, then no denomination is required.
    return 0;
  }
  int result = Integer.MAX_VALUE; // Define Integer max
  for ( int i = 0; i < d.length; i++) {
    int s = S - d[i] // Reduced to a sub-problem
    // Handle case where s < 0
    if (s < 0) {
      continue;
    }
    int r = findMinDenom(d, s); // Solution for lower sum, s
    result = Math.min(result, r + 1); // Plus 1 because we have just used one denomination.
  }
  return result;
}

我们刚刚使用 DP 解决了问题。但是,没有备忘录。我们将介绍使用数组来保存结果。因为如果子问题已经解决,我们就不会这样做。如果是,则只返回该子问题的结果。对于总和 0,面额将为零。解决方案[0] = 0,我们希望找到问题 S 的解决方案,用编码术语来说,解决方案 [S]。因此解数组的大小为 S+1。

// Initialize solution
int[] solution = new int[S+1];
solution[0] = 0; 
for (int i = 1; i <= S; i++)
  solution[i] = -1; // Just to denote that solution is not found yet.

现在,传递我们递归方法的解决方案。

int findMinDenomMemo(int[] d, int S, int[] solution) {
  if (solution[S] > -1) {
    // Solution exists
    return solution[S];
  }
  int result = Integer.MAX_VALUE; // Define Integer max
  for ( int i = 0; i < d.length; i++) {
    int s = S - d[i] // Reduced to a sub-problem
    // Handle case where s < 0
    if (s < 0) {
      continue;
    }
    int r = findMinDenomMemo(d, s, solution); // Solution for lower sum, s
    result = Math.min(result, r + 1); // Plus 1 because we have just used one denomination.
  }
  // Just now solved for sub problem S. So store it.
  solution[S] = result;
  return result;
}

【讨论】:

    猜你喜欢
    • 2015-05-08
    • 2020-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-09
    • 1970-01-01
    • 2020-04-11
    相关资源
    最近更新 更多