原理:
1.确认原问题与子问题:
原问题是求n阶台阶所有走法的数量,子问题是求1阶台阶、2阶台阶、…、n-1阶台阶的走法。
2.确认状态:
本题的动态规划状态单一,第i个状态即为i阶台阶的所有走法数量。
3.确认边界状态的值:
边界状态为1阶台阶与2阶台阶的走法,1阶台阶有1种走法,2阶台阶有2种走法,即dp[1]=1,dp[2]=2。
4.确认状态转移方程:
将求第i个状态的值转移为求第i-1个状态值与第i-2个状态的值,动态规划转移方程:dp[i]=dp[i-1]+dp[i-2];(i>=3)
注解:由于台阶只能走一阶或者两阶,因此第dp[i]只能由dp[i-1]和dp[i-2]来转移。因此能确认出状态转移方程。
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1 阶 + 1 阶
2 阶
到达楼梯的第i阶有多少种爬法,与第几阶的爬法直接相关,如何地推的求出第i阶爬法数量?
由于每次最多爬2阶,楼梯的第i阶,只可能从楼梯的第i-1阶与第i-1阶到达。故到达第i阶有多少种爬法,只与第i-1阶,第i-2阶的爬法数量直接相关。
第i阶的爬法数量=第i-1阶的爬法数量+第i-2阶的爬法数量
比如第i-1阶的爬法数量=1 {1}
第i-2阶的爬法数量=2 {{1,1},{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];
}
};
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 。
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-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
该问题的关键为相邻
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;
}
};
其他解法:最长子序列
322. 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
与上台阶类似,两种走台阶的方法对应多少种钞票面值(均在数组的下标的增值体现),金额i对应台阶i(数组的下标的值体现),多少种方法以数组的值体现。
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> 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];
}
};
120. 三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
例如第3行第3个的7,从上往下考虑,每个元素都要考虑两次,但从下网上考虑,最后一个元素只考虑一次就足够,因此选择从下往上推。
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.size()==0||triangle[0].size()==0)
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);
}
}
int sz=dp.size();
for(int i=0;i< sz;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];
}
};
300. 最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
复杂度为O(n2)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size()<=0)
return 0;
vector<int> dp(nums.size(),0);
dp[0]=1;
int LIS=1;
int sz=dp.size();
for(int i=1;i<sz;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;
}
};
改进:
原理是覆盖,例如当前遍历到第n个元素发现一个长度为3的序列L1,当遍历第n+1个时元素num[n+1]时,比L1最后一个元素小,则在该序列里找到小于等于num[n+1]的元素替换它,若之后的遍历为增序,且长度更大,序列L1会被从num[n+1]开始全部被替换为新的序列L2;若之后的遍历的元素num[n+2]大于序列L1最后一个元素,则push进去,序列L1的长度+1,也就是说num[n+1]不会改变序列L1的长度,若之后的遍历为增序,但长度更小,同理,也不会改变序列L1的长度。
这个方法不需要理会元素是哪些,采用长度更大的对长度更小的选择性覆盖。(适用于找出有一定要求的序列长度)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size()<=0)
return 0;
vector<int> stack;
stack.push_back(nums[0]);
int nums_sz=nums.size();
for(int i=1;i<nums_sz;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();
}
};
查找改为二分查找:复杂度O(nlogn)
class Solution {
public:
int binary_search(vector<int>& nums,int target)
{
int index=-1;
int beg=0,end=nums.size()-1;
while(index=-1)
{
int mid=(beg+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;
}
beg=mid+1;
}
}
return index;
}
int lengthOfLIS(vector<int>& nums) {
if(nums.size()<=0)
return 0;
vector<int> stack;
stack.push_back(nums[0]);
int nums_sz=nums.size();
for(int i=1;i<nums_sz;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();
}
};
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()==0||grid[0].size()==0)
return 0;
int row=grid.size();
int col=grid[0].size();
vector<vector<int> > dp(row,vector<int>(col,0));
dp[0][0]=grid[0][0];
for(int i=1;i<col;++i)
{
dp[0][i]=dp[0][i-1]+grid[0][i];
}
for(int i=1;i<row;++i)
{
dp[i][0]=dp[i-1][0]+grid[i][0];
for(int j=1;j<col;++j)
{
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[row-1][col-1];
}
};
174. 地下城游戏
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
说明:
骑士的健康点数没有上限。
任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
思路:从下往上推:先设定到达公主时血量只剩为1(一个最小值),那么返回原点,-hp换为+hp,+hp换为-hp,则到达某个格子(非p格)当前血量就是该格子到达公主所需要的最小血量。
class Solution {
public:
int calculateMinimumHP(vector<vector<int>>& dungeon) {
if(dungeon.size()==0||dungeon[0].size()==0)
return 0;
int row=dungeon.size();
int col=dungeon[0].size();
vector<vector<int> >dp(row,vector<int>(col,0));
dp[row-1][col-1]=max(1,1-dungeon[row-1][col-1]);//??
for(int i=col-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][col-1]=max(1,dp[i+1][col-1]-dungeon[i][col-1]);
}
for(int i=row-2;i>=0;i--)
{
for(int j=col-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];
}
};