经典算法-并查集、快速排序、字典序算法、二分搜索、牛顿开方法、求质数(筛选法)、编辑距离、滑动窗口、异或求重、长除法
目錄
??????????????
并查集
快速排序
字典序算法
二分搜索
開根號-牛頓開方法
求質數
編輯距離
滑動窗口
異或求重
長除法
???????
并查集
并查集用于解決相同元素集合動態連接的快速構建算法
例:求相等集合a=b,e=d,d=b
初始時,ab為一集合,ed為一集合,又d=b,故應將abed變為一集合,之后e=a就是顯而易見的事了
并查集利用構建樹形結構的方式,每個元素有上級,上級有上級,最高級的上級為其本身,通過查看兩個元素的最高級是否相同來判斷兩元素是否屬于同一集合
即,每個集合為一棵樹,判斷兩個元素是否屬于同一個集合只用判斷其樹根是否相同即可
初始時有數組pre[],pre[i]表示元素i的上一級
find(int x)用于找出元素x的最高級,即樹根
union(int x,int y)用于將x,y所代表的兩個集合合并
例題(leetcode-990):
給定一個由表示變量之間關系的字符串方程組成的數組,每個字符串方程?equations[i]?的長度為?4,并采用兩種不同的形式之一:"a==b"?或?"a!=b"。在這里,a 和 b 是小寫字母(不一定不同),表示單字母變量名。
只有當可以將整數分配給變量名,以便滿足所有給定的方程時才返回?true,否則返回?false。?
示例 1:
輸入:["a==b","b!=a"] 輸出:false 解釋:如果我們指定,a = 1 且 b = 1,那么可以滿足第一個方程,但無法滿足第二個方程。沒有辦法分配變量同時滿足這兩個方程。示例 3:
輸入:["a==b","b==c","a==c"] 輸出:true示例 5:
輸入:["c==c","b==d","x!=z"] 輸出:true提示:
參考:https://blog.csdn.net/liujian20150808/article/details/50848646
快速排序
快速排序以分治法為基本思想,遞歸為基本表現形式。
對于一個數組nums,傳遞參數left,right,為范圍,以第一個數(nums[0])為key,將所有<=key的數放在左邊,所有>key的數放在右邊。
i=left,j=right,向前推進i直到找到第一個>key的數,向后推進直到找到第一個<=key的數
交換i,j的數的位置直到i==j
退出循環,此時i==j,核查nums[i]與key,保證<=i的數全部<=key,>j的數全部>j。因為以nums[0]為基準,nums[0]不能在參加遞歸運算,交換nums[i]與nums[0],繼續遞歸,范圍為[left,i-1],[j,right]
遞歸的結束條件即為left>=right
? ? private void sort(int[] nums,int left,int right){if(left>=right)return ;int i=left,j=right;int key=nums[left];while(i<j){while(i<j&&nums[j]>key){j--;}while(i<j&&nums[i]<=key){i++;}int tmp=nums[i];nums[i]=nums[j];nums[j]=tmp;}if(nums[i]<=key)j++;else i--;nums[left]=nums[i];nums[i]=key;//基準數不再參與數據處理sort(nums,left,i-1);sort(nums,j,right);}字典序算法
字典序即按照a-zA-Z0-9的順序對由這些元素組成的串的從小到大的全排列。
如[1,2,3]進行全排列,將1,2,3組成數字,字典序就相當于按所有組成數字從小到大進行排列。
123->132->213->231->312->321
字典序算法可用于全排列(n!)生成
字典序算法的步驟為:
- 1.從右至左找到第一個左鄰小于右鄰,記錄左鄰位置i
- 2.從右至左找到第一個大于左鄰的數,即位置為j,交換num[i],num[j]
- 3.對位置i之后的所有數進行排列
例題(leetcode-31)
實現獲取下一個排列的函數,算法需要將給定數字序列重新排列成字典序中下一個更大的排列。
如果不存在下一個更大的排列,則將數字重新排列成最小的排列(即升序排列)。
必須原地修改,只允許使用額外常數空間。
以下是一些例子,輸入位于左側列,其相應輸出位于右側列。
1,2,3?→?1,3,2
3,2,1?→?1,2,3
1,1,5?→?1,5,1
二分搜索
太經典了,看代碼即可,nums為已排序數組
? ? ? ? int left=0,right=nums.length-1;while(left<=right){int med=(left+right)/2;if(nums[med]==target)return med;else if(nums[med]<target){left=med+1;}else{right=med-1;}}開根號-牛頓開方法
實際上就是完成一個sqrt()函數,計算機完成開根號的運算,大部分使用牛頓迭代法。
假設a為要開方的數,那么x就是答案,即求f(x)=0時,x為何值
選取函數上一點(t,t^2-a),對于該點,求切線,切線上在x軸方向上的零點必定逼近于結果
切線的斜率對原函數求導即可,k=2t,該切線經過點(t,t^2-a),求切線在x軸方向上的零點,即(t^2+a)/2t
以該點繼續迭代即可不斷逼近與結果
? ? public int mySqrt(int x) {//牛頓迭代法//y=res^2-x,選取點(a,a^2-x)做切點//斜率即求導數k=2a,令y=2ares`+t//帶入化簡,零點為res`=(a^2+x)/2a,繼續迭代if(x==0)return 0;if(x<=3)return 1;long current=x/2;while(current*current>x){current=((current+x/current)/2);}return (int)current;}當然,原理上是這么算的,實際上想要運行的更快還有一些技巧
關于這個方法,還有一個神奇的數字
0x5f3759df?雷神之錘
參看:https://www.cnblogs.com/pkuoliver/archive/2010/10/06/1844725.html
求質數
有兩種方法
1.暴力求解,對每一個數進行判斷,判斷到sqrt(n)
for(int i=2;i<=n;i++){for(int j=2;j<=sqrt(i);j++){if(i%j==0)==>i不是質數;break;}}2.篩選法,對于2來說,2的倍數一定不是質數,對于3來說,3的倍數一定不是質數。。開一個int[n]的數組,對于2,3...n
2是質數,但2的倍數不是,打表,每次先判斷當前是否被打上去,如果是直接跳過,否則是質數,打表。
boolean[] check=new boolean[n+1]int tot = 0;for (int i = 2; i <= n; ++i) {if (!check[i]){prime[tot++] = i;}for (int j = i+i; j <= n; j += i){check[j] = true;}}編輯距離
? ? ? ? 有兩個具有相同性質的物質可以互相轉換,字符串A轉換成字符串B,A,B都由字符構成,給定基本條件,求A能轉換成B所需要的最小步驟,這個問題比較抽象,而所用的就是編輯距離算法,實際上還是動態規劃算法。
? ? ? ? 編輯距離是針對兩個字符串(例如英文字)的差異程度的量化量測,量測方式是看至少需要多少次的處理才能將一個字符串變成另一個字符串。編輯距離可以用在自然語言處理中,例如拼寫檢查可以根據一個拼錯的字和其他正確的字的編輯距離,判斷哪一個(或哪幾個)是比較可能的字。DNA也可以視為用A、C、G和T組成的字符串,因此編輯距離也用在生物信息學中,判斷二個DNA的類似程度。(來自百度百科)
例題(leetcode-72)
給定兩個單詞?word1 和?word2,計算出將?word1?轉換成?word2 所使用的最少操作數?。
你可以對一個單詞進行如下三種操作:
插入一個字符
刪除一個字符
替換一個字符
示例?1:
輸入: word1 = "horse", word2 = "ros"
輸出: 3
解釋:?
horse -> rorse (將 'h' 替換為 'r')
rorse -> rose (刪除 'r')
rose -> ros (刪除 'e')
假設dp[i][j]表示前i個字符轉換成前j個字符所需要的最短距離
對于當前字符word1[i]與word2[j]來說,
如果word[i]==word[j],顯然 dp[i][j]=dp[i-1][j-1]
否則,有三種方法可以對當前字符進行處理。
- 1.在前i個字符前插入一個字符,顯然我們不知道插到哪里合適,但是如果插入一個字符與word2[j]相同,那么就至少是最小變換中的一種,因為如果插入到別的地方,當前字符還是和word2[j]不同,還要執行變換操作,而插入字符放在最前面,那么dp[i][j]=1+dp[i][j-1](因為插入字符和word2[j]相同)
- 2.在前i個字符中刪除一個字符,顯然,刪誰都是可以的,但是因為word1[i]和word2[j]不相同,那么刪除word1[i]至少是最小變換中的一種,即dp[i][j]=1+dp[i-1][j]
- 3.替換一個字符,替換word1[i]=word2[j],則dp[i][j]=1+dp[i-1][j-1]
由以上推斷就可以寫動歸代碼,注意底層條件的初始設置。
class Solution {public int minDistance(String word1, String word2) {int[][] dp=new int[word1.length()+1][word2.length()+1];//初始化底層條件for(int i=1;i<=word1.length();i++){dp[i][0]=i;}for(int i=1;i<=word2.length();i++){dp[0][i]=i;}for(int i=0;i<word1.length();i++){for(int j=0;j<word2.length();j++){if(word1.charAt(i)==word2.charAt(j)){dp[i+1][j+1]=dp[i][j];}else{dp[i+1][j+1]=1+Math.min(Math.min(dp[i][j+1],dp[i+1][j]),dp[i][j]);}}}return dp[word1.length()][word2.length()];} }滑動窗口
這是一個非常經典的思想,常用于在給定的范圍中尋求包含的子范圍。
滑動窗口維護兩個指針:left,right
最開始left和right都處于最左邊,right向右滑動擴大窗口直到子窗口[left...right]已滿足條件
這時就要開始動左指針left,對于left當前狀態對應的元素,判斷是否能將其拋出,如果能left向右滑動,即縮小窗口,同時再次進行判斷,如果不能則動右指針right擴大窗口。簡而言之,right擴大窗口,left縮小窗口,整個窗口一次遍歷時就能遍歷到所有符合條件的子窗口。
leetcode官方描述:
- 初始,left指針和right指針都指向SS的第一個元素.
- 將 right 指針右移,擴張窗口,直到得到一個可行窗口,亦即包含TT的全部字母的窗口。
- 得到可行的窗口后,將left指針逐個右移,若得到的窗口依然可行,則更新最小窗口大小。
- 若窗口不再可行,則跳轉至 22。
例題(leetcode-76)
給你一個字符串 S、一個字符串 T,請在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例:
輸入: S = "ADOBECODEBANC", T = "ABC"
輸出: "BANC"
說明:
如果 S 中不存這樣的子串,則返回空字符串 ""。
如果 S 中存在這樣的子串,我們保證它是唯一的答案。
異或求重
? ? 異或的性質:
? ? 1、交換律:a^b = b^a;
? ? 2、結合律:(a^b)^c = a^(b^c);
? ? 3、對于任意的a:a^a=0,a^0=a,a^(-1)=~a
? ? ? ? 由以上性質可以得出一個重要推導:a^b^c^d^a^k =?b^c^d^k^(a^a) =?b^c^d^k^0 =?b^c^d^k,即如果有多個數異或,其中有重復的數,則無論這些重復的數是否相鄰,都可以根據異或的性質將其這些重復的數消去。如果有偶數個數則異或后為0,如果為奇數個,則異或后只剩一個。(引用于:https://blog.csdn.net/ns_code/article/details/27568975)
?1、1-1000放在含有1001個元素的數組中,只有唯一的一個元素值重復,其它均只出現一次。每個數組元素只能訪問一次,設計一個算法,將它找出來;不用輔助存儲空間,能否設計一個算法實現?
????當然,這道題,可以用最直觀的方法來做,將所有的數加起來,減去1+2+3+...+1000的和,得到的即是重復的那個數,該方法很容易理解,而且效率很高,也不需要輔助空間,唯一的不足時,如果范圍不是1000,而是更大的數字,可能會發生溢出。
????我們考慮用異或操作來解決該問題。現在問題是要求重復的那個數字,我們姑且假設該數字式n吧,如果我們能想辦法把1-1000中除n以外的數字全部異或兩次,而數字n只異或一次,就可以把1-1000中出n以外的所有數字消去,這樣就只剩下n了。我們首先把所有的數字異或,記為T,可以得到如下:
T = 1^2^3^4...^n...^n...^1000 = 1^2^3...^1000(結果中不含n)
????而后我們再讓T與1-1000之間的所有數字(僅包含一個n)異或,便可得到該重復數字n。如下所示:
T^(a^2^3^4...^n...^1000) = T^(T^n) = 0^n = n
????2、一個數組中只有一個數字出現了一次,其他的全部出現了兩次,求出這個數字。
????明白了上面題目的推導過程,這個就很容易了,將數組中所有的元素全部異或,最后出現兩次的元素會全部被消去,而最后會得到該只出現一次的數字。
長除法
用于大精度除法求小數
模擬除法中,小數上的商可以重復,或陷入無限循環(小數循環節),而商實際上是由上一次剩下的模數來決定的,商可以重復而模數不能重復,一旦模數開始重復就代表循環節開始出現
例題:leetcoed分數到小數
給定兩個整數,分別表示分數的分子?numerator 和分母 denominator,以字符串形式返回小數。
如果小數部分為循環小數,則將循環的部分括在括號內。
示例 1:
輸入: numerator = 1, denominator = 2 輸出: "0.5"示例?2:
輸入: numerator = 2, denominator = 1 輸出: "2"示例?3:
輸入: numerator = 2, denominator = 3 輸出: "0.(6)" class Solution {public String fractionToDecimal(int numerato, int denominato) {//模擬除法,由題意判斷不會出現無限不循環小數//***********模擬長除法:(商的)小數循環節出現則每次除下的模數也會重復出現//原理:模數一旦重復則代表要出現循環節if(numerato==0)return "0";char flag='+';if((numerato>0&&denominato<0)||(numerato<0&&denominato>0))flag='-';long numerator=Math.abs((long)numerato);//不用long會溢出long denominator=Math.abs((long)denominato);String res="";//能夠整除if(numerator>=denominator&&numerator%denominator==0){return flag=='+'?String.valueOf(numerator/denominator):'-'+String.valueOf(numerator/denominator);}//不能夠整除,注意符號Map<Long,Integer>map=new HashMap<>();//記錄模數與每次除下的商所在的位置res+=numerator/denominator+".";numerator%=denominator;while(true){//結束條件:能夠除盡或者陷入無限循環(模數重復出現)if(map.containsKey(numerator)){res=res.substring(0,map.get(numerator))+"("+res.substring(map.get(numerator))+")";break;}map.put(numerator,res.length());//放模數numerator*=10;res+=numerator/denominator;//System.out.println(numerator+" "+denominator);numerator%=denominator;//模數更新if(numerator==0){break;}}return flag=='+'?res:'-'+res;}}注意對于int除法來說為保證能求得準確精度應該使用long類型,否則模數一直*10后面可能會溢出
總結
以上是生活随笔為你收集整理的经典算法-并查集、快速排序、字典序算法、二分搜索、牛顿开方法、求质数(筛选法)、编辑距离、滑动窗口、异或求重、长除法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 血泪合集,uniapp超长实践精华总结~
- 下一篇: Flask框架面试题