目录

leetcode 70 爬楼梯

leetcode 198 打家劫舍

leetcode 53 最大子序和

leetcode 322 零钱兑换

leetcode 120 三角形最小路径和

leetcode 300 最长上升子序列

leetcode 64 不同路径

leetcode 174 地下城游戏

leetcode 304 二维区域和检索 - 矩阵不可变(2021 03 02)


leetcode 70 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶

示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

思路:

暴力解法:

int climbStairs(int n) {if (n == 1 || n == 2) {return n;}return climbStairs(n - 1) + climbStairs(n - 2);
}

眼熟吗?这就是费波纳茨。动态规划的优点是把原问题划分成子问题,将每次子问题的计算结果都进行记录,通过查询记录避免了重复计算。

通过暴力搜索,可以推到出动态法规的递推公式:

第 i 阶楼梯爬法的数量 = 第 i -1 阶楼梯爬法的数量 + 第 i -2 阶楼梯爬法的数量

因此可以利用数组记录每一阶台阶的爬法,每次爬的时候,通过查询数组中的数据就可以获得结果。

动态规划的解析原理

通过上面的分析可知,动态规划的解题分为四步

1、确认原问题和子问题

  • 原问题——求n阶台阶所有走法的数量
  • 子问题——第 i 阶台阶走法的数量

2、确认状态

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

3、确认边界条件

  • 边界状态为第 1 阶台阶与第 2 阶台阶的走法。第一节台阶有一种走法,第二阶台阶有两种走法。

4、确认状态转移方程

  • 将第 i 个状态的值转移为求第 i-1 个状态值与第 i-2 个状态的值。动态规划状态转移方程为:dp[i]=dp[i-1]+dp[i-2]
class Solution {
public:int climbStairs(int n) {vector<int>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];}
};

leetcode 198 打家劫舍

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

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

示例 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、暴力枚举的方法

  • 对于每个房间,有两种可能:被盗和没被盗。如果采用暴力求解的方式,有种可能,时间复杂度是

2、贪心算法是否可行

  • 如果采用贪心算法,在满足不触发报警的同时,每次选择财宝最多的房间进行盗取,则贪心算法对于[5,2,6,3,1,7]的盗取结果是 7,6,5。

3、动态规划算法的原问题和子问题、状态、边界条件、状态转移方程如何确定?

思路:

对于房间 i 则:

  • 如果盗取 i 中的财宝,则不能盗取 i-1 号房间的财宝
  • 如果不盗取 i 中的财宝,则第 i-1 号房间的财宝可以选择盗取或者不盗取

动态规划的解析原理

通过上面的分析可知,动态规划的解题分为四步

1、确认原问题和子问题

  • 原问题——求n个房间的盗取财宝最优解
  • 子问题——第 i 个房间的盗取财宝最优解

2、确认状态

  • 第 i 个状态为前 i 个房间能够获得的最大财宝数。

3、确认边界条件

  • 前一个房间的最优解为盗取第一个房间的财宝
  • 前两个房间的最优解为盗取这两个房间中财宝最多的那个房间

4、确认状态转移方程

  • 盗取 i 房间:最优解为——第 i 个房间 + 前 i-2 个房间的最优解
  • 不盗取 i 房间:最优解为——第 i-1个房间的最优解

状态转移方程为:dp[i]=max(dp[i-1], dp[i-2]+nums[i]); (i>=3)

class Solution {
public:int rob(vector<int>& nums) {if(nums.size()==0) return 0;if(nums.size()==1) return nums[0];vector<int>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-1], dp[i-2]+nums[i]);}return dp[nums.size()-1];}
};

leetcode 53 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

思路:

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

第 i 个状态(dp[i])即为以第 i 个数字结尾的最大子段和。由于以第 i-1 个数字结尾的最大子段和 dp[i-1] 与 nums[i] 相邻:

  • 若dp[i-1]>0,则dp[i]=dp[i-1]+nums[i]
  • 否则,dp[i]=nums[i]

边界条件——dp[0]=nums[0]

因此推导出状态转移方程:dp[i]=max(dp[i-1]+nums[i], nums[i]);

class Solution {
public:int maxSubArray(vector<int>& nums) {vector<int>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(max_res<dp[i]){max_res=dp[i];}}return max_res;}
};

leetcode 322 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

示例 1:

输入: coins = [1, 2, 5], amount = 11输出: 3解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3输出: -1

