Week不知道多少的动态规划刷题汇总

动态规划刷题也有一部分了,来总结一下刷到的几大类问题和比较有意思的问题。

1. 打家劫舍系列

这个系列的问题其实相对来说是比较好解决的,只有【337. 打家劫舍 III】是相对来说比较难的树形动态规划。

1.1. 打家劫舍

这道题其实搞清楚了思路,实现起来很简单。

  1. dp[i]的含义:只在 [0, i] 范围的房子里偷钱,能够偷取的最大值。
  2. 递推公式:dp[i] = max(dp[i - 1], dp[i - 2] + money[i])
  3. 初始化方式:dp[p] = money[0](只偷第0间房子的话肯定最大值就是房子里的钱);dp[1] = max(money[0], money[1])(只偷前两间房子的话挑大的偷)
  4. 迭代顺序:正序迭代

这样子写代码就思路清晰很多了:

#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

int main()
{
	int n;
	while (cin >> n) {
		// 处理输入
		vector<int> money(n, 0);
		for (int i = 0; i < n; i++) {
			cin >> money[i];
		}
		// 动态规划
		if (money.size() == 1) { // 别忘了这个逻辑
			cout << money[money.size() - 1];
		}
		else {
			vector<int> dp(n, 0);
			dp[0] = money[0];
			dp[1] = max(money[0], money[1]);
			for (int i = 2; i < n; i++) {
				dp[i] = max(dp[i - 2] + money[i], dp[i - 1]);
			}

			cout << dp[dp.size() - 1] << endl;
		}
	}
}

1.2. 打家劫舍 II

跟前一题不一样的是,这道题的房子在一个环路上。其实思路是:分别去掉开头的房子和结尾的房子,把这俩数组送入第一题中的逻辑,分别返回两个结果,然后取最大值就可以了。

#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

int maxMoney(vector<int> value) {
	if (value.size() == 1)
		return value[value.size() - 1];
	vector<int> dp(value.size(), 0);
	dp[0] = value[0];
	dp[1] = max(value[0], value[1]);
	for (int i = 2; i < value.size(); i++) {
		dp[i] = max(dp[i - 1], dp[i - 2] + value[i]);
	}
	return dp[value.size() - 1];
}

int main()
{
	int n;
	while (cin >> n) {
		// 处理输入
		vector<int> money(n, 0);
		for (int i = 0; i < n; i++) {
			cin >> money[i];
		}
		// 动态规划
		if (money.size() == 1) { // 别忘了这个逻辑
			cout << money[money.size() - 1];
		}
		else {
			int former = maxMoney(vector<int>(money.begin(), money.end() - 1));
			int latter = maxMoney(vector<int>(money.begin() + 1, money.end()));
			cout << max(former, latter) << endl;
		}
	}
}

1.3. 打家劫舍 III

题目链接:打家劫舍III
这道题是这个系列里面最难的一道,也是这类树形动态规划的入门题。

思路:

  1. 用后序遍历(为什么用后序遍历呢?因为根节点的最大值是由子节点的结果求得的,所以是一个自底向上的遍历顺序,用后序遍历)去遍历这个二叉树;
  2. 递归的过程中,我去返回这个节点 【偷】 和 【不偷】 所能获取的最大价值。

所以接下来:

  1. 确定递归函数参数列表和返回值
vector<int> robTree(TreeNode* cur);
  1. 确定递归终止条件
if (!cur) {
	return vector<int> { 0, 0 }; // 因为对于一个空节点,无论偷不偷,收益都是0
}
  1. 确定单层处理逻辑
int moneyStole = cur->val + leftDP[0] + rightDP[0]; // 偷当前节点:左叶子和右叶子都不能偷了
int moneyNotStole = max(leftDP[0], leftDP[1]) + max(rightDP[0], rightDP[1]); // 不偷当前节点:左(右)叶子节点偷不偷都行,那么偷还是不偷由收益大小决定。
return vector<int> { moneyNotStole, moneyStole };

整合起来就是:

vector<int> robTree(TreeNode* cur) {
	if (!cur) {
		return vector<int> { 0, 0 }; // 因为对于一个空节点,无论偷不偷,收益都是0
	}
	vector<int> leftDP = robTree(cur->left);
	vector<int> rightDP = robTree(cur->right);
	int moneyStole = cur->val + leftDP[0] + rightDP[0]; // 偷当前节点:左叶子和右叶子都不能偷了
	int moneyNotStole = max(leftDP[0], leftDP[1]) + max(rightDP[0], rightDP[1]); // 不偷当前节点:左(右)叶子节点偷不偷都行,那么偷还是不偷由收益大小决定。
	return vector<int> { moneyNotStole, moneyStole };
}

