线性预处理|dfs回溯
lc1921
按到达时间排序
class Solution {
public:
int eliminateMaximum(vector<int>& dist, vector<int>& speed)
{
int n = dist.size();
vector<pair<int, int>> w;
for (int i = 0; i < n; ++i) {
w.push_back({dist[i], speed[i]});
}
sort(w.begin(), w.end(), [&](pair<int, int>& a, pair<int, int>& b) {
// 到达时间
return (a.first + a.second - 1) / a.second < (b.first + b.second - 1) / b.second;
});
int t = 0, ret = 0;
for (int i = 0; i < n; ++i) {
if (t < (w[i].first + w[i].second - 1) / w[i].second) {
ret++;
t++;
} else {
break;
}
}
return ret;
}
};
lc08.02
回溯
带回溯的DFS
限定机器人仅向下/向右移动,标记已访问节点
if(a==m-1 && b==n-1)
{
found = true;
return;
}
并在路径无效时回溯
if(!found)
ret.pop_back();
寻找从网格左上角到右下角的可行路径(存在则返回路径,否则返回空数组)。
class Solution {
int dx[2]={0,1};
int dy[2]={1,0};
typedef pair<int,int> pii;
int m,n;
vector<vector<int>> ret,obstacleGrid;
bool found = false; // 标记是否找到路径
public:
vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid)
{
m=obstacleGrid.size();
if(m==0) return {};
n=obstacleGrid[0].size();
this->obstacleGrid=obstacleGrid;
if(obstacleGrid[0][0]==1)
return {};
ret.push_back({0,0}); // 先加入起点
dfs(0,0);
return found ? ret : vector<vector<int>>();
}
void dfs(int a,int b)
{
if(a==m-1 && b==n-1)
{
found = true;
return;
}
for(int k=0;k<2;k++) // 仅遍历向下、向右两种移动
{
int x=a+dx[k],y=b+dy[k];
if(x>=0 && y>=0 && x<m && y<n && obstacleGrid[x][y]==0 && !found)
{
ret.push_back({x,y});
obstacleGrid[x][y]=2; // 标记为已访问
dfs(x,y);
if(!found) // 若这条路径没找到,回溯(移除当前节点)
ret.pop_back();
}
}
}
};
lc2100
预处理+线性校验
求解“适合抢劫银行的日子”:通过两个数组分别记录每个位置向左连续非递增、向右连续非递减的天数
最终筛选出两侧连续天数均不小于time的索引

class Solution {
public:
vector<int> goodDaysToRobBank(vector<int>& security, int time) {
int n = security.size();
vector<int> f1(n), f2(n);
for(int i = 1, j = n-2; i < n; ++i, --j){
if(security[i]<=security[i-1]) f1[i] = f1[i-1] + 1;
if(security[j]<=security[j+1]) f2[j] = f2[j+1] + 1;
}
vector<int> ans;
for(int i = time; i < n-time; ++i){
if(f1[i] >= time && f2[i] >= time) ans.push_back(i);
}
return ans;
}
};
抽象
二维背包
lc474
三维dp
求解0-1二维背包问题的标准实现:在最多使用m个'0'和n个'1'的约束下,从字符串数组中选取最多字符串
三维dp表记录前i个字符串、j个'0'、k个'1'场景下的最大选串数
逐一生成字符串并决策选或不选以更新最优解
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n)
{
int len=strs.size();
vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(m+1,vector<int>(n+1,0)));
//0 个 元素 初始化为0
for(int i=1;i<=len;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=n;k++)
{
int a=0,b=0;
for(char& c:strs[i-1])
{
if(c=='0')
a++;
if(c=='1')
b++;
}
//不选
dp[i][j][k]=dp[i-1][j][k];
if(j>=a && k>=b)
//选
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-a][k-b]+1);
}
}
}
return dp[len][m][n];
}
};
二维背包_优化
在最多用 m 个“0”和 n 个“1”的限制下,从给定字符串数组中选最多的字符串(每个字符串含若干0和1)
核心:
1. 用 dp[j][k] 表示用 j 个0、k 个1能选到的最多字符串数;
2. 逐个遍历字符串,统计其含有的0和1数量;
3. 倒序更新 dp 表,判断是否能选当前字符串(不超0、1配额),选则更新最大数量
01背包_逆序
三维dp变二维时逆序遍历,是为了避免当前轮次刚更新的dp值被重复使用(即防止物品被多次选取) ,保证每个字符串仅被决策“选或不选”一次,符合0-1背包的核心规则。
- 逆序:先更新大j,再更新小j,保证用的是“上一轮未被当前字符串修改的旧值”,避免同一字符串被重复选。
- 正序:先更新小j,再更新大j,后续大j会用到当前轮已修改的新值,相当于同一字符串被多次选
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n)
{
int len=strs.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
//0 个 元素 初始化为0
for(int i=1;i<=len;i++)
{
for(int j=m;j>=0;j--)
{
for(int k=n;k>=0;k--)
{
int a=0,b=0;
//下标映射
for(char& c:strs[i-1])
{
if(c=='0')
a++;
if(c=='1')
b++;
}
if(j>=a && k>=b)
dp[j][k]=max(dp[j][k],dp[j-a][k-b]+1);
}
}
}
return dp[m][n];
}
};
lc879
class Solution {
typedef long long ll;
public:
int profitableSchemes(int n, int m, vector<int>& group, vector<int>& profit)
{
int w=group.size();
vector<vector<ll>> dp(n+1,vector<ll>(m+1));
//针对 dp[0][j][0]=1 //没工作 有人 没利润
for(int j=0;j<=n;j++)
dp[j][0]=1;
for(int i=1;i<=w;i++)
{
//xia biao
for(int j=n;j>=group[i-1];j--)
{
for(int k=m;k>=0;k--)
{
dp[j][k]+=dp[j-group[i-1]][max(0,k-profit[i-1])];
dp[j][k]%=(ll)(1e9+7);
}
}
}
return dp[n][m];
}
};