思考——贪心算法是否可行?

对于钞票 [ 1, 2, 5, 10 ],目标金额是14.最优解需要三张钞票

贪心思想——每次优先使用面值最大的,如:

现选一张10元的,剩下4元,再选一张2元的,剩下2元,再选一张2元的,搞定。

对于钞票 [ 1, 2, 5, 7, 10 ],目标金额是14.最优解需要2张钞票

贪心思想——每次优先使用面值最大的,如:

现选一张10元的,剩下4元,再选一张2元的,剩下2元,再选一张2元的,钞票的使用张数超过了规定。

因此,贪心算法在个别面值组合时是可行的,但是本题的面值不确定,因此贪心思想不可行。需要使用动态规划来解决问题。

思路:

钞票 [ 1, 2, 5, 7, 10 ],目标金额是14.

dp[i] 代表金额 i 的最优解(使用最小张数的解法)

数组dp[]中存储金额 1 至金额 14 的最优解。

在计算dp[i]时,dp[0], dp[1],...dp[i-1]都是已知的,而金额 i 可以由:

  • 金额 i-1 与 一张一块钱组合
  • 金额 i-2 与一张两块钱组合
  • 金额 i-5 与一张五块钱组合
  • 金额 i-7 与一张七块钱组合
  • 金额 i-10 与一张十块钱组合

因此,状态转移方程为: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<int>& coins, int amount) {vector<int>dp(amount+1, -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 三角形最小路径和

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

例如,给定三角形:

[[2],[3,4],[6,5,7],[4,1,8,3]
]

自顶向下的最小路径和为 11(即,3 + 1 = 11)。

说明:

如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。

思路:

class Solution {
public:int minimumTotal(vector<vector<int>>& triangle) {if(triangle.empty()) return 0;vector<vector<int>>dp;for(int i=0; i<triangle.size(); i++){dp.push_back(vector<int>());for(int j=0; j<triangle[i].size(); j++){dp[i].push_back(0);}}//给最后一层赋值for(int i=0; i<dp[dp.size()-1].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], dp[i+1][j+1])+triangle[i][j];}}return dp[0][0];}
};

leetcode 300 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

思路:

方法一:

若第 i 个状态 dp[i] 代表以第 i 个元素结尾的最长上升子序列的长度:dp[i-1] 代表第 i-1 个元素结尾的最长上升子序列。。。。因此dp[n]为前面各个dp中的最大值。

用一个变量LIS来记录当前的最长上升子序列的长度。

class Solution {
public:int lengthOfLIS(vector<int>& nums) {if(nums.empty()) return 0;vector<int>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;}
};

方法二:

使用栈思想(vector实现),从 1 到 n-1 遍历数组。

  • 若num[i]>栈顶,则将num[i]入栈
  • 若num[i]<栈顶,则遍历栈中元素,将栈中第一个大于等于nums[i]的元素的值修改为nums[i]。
class Solution {
public:int lengthOfLIS(vector<int>& nums) {if(nums.empty()) return 0;vector<int>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]){stack[j]=nums[i];break;}}}}return stack.size();}
};

优化方法二:

使用二分查找法查找栈中第一个大于等于 nums[i] 的值。从而降低时间复杂度为O(nlogn)。

class Solution {
public:int binary_search(vector<int>nums, int target){int index=-1;int begin=0;int end=nums.size()-1;while(index==-1){int mid=(begin+end)/2;if(target==nums[mid])index=mid;else if(target<nums[mid]){if(mid==0 || target>nums[mid-1])index=mid;end=mid-1;}else if(target>nums[mid]){if(mid==nums.size()-1 || target<nums[mid+1])index=mid+1;begin=mid+1;}}return index;}int lengthOfLIS(vector<int>& nums) {if(nums.empty()) return 0;vector<int>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{int pos=binary_search(stack, nums[i]);stack[pos]=nums[i];}}return stack.size();}
};

leetcode 64 不同路径

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

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

示例:

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

思路:

开辟一个和输入的数组一样大的二维数组,从左上角开始,分别向右和向下走,用新开辟的数组记录每一步需要走的路径长度。

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

leetcode 174 地下城游戏

一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。

为了尽快到达公主,骑士决定每次只向右或向下移动一步。

编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。

例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7

骑士的健康点数没有上限。说明:

  • 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。

这题让我想起一首歌:达拉崩吧_在线试听_高音质歌曲_酷我音乐  

思路:

从左上到右下,还要保证骑士不死,初始血量是多少这个真不好定。我们从右下角出发吧~

从右下角到左上角,dp[i][j]代表要到达该点,至少需要多少血量。

例如:

  • 若地牢为 1*1 的矩阵,则dp[0][0]=max(1, 1-dungeon[0][0])

  • 若地牢为 1*n 的:dp[0][i] = max(1, dp[0][i+1] - dungeon[0][i] ) ;

  • 若地牢为 n*1 的:dp[i][0] = max(1, dp[i+1][0] - dungeon[i][0] ) ;
  • 若地牢为 n*m 的数组,i 代表行, j 代表列设 dp_min=min(dp[ i+1 ][ j ], dp[ i ][ j+1 ]);,
  • dp[ i ][ j ] = max( 1, dp_min - dungeon[ i ][ j ])
class Solution {
public:int calculateMinimumHP(vector<vector<int>>& dungeon) {if(dungeon.size()<1 || dungeon[0].size()<1) return 0;vector<vector<int>>dp(dungeon.size(), vector<int>(dungeon[0].size()));int row=dungeon.size();int column=dungeon[0].size();// 1*1 的矩阵情况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]);}// n*1for(int i=row-2; i>=0; i--){dp[i][column-1]=max(1, dp[i+1][column-1]-dungeon[i][column-1]);}// 1*nfor(int i=column-2; i>=0; i--){dp[row-1][i]=max(1, dp[row-1][i+1]-dungeon[row-1][i]);}// n*mfor(int i=row-2; i>=0; i--){for(int j=column-2; j>=0; j--){int dp_min=min(dp[i+1][j], dp[i][j+1]);dp[i][j]=max(1, dp_min-dungeon[i][j]);}}return dp[0][0];        }
};

leetcode 304 二维区域和检索 - 矩阵不可变(2021 03 02)

给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。

上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。

示例:

给定 matrix = [
  [3, 0, 1, 4, 2],
  [5, 6, 3, 2, 1],
  [1, 2, 0, 1, 5],
  [4, 1, 0, 1, 7],
  [1, 0, 3, 0, 5]
]

sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12

说明:

  • 你可以假设矩阵不可变。
  • 会多次调用 sumRegion 方法。
  • 你可以假设 row1 ≤ row2 且 col1 ≤ col2。

思路:

使用动态规划法,分别计算出矩阵中各个点到左上角点位置的总和。求sumRegion时,将不需要的区域总和去掉即可。

答案

class NumMatrix {
private:vector<vector<int>> dp;public:NumMatrix(vector<vector<int>> matrix) {if (matrix.size() == 0 || matrix[0].size() == 0) {return;}int n = matrix.size();int m = matrix[0].size();dp.resize(n + 1, vector<int>(m + 1, 0));for (int r = 0; r < n; r++) {for (int c = 0; c < m; c++) {dp[r + 1][c + 1] = dp[r + 1][c] + dp[r][c + 1] + matrix[r][c] - dp[r][c];}}}int sumRegion(int row1, int col1, int row2, int col2) {return dp[row2 + 1][col2 + 1] - dp[row1][col2 + 1] - dp[row2 + 1][col1] + dp[row1][col1];}
};/*** Your NumMatrix object will be instantiated and called as such:* NumMatrix* obj = new NumMatrix(matrix);* int param_1 = obj->sumRegion(row1,col1,row2,col2);*/

更多的动态规划问题(收录来自牛客网) 来啃硬骨头——c++ 动态规划

分门别类刷leetcode——动态规划(C++实现)相关推荐

  1. 从零开始刷Leetcode——动态规划(70.198.303)

    文章目录 70. 爬楼梯 198. 打家劫舍 303. 区域和检索 - 数组不可变 动态规划属于热门问题,在leetcode中主要以medium和hard为主. 70. 爬楼梯 假设你正在爬楼梯.需要 ...

  2. 分门别类刷leetcode——高级数据结构(字典树,前缀树,trie树,并查集,线段树)

    目录 Trie树(字典树.前缀树)的基础知识 字典树的节点表示 字典树构造的例子 字典树的前序遍历 获取字典树中全部单词 字典树的整体功能 字典树的插入操作 字典树的搜索操作 字典树的前缀查询 字典树 ...

  3. 分门别类刷题总结列表 C++ 实现

