当前位置: 首页 > news >正文

7.11 dp 图

 

lcr148.栈

按放入顺序推栈,能弹出的就及时弹出,最后栈空则符合要求。

判断 takeOut 序列是否符合栈的操作逻辑,因为题目中“特殊的数据结构”其实就是栈(先进后出)。

思路如下:

1. 用一个栈来模拟图书放入的过程。
2. 按 putIn 的顺序依次将图书推入栈中。
3. 每次推入后,检查栈顶元素是否和 takeOut 当前待匹配的元素相同:
- 如果相同,就弹出栈顶元素,并移动 takeOut 的匹配指针。
- 重复此检查,直到栈顶元素和待匹配元素不同。
4. 当 putIn 的元素全部处理完后,若栈为空,说明 takeOut 是正确的拿取序列,返回 true ;否则返回 false 。

class Solution {

public:

    bool validateBookSequences(vector<int>& putIn, vector<int>& takeOut) 

    {

        stack<int> st;

        int i=0;

        for(int& p:putIn)

        {

            st.push(p);

            

 while(!st.empty() && st.top()==takeOut[i])

            {

                st.pop();

                i++;

            }

           

        }

        return st.empty();

    }

};

lc2316.dfs无向图联通块计算

class Solution {
public:
long long countPairs(int n, vector<vector<int>> &edges) {
vector<vector<int>> g(n);
for (auto &e: edges) {
int x = e[0], y = e[1];
g[x].push_back(y);
g[y].push_back(x); // 建图
}

        vector<int> vis(n);
function<int(int)> dfs = [&](int x) -> int {
vis[x] = true; // 避免重复访问同一个点
int size = 1;
for (int y: g[x]) {
  if (!vis[y]) {
size += dfs(y);

}
}
return size;
};

        long long ans = 0;
for (int i = 0, total = 0; i < n; i++) {
  if (!vis[i]) { // 未访问的点:说明找到了一个新的连通块
int size = dfs(i);
ans += (long) size * total;

total += size;
}
}
return ans;
}
};

lc1042. 不领接植花

邻接表涂色

给花园涂色:

1. 建邻接表
2. 涂色时,先看它相邻的用了什么颜色(记在 used 里)。
3.  找第一个没被用过的颜色,给当前花园涂上。

最后返回所有花园的颜色数组

class Solution {
public:
vector<int> gardenNoAdj(int n, vector<vector<int>>& paths) {
// 邻接表存连接关系
vector<vector<int>> g(n);
for (auto& p : paths) {
// 花园编号转成0开始(原是1开始)
int x = p[0] - 1, y = p[1] - 1;
g[x].push_back(y);
g[y].push_back(x);
}

        // 存每个花园的颜色(1-4)
vector<int> color(n);
for (int i = 0; i < n; i++)

          {
// 记录邻居用过的颜色
bool used[5] = {false};
for (int j : g[i]) {
used[color[j]] = true;
}

            // 找第一个没被用过的颜色
int c = 1;

while (used[c]) {
c++;
}
color[i] = c;
}
return color;
}
};

 

lc3186.施咒

 

dp[i] 表示“考虑前i个不同数字时,能得到的最大总和”。

排好序后,对每个数字,要么跳过它,要么选它(同时只能搭配前面和它差≥3的数字),始终选总和最大的方案。

注意,就在于对于重复值的处理


1. 先整理数据:统计每个数字出现的次数(比如数字5出现3次,总和就是5×3),再把数字从小到大排序。
2. 动态规划记录最大和:用 dp[i] 表示“考虑前i个不同数字时,能得到的最大总和”。


3. 核心逻辑:
- 对每个数字 x (带次数 c ),先找最前面一个“和 x 的差小于3”的数字位置 j (意味着 j 之前的数字都能和 x 共存)。
- 决策:要么不选 x ,总和就是 f[i] (继承前i-1个的结果);要么选 x ,总和就是 f[j] + x×c ( j 之前的最大和加上 x 的总贡献),取两者更大的那个。


4. 最终结果: f[n] 就是考虑所有数字后能得到的最大总和。

class Solution {
public:
long long maximumTotalDamage(vector<int>& power) {
unordered_map<int, int> cnt;
for (int x : power) {
cnt[x]++;
}

        vector<pair<int, int>> a(cnt.begin(), cnt.end());    //hash转化方法
sort(a.begin(),a.end());

        int n = a.size();
vector<long long> f(n + 1);
for (int i = 0, j = 0; i < n; i++)

          {
auto& [x, c] = a[i];
while (a[j].first < x - 2)

            {
  j++;   // 利用了单调性
}
 f[i + 1] = max(f[i], f[j] + (long long) x * c);

//选不选
}
return f[n];
}
};

 

 

lc2222. 状态机dp

