动态规划题型

LeetCode120——三角形的最小路径和

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

例如,给定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

思路:

这道题开始想着从上往下来看,但是不是每个节点都有左右父亲节点。但是,换个思路从下往上看,每个节点都有左右孩子,这样计算每一层的最小路径,然后往上进行求解最后输出dp[0][0]就可以了。

代码:

class Solution {
public:
	int minimumTotal(vector<vector<int>>& triangle) {
		int rows = triangle.size();
		int cols = triangle[rows-1].size();//这里要千万注意,只有最后一个数组的元素是满的,第一个数组只有一个元素
		vector<vector<int>>dp(rows+1, vector<int>(cols+1));
		//dp[0][0] = triangle[0][0];
		for (int i = 0; i < cols; i++)
		{
			dp[rows - 1][i] = triangle[rows-1][i];
			cout << dp[rows - 1][i] << endl;
		}

		for (int i = rows-2; i>=0;i--)
		{
			for (int j = 0; j <=i; j++)
			{
				dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j];
				cout << "dp[" << i << "]" << "[" << j << "]==" << dp[i][j] << endl;
			}
		}
		return dp[0][0];
	}
};

LeetCode64——最小路径和

题目:

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

思路:

明确dp[i][j]表示到从左上角元素到达网格[i,j]路径的最小和。先初始化第一行和第一列的dp元素,因为它们只有一条路径可以到达,其余的dp元素可以选择左边和上面的较小元素,然后加上自身的值就行了。

代码:

class Solution {
public:
	int minPathSum(vector<vector<int>>& grid) {
		int rows = grid.size();
		int cols = grid[0].size();
		vector<vector<int>>dp(rows,vector<int>(cols));
		dp[0][0] = grid[0][0];
		for (int i = 1; i < rows; i++)
		{
			dp[i][0] = dp[i - 1][0] + grid[i][0];
		}
		for (int i = 1; i < cols; i++)
		{
			dp[0][i] = dp[0][i-1] + grid[0][i];
		}

		for (int i = 1; i < rows; i++)
		{
			for (int j = 1; j < cols; j++)
			{
				dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];
			}

		}
		return dp[rows - 1][cols - 1];
	}
};

LeetCode343——整数拆分

题目:

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

思路1:

递归算法加上一个记忆数组之后就变成了记忆化搜索,这种方式是从顶到下的,是一种比较好的方法。

代码:

class Solution {
private:
	vector<int>memo;
	int breakInteger(int n)
	{
		if (n == 1)
			return 1;

		if (memo[n] != -1)
			return memo[n];
		int res = -1;
		for (int i = 1; i < n; i++)
		{
			res = max(max(res, i*(n - i)), i*breakInteger(n - i));
		}
		memo[n] = res;
		return res;
	}

public:
	int integerBreak(int n) {		
		memo = vector<int>(n + 1, -1);
        return breakInteger(n);
	}
};

思路2

动态规划,要考虑什么是状态,状态就是数字n的大小(变化着的),然后从下至上计算出dp[n]就行了。

class Solution {
private:
	vector<int>dp;
public:
	int integerBreak(int n) {
		assert(n >= 2);
		dp = vector<int>(n + 1, -1);
		dp[1] = 1;
		for (int i = 2; i <= n; i++)
		{
			for (int j = 1; j <= i - 1; j++)
			{
				dp[i] = max(max(dp[i], (i-j)*j), dp[i - j]*j);
			}
		}

		return dp[n];
		
	}
};

LeetCode279——完全平方数

题目:

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4.
示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9.

思路:

按照框架来就行了,没有什么要特别注意的。

代码:

class Solution {
public:
	int numSquares(int n) {
		vector<int>dp(n+1, 9999);
		assert(n >= 1);
		dp[1] = 1;//dp[i]表示组成数字i需要的最少的完全平方数的个数
		for (int i = 2; i <= n; i++)
		{
			for (int j = 1; j <= int(sqrt(i));j++)
			{
				dp[i] = min(dp[i], dp[i - j * j] + 1);
			}
		}
		return dp[n];
	}
};

LeetCode91——解码方法

题目:

一条包含字母 A-Z 的消息通过以下方式进行了编码:

'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例 1:

输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:

输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

思路:

题目意思其实不好理解,主要是情况要分好,这里默认数据都是有效的,即没有02的情况,0只能跟着另外一个数的后面。dp[i]表示到第i个字符为止,可以有的译码情况。那么当s[i]为‘0’的时候,那就需要跟着前面的数组成一个数,dp[i]=dp[i-2]。如果不为零,那就有两种情况,一种是s[i]和s[i-1]可以组合成一个有效的字母(1-16)那么dp[i]=dp[i-1]+dp[i-2],还有一种情况就是它们两个不能组成有效的字母,那么dp[i]=dp[i-1]。这就是所有的情况。

