📜  如何解决动态规划问题?

📅  最后修改于: 2021-05-04 08:42:01             🧑  作者: Mango

d ynamic P在AGC(DP)是一种技术,解决了一些特定类型的在多项式时间的问题。动态编程解决方案比指数暴力方法更快,并且可以很容易地证明其正确性。在研究如何对问题进行动态思考之前,我们需要学习:

  1. 重叠子问题
  2. 最佳子结构属性
Steps to solve a DP
1) Identify if it is a DP problem
2) Decide a state expression with 
   least parameters
3) Formulate state relationship    
4) Do tabulation (or add memoization)

步骤1:如何将问题归类为动态编程问题?

  • 通常,可以通过使用动态编程来解决所有需要最大化或最小化一定数量的问题,或者说需要在一定条件下对布置进行计数的计数问题或某些概率问题。
  • 所有动态规划问题都满足重叠子问题属性,大多数经典动态问题也满足最佳子结构属性。一次,我们在给定问题中观察了这些属性,请确保可以使用DP解决该问题。

步骤2:确定状态
DP问题都与状态及其过渡有关。这是最基本的步骤,必须非常小心地完成,因为状态转换取决于您对状态定义的选择。因此,让我们看看“状态”一词的含义。

状态状态可以定义为一组参数,可以唯一地标识给定问题中的某个位置或立场。这组参数应尽可能小,以减少状态空间。

例如:在我们著名的背包问题中,我们通过两个参数indexweight定义状态,即DP [index] [weight]。在这里,DP [index] [weight]告诉我们,将范围从0到具有麻袋容量的索引作为权重,可以获取的最大利润。因此,这里的参数indexweight可以唯一地标识背包问题的一个子问题。

因此,我们的第一步将是在确定问题是DP问题之后确定问题的状态。
众所周知,DP就是要使用计算结果来制定最终结果。
因此,我们的下一步将是找到先前状态之间的关系以达到当前状态。

步骤3:在州之间建立关系
这部分是解决DP问题中最难的部分,需要大量的直觉,观察和实践。让我们通过考虑一个样本问题来理解它

Given 3 numbers {1, 3, 5}, we need to tell
the total number of ways we can form a number 'N' 
using the sum of the given three numbers.
(allowing repetitions and different arrangements).

Total number of ways to form 6 is: 8
1+1+1+1+1+1
1+1+1+3
1+1+3+1
1+3+1+1
3+1+1+1
3+3
1+5
5+1

让我们动态地考虑这个问题。因此,首先,我们确定给定问题的状态。我们将使用参数n来决定状态,因为它可以唯一地标识任何子问题。因此,我们的状态dp看起来像state(n)。此处,状态(n)表示通过使用{1、3、5}作为元素形成n的排列总数。
现在,我们需要计算state(n)。

怎么做?
因此,直觉在这里付诸实践。由于我们只能使用1、3或5来形成一个给定的数字。让我们假设我们知道n = 1,2,3,4,5,6的结果;作为白话语,我们可以说我们知道
状态(n = 1),状态(n = 2),状态(n = 3)…………状态(n = 6)
现在,我们希望知道状态的结果(n = 7)。瞧,我们只能加1、3和5。现在我们可以通过以下3种方式得到7的总和:

1)将1加到所有可能的状态组合(n = 6)
例如:[(1 + 1 + 1 + 1 + 1 + 1)+1]
[(1 + 1 + 1 + 3)+1]
[(1 + 1 + 3 + 1)+1]
[(1 + 3 + 1 + 1)+1]
[(3 + 1 + 1 + 1)+1]
[(3 + 3)+1]
[(1 + 5)+1]
[(5 + 1)+1]

2)将3加到所有可能的状态组合中(n = 4);
例如:[(1 + 1 + 1 + 1)+ 3]
[(1 + 3)+ 3]
[(3 + 1)+ 3]

3)将5加到所有可能的状态组合(n = 2)
例如:[(1 + 1)+ 5]

现在,请仔细考虑并让自己感到满意,以上三种情况涵盖了所有可能的方式,从而形成总计7种情况;
因此,我们可以说结果
状态(7)=状态(6)+状态(4)+状态(2)
或者
状态(7)=状态(7-1)+状态(7-3)+状态(7-5)
一般来说,
状态(n)=状态(n-1)+状态(n-3)+状态(n-5)
因此,我们的代码如下所示:

Python
// Returns the number of arrangements to
// form 'n'
int solve(int n)
{
   // base case
   if (n < 0)
      return 0;
   if (n == 0) 
      return 1; 
 
   return solve(n-1) + solve(n-3) + solve(n-5);
}


Java
// Returns the number of arrangements to
// form 'n'
static int solve(int n)
{
   // base case
   if (n < 0)
      return 0;
   if (n == 0) 
      return 1; 
 
   return solve(n-1) + solve(n-3) + solve(n-5);
}   
 
// This code is contributed by Dharanendra L V.


Python3
# Returns the number of arrangements to
# form 'n'
def solve(n):
   
  # Base case
  if n < 0:
    return 0
  if n == 0:
    return 1
   
  return (solve(n - 1) +
          solve(n - 3) +
          solve(n - 5))
 
# This code is contributed by GauriShankarBadola


C#
// Returns the number of arrangements to
// form 'n'
static int solve(int n)
{
   // base case
   if (n < 0)
      return 0;
   if (n == 0) 
      return 1; 
 
   return solve(n-1) + solve(n-3) + solve(n-5);
}   
 
