《面试算法 LeetCode 刷题班》——9. 动态规划

本文内容是基于小象学院——林沐 《面试算法 LeetCode 刷题班》,后期仍将对相关内容进行不定期更新!

9. 动态规划

文章目录

      • 9. 动态规划
        • LeetCode 70 爬楼梯 (E)
        • LeetCode 198 打家劫舍 (E)
        • LeetCode 53 最大子段和(E)
        • LeetCode 322 找零钱(M)
        • LeetCode 120 三角形(M)
        • LeetCode 300 最长上升子序列
        • LeetCode 64 最小路径和(M)
        • LeetCode 174 地牢游戏(H)

LeetCode 70 爬楼梯 (E)

问题描述:

在爬楼梯时,每次可向上走 1 阶台阶或 2 阶台阶,问有 n 阶楼梯有多少种上楼方式。

问题分析:
由于每次最多爬 2 阶, 楼梯的第 i 阶,只可能从楼梯第 i-1 阶与第 i-2 阶到达。所有到达第 i 阶有多少种爬法,只与第 i-1、i-2 阶的爬法数量直接相关。

《面试算法 LeetCode 刷题班》——9. 动态规划_第1张图片

算法思路:

  1. 设置递推数组 dp[0…n],dp[i]代表到达第 i 阶,有多少种走法,初始化数组为 0
  2. 设置到达第 1 阶台阶,有1种走法,到达第2阶台阶,有2种走法
  3. 利用i循环递推从第3阶至第n阶结果:
第 i 阶的爬法数量 = 第i-1阶的爬法数量 + 第 i-2 的爬法数量

解决代码:

class Solution {
public:
	int climbStairs(int n) {
		vector dp(n + 3, 0);
		dp[1] = 1;
		dp[2] = 2;
		for (int i = 3; i <= n; i++)
		{
			dp[i] = dp[i-1] + dp[i-2];
		}
		return dp[n];
	}
};

动态规划原理:

1.确认原问题子问题

原问题为求 n 阶台阶所有走法的数量,子问题是求1阶台阶、2阶台阶、…、n-1阶台阶的走法。

2.确认状态

上题的动态规划状态单一,第i个状态即为i阶台阶的所有走法数量。

3.确认边界状态的值

边界状态为1阶台阶与2阶台阶的走法,即dp[1]=1,dp[2]=2

4.确认状态转移方程

将求第i个状态的值转移为求第i-1个状态值与第i-2个状态的值,动态规划转移方程, dp[i] = dp[i-1] + dp[i-2];(i>=3)

LeetCode 198 打家劫舍 (E)

问题描述:

在一条直线上,有n个房屋,每个房屋中有数量不等的财宝,有一个盗贼希望从房屋中盗取财宝,由于房屋中有报警器,如果同时从相邻的两个房屋中盗取财报就会触发报警器。问在不触发报警器的前提下,最多可获取多少财宝。

Example :

