并查集
并查集定义
并查集:一种维护集合的数据结构, 可以支持两种操作
1. union: 合并两个集合 2. find:判断两个元素是否在一个集合中
并查集的操作
并查集是由father链接构成的树
1 | int father[MAX]; //每个结点保存自己的father结点的下标 |
1 | // |
相关题目:
[code]:
1 |
|
并查集:一种维护集合的数据结构, 可以支持两种操作
1. union: 合并两个集合 2. find:判断两个元素是否在一个集合中
并查集是由father链接构成的树
1 | int father[MAX]; //每个结点保存自己的father结点的下标 |
1 | // |
[code]:
1 | #include<iostream> |
这里用来记录PAT解题的一些考点,注意事项,源代码请到这里[link]去找. 按照题号写的, “XX_w2.cpp”是参考的大神的代码
同类题目:
考点: 利用 图的DFS / BFS 求叶子结点的最小层(要减枝,不然会超时)
利用vector<int> Nodes[max] 构建邻接链表
问题
- 结果要求保留小数点后4位, 需要控制输出格式 , cout<<setiosflag(ios::fixed)<<setprecision(4)<<…
- 一般用double代替float,保留一定的精度
- 边界min_depth的初值我设置成9999, 有点小,卡了三个样例,要注意边界值的设置
- bfs,我经常容易忘记结点出队/=,害
相关考点:
1. BST的构建--insert,creatBST模板函数 2. DFS
问题:
这次还是边界设置错啦,–int depth=-1; //只有一个结点的时候,出错
相关考点:
1.并查集的find 和 union 模板(==遗留问题, 路径压缩的并查集和非路径压缩有什么区别??,这里带路径压缩的find会导致出错)
这里的4位的id是直接用int来表示的,方便并查集的实现;不过最后的输出格式要额外处理
- 如何把int转换成string,并且高位用0填充
- char des[10] ; int src = 1998 ;
- sprintf(des,”%04d”,src); //把src转换为4位的string,并用0填充,转换的结果存储到des中.
经常开一个比较大的数组作为全局变量.节省一些处理.
这道题开大数组,索引起来特别方便.不过要增加一个vis[10000]数组表示id是有效的.
(自己做题总是小心翼翼,不想浪费太多的空间)
这道题数据处理的过程,可以多学习一些,自定义了两个struct,一个用来处理输入(输入一个结点,进行一次union操作,int father[]用来保存并查集结构) ;另外一个用来处理输出,(保存每个集合的统计信息)
判断图是否是欧拉图,半欧拉图,非欧拉图
欧拉图的定义
欧拉回路:图G中经过每条边一次并且仅一次的回路称作欧拉回路
欧拉通路:图G中经过每条边一次并且仅一次的路径称作欧拉通路。
欧拉图(Eulerian graph)指的就是存在欧拉回路的图。
半欧拉图(semi-Eulerian graph)指的是存在欧拉通路但不存在欧拉回路的图。1.无向图中:
无向图G为欧拉图,当且仅当G为连通图且所有顶点的度为偶数。
无向图G为半欧拉图,当且仅当G为连通图且除了两个顶点的度为奇数之外,其它所有顶点的度为偶数。
2.有向图中:
有向图G为欧拉图,当且仅当G为连通图,且所有顶点的入度等于出度。
有向图G为半欧拉图,当且仅当G为连通图,且存在顶点u的入度比出度大1,v的入度比出度小1,其它所有顶点的入度等于出度。
基本思路:
考点
如何判断是否是连通图?
1.利用并查集(在接收输入时,构建并查集) ,时间复杂度
2. 一次深搜DFS ,时间复杂度:
1 | struct Node{ |
1 | Node* find(Node* root ,int val){ |
1 | void L(Node*& root){ |
1 | void R(Node* & root){//指针引用型 |
1 | /* |
1 | void insert(Node*& root, int v){ |
之前就是插入之后,忘记updateHeight(),出错了
<==================较为复杂,之后再填坑======>
1 | Node* CreatAVL(vector<int> nodes){ |
[link]大致意思是判断一个插入序列是否是合法的BST/镜像BST先根序列,如果是的话,求它的后根
原理: root是第一个元素, 比root小的 // 比root大的 聚集在一起
方法2:也可以把它作为插入序列,跟据他构建一个BST树,再求这个新建的BST的先根序列,如果一致,则之前那个插入序列就是先根序列.
如果是合法的镜像BST前缀,按照次作为插入序列,构造镜像BST
[代码]
1 | ///还是只能过部分测试样例,==,害,好难嗷 |
如何跟据插入序列构建一个BST树
1 | struct Node{ |
BST的中根遍历序列是递增的
BST的删除操作
//核心思想:如果N为要删除的结点, 用结点N的前驱Pre 或者 后继 next 来替换N
//前驱为BST树中左子树最右的结点
//后继为BST树中右子树最左的结点
删除值为X的结点
1.当前root为空,说明不存在值为x的结点,直接返回
2.如果当前root的权值恰好为给定的权值x,root即为我们要删除的结点
1)如果root不存在左右孩子(叶子),则直接删除.
2)如果root只存在左子树,则用它的前驱pre覆盖root,在左子树中删除结点pre
3)如果root存在左右子树,则在右子树中寻找后继next,然后让next覆盖root,接着在左子树中删除pre
3.如果当前root的值大于给定的权值x,从root的左子树中寻找要删除的结点
4.如果当前root的值小于给定的权值x,从root的右子树寻找
1 | Node* findMax(Node* root){ |
BFS:搜索过程中,遇到分叉口时,总是先依次访问从该分叉口能直接到达的所有结点,然后再按照这些结点被访问的顺序去依次访问他们能直接到达的所有结点。以此类推,直到所有结点都被访问为止。
1 | #include<iostream> |
题解思路:
首先要先确定解题的状态(构造一个结构体,保存当前字符串,移位的次数)
合适拓展状态:
出队时,把当前字符串从0-k-1,都与右侧的元素交换, 移位次数加一(另外要有一个vis保存已经遍历过的字符串,已经判断过的字符串不再入队) 结束状态:
找到符合要求的字符串,返回移位次数
队列为空,返回.
1 | #include<iostream> |
分析
1 | class Solution { |
动态规划问题的一般形式是求最值,e.g. 最长递增子序列。
核心问题:穷举求最值. 不过,动态规划的穷举有点特别,因为这类问题存在「重叠子问题」,如果暴力穷举的话效率会极其低下,所以需要「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。而且,动态规划问题一定会具备「最优子结构」,才能通过子问题的最值得到原问题的最值。
动态规划三要素:重叠子问题、最优子结构、状态转移方程, 如果能够列出来状态转移方程,算法的框架也就基本确定了,只需要考虑一些细节问题即可。
可以从子问题的最优结果中推导出更大规模问题的最优结果
可以采用递归调用树,如果递归调用树中存在多个相同的状态结点,则问题具有重叠的子问题
明确「状态」 -> 定义 dp 数组/函数的含义 -> 明确「选择」-> 明确 base case
问题中可能会发生变化的变量,我们做选择的条件(划分了不同的子问题)
e.g. : 在0-1背包中,存在两个状态,「 背包的容量」和 「可选择的物品」
dp概括了原问题和子问题的最优解的目标
e.g. 在0-1背包中,dp[i][j]表示对于前i
个物品,当前背包的容量为j
时.可以容纳的最大重量
导致状态转移的操作,在0-1背包中,表示第i个物品是否装入背包
初始化条件
1、遍历的过程中,所需的状态必须是已经计算出来的。
2、遍历的终点必须是存储结果的那个位置。
目前我认为回溯是自顶向下求解,可以利用备忘录优化( C++的备忘录,可以采用 vector<int>mem 存储某个状态下dp的内容,也可以用 unordered_map<string, int> string是状态下标转换的字符)
动态规划是自低向上求解,是利用循环遍历代替暴力递归的一种思路(回溯大多是暴力递归).
可以参考下面的文章中的例子仔细体会
常见的0-1背包的问题描述:给你一个可装载重量为W
的背包和N
个物品,每个物品有重量和价值两个属性其中第i
个物品的重量为wt[i]
,价值为val[i]
,现在让你用这个背包装物品,最多能装的价值是多少?
解决两个字符串的动态规划问题,一般都是用两个指针i,j
分别指向两个字符串的最后,然后一步步往前走,缩小问题的规模。
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
1
2 >输入:s = "aa" p = "a*"
>输出:true
按照上面介绍的过程分析一下吧!
明确「状态」 -> 定义 dp 数组/函数的含义 -> 明确「选择」-> 明确 base case
状态:在s,p匹配过程中,状态有两个:匹配到s串的位置i, 匹配到p串的位置j
定义dp含义:题目要求是判断两个串是否匹配成功, dp[i][j]表示s[:i] 和 p[:j]两个子串是否匹配,为true 表示匹配成功,否则失败
最终目标是求得dp[s.size()][p.size()]的值
选择:这里的选择感觉指的是 如果当前p串要匹配的字符是*
,s串是选择匹配0个/1个/多个
base case以及一些细节信息:如果出现空串如何匹配
为了方便计算,我们定义dp[i+1][j+1]表示s[:i] 和 p[:j]的匹配结果,并且dp[0][0]=true,
同时考虑到,如果出现s为空串,p为”#*"类型,e.g. s=””,p=”a*”,
1 | s=' '+s; |
如果不设置这个,不会进入状态转移判断方程,直接退出循环,误判为 匹配失败。
dp转移方程
1 | bool isMatch(string s, string p) { |
(不增加备忘录,会出现超时)
1 | unordered_map<string,int> dp; |
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
Sunday 算法
主要思路:
从前往后进行匹配,匹配成功时,模式串和主串索引同时+1,指向下一个
如果匹配失败,关注的是主串中参与匹配的最末位字符的下一位字符
如果该字符没有出现在模式串中,移动位数=模式串长度+1(因为移动位数<模式串长度,都会失败)
eg: sudbccsde 模式串为:sde,此时b没有出现在模式串中,可以直接略过它,主串从b的下一个位置,与模式串从头匹配
如果该字符出现在该模式串中,移动位数 = 模式串长度 - 该字符最右出现的位置(以0开始) = 模式串中该字符最右出现的位置到尾部的距离 + 1,主串移动到该位置 和模式串从头开始匹配
1 | int Sunday( string mstr,string des){//mstr为主串,des为模式串,返回模式串在主串中第一次出现的位置,如果不存在返回-1 |
思路:定义状态,设计状态转移方程
bool dp[i][j]表示str.substr(i,j-i+1)是否是回文字
动态转移方程: dp[i][j] = dp[i+1][j-1] && (str[i]==str[j])
记录使 dp[i][j]为true的j-i+1的最大值。
时间复杂度: O(n*n)
空间复杂度: O(n*n)
思路:定义状态,设计状态转移方程
目标串 s ,模式串p
bool dp[i][j]表示s[0:i]和p[0:j]之间匹配的结果
利用堆栈的思路解决
如果遇到字符串,压入堆栈中,如果遇到
.
不变
巧用栈,但是由于
1 |
|
细节题。注意测试样例
9.利用str模拟大数相加
10.Add Binary
确定有限状态自动机(以下简称「自动机」)是一类计算模型,能够回答某种形式的「对于给定的输入字符串 S,判断其是否满足条件 P」的问题。它包含一系列状态,这些状态中:
起初,这个自动机处于「初始状态」。随后,它顺序地读取字符串中的每一个字符,并根据当前状态和读入的字符,按照某个事先约定好的「转移规则」,从当前状态转移到下一个状态;当状态转移完成后,它就读取下一个字符。当字符串全部读取完毕后,如果自动机处于某个「接受状态」,则判定该字符串「被接受」;否则,判定该字符串「被拒绝」
注意:如果输入的过程中某一步转移失败了,即不存在对应的「转移规则」,此时计算将提前中止。在这种情况下我们也判定该字符串「被拒绝」
如何挖掘所有可能的状态,得到一个完备的状态集?
跟据接受的内容,确定划分接受数据的类型
确定状态转移的规则
在c++中,可以用enum(枚举)存储自动机状态State,定义接受的数据类型(InputType)
用unordered_map<State , unordered_map<InputType,State>>
相关例题
1 | class Solution { |
使用时,需要#include<string>
1 | string str; |
1 | //1.直接通过下标访问 |
1 | //operator+=,支持两个string直接拼接 |
1 | length()/size() //返回字符串长度 |
1 | bool isalnum(char c); //用于检测一个字符是否是字母或者十进制数字 |
1 | //===大小写字母转换===transform |
版权声明:本文为CSDN博主「ningto.com」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tujiaw/article/details/6976424
给定一个增序排列数组 nums ,你需要在 原地 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例:输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。你不需要考虑数组中超出新长度后面的元素。
思路分析:
首先,用变量maxRepeat=2来表示每个元素最多出现两次。定义指针slow,其初始值为maxRepeat-1=1。**
规定区间[0,slow]内的元素为最多出现两次的元素。 [0,slow]中的元素保存的是符合出现次数要求的元素集合
定义变量fast=maxRepeat=2,指向当前考察的元素。
当前考察的元素nums[fast]== nums[slow-maxRepeat-1],即变量fast所指向的元素1在区间[0,slow]中已出现两次。
因此继续考察下一个元素,fast向右移动一位。
当nums[fast] != nums[slow - maxRepeat + 1],即指针fast指向的元素2不等于指针slow - maxRepeat + 1所指向的元素1,
nums[fast]所指的元素在区间[0,slow]内,未出现超过两次,因此需要将慢指针slow向右移动一位来扩大区间。
故slow++;接着将快指针fast指向的元素值赋予慢指针slow所指向的位置。
1 | class Solution { |
作者:hardcore-aryabhata
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array-ii/solution/kuai-man-zhi-zhen-80-shan-chu-pai-xu-shu-4rnk/
来源:力扣(LeetCode)
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
自己的思路:使用两个指针pv,lv; 遍历时,保证pv之前保存的都是0,保证 lv之后保存的都是2;
注意:cv计数器:遍历的范围是(0,lv] ,否则会重新调换回来;当cv所指内容是2时,cv所指元素与–lv所指元素调换之后,cv不能直接指向下一个元素; 而和++pv调换之后,cv需要直接指向下一个(因为++pv所指内容只能是1)
1 | class Solution { |