1. 求最大公约数(欧几里得算法)
def gcd(a,b):
return a if b == 0 else gcd(b, a % b)
2. 判断两个时间段是否有交集
def book(self, start, end):
for x, y in self.timeList:
# 如果时间段1的结束时间大于时间段2的开始时间 并且 时间段1的开始时间小于时间段2的结束时间 就是有交集的情况
if(x < end and y > start):
return False
self.timeList.append([start, end])
return True
3. 广度优先(bfs)
def maxDepth(self, root: Optional[TreeNode]) -> int:
maxDeep = 0
queue = Queue() # 需要一个队列用来存放遍历的节点
queue.put(root)
while not queue.empty():
size = queue.qsize() # 进到第二层循环前,队列中有多少元素代表了当前层有多少元素,当size为0,就退出
while size > 0:
node = queue.get()
if node.left:
queue.put(node.left)
if node.right:
queue.put(node.right)
size -= 1
maxDeep += 1
return maxDeep
4. 位运算的骚操作
异或操作:相同为0,不同为1。同或操作:相同为1,不同为0。
提取一个二进制最右边的1
num = 01010010
rightOne = num & (~num + 1)
找一个数组中出现一次的数字其他数字都出现两次,直接进行全部异或即可,但是找一个数组中两个数字出现一次其他数字都出现两次,就需要变换一下思路
eor = 0
for i in targetList:
eor ^= i
rightOne = eor & (~eor+1) # 找到1的位置
realEor = 0
for i in targetList:
if i & rightOne != 0: # 如果是不为0的部分则进行新的异或
realEor ^= i
another = realEor ^ eor # 找到另外一个值
print(another, realEor)
思路:整个数组只有两个数字出现了奇数次,整个数组异或下来结果实际上就是a ^ b, 也就是说结果肯定不等于0,那么肯定有一位是1,由于是异或出来的,所以整个数组分成两部分, 一部分一个位置为0,一部分相同位置是1,那么找到a ^ b的最右边的一个1提取出来,再次进行遍历,每个元素和提取出来的1进行与运算结果不为0的在进行一个全新的异或,这样就找到了那个位置不为1的,数字,然后a ^ b ^ b这样就能找出来a。
二进制进行加减乘除(不使用相关的运算符)
加法:思路是先将a和b进行异或(无进位相加),再将a和b先与运算然后左移一位(进位信息)如果结果不是0则继续将a和b进行上述操作,如果为0则直接返回异或的结果。
public int binAdd(int a, int b){ // 如果a和b溢出 那就是用户自己活该
int sum = a;
while (b != 0){ // 当b的为0的时候 异或的值就是相加的结果
sum = a ^ b; // 异或
b = (a & b) << 1; // 先与然后左移
a = sum;
}
return sum;
}
减法:就是在加法的基础上加个负数(取反再加一)
public int binMinus(int a, int b){
return this.binAdd(a, this.negtive(b));
}
public int negtive(int a){
return this.binAdd(~a, 1);
}
乘法:和平时的竖式计算一样,只不过需要转换成机器的形式。
public int binMulti(int a, int b){
int sum = 0;
while (b != 0){
if((b & 1) != 0){
sum = this.binAdd(sum, a); // 因为1*a还是a 所以直接加a
}
a = a << 1;
b = b >>> 1;
}
return sum;
}
除法:思路就是通过a右移知道b能够减掉a,然后再可以被减的位置打标记
public int binDiv(int a, int b){
int target = 0;
for (int i = 0; i < 32; i++) { // 最多右移32位
if((a >> i) >= b){ // 这里也可以进行左移,但是左移容易溢出
target |= 1 << i;
a = this.binMinus(a, b << i);
}
}
return target;
}
二进制进行大小比较(不使用相关运算符)
public int bigOrSmall(int a, int b){
int c = a - b;
int SA = this.signal(a); // 获取a的符号
int SB = this.signal(b); // 获取b的符号
int SC = this.signal(c); // 获取相减结果的符号
int diffAb = SA ^ SB; // 判断a和b的符号是否相同
int sameAb = this.opposite(diffAb);
int returnA = SA * diffAb + SC * sameAb; // 如果a和b符号不同 并且a是正数 返回a,如果a和b符号相同并且差值为正 也返回a,这两个是互斥条件
int returnB = this.opposite(returnA);
return a * returnA + b * returnB;
}
// 进行取反
public int opposite(int a){
return a ^ 1;
}
// 返回符号
public int signal(int a){
return (a >> 31) & 1;
}
5. 找入环的第一个位置
思路:先用快慢指针找到相遇的节点,然后快指针回到头结点每次一步,慢指针在相遇的节点每次一步,当再次相遇就是入环的第一个节点。
def detectCycle(self, head: ListNode) -> ListNode:
if not head or not head.next:
return None
slow = head.next
fast = head.next.next
while slow != fast:
if not fast or not fast.next:
return None
slow = slow.next
fast = fast.next.next
fast = head
while fast != slow:
fast = fast.next
slow = slow.next
return slow
6. 图解KMP算法
主要解决的问题就是在子串在主串中的定位问题。就是常用的 indexOf 这个功能,但是在Java语言中indexOf底层用的不是KMP算法。
在常规的字符串匹配中如果遇见不同的字符串导致本次匹配失败则从上次匹配位置的下一个位置继续匹配,如此循环往复。
KMP算法是在这个基础上对字符串匹配失败的时候进行了优化加速,使得复杂度为O(n)。主要是通过运用后缀数组的方式进行了 优化加速。
KMP算法流程图解:
首先需要对子串的 next数组(当前子串的最长公共前后缀的长度) 进行计算。
由于0位置和1位置的next数组的值是固定的,即 -1(人为规定) 和 0,则可以 推出i位置的next数组的值。
当来到2位置的时候会发现,2位置前的字符串没有最长公共前后缀,则当前位置为0.
继续来到3位置会发现此时的最长公共前后缀长度为1。
当来到4位置的时候会先去比较 i-1(也就是3位置) 和 cn(也就是1位置)的位置是否相等,如果相等则在原来的 i-1 基础上直接+1。如果不等直接跳到next[cn]的位置继续和 i-1 位置进行比对,知道next[cn] == -1 为止。
有了next数组,开始进行KMP匹配的时候如果两个位置的字符相同则继续向下个位置进行比较,直到遇见不相同的字符。
此时需要从子串字符N索引的后缀数组中取到子串指针需要跳到的位置进行再次比较。
如果相等继续向后进行匹配,如果不等还是从子串字符K索引的后缀数组中取到子串指针需要跳到的位置进行再次比较。
此时子串已经来到头部,已经无法向前再跳,则说明主串的F字符无法和子串进行匹配,此时需要将主串的位置+1,继续进行比对。
此时就会发现已经匹配成功了。
代码:
public int indexOf(String source, String target){
// 如果模式串的长度大于目标串直接返回-1
if(source == null || target == null || target.length() < 1 || target.length() > source.length()){
return -1;
}
char[] sourceChars = source.toCharArray();
char[] targetChars = target.toCharArray();
int[] nexts = this.getNextArr(targetChars); // 获取后缀数组
int i1 = 0;
int i2 = 0;
while (i1 < source.length() && i2 < target.length()){ // 保证都不会越界 如果i2越界代表已经完全匹配出目标串
if(sourceChars[i1] == targetChars[i2]){ // 如果两个都相等继续向下比对
i1++;
i2++;
}else if(i2 == 0){ // 这里判断条件也可以写成 next[i2] == -1
i1++; // i2已经到了起始位置,说明已经全部都匹配不上了,需要i1换个地方继续去比对
}else {
i2 = nexts[i2]; // 需要跳到最长公共前后缀的下一个位置, 真正的优化在这里, 往前跳的操作
}
}
return i2 == target.length()? i1 - i2 : -1;
}
// 计算公共最长前后缀
public int[] getNextArr(char[] target){
if(target.length == 1){
return new int[]{-1};
}
int[] nexts = new int[target.length];
nexts[0] = -1; // 规定next数组的第一个位置永远是-1
nexts[1] = 0; // 规定next数组的第二个位置永远是0
int cn = 0; // 要比对的位置,有点双指针的味道
int i = 2;
while(i < target.length){
// i位置的值取决于i-1和cn位置共同决定的,如果i-1和cn相等直接在原来的基础上进行+1
if(target[cn] == target[i - 1]){
nexts[i++] = ++cn;
}else if(cn > 0){ // 第一个条件不满足,则去找cn位置的最长公共前后缀长度的下一个位置和i-1进行比对
cn = nexts[cn]; // 往前跳的操作
}else {
nexts[i++] = 0; // 没有符合直接给0
}
}
return nexts;
}