d ynamic P在AGC(DP)是一种技术,解决了一些特定类型的在多项式时间的问题。动态规划解决方案比指数蛮力方法更快,并且可以轻松证明其正确性。在我们学习如何动态思考问题之前,我们需要学习:
- 重叠子问题
- 最优子结构属性
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 问题都是关于状态及其转换的。这是最基本的步骤,必须非常小心地完成,因为状态转换取决于您所做的状态定义选择。那么,让我们看看术语“状态”是什么意思。
状态状态可以定义为一组参数,可以唯一地标识给定问题中的某个位置或立场。这组参数应该尽可能小以减少状态空间。
例如:在我们著名的背包问题中,我们通过两个参数index和weight 来定义我们的状态,即 DP[index][weight]。这里 DP[index][weight] 告诉我们它可以通过从范围 0 到具有 sack 能力作为权重的指数的项目获得的最大利润。因此,这里参数index和weight一起可以唯一标识背包问题的子问题。
因此,我们的第一步将是在确定问题是 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)。这里,state(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)
因此,我们的代码将如下所示:
C++
// 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.
Javascript
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.
蟒蛇3
# 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/
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。