算法笔记(一)
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 Falseself.timeList.append([start, end])return True3. 廣度優先(bfs)
def maxDepth(self, root: Optional[TreeNode]) -> int:maxDeep = 0queue = 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 -= 1maxDeep += 1return maxDeep4. 位運算的騷操作
異或操作:相同為0,不同為1。同或操作:相同為1,不同為0。
提取一個二進制最右邊的1
num = 01010010 rightOne = num & (~num + 1)找一個數組中出現一次的數字其他數字都出現兩次,直接進行全部異或即可,但是找一個數組中兩個數字出現一次其他數字都出現兩次,就需要變換一下思路
eor = 0 for i in targetList:eor ^= irightOne = eor & (~eor+1) # 找到1的位置realEor = 0 for i in targetList:if i & rightOne != 0: # 如果是不為0的部分則進行新的異或realEor ^= ianother = 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 Noneslow = head.nextfast = head.next.nextwhile slow != fast:if not fast or not fast.next:return Noneslow = slow.nextfast = fast.next.nextfast = headwhile fast != slow:fast = fast.nextslow = slow.nextreturn slow6. 圖解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){// 如果模式串的長度大于目標串直接返回-1if(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] == -1i1++; // 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數組的第一個位置永遠是-1nexts[1] = 0; // 規定next數組的第二個位置永遠是0int cn = 0; // 要比對的位置,有點雙指針的味道int i = 2;while(i < target.length){// i位置的值取決于i-1和cn位置共同決定的,如果i-1和cn相等直接在原來的基礎上進行+1if(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; }總結
- 上一篇: Linux 监控数据库资源占用
- 下一篇: 机器学习——深度学习之卷积神经网络(CN