动态问题问题(硬币更改问题)中的子问题重叠

时间:2019-07-27 12:00:37

标签: algorithm recursion logic dynamic-programming backtracking

我正在尝试很好地理解动态编程问题,但是我无法理解这些问题的特定方面。

我将以leetcode https://leetcode.com/problems/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;
}

1 个答案:

答案 0 :(得分:0)

在找到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;
}