从lintcode做题的角度来说,递归往往会开销较大。不过在做动态规划的时候,我们能发现递归是个不错的工具,对于解决问题的代码量能够缩小很多。同时,如果使用了带有备忘的递归,能够减少很大的开销。这次列举几个典型的使用带备忘的递归的例子,来总结一下带备忘的递归的使用。这篇文章中的三个题目均是上篇博客 中出现过的,便于比较。 首先我们来看第一题。
1.钢条切割 题目描述:
一家公司有一根固定长度的钢条,要切割为短的钢条出售。切割工序本身没有成本。公司希望知道最佳的切割方案(即如何切割能够获得价格的最大值)。假设钢条的长度为n
(此处假设n <= 10
),不同长度i
的钢条的价格如下表。
长度j 1 2 3 4 5 6 7 8 9 10 价格p 1 5 8 9 10 17 17 20 24 30
假设长度为
i
时,最佳切割方案时的价格总和为$r[i]$$(0 <= i<= n)$,根据
上篇博客 所说,此题目的状态转移方程为:
$r[i+1] = max(p[j] + r[i+1-j]),1<= j<= i+1$ 这样,便很容易写出递归的代码:
1
2
3
4
5
6
7
8
9
10
11
12
int cutRod (vector <int > &p, int n)
{
if (n == 0 )
return 0 ;
int q = INT_MIN;
for (int i = 1 ; i <= n; i++)
q = max(q, p[i-1 ] + cutRod2(p, n-i));
return q;
}
大家都知道,使用递归时,往往会因为不断的求解之间已经解决过的问题而使用更多的空间,那么有没有好办法能解决这个问题呢?那么用备忘来记录中间求解过的值就能够解决。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int cutRod (vector <int > &p, int n)
{
vector <int > r(n+1 , INT_MIN);
r[0 ] = 0 ;
return cutRodAux(p, r, n);
}
int cutRodAux (vector <int > &p, vector <int > &r, int index)
{
if (r[index] != INT_MIN)
return r[index];
int q = INT_MIN;
for (int i = 1 ; i <= index; i++)
q = max(q, p[i-1 ] + cutRodAux(p, r, index-i));
r[index] = q;
return q;
}
接下来再看下一题。
2.最长公共子序列 题目描述:
给定两个序列X = {$x_1$, $x_2$, $x_3$, …, $x_m$ }, 和Y = {$y_1$, $y_2$, …, $y_n$},求X
和Y
长度最长的公共子序列。公共子序列的定义为,给定两个序列X
,Y
,如果Z
即是X
的子序列,也是Y
的子序列,我们称它是X
和Y
的公共子序列。
例如:X = {A, B, C, D, A, B},Y = {B, D, C, B, A},那么序列{B, C, A}就是X和Y的公共子序列,但不是最长公共子序列。X和Y的最长公共子序列是{B, C, A, B},其长度为4
。
根据上篇文章 ,状态转移方程为:
情况1: $i==0$ 或者 $j== 0$时,$c[i][j] = 0$; 情况2: $i, j > 0$且$x_i$ == $y_j$时, $c[i][j] = c[i-1][j-1] + 1$; 情况3: $i,j > 0$且$x_i != y_j$时, $c[i][j] = max(c[i][j-1], c[i-1][j])$;
那么直接用递归实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int LCS (string A, string B)
{
int len1 = A.length();
int len2 = B.length();
return LCS_AUX(A, B, len1, len2);
}
int LCS_AUX (string A, string B, int idx1, int idx2)
{
if (idx1 == 0 || idx2 == 0 )
return 0 ;
if (A[idx1-1 ] == B[idx2-1 ])
return LCS_AUX(A, B, idx1-1 , idx2-1 ) + 1 ;
else
return max(LCS_AUX(A, B, idx1-1 , idx2), LCS_AUX(A, B, idx1, idx2-1 ));
}
同样,加入备忘,记录过程中的求解的值,就可以节省很多开销。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int LCS (string A, string B)
{
int len1 = A.length();
int len2 = B.length();
vector <vector <int > > c(len1+1 , vector <int >(len2+1 , INT_MAX));
return LCS_AUX(A, B, c, len1, len2);
}
int LCS_AUX (string A, string B, vector <vector <int > > &c, int idx1, int idx2)
{
if (idx1 == 0 || idx2 == 0 )
return 0 ;
if (c[idx1][idx2] != INT_MAX)
return c[idx1][idx2];
if (A[idx1-1 ] == B[idx2-1 ])
c[idx1][idx2] = LCS_AUX(A, B, c, idx1-1 , idx2-1 ) + 1 ;
else
c[idx1][idx2] = max(LCS_AUX(A, B, c, idx1-1 , idx2), LCS_AUX(A, B, c, idx1, idx2-1 ));
return c[idx1][idx2];
}
题目描述:
Given an integer array, adjust each integers so that the difference of every adjacent integers are not greater than a given number target.If the array before adjustment is A
, the array after adjustment is B
, you should minimize the sum of |A[i]-B[i]|
Notice: You can assume each number in the array is a positive integer and not greater than 100
.
Example: Given [1,4,2,3]
and target = 1
, one of the solutions is [2,3,2,3]
, the adjustment cost is 2
and it’s minimal.
第三题有点难度,是深度优先搜索的思想,解释的话有些太费时间,不过如果手动着算一下,结合我代码的注释,也能比较容易就看懂,所以直接上代码,具体的解析见上篇博客 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int MinAdjustmentCost (vector <int > A, int target)
{
if (A.size() == 0 )
return 0 ;
int minVal = INT_MAX;
int maxVal = INT_MIN;
for (int i = 0 ; i < A.size(); i++)
{
minVal = min(minVal, A[i]);
maxVal = max(maxVal, A[i]);
}
vector <vector <int > > M(A.size(), vector <int >(maxVal - minVal + 1 , INT_MAX));
vector <int > B(A);
return MinAdjustmentCostAux(A, B, target, 0 , M, minVal, maxVal);
}
int MinAdjustmentCostAux (vector <int > A, vector <int > B, int target, int index, vector <vector <int > > &M, int minValue, int maxValue)
{
int n = A.size();
if (index >= n)
return 0 ;
int dif = 0 ;
int minCost = INT_MAX;
for (int i = minValue; i <= maxValue; i++)
{
if (index != 0 && abs (i - B[index-1 ]) > target)
continue ;
if (M[index][i-minValue] != INT_MAX)
{
dif = M[index][i-minValue];
minCost = min(minCost, dif);
continue ;
}
B[index] = i;
dif = abs (i - A[index]);
dif += MinAdjustmentCostAux(A, B, target, index + 1 , M, minValue, maxValue);
minCost = min(minCost, dif);
M[index][i-minValue] = dif;
B[index] = A[index];
}
return minCost;
}
比较一下,是不是使用带有备忘的递归会使得程序简单很多呢?