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

7.16 拓扑排序 | 欧拉回路 |链表排序 前缀和

 

lc1109.前缀和

res[book[0] - 1] += book[2], res[book[1]] -= book[2];

多开一块,记录和上一个的变化后,来累加

  res[i] += res[i - 1];

暴力tle

class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n)
{
vector<int> ret(n,0);
for(auto& b:bookings)
{
for(int i=b[0];i<=b[1];i++)
{
ret[i-1]+=b[2];
}
}
return ret;
}
};

 class Solution {

public:

    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {

        vector<int> res(n+1, 0);

        for (auto& book : bookings) 

            res[book[0] - 1] += book[2], res[book[1]] -= book[2];

        res.pop_back();

   //多开一块,记录和上一个的变化

 

        for (int i = 1; i < n; ++i)

            res[i] += res[i - 1];

        return res;

    }

};

 

lc147.链表插入排序

 

 

lc2065.含返回&限制的最大价值和

class Solution {
public:
int maximalPathQuality(vector<int>& values, vector<vector<int>>& edges, int max_time) {
int n = values.size();
vector<vector<pair<int, int>>> g(n);
for (auto& e : edges) {
int x = e[0], y = e[1], t = e[2];
g[x].emplace_back(y, t);
g[y].emplace_back(x, t);
}

        int ans = 0;
vector<int> vis(n);
vis[0] = true;
auto dfs = [&](auto&& dfs, int x, int sum_time, int sum_value) -> void

     {
if (x == 0) {
ans = max(ans, sum_value);
// 注意这里没有 return,还可以继续走
}
for (auto& [y, t] : g[x]) {
if (sum_time + t > max_time) {
continue;
}
if (vis[y]) {
dfs(dfs, y, sum_time + t, sum_value);
} else {
vis[y] = true;
// 每个节点的价值至多算入价值总和中一次
dfs(dfs, y, sum_time + t, sum_value + values[y]);
vis[y] = false; // 恢复现场
}
}
};
dfs(dfs, 0, 0, values[0]);
return ans;
}
};

 

欧拉回路

lc753.欧拉回路-破解密码箱

能从图中一点出发,不重复地走完所有边,最后回到起点的路径,就是欧拉回路。

每个点的入度==出度,则定为欧拉图


 

用深度优先搜索(DFS)结合集合去重,构造出满足条件的字符串:

#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;

class Solution {
public:
string crackSafe(int n, int k) {
// n 为 1 时,直接返回 0 到 k - 1 拼接的字符串
if (n == 1) {
string res;
for (int i = 0; i < k; ++i) {
res += to_string(i);
}
return res;
}
unordered_set<string> S;  // 用于记录已经用过的 n 长度(或相关)的子串,避免重复走
string ans;  // 存储最终要返回的结果字符串
int m = pow(k, n) + n - 1;  // 计算结果字符串的总长度

        // 定义深度优先搜索函数,参数 a 是当前已经构造的字符串
function<void(string)> dfs = [&](string a) {
if (a.size() == m) {  // 如果当前构造的字符串长度达到了目标长度
ans = a;  // 把当前字符串赋值给结果
return;
}
// 取当前字符串的后 n - 1 个字符,作为下一次拼接的基础
string o = a.substr(a.size() - (n - 1), n - 1);  
for (int i = 0; i < k; ++i) {  // 尝试拼接 0 到 k - 1 这些数字
string s = o + to_string(i);  
if (S.find(s) == S.end()) {  // 如果这个拼接后的子串没在集合里,说明没走过
S.insert(s);  // 标记为已走过
dfs(a + to_string(i));  // 继续往下构造字符串
S.erase(s);  // 回溯,把标记去掉,尝试其他可能
if (!ans.empty()) {  // 如果已经找到结果了,就直接返回,不用再搜了
return;
}
}
}
};

        dfs(string(n - 1, '0'));  // 初始字符串用 n - 1 个 '0' 开头,开始深度优先搜索
return ans;
}
};


解释

1. 整体功能:
要生成一个特殊字符串,这个字符串能包含所有由  n  位、每位取值是  0  到  k - 1  组成的不同“片段” 。打个比方, n = 2 , k = 2  时,要涵盖  00 、 01 、 10 、 11  这些两位的组合情况,最终构造出一个把这些片段按规则串起来的长字符串。
2. 特殊情况处理( n == 1 ):
如果  n  是  1  ,那直接把  0  到  k - 1  这些数字转成字符拼接起来返回就行。比如  k = 3  , n = 1  时,就返回  "012"  。
3. 核心逻辑(深度优先搜索  dfs  ):
- 参数与终止条件: dfs  函数参数  a  是当前已经构造出的字符串。当  a  的长度达到  m (前面计算好的目标总长度 ),就把  a  赋值给  ans  ,结束当前这条搜索路径。
- 子串截取( o  的作用):每次从当前字符串  a  的末尾取  n - 1  个字符作为  o  。这  n - 1  个字符是为了和后面要拼接的数字  i  组成一个新的、长度可能符合我们要去重判断的子串(用来保证每个  n  位的片段只出现一次 )。
- 尝试拼接与去重:循环  i  从  0  到  k - 1  ,把  o  和  i  转成的字符拼接成  s  。用集合  S  检查  s  有没有出现过,没出现过就标记(插入集合)、接着递归调用  dfs  往下构造字符串,递归返回后再回溯(从集合移除  s  )。一旦  ans  被赋值了(找到结果了 ),就直接返回,停止继续搜索。
- 初始调用:最开始调用  dfs  时,用  n - 1  个  '0'  组成的字符串开头,启动整个深度优先搜索的过程,去尝试构造出符合要求的长字符串。

