写两个字收缩
记录了一些刷题时遇到的算法。
快速幂取余
题目:剑指 Offer 14- II. 剪绳子 II、50. Pow(x, n)
定理:
引理:
证明:
结论:积的取余等于取余的积的取余。
可以推知:。
快速幂取余:
根据求余运算性质可以推出:
当 为奇数时 不是整数,所以分为以下两种情况:
利用以上公式进行循环迭代,可以将时间复杂度降低至对数级别O(logn),并且能够避免溢出。
C++实现:
1 | // 求 a ^ b % c |
参考:
- https://www.cnblogs.com/Dfkuaid-210/p/12115238.html
- https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/mian-shi-ti-14-ii-jian-sheng-zi-iitan-xin-er-fen-f/
Morris 中序遍历
题目:99. Recover Binary Search Tree
manacher 算法
用于匹配回文串。
KMP算法
题目:用于匹配字符串。
推导:
- 当T[i] != P[j]时
- 有T[i-j ~ i-1] == P[0 ~ j-1]
- 由P[0 ~ k-1] == P[j-k ~ j-1]
- 必然:T[i-k ~ i-1] == P[0 ~ k-1]
459. Repeated Substring Pattern
KMP由两部分组成,第一部分是是构建next数组,第二部分是进行匹配。
1 | class Solution { |
Hierholzer 算法
用于寻找欧拉通路。
Rabin-Karp 字符串哈希算法
用于匹配字符串。思路将字符串看成一个 进制的数,它对应的十进制值就是哈希值。显然,两个字符串的哈希值相等,当且仅当这两个字符串本身相同。然而如果字符串本身很长,其对应的十进制值在大多数语言中无法使用内置的整数类型进行存储。因此,我们会将十进制值对一个大质数 进行取模。
最大流之Ford-Fulkerson算法
bfptr找中位数算法
快速选择找中位数算法
该算法与快速排序算法类似,在一次递归调用中,首先进行partition过程,即利用一个元素将原数组划分为两个子数组,然后将这一元素放在两个数组之间。两者区别在于快速排序接下来需要对左右两个子数组进行递归,而快速选择只需要对一侧子数组进行递归,所以快速选择的时间复杂度为O(n)。
在C++中,可以用STL的nth_element()
1 | auto midptr = nums.begin() + nums.size() / 2; |
三向切分
1 | int i = 0, j = 0, k = n - 1; |
约瑟夫环
n
个数字排成一圈,从数字 0
开始,每次删除第 m
个数字,将问题的解记为f(n, m)
,其表示N个人报数,每报到M时杀掉那个人,最终胜利者的编号,有以下递推公式:
卡特兰数
卡特兰数(Catalan number)是组合数学中一个常出现在各种计数问题中的数列。
https://leetcode.cn/circle/article/lWYCzv/
https://blog.csdn.net/cz9797/article/details/105366774/
并查集
参考:https://zhuanlan.zhihu.com/p/93647900/
初始化(按秩合并)
1 | inline void init(int n) |
查找(路径压缩)
1 | int find(int x) |
合并(按秩合并)
1 | // 秩:以当前结点为根节点的子树的高度(rank) |
树状数组
其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和, 区间和。
简单介绍:https://blog.csdn.net/qq_40941722/article/details/104406126
建立树状数组
线状树
使用结构定义(待补充):
1 | struct SegNode { |
使用数组定义:
1 | int n = nums.size(); |
sum型线状树:307. 区域和检索 - 数组可修改
1 | // 线状树 |
max型线状树:
1 | // 线段树 |
二进制
原码、补码、反码的概念:https://blog.csdn.net/weixin_30940783/article/details/96858629
常见规则:
- 相同的数异或相抵,如果 $x_1\oplus x_2=0$,则$x_1==x_2$
- $x_1 \& -x_1$ 可以取出 $x$ 的二进制表示中最低位的那个 1。例子:12的二进制表示为 0000 1100,-12的二进制表示为12的反码+1为 1111 0011+1 = 1111 0100,使用上述公式得到最低位为 0000 0100。如果 $x_1==INT_MIN=-2147483648$会强制赋结果为
位运算
ASCII: 97~122小写字母,ASCII: 65~90大写字母
大写变小写、小写变大写 : 字符 ^= 32;
大写变小写、小写变小写 : 字符 |= 32;
小写变大写、大写变大写 : 字符 &= -33;
string toChangeCase(string str) {
for (int i = 0; i < str.size(); i++)
{
//大转小
if (str[i] >= 65 && str[i] <= 90)
{
str[i]=tolower(str[i]);
}
//小转大
else if (str[i] >= 97 && str[i] <= 122)
{
str[i]=toupper(str[i]);
}
}
return str;
}
会让最低位变成0,通过下列代码可以获得二进制的个数,类似__builtin_popcount(int n)
1 | int __builtin_popcount(int num) { |
可以取出最低位 1,如13的二进制表示为1101,那么-13的二进制表示为0010 + 0001 = 0011,那么1101 & 0011 = 0001,二进制末尾1的位置是:
1 | int lowbit(int m) { |
__builtin_ctz、__builtin_ctzl、__builtin_ctzll 返回输入数二进制表示从最低位开始(右起)的连续的0的个数
1 | int __builtin_ctzl(unsigned long x) { |