- 博客(60)
- 收藏
- 关注
原创 C++中线程库的基本操作
结果之所以是随机的,是因为线程的调度顺序是由操作系统决定的,具有不确定性。两个线程是并发执行的,可能同时竞争输出资源std::cout.如果对顺序有要求,可以考虑使用互斥锁等,我们在接下来的文章再讲。有一些情况下,线程对象不能被.join()或.detach(),就要使用.joinable()进行检查。也就是说,你可能只能见到"h",也可能是"he",也可能是"hel","hell","hello".有时候我们不需要等待线程完成,而是希望它在后台运行,可以使用.detach()方法来分离线程。
2025-06-10 00:31:51
535
原创 力扣1584:最小生成树(prim/kruscal)
原本cur没加入之前,j到最小生成树的最小距离为dis1,现在cur加入了,由于graph[cur][j]比dis1还要小,所以j到最小生成树的最小距离需要更新。最小生成树问题一般指:在无向图中,希望用最小的成本将所有点都连接起来,其中成本指边权值。可以理解为修路,每个节点是一个城市,现在希望用最小的成本修路,以将这些城市都连接起来。然后根据边权值进行排序,贪心地从小到大选择,选择条件为:边两端的节点不在同一个集合当中。要定义一个数组min_dist,用来记录每个点到最小生成树的最小距离。
2025-05-16 09:05:55
462
原创 力扣210(拓扑排序)
需要注意三点:1.移除不是真的移除,只不过是把与这个节点相连的节点的入度减一;2.如果一个节点与两个或以上个节点相连,那么在移除了这个节点之后就会有多个选择,因此拓扑排序的结果不唯一;3.如果结果集的元素个数不等于图中节点个数,那么就必定有环。这是一道拓扑排序的模板题。简单来说,给出一个有向图,把这个有向图转成线性的排序就叫拓扑排序。如果有向图中有环就没有办法进行拓扑排序了。因此,拓扑排序也是图论中判断有向无环图的方法。用bfs的拓扑排序思路如下:1.找到入度为0的节点,加入结果集;
2025-05-13 00:01:15
390
原创 力扣1448/1457 二叉树中的dfs
如果root是叶子节点,那么root->left和root->right都为nullptr.这时候,root的value就很显然了,如果它是好节点,那么就为1,否则为0.这是可以直接return的。我们可以看到,每一棵(子)树的根节点都对应的一个值,我们称之为value,这个value代表的是以该节点为根节点的树中“好节点”的个数。实际上就是一个回溯的过程。那么,对于一个普通的处于中间层的节点,当它的value被计算出来后,就会return,给它的父亲节点去计算,它的使命也就完成了,逻辑合理,没有问题。
2025-05-09 00:17:09
804
原创 文件操作、流对象示例
我们现在桌面创建一个.txt文件,将里面的内容设置为abcdefghijklmn如果想要向这个文件中写入内容,我们需要借助文件流对象建立起文件与程序之间的联系。这么写会报错,因为在C++中,\是转义字符,如果只使用单个反斜杠,会报错:通用字符名的格式不正确。解决方法有两个:方法一:加两个反斜杠,就代表我们真的想写入反斜杠。方法二:使用原始字符串字面量。从 C++11 开始,可以使用原始字符串字面量,在字符串前加上R,并用括起字符串内容,这样字符串中的反斜杠就不会被当作转义字符处理。
2025-04-24 11:10:25
976
原创 对流对象的理解
既然如此,我们要进行输入输出的时候,就不一定非得用cin与cout了,我们可以自定义istream和ostream对象,比如定义一个istream对象叫input,定义一个ostream对象叫output,那么input>>和cin>>,output<<和cout<<的效果是一样的。所以每读入一行,就要输出line的内容。在C++中使用文件流,需要包含fstream头文件,它包含ifstream(用于从文件中读取数据)与ofstream(用于向文件写入数据)和fstream(既能读又能写)这几个类。
2025-04-23 23:01:12
492
原创 力扣3243(bfs/dp)
假设是0->1->2->3->4->5.现在我们新建一条路2->4,那么从4开始往后的节点到节点0的最短路长度就会改变。从4开始:这时与4直接相连的节点有3,2.开始遍历这两个节点:当u=3,dis[3]+1=dis[4],不改变;当u=2,dis[2]+1=3<dis[4],更新dis[4]。接着到5:由于改变了dis[4],所以dis[5]也会发生改变。也就是说,这种改变是从前往后推的。此题中,只需从0开始搜索与它直接相连的节点,更新距离。再进一步搜索与这些节点直接相连的节点,继续更新距离即可。
2025-04-23 10:24:57
357
原创 力扣2685(dfs)
我们对每个连通块进行dfs,在深搜的过程中,定义两个变量v,e.其中v表示该连通图的节点数量,e表示该连通图中边的数量的两倍。因为我们针对某个节点进行dfs的过程中,我们让e加上这个节点所连边的数量,如此一来,每条边都会被重复计算一遍。为什么是v*(v-1)?因为在完全连通分量中,边的数量为v*(v-1)/2(相当于在v个节点中选择2个的组合数),而每条边都被重复计算了一遍,所以要乘2.最后,我们看e是否等于v*(v-1)。如果是,那么完全连通分量的数量就+1,否则不变。
2025-04-22 11:35:00
281
原创 力扣2492:并查集/dfs
如果不仔细读题,可能会想着把每一个一维数组下标为2的位置进行排序即可。如果这些节点之间存在一个边权值比节点1所在集合的最小边权值还要小,那么求出来的答案就是错的。由于题目说了确保节点1与节点n之间存在至少一条路径,因此只需要对节点1所在的集合进行深度优先搜索即可。在深度优先搜索的过程中维护边权最小值。由于城市1与城市n之间至少有一条路径,那么也就意味着城市1与城市n是在同一个集合中。我们只需要求出这个集合中的边权值的最小值即可。在深搜之前,我们需要构建一个邻接矩阵进行节点的存储。方法二:深度优先搜索。
2025-04-21 20:14:37
335
原创 并查集(力扣2316)
现在我们要求的是无法互相到达的点对。根据观察易得,我们只需要求出每个并查集的元素数量,然后遍历每个点,设它所在的并查集元素数量为size,那么它所不能到达的点的数量就为n-size.最后除2即可。这种涉及不同连通分量的,看上去就可以用并查集。并查集的模板请参见上一篇内容。
2025-04-17 23:40:15
232
原创 并查集(力扣1971)
但当我们调用 isSame(1, 3)的时候,find(1) 返回的是1,find(3)返回的是3。return 1 == 3 返回的是false,代码告诉我们 1 和 3 不在同一个集合,这明显不符合我们的预期。将集合抽象为一棵n叉树后,为了方便判断两个节点的根是否相同,我们尽可能地让在同一个集合中的节点都直接与该集合的“根”连接,这样尽可能地缩小了这棵n叉树的高度,因而在查询的时候就提高了效率。因为在join函数里,我们有find函数进行寻根的过程,这样就保证元素 1,2,3在这个有向图里是强连通的。
2025-04-16 22:55:04
696
原创 图的深度优先搜索与广度优先搜索
为了节省空间,我们用一个结构体vector存储每个边的起点和终点,然后用一个二维vector存储边的信息。我们用e[a][b]=c,表示顶点a的第b条边是c号边。举个例子: 则在二维vector中的存储会如下所示: 由于题目要求:如果有很多篇文章可以参阅,先看编号较小的那篇。因此我们需要进行排序。按照题目要求,将终点从小到大排列,如果终点相同,则按起点从小到大排列。准备工作如下: main函数里的内容:深度优先搜索:一条路走到底,然后不断回溯。广度优先搜索:层序遍历
2025-04-15 10:56:21
205
原创 位运算与集合
包含于:A包含于B等价于:a&b==a或a|b==b.解释:a&b==a意味着a中为1的位置在b中对应的位置也为1,意即A有的元素B也有,反之a中为0的位置在b中可能为1也可能为0,因为我们进行的是按位与,所以无论是1还是0,和0进行按位与后都会变成0。然后再+1,把t中次低位的0改成1,后面的1改成0,与t按位或,由于t中第i位为0,且+1后s的第i位也为0,因此按位或之后该位还是保持为0,假设次低位为0的位置是第j位,那么此时S就是T加上第j位元素构成的集合。直到sub成为s去掉最大元素的集合。
2025-04-03 11:48:02
663
原创 C++中的智能指针
优势:1.更高效:make_shared会一次性分配内存(对象+控制块),而shared_ptr<Node>(new Node())需要两次分配 2.更安全:避免内存泄漏。make_shared<Node>()的功能:在堆内存中动态创建一个Node对象,并返回一个shared_ptr<Node>智能指针来管理它。每次析构shared_ptr,引用计数-1.计数归零时,自动释放内存。2.明确所有权语义:unique_ptr独占所有权,shared_ptr+weak_ptr共享所有权。
2025-04-01 19:20:55
946
原创 C++中指针与数组的关系
它们并不会引入新功能,只是让现有功能的表达更简洁、更符合人类直觉。这些语法在编译或解释时会被转换为更基础的底层实现。ptr[i]实际上是语法糖,等价于*(ptr+i).编译器会根据指针类型T*自动计算偏移量(i*sizeof(T))是编程语言中的一个术语,指那些。在 C/C++ 中,2.容器代替原生数组。
2025-04-01 11:32:47
174
原创 动态规划(01背包恰好装满型详解):和为目标值的最长子序列长度
前面我们说过,dp[i][x]实际上只由dp[i-1][x]决定,所以填充b只需要依赖a中的值。如果不选则dp[i][j]=dp[i-1][j].如果选,那么dp[i][j]=dp[i][j-w[i]]+v[i].选择二者中的最大值即可。当选择物品的范围为0-0时,意味着我们只能选第一个物品,那么最大价值和只能为v[0],由此可令dp[0][i]==v[0].0-1背包:有n个物品,第i个物品的体积为w[i],价值为v[i],每个物品至多选择一个,求体积和不超过capacity的最大价值和。
2025-03-24 23:59:45
518
原创 贪心:一道简单题的细节问题
这里我们不仅处理清楚了逻辑,还进行了优化。实际上,没有必要再遍历一次capacity数组找其中有多少个0了,因为答案=我们填充过的背包数量+原本就没有剩余容量的背包数量。因此,我们在遍历的过程中,直接处理ans即可。错在第19行和第20行。我们的逻辑是:剩余的额外石头的数量left减去填充进去的石头数量。但这里我们先让capacity[i]变成了0,那么left再自减capacity[i]就不起作用了。思路:计算出每个背包剩余的容量,再贪心即可。
2025-03-24 21:36:32
176
原创 剖析C++中继承、多态的底层原理
当通过基类指针调用虚函数时,程序通过对象的vptr找到虚函数表(因为创建对象的时候,vptr就被初始化指向虚函数表了),然后从虚函数表中查找对应虚函数的地址,最后调用该地址指向的函数。每个包含虚函数的类对象中都会包含一个指向虚函数表的指针(vptr),这个指针在对象创建时被初始化,指向该类的虚函数表。每个类都有自己的虚函数表,而不是每个对象都有自己的虚函数表。对象的内存布局中只包含数据成员和指向虚函数表的指针,该指针指向的是类的虚函数表,而不是对象的虚函数表(类对象的内存布局中不包含虚函数表)。
2025-03-23 14:19:21
633
原创 C++中的Lambda表达式详解
是 C++11 引入的一种匿名函数语法,允许你在代码中直接定义和使用函数,而无需显式声明一个命名函数。Lambda 表达式通常用于简化代码,尤其是在需要传递函数作为参数的场景中(如 STL 算法、多线程编程等)。
2025-03-06 13:33:15
746
原创 线程、进程
线程是进程中的一个执行单元,可以并发执行任务。线程共享进程的资源,但有自己的栈空间。多线程可以提高程序性能,但也带来了数据竞争、死锁等挑战。
2025-03-05 22:46:27
594
原创 小明的购物车(单例模式)
的作用是:(1)确保只有一个实例。(2)提供一个全局访问点来获取这个实例。(3)使用静态局部变量实现延迟初始化和线程安全。
2025-03-05 22:28:16
703
原创 力扣203.移除链表元素
一个小细节:为什么while的条件是cur!这两个看似重复的条件实际上是不一样的。因为有一种可能是,链表中所有节点的值都为val,那么这时cur就为nullptr了。=nullptr好理解,就是正常的终止条件(最后一个节点)最后,由于要返回新链表的头节点,所以不要忘记将virtualhead->next赋给head,因为原来的head可能被删除了,会有一个新head。我们构造一个新的头节点,让这个新的头节点指向原来的头节点即可。给你一个链表的头节点。
2025-03-03 21:20:53
205
原创 头文件保护宏
在 C++ 中,如果一个头文件被多次包含(例如,多个源文件都包含了同一个头文件),可能会导致重复定义错误。头文件保护宏的作用就是确保头文件的内容只被编译一次,避免重复定义。如果头文件被再次包含,#ifndef EXAMPLE_H会发现EXAMPLE_H已经定义,因此会跳过整个头文件的内容,避免重复定义。除了头文件保护宏,现代编译器还支持#pragma once,它的作用与头文件保护宏相同,但更简洁。在第一次包含头文件时,#define EXAMPLE_H会定义EXAMPLE_H宏。
2025-03-03 20:07:22
365
原创 静态成员函数的特点
静态成员函数只能访问类的静态成员变量和静态成员函数,不能直接访问非静态成员变量或非静态成员函数。3.静态成员函数通常用于操作静态成员变量或提供与类相关但不依赖于对象的功能。这是因为非静态成员属于类的实例,而静态成员函数不依赖于任何实例。静态成员函数没有 this 指针,因为它们不与任何对象绑定。2.可以通过类名直接调用静态成员函数,而不需要创建对象。因此,静态成员函数不需要通过类的实例(对象)来调用。静态成员函数属于类本身,而不是类的某个特定对象。1.静态成员函数属于类本身,而不是类的实例。
2025-03-03 19:57:50
169
原创 力扣977.有序数组的平方(双指针)
操作方法如下:由于是非递减数组,并且其中可能有负数,那么平方后最大的两个数只有可能是第一个(即负数中最小的那个)和最后一个(即正数中最大的那个)。我们可以想到双指针,一个从前往后,一个从后往前,分别比较指向元素平方后的大小。比较后我们将较大的元素存到k这个位置,然后k自减,指向平方后较大的元素的指针就移动一位,重复上述过程。题目给我们的数组是一个非递减的数组,那么我们可以利用好这个条件减小时间复杂度。方法一:直接将每个元素的平方压入ans数组中,再对ans数组进行排序。组成的新数组,要求也按。
2025-03-03 09:09:03
184
原创 力扣27.移除元素(双指针)
如果快指针指向的元素的值不等于val,那么先覆盖,然后慢指针也跟着往后遍历,这保证了不等于val的元素都放在了前面。如果值等于val,那么慢指针停下,快指针继续移动,直到遇见了不等于val的元素,再将其进行覆盖。最后快指针指向末尾,快指针与慢指针之间的差值就是等于val的元素的个数,因此慢指针的值就是不等于val的元素个数。题目看起来很乱,实际上意思是:把数组中值不等于val的元素放在下标为0,1,2,3......,并且返回数组中值不等于val的元素的个数。方法一:直接判断+覆盖。
2025-03-02 17:10:01
206
原创 二叉树的中序遍历(三种方法)
从这个例子看,我们可以受到启发。递归与迭代都是利用栈LIFO的性质实现的,而Morris是通过找到当前遍历到的节点x的左子树的最右侧节点predecessor并建立一个自下而上访问的通道,即:将predecessor的右孩子指向x.通过predecessor右孩子的指向可以知道x的左子树是否全都遍历了,如果是,那么通过我们前面的通道返回x。2.如果x有左孩子,那么我们找到x左子树上最右侧的节点,记为predecessor(predecessor是x的左子树中序遍历的最后一个节点,意味着它是x的前驱节点)。
2025-02-16 14:17:23
811
原创 两种方法反转链表(双指针迭代与递归)
解释:假设一个链表为[1,2,3],一开始传入1那个节点,不满足return条件,继续跑代码,需要递归调用该函数,于是传入2那个节点,不满足return条件,继续跑代码,需要递归调用该函数,传入3那个节点,满足return条件,那么直接return,所以cur就是最后一个节点3.由于head->next为3,那么head就为倒数第二个节点。但这时候head的next还是指向最后一个节点,所以应该让head的next指针指向空,防止尾节点与倒数第二个节点形成循环。最后cur指向空,pre指向尾节点。
2025-02-11 23:07:49
206
原创 使用按位或设计大小写字母转换的API
进而我们发现,[65,90]对应的二进制表示为[(01000001),(01011010)],32对应的二进制表示为(00100000).即:所有大写字母ASCII码从右往左数第六位(也就是表示32的位)都为0(因为字母A的ASCII码的二进制表示为01000001,而大写字母只有26个),而所有小写字母ASCII码表示32的位都为1,所以我们不妨对大写字母进行按位或运算。按位或运算:只要有1则为1,否则为0(即只有两个都为0才为0)。如果是0,0|0=0),只有表示32的位会从0变成1.
2025-02-08 23:15:32
208
原创 题目26(盛最多水的容器·贪心)
而min(height[L0],height[r])要么等于height[r],要么等于height[L0].如果等于height[L0],那么意味着height[L0]比height[r]还小,在宽度由(l-r)变小为(L0-r)的前提下,高度还减小了,面积就必然减小。如果等于height[r],那么在宽度由(l-r)变小为(L0-r)的前提下,高度保持不变,面积也必然减少。如果移动的是长木板对应的指针,假设是左指针,记为L0,那么min(height[l],height[r])==height[r].
2025-02-08 20:57:45
227
原创 题目25(有效的字母异位词)
由于字符串只包含 26 个小写字母,因此我们可以维护一个长度为 26 的频次数组 cnt,先遍历记录字符串 s 中字符出现的频次,然后遍历字符串 t,减去cnt中对应的频次,如果出现 cnt[i]!=0,则说明 t 包含一个不在 s 中的额外字符,返回 false 即可。因此我们可以对字符串 s 和 t 分别排序,看排序后的字符串是否相等即可。此外,如果 s 和 t 的长度不同,t 必然不是 s 的异位词。即两个字符串的字符并不相同,但ASCII码之和相同的情况。法一:统计字母的出现次数。
2025-02-04 11:57:42
287
原创 C++中全排列函数next_permutation和prev_permutation
这两个函数的作用是一样的,区别在于前者求的是当前排列的下一个排列,后一个求的是当前排列的上一个排列。此外,对于next_permutation(),只能找出当前序列之后的全排列数。同理,对于prev_permutation,如果将保持上述排列为{1,2,3}并且保持(num,num+3),那么什么都不会输出。可见,next_permutation(num.num+n)是对数组num中的前n个元素进行全排列,同时改变num数组的值。若当前序列不存在下一个排列时,函数返回false,否则返回true。
2025-02-01 20:29:51
178
原创 二维vector
/通过a[i].size()获取第i+1行(假定第一行为首行)有多少元素,即可知列数。//通过a.size()获取行数。因为a中的元素是vector,有几个vector就有几行。size()可求vector中有效数据的数量。capacity()求的是容量。定义了一个r行c列初始值为3的二维数组。定义了一个r行c列的二维数组,且初始值为0.二、添加与删除:以第一行、第一列为首行首列。方法一:直接在定义时进行初始化。
2025-01-30 23:20:19
607
原创 rand()函数
这个时间点被称为Unix纪元(Unix epoch).所以,每次运行时间不一样,通过srand()函数给rand()函数设定的seed的值X0也就不一样,因此产生的随机数序列也就不一样了。time(NULL)与getpid()的不同之处是:time(NULL)获取的是时间戳值,与程序的进程无关。需要注意的是,rand()函数为伪随机数,其内部实现是用线性同余法做的,并不是真的随机数,因其周期特别长,故在一定的范围内可以看成是随机的。也就是说,对于rand()%a+b,a控制的是整数的个数,b控制的是起点。
2025-01-30 18:40:28
332
原创 题目反思 24(数字字符串格式化)
去除前导零有两种方法:1.使用stod函数字符串转换为double精度的浮点数 2.使用find_first_not_of函数 这里第一种方法显然是不合适的,虽然在转换为double精度的浮点数的过程中,会自动去掉前导零,但是如果小数点后有很多位,使用这种方法只会保留六位。思路比较清晰:1.去除前导零和后导零 2.找到小数点位置,将字符串分割成整数部分和小数部分。用法:substr(pos,pos+len).pos表示子串的起始位置,len表示子串的长度,如果没有写pos+len,默认到字符串的末尾。
2025-01-07 11:33:06
203
原创 题目反思23(利用异或)
解释一下:假设cards里面的元素为a,b,c,a,b,c,d.那么经过上述运算后结果为0^a^b^c^a^b^c^d,由于异或运算满足结合律,因此可以写为a^a^b^b^c^c^d,由于相同的数异或为0,因此最终结果就是d。先来看异或的性质:相同则为0,不同则为1.同时异或运算满足交换律和结合律。即(a^b)^c==a^(b^c).有了以上的性质,我们可以设结果为res并初始化为0.将res与cards里面的所有元素进行异或并自增即可。一个很巧妙的做法:利用异或。
2025-01-07 00:04:18
114
原创 题目反思 21
思路比较清晰:创建一个字符数组a,输入,然后把不是‘-’的位置转换成整数进行运算(转换成整数的方法:减去'0')。问题在于,如果最后的识别码有误,该怎么改正呢?solution:创建一个字符数组mod[12]="0123456789X“.这样我们就可以把mod中的元素赋给a[12]。
2024-12-22 20:17:33
229
原创 题目反思 20(结构体数组的排序)
结果有两个数据点超时了,这里的冒泡排序用了两层for循环,可以再优化一下。问题是:如何在对z直接使用swap进行排序的同时能够让x和y一直伴随着z呢?
2024-11-28 21:56:12
211
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人