本次通过两个简单的算法题来进一步练习和理解动态规划,废话不多说,来上题吧。
题目一
给出一个数组,如[6,1,1,9,3]在里面取任意个数,要求所取得数的位置不能相邻,比如取了第一个数就不能取第二个数,但是可以取第三第四个数,求取出数之和的最大值。
下面来让我们分析一下这个题,我们可以分解成求每一位的最优解,从前往后查,如果要求第n个数的最优解,那么只能有两种情况:
第一种情况是 取第n个数和第n+2个数的最优解
第二种情况是 取第n+1个数的最优解
因此fib( n ) = max ( arr[n] + fib(n-2) , fib(n-1) ),下面我们用递归来解决一下这个问题:
递归
static void Main()
{
int[] arr = { 6, 1, 1, 9, 3 };
Console.WriteLine(max_sum(0, arr));
Console.Read();
}
static int max_sum(int index, int[] arr)
{
if (index == arr.Length - 1)
return arr[index];
if (index == arr.Length - 2)
return arr[index] > arr[index + 1] ? arr[index] : arr[index + 1];
int A = max_sum(index + 2, arr) + arr[index];
int B = max_sum(index + 1, arr);
return A > B ? A : B;
}
结果为15,即取得数为6和9.
但是用递归的方法依然会出现上一篇中重复计算的现象,时间复杂度是O(2^n)。
解决方法同样和上一篇中的一样,只需把计算过的结果记录下来,用到时直接调用即可。
自顶而下
static void Main()
{
int[] arr = { 6, 1, 1, 9, 3 };
Dictionary<int, int> index_max = new Dictionary<int, int>();
Console.WriteLine(max_sum(0, arr, index_max));
Console.Read();
}
static int max_sum(int index, int[] arr, Dictionary<int, int> index_max)
{
int A, B;
if (index == arr.Length - 1)
return arr[index];
if (index == arr.Length - 2)
return arr[index] > arr[index + 1] ? arr[index] : arr[index + 1];
if (index_max.ContainsKey(index + 2))
A = index_max[index + 2] + arr[index];
else
{
A = max_sum(index + 2, arr, index_max);
index_max.Add(index + 2, A);
A += arr[index];
}
if (index_max.ContainsKey(index + 1))
B = index_max[index + 1];
else
{
B = max_sum(index + 1, arr, index_max);
index_max.Add(index + 1, B);
}
return A > B ? A : B;
}
自底而上
static void Main()
{
int[] arr = { 6, 1, 1, 9, 3 };
int[] opt = new int[arr.Length];
opt[0] = arr[0];
if (arr[1] > opt[0])
opt[1] = arr[1];
else
opt[1] = opt[0];
for (int i = 2; i < arr.Length; i++)
opt[i] = arr[i] + opt[i - 2] > opt[i - 1] ? arr[i] + opt[i - 2] : opt[i - 1];
Console.WriteLine(opt[arr.Length - 1]);
Console.Read();
}
题目二
这个题我在leetcode上练习时遇到的,虽然不难,但是容易陷入思维误区。
数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)。每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
示例 1:
输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
示例 2:
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。
注意:
cost 的长度将会在 [2, 1000]。
每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。
这个题是不是和上一题很像?
上一个题是fib(n)=max(arr[n]+fib(n-2),fib(n-1))求的是最大值,这个题求得是最小值是不是应该是fib(n)=min(arr[n]+fib(n-2),fib(n-1))?
这和我一开始想的一样的,但实际上这样是不对的,如果按照这个思路来写只会输出最初的最小值,你可以自行写一下看看。
这个题实际上是fib(n)=min(fib(n-1),fib(n-2))+arr[n]
因为最后一个台阶是必要走的,可以这样理解,如果只有一个台阶,那这个台阶是必须要走的。
static void Main()
{
int[] cost = { 1, 100, 1, 1, 1, 100, 1, 1, 100, 1 };
int[] opt = new int[cost.Length+1];
opt[0] = 0;
opt[1] = cost[0];
for (int i = 1; i < cost.Length; i++)
opt[i + 1] = (opt[i] < opt[i - 1] ? opt[i] : opt[i - 1]) + cost[i];
Console.WriteLine(opt[cost.Length] < opt[cost.Length - 1] ? opt[cost.Length] : opt[cost.Length - 1]);
Console.Read();
}