代码:

class Solution {
public:
	int numDecodings(string s) {
		int length = s.length();
		vector<int>dp(length + 1, 0);
		dp[0] = 1;
		if (s[0] == '0') return 0;
		for (int i = 2; i <= length; i++)
		{
			if (s[i-1] == '0')
			{
				if (s[i - 2] == '1' || s[i - 2] == '2')
					dp[i] = dp[i - 2];
			}
			else
			{
				if((s[i-2]=='1' || s[i-2]=='2')&&(s[i-1]>='0' && s[i-1]<='6'))
					dp[i]=dp[i-1]+dp[i-2];
			}

		}
		return dp[length];
	}
};

LeetCode62——不同的路径 

比较简单https://leetcode-cn.com/problems/unique-paths/

LeetCode63——不同的路径II

题目:

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 n 的值均不超过 100。

示例 1:

输入:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

思路:

这道题关键是对障碍物的处理,障碍物所处的位置dp[i][j]==0,再处理边界的dp值,注意如果边界上的路径被挡住,那么后面的点也就无法到达了,要考虑点本身是否为障碍物和点的前驱节点是否可以到达这两种情况。然后其他点的dp值还是由上面的dp值加左边点的dp值,障碍物的dp值为0,不用考虑。

代码:

class Solution {
public:
	int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
		int rows = obstacleGrid.size();
		int cols = obstacleGrid[0].size();
		if (rows == 0 || cols == 0) return 0;
		vector<vector<long>>dp(rows, vector<long>(cols, 0));
		dp[0][0] = !obstacleGrid[0][0];
		for (int i = 1; i < rows; i++)
		{
			if (obstacleGrid[i][0]==0  && dp[i-1][0]==1)  dp[i][0] = 1;
			else  dp[i][0] = 0;//如果本身是障碍物,那么路径设为0,由于是第一列的元素,如果前面的元素有一个不可达
			
		}//那么也就到达不了。
		
		for (int i = 1; i < cols; i++)
		{
			if (obstacleGrid[0][i]==0 && dp[0][i - 1]==1) dp[0][i] = 1;
			else  dp[0][i] = 0;
			
		}

		for (int i = 1; i < rows; i++)
		{
			for (int j = 1; j < cols; j++)
			{
				//本身是障碍或者左边的节点和上边的节点都不可以到达
				if (obstacleGrid[i][j] == 1 )
				{
					dp[i][j] = 0;
				
				}
				else
				{
					dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
				
				}
					
			}
		}
		return dp[rows - 1][cols - 1];

	}
};

LeetCode98——打家劫舍

题目:

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

思路1:

其实这也是一个树类问题可以用递归的方法来做,但是会有很多重复的子情况,这个时候我们就可以用一个数组记忆已经计算过的值,这样使用记忆化搜索,时间复杂度就降低了很多,省去了很多的重复计算。

代码:

class Solution {
private:
	vector<int>memo;//表示考虑抢劫nums[i....n]所能获得的最大收益
	//考虑抢劫nums[index...nums.size())这个范围的所有房子。
	int search(vector<int>& nums,int index)
	{
		if (index >= nums.size())
			return 0;

		if (memo[index] != -1)
			return memo[index];

		int res = 0;
		for (int i = index; i <= nums.size(); i++)
		{
			res = max(nums[i] + search(nums, i+ 2), res);
		}
		memo[index] = res;

		return res;

	}

public:
	int rob(vector<int>& nums) {
		int len = nums.size();
		memo = vector<int>(len,-1);
		return search(nums, 0);
	}
};

LeetCode213——打家劫舍II

题目:

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

思路:

其实和前面的打家劫舍是一样的方法,但是要处理这个环,我们知道首尾的房子不能同时去偷,那么就有两种情况。第一种就是打劫前n-1个房子。第二种就是打劫后n-1个房子。然后求最大值就可以了。

代码:

class Solution {
public:

	int search(vector<int>& nums)
	{
		int len = nums.size();
		vector<int>dp(len + 1, -1);
		dp[0] = 0; dp[1] = nums[0];
		for (int i = 2; i <= len; i++)
		{
			if (dp[i - 2] + nums[i-1] > dp[i - 1])
				dp[i] = dp[i - 2] + nums[i-1];
			else
				dp[i] = dp[i - 1];
		}
		return dp[len];

	}

	int rob(vector<int>& nums) {

		if (nums.size() == 0) return 0;
		if (nums.size() == 1) return nums[0];
		vector<int>a;
		vector<int>b;
		for (int i = 0; i < nums.size() - 1; i++)
			a.push_back(nums[i]);
		for (int i = 1; i < nums.size(); i++)
			b.push_back(nums[i]);
		return max(search(a), search(b));
	}
};

练习:213. 打家劫舍 II 309. 最佳买卖股票时机含冷冻期

0-1 背包问题

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值