动态规划(Dynamic Programming)
理解动态规划的好文:https://www.sohu.com/a/153858619_466939
1、基础
**定义:**递归算法经常会有一些重复的计算,导致程序效率低。动态规划就是解决这个问题的方法。
动态规划的组成部分:
- 最优子结构:分解问题
- 边界:递归的终止条件
- 状态转移方程:递归的方程
动态规划的解题步骤:
- 分解问题,得到状态转移方程
- 暴力搜索,找到冗余,通常用纯递归
- 存储已经计算过的结果,通常用数组和字典
- 编程时通常采用自底向上思想(递推)
2、走台阶问题
**题目:**有一座10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法。
递归解法:
int solve(int n)
{
if(n < 1)
return 0;
if(n == 1)
return 1;
if(n == 2)
return 2;
return solve(n-1)+solve(n-2);
}
备忘录解法:
int solve(int n,HashMap<Interger, Interger>map)
{
if(n < 1)
return 0;
if(n == 1)
return 1;
if(n == 2)
return 2;
if(map.contains(n))
return map.get(n);
else
{
int value = solve(n-1)+solve(n-2);
map.put(n, value);
return value;
}
}
动态规划解法:
int solve(int n,HashMap<Interger, Interger>map)
{
if(n < 1)
return 0;
if(n == 1)
return 1;
if(n == 2)
return 2;
int a = 1;
int b = 2:
int temp = 0;
for(int i=3; i<=n; i++)
{
temp = a + b;
a = b;
b = temp;
}
return temp;
}
题目改为一次最多能走k阶台阶
思路:其实是一样的,只要把边界和递归式扩展到 k 即可
// 边界条件, 转化为总共 k 阶台阶,可以任意走,到达k阶有2^(k-1)种走法
num[0] = 1
for(int i=1; i =< k; ++i)
num[i] = power(2, i-1);
// 递推
for(int j = k+1; j<n+1; ++j)
{
z = 1;
while(z < k)
{
num[j] += num[j-z];
z += 1;
}
}
3、打家劫舍问题
**题目:**偷商店,不能连续偷两家,求偷的总额最大
备忘录解法:
vector<int> save;
int solve(vector<int>& nums, int n)
{
if(n < 0)
return 0;
if(save[n] >= 0)
return save[n];
save[n] = max(solve(nums, n-2)+nums[n], solve(nums, n-1));
return save[n];
}
int rob(vector<int>& nums) {
int len = nums.size();
for(int i=0; i<len; i++)
save.push_back(-1);
return solve(nums, len-1);
}
动态规划解法:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 0)
return 0;
if(n == 1)
return nums[0];
vector<int> save(n, -1);
save[0] = nums[0];
save[1] = max(nums[0], nums[1]);
for(int i=2; i<n; ++i)
save[i] = max(nums[i]+save[i-2], save[i-1]);
return save[n-1];
4、01背包问题
题目: 给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi ,应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
// 下标为0的元素,没有用到,置为0
int v[N]={0,8,10,6,3,7,2};
int w[N]={0,4,6,2,2,5,1};
int n=6,c=12;
int solve()
{
// 需要知道的初始值是 m[i][0],m[0][j] 都等于0,所以数组都初始化为0
int m[n+1][c+1] = {0};
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
}
}
return m[n][c]
可以进一步压缩空间,只用2x(c+1)或1x(c+1)大小的数组:
# 2x(c+1)数组
int m[2][c+1] = {0};
m[i%2][j]=max(m[(i-1)%2][j],m[(i-1)%2][j-w[i]]+v[i]);
# 1x(c+1)大小的数组
int m[c+1] = {0};
m[j]=max(m[j],m[j-w[i]]+v[i]);
5、硬币找零
题目: 硬币的种类值列表 coin_list,要找零钱 change 元,问最少需要几个硬币?
递归解法:
def find_coins(coin_list, change):
min_coins = change
if change in coin_list:
return 1
else:
for i in [c for c in coin_list if c <= change]:
num_coins = 1 + find_coins(coin_list, change-i)
if num_coins < min_coins:
min_coins = num_coins
return min_coins
print(recDC([1,5,10,25], 63)
备忘录解法:
# save_list 缓存已经算过的零钱最少找币数,列表大小=最初需要找零的值+1,初始值都=0
def find_coins(coin_list, change, save_list):
min_coins = change
if change in coin_list:
return 1
elif save_list[change] > 0:
return save_list[change]
else:
for i in [c for c in coin_list if c <= change]:
num_coins = 1 + find_coins(coin_list, change-i, save_list)
if num_coins < min_coins:
min_coins = num_coins
save_list[change] = min_coins
return min_coins
print(recDC([1,5,10,25], 63, [0]*64))
动态规划解法:
# 递推
def find_coins(coins_list, change, min_coins_list):
for num in range(change+1):
num_coins = num
for j in [c for c in coins_list if c <= num]:
if min_coins_list[num-j] + 1 <= num_coins:
num_coins = min_coins_list[num-j]+1
min_coins_list[num] = num_coins
return min_coins_list[change]
print(find_coins([1, 5, 10, 25], 63, [0]*64))
GOOD LUCK!