     目录 输入输出 leetcode 牛客网 算法训练营 SQL shell编程 零七八碎 买的课 真题 输入输出 1 牛客刷题输入输出总结 2 记录各个七七八八的输入 持续更新中 leetcode 1 ...

  4. 化工热力学补考成功,几天没有头脑了,赶紧赏自己几题Leetcode动态规划算法最长系列

    @Author:Runsen @Date:2020/10/9 "恭喜你昨天,化工热力学补考成功!" "区区化工热力学还想让我重修,只不过浪费了我九月一半的精力和十月的九成 ...

  5. 二叉搜索树的中序遍历为 递增序列_Go 刷 Leetcode 系列:恢复二叉搜索树

    二叉搜索树中的两个节点被错误地交换. 请在不改变其结构的情况下,恢复这棵树. 示例 1: 输入: [1,3,null,null,2] 1 / 3 \ 2输出: [3,1,null,null,2] 3 ...

  6. java开发有必要刷leetcode吗_刷 leetcode 需要哪些基础?

    首先要知道 基础的数据结构:数组.字符串.树.堆.栈.队列.哈希表 基础的算法: 枚举遍历, 二分查找,递归,回溯 明白基础的数据结构之后,我们可以发现 leetcode上已经做好了分类, 首先我们要 ...

  7. 如何快速高效的刷Leetcode

    前言 随着互联网寒潮的到来, 越来越多的互联网公司提高了面试的难度,其中之一就是加大了面试当中手撕算法题的比例.这里说的算法题不是深度学习,机器学习这类的算法,而是排序,广度优先,动态规划这类既考核数 ...

  8. 算法小白如何高效、快速刷 LeetCode ?

    算法很重要,但算法也是学起来最难,最令人生畏的. 特别是刷 LeetCode 的时候!!! 很多初学者在刷题的时候,思路飞来飞去,有时候以为是 动态规划 的知识点,结果写了半天代码越写越乱,最后一看 ...

  9. 手把手带你刷Leetcode力扣 学习总结

    文章目录 1. 总体规划 2. 算法复杂度 2.1 时间复杂度 2.2 空间复杂度 3. 数据结构 3.1 数组[Array] 3.1.1 Python常用操作 3.1.2 Java常用操作 3.1. ...

  10. LeetCode动态规划股票系列整理

    写在前面 股票感觉是LeetCode动态规划中系列最多的一类,交易次数不同,有冷冻期,含手续费,让买卖的最佳时机千奇百怪,但是只要掌握dp的方法,解决起来还是有套路可循的.依据dp的常规思想,股票问题 ...

最新文章

  1. 使用JIRA搭建企业问题跟踪系统(转)
  2. delphi指针简单入门
  3. 在Spring中使用jOOQ:CRUD
  4. TinaFace:人脸检测新纪录!
  5. MyCat分布式数据库集群架构工作笔记0006---Mycat启动
  6. FL Studio20.8中文完整果味版编曲
  7. Python全局解释锁
  8. matlab pcm仿真,基于MATLAB的PCM调制系统的仿真与分析
  9. 解决华为手机用rem单位,内容超出屏幕宽度问题
  10. sql 2008 R2 备份和还原
  11. dft中X(K)的k的含义
  12. 让你的电脑装上Remix os 技德
  13. 触摸!天空龙 - 锻炼极速反应力
  14. WSUS服务器不能下载补丁的最终解决办法
  15. 批量更新mysql数据(万条数据秒完成)
  16. 计算机中丢失d3dx941,d3dx9_41.dll(支持64位)
  17. JSJ-3/AC220V时间继电器
  18. 服务器固态硬盘的优缺点是什么
  19. 云展网教程 | 图片怎么转成PDF?
  20. MongoDB:什么是MongoDB ?

热门文章

  1. 什么是瀑布图_什么是瀑布图以及为什么我需要一个
  2. jbod ugood 磁盘驱动状态_JBOD磁盘配置和StorCLI命令使用小结
  3. 华为大数据云管理平台实测
  4. 【思维导图】万科王石自传《我的改变:个人的现代化四十年》做的一些摘录
  5. 企业上云,安全合规如何进阶 ——一文拆解亚马逊云科技云安全理念与实践
  6. python-百分号字符串拼接
  7. 收藏 | 超全开源数据集,你真的不想要吗?(附链接)
  8. python实现熵权法
  9. 自考2018版《管理经济学》第一章导论——思维导图
  10. python怎么创建窗口_python如何设计窗口