c++算法二
1.位运算
常见位运算总结
1.基础位运算
&:有0就是0
|:有1就是 1
^:相同为 0,相异为1/无进位相加
2.给一个数 n,确定它的二进制表示中的第x位是0还是 1
(n>>x)&1:结果是0就是0,结果是1就是1
3.将一个数 n 的二进制表示的第 x 位修改成 1
n=n|(1<<x)
4.将一个数 n 的二进制表示的第 x位修改成 0
n=n&(~(1<<x))
5.位图的思想
6.提取一个数(n)二进制表示中最右侧的
n&(-n)
-n:将最右侧的 1,左边的区域全部变成相反
7.干掉一个数(n)二进制表示中最右侧的
n&(n-1)
将最右侧的 1,右边的区域(包含1)全部变成相反
9.异或(^)运算的运算律
1.1.判断字符是否唯一
解题思路:
在使用数据结构的时候,想到的解题思路就是
将字符串遍历一遍,并使用hash表,将每个字符进行记录。在不使用hash表的时候,我们可以使用位图来对hsh表进行替代
代码实现:
细节处理:
1.2.丢失的数字
解题思路:
使用异或的运算规律
代码实现:
1.3.两整数之和
解题思路:
此时我们要使用异或来解决此道题
A^B:结果两者无进位相加
(A&B)<<1:恰好是两者进位的位置
正常操作是要将两者进行相加得到最终的结果但是,不能使用运算符,
所以继续重复刚才的操作,直到(A&B)<<1(进位)为0为止
代码实现:
1.4.只出现一次的数字
解题思路:
我们将数组中所有的数给相加,每一个bit位上无非出现四种情况
我们将相加后的最终的结果给%3,会发现,和只有一个数的bit位一样的,就是我们要找的数
其实我们就可以进行衍生,如果是n个数的话,我们就%n就行
代码实现:
2.模拟
2.1.替换所有的问号
解题思路:
从前往后遍历数组一边,遇见?先判断?的位置,保证?的左边和右边或者只是左边,只是右边和替换的内容都不相同
代码实现:
2.2.提莫攻击
解题思路:
计算总共的中毒时间,无非就两种情况,
第一种情况:两次释放技能的时间间隔小于中毒时间,统计的是两次释放技能的间隔时间
第二种情况:两次释放技能的时间间隔大于中毒时间,统计的是正常中毒作用的时间
代码实现:
细节注意:
记得统计最后一次中毒的时间
2.3.Z字形变换
解题思路:
这道题其实就是找规律的一道题
第一步:计算我们的公差 d=2n-2
第二步:找到每一行之间,每一个元素之间的规律
代码实现:
细节注意:
当公差为0的时候,直接返回原数组即可
2.4.外观数列
解题思路:
模拟+双指针的思路:
定义left和right ,从前向后进行遍历,right先向后走,当找到left和right指向的字符不相同的时候,right停下,right-left计算这一段相同字符的个数,再次使left=right,重复刚才的操作
代码实现:
细节注意:
1.将数字转换成字符串
j
2.5.数🐸
解题思路:
这道题我们可以借助一个hash表来实现
主要的流程就是从前向后遍历一边数组,此时会遇见下面几种情况
代码实现:
3.分治
3.1.快排
3.1.1.移动零
解题思路:
数组分块
在一开始的时候,定义两个指针
cur:从左往右扫描数组,遍历数组
dest:已处理的区间内,非零元素的最后一个位置
所以此时数组就会被我们划分成这几部分
两个大区间:
以cur为分界,分成已处理的区间和待处理的区间
三个小区间:
当以这个特性遍历完数组,数组就被分成非0区间和0区间
cur向后遍历的流程:
1.遇到 0 元素:cur++;
2.遇到 非零元素:
swap(dest + 1, cur);dest++,cur++;
代码实现:
3.1.2.颜色划分
解题思路:
移动零是将数组分成 0区间和非0区间两块
颜色划分是将数组分成 红 白 蓝 三块区间
所以此时我们我们要在上道题的基础上再加上一个指针
定义三个指针:
i:遍历数组
left:标记 0 区域的最右侧
right:标记 2 区域的最左侧
此时数组会被分成四块区间:
[0,left]:全都是 0
[left +1,i-1]:全都是 1
[i,right-1]:待扫描的元素
[right, n-1]:全都是 2
i向后遍历的流程:
在于++left交换的时候,此时0~ i是已处理的区间所有 i++
但是--right交换的时候,i和--right是未处理的element,所以i不能++
代码实现:
细节注意:
此时循环结束的条件是i<right;
3.1.3.快速排序
解题思路:
参照颜色分类,这题也是将数组分成三块,
然后再对<key和>key部分进行相同的操作
i向后遍历的流程:
代码实现:
细节处理:
如何挑选出基准值?
采用随机的方式挑选基准值
使用rand和time首先要包含下面两个头文件
获取随机数流程:
3.1.4.数组中的第K个最大的元素
解题思路:
这道题也就是上道题的延伸,我们可以再上面的步骤加上一步就可以解决这道题
在我们对数组进行分成>key和=key和<key三个区间后,我们计算三个区间中各有多少个element
再根据每个区间的个数,做出不同的操作
代码实现:
3.1.5.最小K个数
解题思路:
依旧是数组分成三块
再计算每一个区间中element的个数
代码实现:
细节处理:
记得处理边界情况:
使用迭代器构造:
3.2.归并
3.2.1.归并排序
解题思路:
- 分割:将数组递归二分至单个元素
- 合并:有序合并两个已排序子数组
代码实现:
代码实现:
这里采用的辅助数组是全局变量,只需在代码执行之前创建一次,提升了时间复杂度和空间复杂度
3.2.2.逆序对
解题思路:
假设此时我将数组分成左右两块
那么此时
逆序对总数=左边的逆序对+右边的逆序对+再将左边和右边进行比较的逆序对
此时我们就知道,逆序对可以使用分治的思想来计算,
但是我们再想想有上面办法可以很快的计算出,左右俩块之间的逆序对
假设此时左右两块都是升序的
恰好此时nums[cur1]>nums[cur2]
那么逆序对=mid-cur1+1,
仔细的就可以发现其实这就是归并排序的步骤,使用我们只需要在归并排序的步骤中加入一个统计两块数组之间的逆序对的操作就行
代码实现:
细节处理:
注意:逆序对是严格的前方的大于后方的等于是不行的,所以这里的判断条件是<=
3.2.3.计算右侧小于当前元素的个数
解题思路:
这道题和上道题的题意是一样的,只是上道题求的是逆序对的总数,这道题求解的是每一个数,所对应的逆序对,
上题:找出该数之前,有多少个数比我大,我们数组是升序的,比较好
这题:找出该数之后,有多少个数比较小,这中数组是降序的,比较好
代码实现:
细节处理:
在我们排序的时候会改变数组中元素中原有的顺序,所以我们要定义一个数组index,用于记录原数组中他的下标位置。
所以我们在对数组进行排序的时候,不仅要对数组进行记录,对数组对应的下标也要进行记录
3.2.4.翻转对
解题思路:
其实这几道题多有一个公共的特性,就是我虽然在你的前面,但是我比你要大,而此时这个的条件是 你是要是我的两倍大才行,像之前那样,边排序边统计肯定是不行的,因为这次要两倍
但是我们这道题还是使用分治的思想来做的
此时我们就可以利用左右两块数组都是有序的特性,定义两个同向双指针来统计翻转对,
和之前一样有两种策略
策略一:计算当前元素后面,有多少元素的两倍比我小------降序
策略二:计算当前元素之前,有多少元素的一半比我大------升序
所以我们可以在数组排序之前使用O(n)级别的复杂度,就可以完成此题目
代码实现:
细节处理:
1.统计翻转对的时机是在排序之前进行统计
2.有个2*nums[cur2]的操作,此时可能会有溢出的风险,所以要改变其类型