本文内容是基于小象学院——林沐 《面试算法 LeetCode 刷题班》,后期仍将对相关内容进行不定期更新!
问题描述:
在爬楼梯时,每次可向上走 1 阶台阶或 2 阶台阶,问有 n 阶楼梯有多少种上楼方式。
问题分析:
由于每次最多爬 2 阶, 楼梯的第 i 阶,只可能从楼梯第 i-1 阶与第 i-2 阶到达。所有到达第 i 阶有多少种爬法,只与第 i-1、i-2 阶的爬法数量直接相关。
算法思路:
第 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)
问题描述:
在一条直线上,有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];
}
};
问题描述:
给定一个数组,求这个数组的连续子数组中,最大的那一段的和
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;
}
};
问题描述:
已知不同面值的钞票,求如何用最少数量的钞票组成某个金额,求可以使用的最少钞票数量。如果任意数量的已知面值钞票都无法组成该金额,则返回 -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 个状态所转移到, 故,
代码实现:
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];
}
};
问题描述:
给定一个二维数组,其保存了一个数字三角形,求从数字三角形顶端到底端各数字和最小的路径之和,每次可以向下走相邻的两个位置。
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];
}
};
问题描述:
已知一个未排序的数组,求这个数组最长上升子序列的长度。
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 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];
}
};
问题描述:
已知一个二维数组,左上角代表骑士的位置,右下角代表公主的位置,二维数组存储整数,正数代表可以给骑士增加生命值,负数会减少骑士的生命值,问骑士初始时至少是有多少生命值,才可以保证骑士在行走的过程中至少保持生命值为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];
}
};