Input: [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
 			 Total amount you can rob = 2 + 9 + 1 = 12.

问题分析:

由于同时从相邻的两个房屋中盗窃就会触发报警器,故:

a. 若选择第 i 个房间盗取财报,就一定不能选择第 i-1 个房间盗取财报

b. 若不选择第 i-1 个房间盗取财报,则相当于只考虑前 i-1 个房间盗取财宝

算法思路:

1.确认原问题与子问题

  原问题为求n个房间的最优解,子问题为求前1个房间,前2个房间…前n个房间的最优解

2.确认状态

  第 i 个状态即为前 i 个房间的最优解

3.确认边界状态的值

  前1个房间的最优解,第1个房间的财宝

  前2个房间的最优解,第1、2个房间中较大的财宝

4.确认状态转移方程

  a.选择第 i 个房间 : 第 i 个房间 + 前 i-2 个房间的最优解

  b.不选择第 i 个房间 : 前 i-1 个房间的最优解

  动态规划转移方程:

dp[i] = max(dp[i-1] , dp[i-2] + nums[i]);(i>=3)

代码实现:

class Solution {
public:
	int rob(vector& nums) {
		if (nums.size() == 0)
		{
			return 0;
		}
		if (nums.size() == 1)
		{
			return nums[0];
		}

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

LeetCode 53 最大子段和(E)

问题描述:

给定一个数组,求这个数组的连续子数组中,最大的那一段的和

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

算法思路:

将求 n 个数的数组的最大子段和,转换为分别求出以 第1个、第2个、…、第i个、…、第n个数字结尾的最大字段和,再找出这n个结果中最大的,即为结果。

可以得到如下关系:

若dp[i-1]>0:

 dp[i]=dp[i-1]+nums[i];

否则:

 dp[i]=nums[i];

代码实现:

class Solution {
public:
	int maxSubArray(vector& nums) {
		vector dp(nums.size(), 0);
		dp[0] = nums[0];
		int max_res = dp[0];
		for (int i = 1; i < nums.size(); i++)
		{
			dp[i] = max(dp[i - 1] + nums[i], nums[i]);
			if (dp[i]>max_res)
			{
				max_res = dp[i];
			}
		}
		return max_res;
	}
};

LeetCode 322 找零钱(M)

问题描述:

已知不同面值的钞票,求如何用最少数量的钞票组成某个金额,求可以使用的最少钞票数量。如果任意数量的已知面值钞票都无法组成该金额,则返回 -1。

Example 1:

Input: coins = [1, 2, 5], amount = 11
Output: 3 
Explanation: 11 = 5 + 5 + 1

Example 2:

Input: coins = [2], amount = 3
Output: -1

算法思路:

以 coins = [1,2,5,7,10]; 金额: 14 为例:
dp[i],代表金额 i 的最优解,数组中存储金额1至金额n的最优解。
在计算 dp[i] 时,dp[0] … dp[i-1] 都是已知的:
  而金额 i 可由:
   金额i-1 与 金额1组合
   金额i-2 与 金额2组合
   金额i-5 与 金额5组合
   金额i-7 与 金额7组合
   金额i-10 与 金额10组合
即状态 i 可由状态 i-1、i-2、i-5、i-7、i-10,5 个状态所转移到, 故,

`dp[i] = min(dp[i-1],dp[i-2],dp[i-5],dp[i-7],dp[i-10]) + 1;`

代码实现:

class Solution {
public:
	int coinChange(vector& coins, int amount) {
		vector dp;
		for (int i = 0; i <= amount; i++)
		{
			dp.push_back(-1);
		}
		dp[0] = 0;
		for (int i = 1; i <= amount; i++)
		{
			for (int j = 0; j < coins.size(); j++) {
				if (i-coins[j]>=0 && dp[i-coins[j]] != -1)
				{
					if (dp[i] == -1 || dp[i] > dp[i-coins[j]]+1)
					{
						dp[i] = dp[i - coins[j]] + 1;
					}
				}
			}
		}
		return dp[amount];
	}
};

LeetCode 120 三角形(M)

问题描述:

给定一个二维数组,其保存了一个数字三角形,求从数字三角形顶端到底端各数字和最小的路径之和,每次可以向下走相邻的两个位置。

Example:

[
 	 [2],
	[3,4],
   [6,5,7],
  [4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

问题分析:

算法思路:

1.设置一个二维数组,最优值三角形dp[][],并初始化数组元素为0。 dp[i][j]代表了从底向上递推时,走到三角形第 i 行第 j 列的最优解

2.从三角形的底面向三角形上方进行动态规划
  a.动态规划边界条件:底面上的最优值即为数字三角形的最后一层。
  b.利用i循环,从倒数第二层推至第一层:
    第i行,第j列的最优解 dp[i][j],可到达(i,j)的两个位置的最优解 dp[i+1][j]、dp[i+1][j+1] :
            dp[i][j] = min(dp[i+1][j],dp[i+1][j+1]) + triangle[i][j]

3.返回dp[0][0]

代码实现:

class Solution {
public:
	int minimumTotal(vector>& triangle) {
		if (triangle.size() == 0)
		{
			return 0;
		}
		vector> dp;
		for (int i = 0; i < triangle.size(); i++)
		{
			dp.push_back(vector());
			for (int j = 0; j < triangle[i].size(); j++)
			{
				dp[i].push_back(0);
			}
		}

		for (int i = 0; i < dp.size(); i++)
		{
			dp[dp.size() - 1][i] = triangle[dp.size() - 1][i];
		}

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

LeetCode 300 最长上升子序列

问题描述:

已知一个未排序的数组,求这个数组最长上升子序列的长度。

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.

代码实现:

class Solution {
public:
	int lengthOfLIS(vector& nums) {
		if (nums.size() == 0)
		{
			return 0;
		}
		vector dp(nums.size(),0);
		dp[0] = 1;
		int LIS = 1;
		for (int i = 1; i < dp.size(); i++)
		{
			dp[i] = 1;
			for (int j = 0; j < i; j++)
			{
				if (nums[i] > nums[j] && dp[i] < dp[j] + 1) {
					dp[i] = dp[j] + 1;
				}
			}
			if (LIS < dp[i]) {
				LIS = dp[i];
			}
		}
		return LIS;
	}
};

用栈实现:

class Solution {
public:
	int lengthOfLIS(vector& nums) {
		if (nums.size() == 0)
		{
			return 0;
		}
		vector stack;
		stack.push_back(nums[0]);
		for (int i = 1; i < nums.size(); i++)
		{
			if (nums[i] > stack.back()) {
				stack.push_back(nums[i]);
			}
			else
			{
				for (int j = 0; j < stack.size(); j++)
				{
					if (stack[j] >=nums[i]) {  //若栈中元素大于 nums[i],替换,并跳出循环
						stack[j] = nums[i];
						break;
					}
				}
			}
		}
		return stack.size();
	}
};

LeetCode 64 最小路径和(M)

问题描述:

已知一个二维数组,其中存储了非负整数,找到从左上角到右下角的一条路径,使得路径上的和最小。(移动过程中只能向下或向右)

问题分析:

思考与 LeetCode 120 相似之处,dp[i][j] 与 dp[i-1][j]、dp[i][j-1]、grid[i][j]之间的关系

代码实现:

class Solution {
public:
	int minPathSum(vector>& grid) {
		if (grid.size() == 0)
		{
			return 0;
		}
		int row = grid.size();
		int column = grid[0].size();
		vector> dp(row,vector(column,0));
		dp[0][0] = grid[0][0];
		for (int i = 1; i < column; i++)
		{
			dp[0][i] = dp[0][i-1] + grid[0][i];
		} // 处理边界情况 1

		for (int i = 1; i < row; i++) {
			dp[i][0] = dp[i-1][0] + grid[i][0]; // 处理边界情况 2
			for (int j = 1; j < column; j++) {
				dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
			}
		}
		return dp[row-1][column-1];
	}
};

LeetCode 174 地牢游戏(H)

问题描述:

已知一个二维数组,左上角代表骑士的位置,右下角代表公主的位置,二维数组存储整数,正数代表可以给骑士增加生命值,负数会减少骑士的生命值,问骑士初始时至少是有多少生命值,才可以保证骑士在行走的过程中至少保持生命值为1.(骑士只能向下或向右行走)

问题分析:

直接思考动态规划,我们应该从左上向右下递推,还是从右下向左上递推? 若用一个二伟数组代表每个格子的状态,dp[i][j] 具体代表什么?

若我们直接从左上角往右下角推,按照 LeetCode 64 的思路,

代码实现:

class Solution {
public:
	int calculateMinimumHP(vector>& dungeon) {
		if (dungeon.size() == 0)
		{
			return 0;
		}
		vector > dp(dungeon.size(), vector(dungeon[0].size(), 0));
		int row = dungeon.size();
		int column = dungeon[0].size();
		dp[row - 1][column - 1] = max(1, 1 - dungeon[row - 1][column - 1]);
		for (int i = column - 2; i >= 0; i--) {
			dp[row - 1][i] = max(1, dp[row - 1][i + 1] - dungeon[row - 1][i]);
		}
		for (int i = row - 2; i >= 0; i--)
		{
			dp[i][column - 1] = max(1, dp[i + 1][column - 1] - dungeon[i][column - 1]);
		}
		for (int i = row-2; i >= 0; i--)
		{
			for (int j = column - 2; j >= 0; j--)
			{
				int dp_min = min(dp[i][j+1],dp[i+1][j]);
				dp[i][j] = max(1, dp_min-dungeon[i][j]);
			}
		}
		return dp[0][0];
	}
};

你可能感兴趣的:(C++,数据结构,leetcode)