动态规划原理与LeetCode题解

目录

动态规划的三个特征:

动态规划解题思路:

1.状态转移表法

2. 状态转移方程法

3. LeetCode题解

3.1 LeetCode 509. 斐波那契数

3.2 LeetCode 70. 爬楼梯

3.3 LeetCode 198. 打家劫舍

3.4 LeetCode 53. 最大子序和

3.5 LeetCode 152. 乘积最大子数组

3.6 LeetCode 1014. 最佳观光组合

3.7 LeetCode 55. 跳跃游戏


动态规划适合解决多阶段决策最优解模型问题

动态规划的三个特征:

1.最优子结构

2.无后效性

3.重复子问题

动态规划解题思路:

1.状态转移表法

回溯算法实现-定义状态-画递归树-找重复子问题-画状态转移表-根据递推关系填表

0-1背包问题:我们有一个背包,背包总的承载重量是Wkg。现在我们有n个物品,每个物品的重量不等,并且不可分割。我们现在期望选择几件物品,装载到背包中。在不超过背包所能装载重量的前提下,如何让背包中物品的总重量最大?

我们用一个二维数组states[n][w+1],来记录n个物品放入载重w公斤背包的状态。

第0个(下标从0开始编号)物品的重量是2,要么装入背包,要么不装入背包,决策完之后,会对应背包的两种状态,背包中物品的总重量是0或者2。我们用states[0][0]=true和states[0][2]=true来表示这两种状态。

第1个物品的重量也是2,基于之前的背包状态,在这个物品决策完之后,不同的状态有3个,背包中物品总重量分别是0(0+0),2(0+2 or 2+0),4(2+2)。我们用states[1][0]=true,states[1][2]=true,states[1][4]=true来表示这三种状态。

以此类推,直到考察完所有的物品后,整个states状态数组就都计算好了。我把整个计算的过程画了出来,你可以看看。图中0表示false,1表示true。我们只需要在最后一层,找一个值为true的最接近w(这里是9)的值,就是背包中物品总重量的最大值。

 根据上面的状态转移表推导出递推关系,写出动态规划代码

weight:物品重量,n:物品个数,w:背包可承载重量
public int knapsack(int[] weight, int n, int w) {
  boolean[][] states = new boolean[n][w+1]; // 默认值false
  states[0][0] = true;  // 第一行的数据要特殊处理,可以利用哨兵优化
  if (weight[0] <= w) {
    states[0][weight[0]] = true;
  }
  for (int i = 1; i < n; ++i) { // 动态规划状态转移
    for (int j = 0; j <= w; ++j) {// 不把第i个物品放入背包
      if (states[i-1][j] == true) states[i][j] = states[i-1][j];
    }
    for (int j = 0; j <= w-weight[i]; ++j) {//把第i个物品放入背包
      if (states[i-1][j]==true) states[i][j+weight[i]] = true;
    }
  }
  for (int i = w; i >= 0; --i) { // 输出结果
    if (states[n-1][i] == true) return i;
  }
  return 0;
}

2. 状态转移方程法

找最优子结构-写状态转移方程-将状态转移方程翻译成代码

3. LeetCode题解

3.1 LeetCode 509. 斐波那契数

    例如斐波那契数列数列,我们可以很容易知道他的状态转移方程是 f(n)=f(n-1)+f(n-2)。递归的实现如下:

int fib(int n) {
	if(n<=1)
		return n;
	return fib(n-1)+fib(n-2);
}

        由于计算f(n)需要先计算f(n-1)和f(n-2),但计算f(n-1)又需要计算f(n-2)和f(n-3);计算f(n-2)需要计算f(n-3)和f(n-4)。这个递归推导计算中有大量重复计算,时间复杂度是O(n!)。

Introduction To Dynamic Programming - Fibonacci Series | TutorialHorizon

 

      重复子问题正是动态规划要解决的重复子问题,很适合用动态规划解决。动态规划实现:

int fib(int n) {
	if(n<=1)
		return n;
	vector<int> dp(n+1);
	dp[0] = 0;
	dp[1] = 1;
	for(int i = 2; i <= n; i++) {
		dp[i] = dp[i-1] + dp[i-2];
	}
	return dp[n];
}

        此实现使用了动态规划,问题最优解包含了子问题的最优解(最优子结构),每一步计算都只依赖上一步(无后效性),没有重复计算(解决重复子问题)。这个算法的时间复杂度是O(n),不过空间复杂度也是O(n)。一般动态规划问题都可以写出一个O(n)大小的状态转移方程,不过大部分问题都可以转换为O(1)的空间,因为我们只需要上一步的状态和当前的状态。

int fib(int n) {
	if(n<=1)
		return n;
	int a1 = 0, a2 = 1;
	int res = 0;
	for(int i = 2; i <= n; i++) {
		res = a1+a2;
		a1 = a2;
		a2 = res;
	}
	return res;
}

        上面这个算法使用动态规划实现了O(n)的时间复杂度,空间复杂度O(1)。

3.2 LeetCode 70. 爬楼梯

int climbStairs(int n) {
	if(n<=2)
		return n;
	int a1 = 1, a2 = 2;
	int res = 0;
	for(int i = 3; i <= n; i++) {
		res = a1+a2;
		a1 = a2;
		a2 = res;
	}
	return res;
}

归纳总结后发现状态转移方程也是f(n) = f(n-1)+f(n-2),只是要注意初始状态f(1)=1,f(2)=2

3.3 LeetCode 198. 打家劫舍

int rob(vector<int>& nums) {
	if(nums.size() == 1)
		return nums[0];
	if(nums.size() == 2)
		return max(nums[0], nums[1]);
	int a1 = nums[0];
	int a2 = max(nums[0], nums[1]);
	int an = 0;
	for (int i = 2; i < nums.size(); ++i) {
		an = max(a1+nums[i],a2);
		a1 = a2;
		a2 = an;
	}
	return an;
}

状态转移方程 f(n) = max(f(n-2)+num[n], f(n-1))

3.4 LeetCode 53. 最大子序和

int maxSubArray(vector<int>& nums) {
	int maxCur = nums[0], maxRes = nums[0];
	for (int i = 1; i < nums.size(); ++i) {
		maxCur = max(maxCur+nums[i], nums[i]);
		maxRes = max(maxRes, maxCur);
	}
	return maxRes;
}

状态转移方程 f(n) = max(f(n)+num[i], num[i]); 

3.5 LeetCode 152. 乘积最大子数组

int maxProduct(vector<int>& nums) {
	int maxCur = nums[0];
	int minCur = nums[0];
	int maxRes = nums[0];
	for(int i = 1; i < nums.size(); i++){
		int mx = maxCur, mn = minCur;
		maxCur = max(mx*nums[i], max(nums[i], mn*nums[i]));
		minCur = min(mn*nums[i], min(nums[i], mx*nums[i]));
		maxRes = max(maxCur, maxRes);
	}
	return maxRes;
}

3.6 LeetCode 1014. 最佳观光组合

int maxScoreSightseeingPair(vector<int>& values) {
	int maxSum = values[0], maxRes = 0;
	for (int i = 1; i < values.size(); ++i) {
		maxRes = max(maxRes, maxSum+values[i]-i);
		maxSum = max(maxSum, values[i]+i);
	}
	return maxRes;
}

3.7 LeetCode 55. 跳跃游戏

bool canJump(vector<int>& nums) {
	if(nums.size()==1)
		return true;

	int maxIndex{};
	for (int i = 0; i < nums.size()-1;i++) {
		maxIndex = max(maxIndex, i+nums[i]);
		if(maxIndex < i+1)
			return false;
	}
	return true;
}

状态转移方程 f(n) = max(f(n), i+num[i]); 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值