【前言】OJ:AcWing + LeetCode (打卡活动:b站 大雪菜)
DP
从集合角度考虑 DP 问题
此时 DP 问题思考方式类似于暴力搜索:如何能枚举出所有情况?
核心思想:不要去枚举每一种情况,用某一个数(某状态)代表某一类数
1 最大子序和
题目描述
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
样例
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
笔记
枚举所有字段,求出所有子段和的最大值。枚举所有起点终点
代码
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res=INT_MIN,last=0;
for(int i=0;i<nums.size();i++){
int now=max(last,0)+nums[i];
res=max(res,now);
last=now;
}
return res;
}
};
2 三角形最小路径和
题目描述
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
样例
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
笔记
代码
方法1 二维数组
/*
求某一对东西的最大值:从上往下或从下往上
*/
class Solution {
public:
int minimumTotal(vector<vector<int>>& nums) {
int n=nums.size();
vector<vector<long long>> f(n,vector<long long>(n));
f[0][0] = nums[0][0];
for(int i=1;i<n;i++){
for(int j=0;j<=i;j++){
f[i][j]=INT_MAX;
if(j>0)f[i][j]=min(f[i][j],f[i-1][j-1]+nums[i][j]);
if(j<i)f[i][j]=min(f[i][j],f[i-1][j]+nums[i][j]);
}
}
long long res=INT_MAX;
for(int i=0;i<n;i++) res=min(res,f[n-1][i]);
return res;
}
};
方法2 滚动数组
/*
用滚动数组修改
模2=&1
& 的优先级小于 + —
*/
class Solution {
public:
int minimumTotal(vector<vector<int>>& nums) {
int n=nums.size();
vector<vector<long long>> f(2,vector<long long>(n));
f[0][0] = nums[0][0];
for(int i=1;i<n;i++){
for(int j=0;j<=i;j++){
f[i&1][j]=INT_MAX;
if(j>0)f[i & 1][j]=min(f[i & 1][j],f[i-1 & 1][j-1]+nums[i][j]);
if(j<i)f[i & 1][j]=min(f[i & 1][j],f[i-1 & 1][j]+nums[i][j]);
}
}
long long res=INT_MAX;
for(int i=0;i<n;i++) res=min(res,f[n-1 & 1][i]);
return res;
}
};
3 打家劫舍
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
样例
示例 1:
示例 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 。
笔记
f[i]:在前 i 个数中选,不选 nums[i] 的最大值,f[i]=max(f[i-1],g[i-1]);
g[i]: 在前 i 个数中选, 选 nums[i] 的最大值 ,g[i]=f[i-1] + nums[i];
代码
class Solution {
public:
int rob(vector<int>& nums) {
int n=nums.size();
vector<int> f(n+1),g(n+1); //多开一个空间,边界问题存到 0
for(int i=1;i<=n;i++){
f[i]=max(f[i-1],g[i-1]);
g[i]=f[i-1]+nums[i-1];
}
return max(f[n],g[n]);
}
};
4 不同路径 II
题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
说明:m 和 n 的值均不超过 100。
样例
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
笔记
代码
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& g) {
int n=g.size(),m=g[0].size();
vector<vector<long long>> f(n,vector<long long>(m));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(g[i][j]) continue;
if(!i && !j) f[i][j]=1;
if(i>0)f[i][j]+=f[i-1][j];
if(j>0)f[i][j]+=f[i][j-1];
}
}
return f[n-1][m-1];
}
};
5 解码方法
题目描述
一条包含字母 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) 。
笔记
DP 下标从1开始好处理,f[0] 设成边界
f[i]=f[i-1] + f[i-2];
代码
class Solution {
public:
int numDecodings(string s) {
int n=s.size();
vector<int> f(n+1);
f[0]=1;
for(int i=1;i<=n;i++){
if(s[i-1]!='0') f[i]+=f[i-1];
if(i>=2){
int t=(s[i-2]-'0')*10 + (s[i-1]-'0');
if(t>=10 && t<=26) f[i]+=f[i-2];
}
}
return f[n];
}
};