这样通过深度优先搜索不断尝试拼接数字,结合集合去重,就能构造出涵盖所有  n  位  k  进制组合片段的结果字符串啦

 

lc3243

优化方向:图的dp

 class Solution {
public:
vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {
vector<vector<int>> g(n - 1);
for (int i = 0; i < n - 1; i++) {
g[i].push_back(i + 1);
}
vector<int> vis(n - 1, -1);

        auto bfs = [&](int i) -> int {
vector<int> q = {0};
for (int step = 1; ; step++) {
vector<int> nxt;
for (int x : q) {
for (int y : g[x]) {
if (y == n - 1) {
return step;
}
if (vis[y] != i) {
vis[y] = i;
nxt.push_back(y);
}
}
}
q = move(nxt);
}
};

        vector<int> ans(queries.size());
for (int i = 0; i < queries.size(); i++) {
g[queries[i][0]].push_back(queries[i][1]);
ans[i] = bfs(i);
}
return ans;
}
};

图的dp

 class Solution {

public:

    vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {

        vector<vector<int>> from(n);

        vector<int> f(n);

        iota(f.begin(), f.end(), 0);

 

        vector<int> ans(queries.size());

        for (int qi = 0; qi < queries.size(); qi++) {

            int l = queries[qi][0], r = queries[qi][1];

            from[r].push_back(l);

            if (f[l] + 1 < f[r]) {

                f[r] = f[l] + 1;

                for (int i = r + 1; i < n; i++) {

                    f[i] = min(f[i], f[i - 1] + 1);

                    for (int j : from[i]) {

                        f[i] = min(f[i], f[j] + 1);

                    }

                }

            }

            ans[qi] = f[n - 1];

        }

        return ans;

    }

};

 

lcr113

解决“课程安排”问题的,简单说就是找一个合理的上课顺序,满足先修课要求。

核心思路

用“拓扑排序”的方法:先上没有先修课的,上完一门课后,把它的后续课程的先修要求减一,直到所有课都排好,或者发现排不了(有循环依赖)。

步骤拆解

1. 建图和统计先修课数量:
- 用 graph 记录每个课程的后续课程(比如上完课A才能上B,就记A→B)。
- 用 inDegress 记录每个课程需要的先修课数量(比如B需要1门先修课A,就记为1)。
2. 找起点:
- 把所有没有先修课( inDegress 为0)的课程放进队列,这些是可以先上的。
3. 按顺序选课:
- 从队列里选一门课,放进结果列表。
- 这门课的后续课程,先修课数量减一(因为这门课已经上完了)。
- 如果某后续课程的先修课数量变成0,就把它放进队列,接下来可以上。
4. 检查结果:
- 如果结果里的课程数量等于总课程数,说明能排好,返回结果。
- 否则说明有循环依赖(比如A要先上B,B要先上A),返回空列表。

比如有3门课,要求先上0才能上1,先上0才能上2,那代码会返回 [0,1,2] 或 [0,2,1] ,都是合理的顺序。

class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
unordered_map<int, vector<int>> graph;
vector<int> inDegress(numCourses, 0);
for (auto& pre : prerequisites) {
graph[pre[1]].push_back(pre[0]);
inDegress[pre[0]]++;
}

        vector<int> ret;
queue<int> que;
for (int i = 0; i < inDegress.size(); ++i) {
if (inDegress[i] == 0) {
que.push(i);
}
}

while (!que.empty()) {
int node = que.front();
que.pop();
ret.push_back(node);
for (auto& n : graph[node]) {
inDegress[n]--;
if (inDegress[n] == 0) {
que.push(n);
}
}
}

        if (ret.size() != numCourses) {
return {};
}       
return ret;
}
};

 

子序列dp

 

lc3202

利用取余后,交替出现的性质

“找出整数数组  nums  中最长有效子序列长度”问题的

 

 1. 整体思路

要找满足条件的最长子序列,条件是子序列中相邻两个数的和对  k  取余的结果都相等。

代码通过动态规划的思路,用一个二维数组  f  来记录状态,逐步更新并找到最长有效子序列的长度。

 

2. 变量和数组含义

-  ans :用来保存最终找到的最长有效子序列的长度,初始为  0  。

-  f :是一个  k×k  的二维数组, f[a][b]  可以理解为:以对  k  取余后结果为  a  的数开头,接着一个对  k  取余后结果为  b  的数时,能形成的有效子序列的长度 。

 

3. 循环过程