简单dp:

 dp[k][j]  记录“选  k  个字符、最后是  j ”的方案数,

class Solution {
typedef long long ll;
public:
long long numberOfWays(string s)
{

int n=s.size();
if(n<3) return 0;
vector<vector<ll>> dp(4,vector<ll>(2,0));
dp[0][1]=dp[0][0]=1;

for(int i=0;i<n;i++)  //遍历子串,填表
{
//k要倒着加,要不然就多加了
for(int k=3;k>=1;k--)

{
if(s[i]=='0')
dp[k][0]+=dp[k-1][1];
else
dp[k][1]+=dp[k-1][0];
}
}
return dp[3][0]+dp[3][1];
}
};

k  倒着遍历是为了 确保状态转移时, dp[k-1][...]  用的是“上一轮”的旧值,避免当前轮更新的

 


lc442. 数组中重复的数据

模拟hash映射的原理  o(n)且不额外空间

“原地标记”

  • 压榨完num的价值,数组自身的下标和符号 代替哈希表,num遍历查看完一个元素后,还原地通过符号标记修改。
  • 利用了数也在1,n范围的特性

找出数组里恰好出现两次的数,返回这些数。

常规思路(哈希表,空间不够好)

优化思路(原地标记,空间 O(1))

 

利用数组下标和元素值的特殊关系:

- 数组长度是 n,元素值范围是  [1, n] ,下标范围是  [0, n-1] 。

- 可以把下标和元素值对应起来(比如值为 num 的数,对应下标 num-1  ),通过改元素正负,标记这个数是否出现过。

 

具体步骤(遍历+标记)

1. 遍历数组,对当前数 num :

- 先取绝对值(因为可能被改过符号),算对应下标  index = |num| - 1 ;

- 看 nums[index] 的符号:

- 若为正:说明 num 是第一次出现,把 nums[index] 改成负数(标记“已访问”);

- 若为负:说明 num 之前出现过,现在是第二次,把 num 加入结果(这就是重复的数)。

 

举个例子

比如数组是  [4,3,2,7,8,2,3,1] :

- 遍历到 4 ,对应下标 4-1=3 , nums[3] 是 7 (正)→ 改成 -7 ;

- 遍历到 3 ,对应下标 3-1=2 , nums[2] 是 2 (正)→ 改成 -2 ;

- ……

- 遍历到 2 ,对应下标 2-1=1 , nums[1] 是 -3 (负)→ 说明 2 重复,加入结果;

- 最终找出所有重复两次的数。

 

核心巧妙点

数组自身的下标和符号代替哈希表,不用额外空间,完美解决“找重复两次的数”的问题,时间 O(n)、空间 O(1) 。

 

class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> duplicates;
int n = nums.size();
for (int i = 0; i < n; i++) {
int num = nums[i];
int index = abs(num) - 1;
  if (nums[index] > 0) {
nums[index] = -nums[index];

}

          else {
duplicates.push_back(index + 1);
}
}
return duplicates;
}
};

 

http://www.dtcms.com/a/274586.html

相关文章:

  • 企业IT管理——医院数据备份与存储制度模板
  • spring-ai RAG(Retrieval-Augmented Generation)
  • 【网络】Linux 内核优化实战 - net.netfilter.nf_conntrack_max
  • 【网络】Linux 内核优化实战 - net.netfilter.nf_conntrack_buckets
  • 基于深度学习的人类活动识别模型研究:HAR-DeepConvLG的设计与应用
  • C++,从汇编角度看《虚拟继承的邪恶》
  • 多模态联邦学习
  • STM32F103ZET6 TFTLCD显示图片
  • Docker构建银河麒麟arm架构redis镜像
  • Windows下基于docker desktop 使用Claude code
  • MySQL中使用group_concat遇到的问题及解决
  • 容器管理: 单机用Docker Compose,多机用Kubernetes
  • Docker高级管理--Dockerfile 镜像制作
  • 8.卷积神经网络基础
  • CSS 版本迭代历史和版本特性
  • ffmpeg-api记录
  • 8、鸿蒙Harmony Next开发:相对布局 (RelativeContainer)
  • PortsWiggerLab: Blind OS command injection with output redirection
  • 2D下的几何变换(C#实现,持续更新)
  • linux获取NTP方式
  • ubuntu22默认安装firefox使用snap安装还老打不开解决办法
  • 【Spring】拦截器详解
  • jenkins部署springboot+Docker项目
  • Playwright Python 教程:网页自动化
  • xAI发布Grok4+代码神器Grok4 Code,教你如何在国内升级订阅SuperGrok并使用到Grok4教程
  • 信号量机制
  • 【机器学习】保序回归平滑校准算法
  • 设计模式(结构型)-适配器模式
  • 深度学习入门教程(三)- 线性代数教程
  • 使用you-get命令下载视频/音频/图像