int rob(TreeNode* root) {
	vector<int> res = robTree(root);
	return max(res[0], res[1]);
}

2. 买卖股票系列

这个系列也是前两道题很简单,第三道题开始上压力。

2.1. 买卖股票的最佳时机

你只能在某一天买入,之后的某一天卖出。这道题会有一点难想,但是写过之后就很清晰了。这道题有一个很基本的用贪心的方法来做:

#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

int main()
{
	int n;
	while (cin >> n) {
		// 输入
		vector<int> nums(n, 0);
		for (int i = 0; i < n; i++) {
			cin >> nums[i];
		}
		// 处理
		int cost = INT_MAX, profit = 0;
		for (int i : nums) {
			cost = min(cost, i);
			profit = max(profit, i - cost);
		}
		cout << profit << endl;
	}
}

另外,这道题也可以使用动态规划的方法来做:

  1. 动态规划数组的含义:dp[i][0]表示第i天持有这支股票所可能产生的最大收益,dp[i][1]表示第i天不持有这支股票所可能产生的最大收益。
  2. 递推公式:dp[i][0] = max(dp[i - 1][0], -nums[i])要么状态继承自前一天持有,要么在第i天买入,取最大;dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + nums[i])要么状态继承自前一天不持有,要么在第i天卖出,取最大。
  3. 迭代顺序:正序
  4. 初始化:dp[0][0] = -nums[i], dp[0][1] = 0
#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

int main()
{
	int n;
	while (cin >> n) {
		// 输入
		vector<int> nums(n, 0);
		for (int i = 0; i < n; i++) {
			cin >> nums[i];
		}
		// 动态规划数组
		vector<vector<int>> dp(n, vector<int>(2, 0));
		// 初始化
		dp[0][0] = -nums[0];
		dp[0][1] = 0;
		for (int i = 1; i < n; i++) {
			dp[i][0] = max(dp[i - 1][0], - nums[i]);
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + nums[i]);
		}
		cout << max(dp[n - 1][0], dp[n - 1][1]) << endl;
	}
}

2.2. 买卖股票的最佳时机 II

每一天都可以买入卖出,这道题就别用动态规划了,老实用贪心吧,还好记:

#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

int main()
{
	int n;
	while (cin >> n) {
		// 输入
		vector<int> nums(n, 0);
		for (int i = 0; i < n; i++) {
			cin >> nums[i];
		}
		// 贪心
		int sum = 0;
		for (int i = 1; i < n; i++)
		{
			if (nums[i] - nums[i - 1] > 0) {
				sum += nums[i] - nums[i - 1];
			}
		}
		cout << sum << endl;
	}
}

2.3. 买卖股票的最佳时机III

这道题其实思路也不是很难,跟 2.1 的思路是类似的,但实际情况多了很多,因为是买卖两次,所以有5种状态

  1. 动态规划数组含义:
    1. dp[i][0]:一次操作都没进行
    2. dp[i][1]:只进行了一次买入
    3. dp[i][2]:进行了一次买入和卖出
    4. dp[i][3]:进行了一次买卖的基础上,又进行了一次买入
    5. dp[i][4]:完成了两次买卖
  2. 递推公式:
    1. dp[i][0] = dp[i - 1][0]
    2. dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - nums[i])
    3. dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + nums[i])
    4. dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - nums[i])
    5. dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + nums[i])
      (其实这里不难看出,思路其实很简单的)
  3. 迭代顺序:正序
  4. 初始化:
    1. dp[0][0] = 0
    2. dp[0][1] = -nums[0]
    3. dp[0][2] = 0
    4. dp[0][3] = -nums[0]
    5. dp[0][4] = 0
      梳理完了,根据以上的规则,代码其实实现起来并不复杂:
#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

int main()
{
	int n;
	while (cin >> n) {
		// 输入
		vector<int> nums(n, 0);
		for (int i = 0; i < n; i++) {
			cin >> nums[i];
		}
		// 动态规划
		vector<vector<int>> dp(n, vector<int>(5, 0));
		// 初始化
		dp[0][1] = -nums[0]; dp[0][3] = -nums[0];
		// 迭代
		for (int i = 1; i < n; i++) {
			dp[i][0] = dp[i - 1][0];
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - nums[i]);
			dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + nums[i]);
			dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - nums[i]);
			dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + nums[i]);
		}
		// 输出
		cout << dp[n - 1][4] << endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值