- 外层循环(遍历  nums  数组):

每次取出数组里的一个数  x ,先把  x  对  k  取余(因为我们关心的是和对  k  取余的结果,取余后计算更方便 )。

- 内层循环(遍历  0  到  k - 1 ):

对于当前取余后的  x ,遍历  y ( y  从  0  到  k - 1  )。 f[y][x] = f[x][y] + 1  这一步是核心:

- 可以想象成,之前有以  x  开头、 y  接着的情况(对应  f[x][y]  ),现在反过来,以  y  开头、 x  接着,就能形成新的序列,长度就是之前  f[x][y]  的长度加  1  。

- 然后用  f[y][x]  去更新  ans ,保证  ans  始终是当前找到的最长有效子序列长度。

 

4. 举个简单例子辅助理解

比如  nums = [1,2,3] , k = 2  :

- 处理  x = 1 ( 1 % 2 = 1  ):

内层循环  y  从  0  到  1  。此时  f  里的值都是初始的  0  ,所以  f[0][1] = f[1][0] + 1 = 0 + 1 = 1  , f[1][1] = f[1][1] + 1 = 0 + 1 = 1  ,然后  ans  会被更新为  1  。

- 处理  x = 2 ( 2 % 2 = 0  ):

内层循环  y  从  0  到  1  。当  y = 0  时, f[0][0] = f[0][0] + 1 = 0 + 1 = 1  ;当  y = 1  时, f[1][0] = f[0][1] + 1 = 1 + 1 = 2  ,这时候  ans  会被更新为  2  。

- 后续再继续处理  x = 3 ( 3 % 2 = 1  ),不断更新  f  和  ans  ,最终得到最长有效子序列的长度。

 

简单来说,代码就是利用二维数组  f  记录不同余数开头和接着的序列长度情况,通过不断遍历数组元素,更新这些状态,从而找到最长有效子序列的长度,巧妙地利用动态规划思路解决了问题。不过要注意,这种实现背后的数学逻辑是基于子序列相邻和取余相等的条件,通过这样的状态转移来覆盖可能的有效子序列情况 。

 class Solution {

public:

    int maximumLength(vector<int>& nums, int k) {

        int ans = 0;

        vector f(k, vector<int>(k));

        for (int x : nums) {

            x %= k;

            for (int y = 0; y < k; y++) {

                f[y][x] = f[x][y] + 1;

                ans = max(ans, f[y][x]);

            }

        }

        return ans;

    }

};

lc3201

 class Solution {

public:

    int maximumLength(vector<int>& nums)

     {

        int k=2;

        int ans = 0;

        vector f(k, vector<int>(k));

        for (int x : nums) {

            x %= k;

            for (int y = 0; y < k; y++) {

                f[y][x] = f[x][y] + 1;

                ans = max(ans, f[y][x]);

            }

        }

        return ans;

    }

};

 

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

相关文章:

  • Vue在线预览Excel和Docx格式文件
  • Redis学习其一
  • Python学习之路(十三)-常用函数的使用,及优化
  • Redis读写策略深度解析:高并发场景下的缓存兵法
  • python基础语法9,用os库实现系统操作并用sys库实现文件操作(简单易上手的python语法教学)
  • 猫眼娱乐IOS开发一面手撕算法
  • 嵌入式学习笔记--MCU阶段--DAY06DHT11练习
  • AR智能巡检:电力行业数字化转型的“加速器”
  • 基于Llama的RAG 3种模型配置方法
  • 51c自动驾驶~合集7
  • 基于C#开发solidworks图库中文件(SLDPRT,SLDASM,SLDDRW等)转换为HTML和PDF,提供批量和实时转换
  • AI产品经理面试宝典第28天:自动驾驶与智慧交通融合面试题与答法
  • 自动驾驶激光3D点云处理系统性阐述及Open3D库函数应用
  • MR 处于 WIP 状态的WIP是什么
  • 小模型的价值重估:从“缩水版DeepSeek”到AI系统的基础执行单元20250716
  • Linux 挂载新磁盘导致原文件被隐藏解决方案
  • 【代码】Matlab鸟瞰图函数
  • sqli-labs靶场通关笔记:第23关 注释符过滤
  • 叉车机器人如何实现托盘精准定位?这项核心技术的原理和应用是什么?
  • 静默的田野守护者:Deepoc具身智能如何让除草机器人读懂大地密语
  • Mybatis08-使用pageHelper
  • 本地 AI 问答机器人搭建项目(Ollama + Qwen-7B + LangChain + FastAPI)
  • AI对话聊天与桌宠工具调研报告
  • 【案例分享】基于FastCAE-Acoustics软件对车门进行噪声预测
  • 移动平板电脑安全管控方案
  • 祥云系统开源云商城程序全开源版 个人程序云商城(源码下载)
  • 前端学习7:CSS过渡与动画--补间动画 (Transition) vs 关键帧动画 (Animation)
  • xss-lab1-8关
  • AdsPower 功能详解 | 应用中心使用指南:插件统一管理更高效、更安全!
  • [NOIP][C++] 树的重心