// This code is contributed by Dharanendra L V.


C++
// initialize to -1
int dp[MAXN];
 
// this function returns the number of
// arrangements to form 'n'
int solve(int n)
{
  // base case
  if (n < 0) 
      return 0;
  if (n == 0)
      return 1;
 
  // checking if already calculated
  if (dp[n]!=-1)
      return dp[n];
 
  // storing the result and returning
  return dp[n] = solve(n-1) + solve(n-3) + solve(n-5);
}


Java
// initialize to -1
public static int[] dp = new int[MAXN];
 
// this function returns the number of
// arrangements to form 'n'
static int solve(int n)
{
  // base case
  if (n < 0) 
      return 0;
  if (n == 0)
      return 1;
 
  // checking if already calculated
  if (dp[n]!=-1)
      return dp[n];
 
  // storing the result and returning
  return dp[n] = solve(n-1) + solve(n-3) + solve(n-5);
}
 
// This code is contributed by Dharanendra L V.


Python3
# This function returns the number of
# arrangements to form 'n'
 
# lookup dictionary/hashmap is initialized
def solve(n, lookup = {}):
     
    # Base cases
    # negative number can't be
    # produced, return 0
    if n < 0:
        return 0
 
    # 0 can be produced by not
    # taking any number whereas
    # 1 can be produced by just taking 1
    if n == 0:
        return 1
 
    # Checking if number of way for
    # producing n is already calculated
    # or not if calculated, return that,
    # otherwise calulcate and then return
    if n not in lookup:
        lookup[n] = (solve(n - 1) +
                     solve(n - 3) +
                     solve(n - 5))
                      
    return lookup[n]
 
# This code is contributed by GauriShankarBadola


C#
// initialize to -1
public static int[] dp = new int[MAXN];
 
// this function returns the number of
// arrangements to form 'n'
static int solve(int n)
{
  // base case
  if (n < 0) 
      return 0;
  if (n == 0)
      return 1;
 
  // checking if already calculated
  if (dp[n]!=-1)
      return dp[n];
 
  // storing the result and returning
  return dp[n] = solve(n-1) + solve(n-3) + solve(n-5);
}
 
// This code is contributed by Dharanendra L V.


上面的代码似乎是指数形式的,因为它一次又一次地计算相同的状态。因此,我们只需要添加一个备注即可。

步骤4:为状态添加备注或列表
这是动态编程解决方案中最简单的部分。我们只需要存储状态答案,以便下次需要该状态时,我们可以直接从内存中使用它

在上面的代码中添加备忘录

C++

// initialize to -1
int dp[MAXN];
 
// this function returns the number of
// arrangements to form 'n'
int solve(int n)
{
  // base case
  if (n < 0) 
      return 0;
  if (n == 0)
      return 1;
 
  // checking if already calculated
  if (dp[n]!=-1)
      return dp[n];
 
  // storing the result and returning
  return dp[n] = solve(n-1) + solve(n-3) + solve(n-5);
}

Java

// initialize to -1
public static int[] dp = new int[MAXN];
 
// this function returns the number of
// arrangements to form 'n'
static int solve(int n)
{
  // base case
  if (n < 0) 
      return 0;
  if (n == 0)
      return 1;
 
  // checking if already calculated
  if (dp[n]!=-1)
      return dp[n];
 
  // storing the result and returning
  return dp[n] = solve(n-1) + solve(n-3) + solve(n-5);
}
 
// This code is contributed by Dharanendra L V.

Python3

# This function returns the number of
# arrangements to form 'n'
 
# lookup dictionary/hashmap is initialized
def solve(n, lookup = {}):
     
    # Base cases
    # negative number can't be
    # produced, return 0
    if n < 0:
        return 0
 
    # 0 can be produced by not
    # taking any number whereas
    # 1 can be produced by just taking 1
    if n == 0:
        return 1
 
    # Checking if number of way for
    # producing n is already calculated
    # or not if calculated, return that,
    # otherwise calulcate and then return
    if n not in lookup:
        lookup[n] = (solve(n - 1) +
                     solve(n - 3) +
                     solve(n - 5))
                      
    return lookup[n]
 
# This code is contributed by GauriShankarBadola

C#

// initialize to -1
public static int[] dp = new int[MAXN];
 
// this function returns the number of
// arrangements to form 'n'
static int solve(int n)
{
  // base case
  if (n < 0) 
      return 0;
  if (n == 0)
      return 1;
 
  // checking if already calculated
  if (dp[n]!=-1)
      return dp[n];
 
  // storing the result and returning
  return dp[n] = solve(n-1) + solve(n-3) + solve(n-5);
}
 
// This code is contributed by Dharanendra L V.

另一种方法是添加列表并使解决方案迭代。请参阅列表和备注以获取更多详细信息。
动态编程附带许多练习。必须尝试解决各种经典的DP问题,这些问题可以在这里找到。

您可以先检查以下问题,然后尝试使用上述步骤解决它们:

  • http://www.spoj.com/problems/COINS/
  • http://www.spoj.com/problems/ACODE/
  • https://www.geeksforgeeks.org/dynamic-programming-set-6-min-cost-path/
  • https://www.geeksforgeeks.org/dynamic-programming-subset-sum-problem/
  • https://www.geeksforgeeks.org/dynamic-programming-set-7-coin-change/
  • https://www.geeksforgeeks.org/dynamic-programming-set-5-edit-distance/