图码详解算法|理解八大思想,胜刷百道力扣!
文章首發:微信搜索「Kevin的學堂」
零、前言
Hello,我是Kevin,之前和大家刷題打卡了很多天,但是發現效果并不理想,于是我在反思,我知道由于我追求數量導致了文章質量不高,另外我只是分享題解并未讓大家理解算法的內涵,授之以魚,不如授之以漁。 比起分享一道道題解,我想分享核心的算法思想應該更為重要。
所以,我打算寫這篇文章帶大家學習算法的思想,領悟“接化發“般的精髓,一起修煉內功,做到無招勝有招!
在講解八大算法思想之前我想先簡述以下三個問題,以便大家更好的理解算法。
1. 什么是算法?
《算法導論》中對算法的定義是:算法就是任何良定義的計算過程。
在數學中,算法可以看作是《九章算術》等;
在計算機科學中,算法可以看作是數據傳遞和處理的順序、方法和組成方式,如各種排序算法等;
其實從廣義上講,算法還可以體現在很多方面:
在自然中,算法可以看作是太陽東升西落,海水潮汐潮流,月兒陰晴圓缺;
在生活中,算法可以看作是媽媽烹飪紅燒雞翅時出現的食譜,先煎,再炒,再小火收汁等步驟(流口水.gif)。
2. 算法的重要性?
不知道你有沒有想過這樣一個問題,算法對于個人而言有什么意義嗎?有此疑問的推薦小浩的一篇良心文章給你看看。漫畫:嘔心泣血算法指導篇(真正的干貨,怒懟那些說算法沒用的人)
這篇文章的重點不是強調算法的重要性,所以我就不多做講解,可以分享 N.Wirth 教授所說的:程序 = 數據結構 + 算法。
3. 算法怎么學?
這一塊其實也可以單獨拿出來絮叨一番,這里簡單的描述就是理論+實踐,理論指算法相關的知識(我其實很少分享這塊的知識,因為已經有很多伙伴寫了不錯的文章,在一個我覺得理論知識還是以書本更為規范),實踐則主要指刷題(我想大多數學算法的伙伴是以面試為目的,而面試和考試很像,都需要刷題,而枯燥的刷題一個人往往難以堅持,所以我制定了刷題打卡計劃,希望幫助更多小伙伴一起堅持,在我看來養成習慣更為重要)
這篇文章不僅用圖文并貌的形式闡述了常用的八大算法思想,每個算法思想我還分別選取了一個經典案例予以代碼分析。
本文耗費了我很多心血,希望大家一定要耐心看完(暫時沒時間的話記得先「收藏」哦~),如果對大家有幫助懇請「轉發」分享給更多朋友,這就是對我原創最大的動力!感謝!
好了,說了這么多,接下來我們進入正題。
一、枚舉
首先,最簡單的思想,枚舉算法。枚舉也叫窮舉,言下之意便是根據題目的部分條件確定范圍,并在次范圍內對所有情況逐一窮盡驗證,直到找到那個最符合的解。 我們常用的循環遍歷,就是用的窮舉思想。
枚舉算法的思想可以用下圖來表示。通過實現事先確定好的「可能解」X,然后逐一在 f()計算中進行驗證,根據驗證結果對「可能解」進行驗證。這是一種結果導向型的思想,簡單粗暴地試圖從最終結果反向分析「可能解」。
枚舉算法的劣勢也很明顯。在實際的運行過程中,能夠直接通過枚舉方法進行求解的問題少之又少。一方面,當「可能解」的篩選條件不清晰,導致「可能解」的數量和范圍無法確定時,枚舉就失去了意義。另一方面,枚舉發揮不了作用的大部分場景都是由于「可能解」的數量過多,無法在有限空間或有限時間內完成所有可能性的驗證。
不過,枚舉思想是最接近人類的思維方式,我們在沒有更好的思路時先用枚舉算法得出解不失為一種方法,其實在判斷優化方法的正確性時往往就是先用枚舉法暴力求出解在驗證測試用例。
另外,想分享一個 Helsgaun 改進 Lin-Kernighan 算法,形成 Lin-Kernighan-Helsgaun 算法(簡稱 LKH 算法)的故事。
Lin-Kernighan 算法,以及之后的改進算法:Lin-Kernighan-Helsgaun 算法:
這個改進思路其實并不復雜,簡單來講,就是每次針對某一個解,同時考慮變換 10 條邊,生成一個更優解。
關鍵是,10 條邊太多了,所以變換 10 條邊的方式非常復雜,大概有 148 種可能之多。這些變換方式之間沒有明顯的規律,至少數學家們沒有找到這個規律。也因為如此,沒有人知道要如何實現出這個優化。
但是,1998 年,計算機科學家 Keld Helsgaun 給數學界帶來了一枚重磅炸彈。他實現了這個改進,完成了 LKH 算法!
LKH 算法的實際性能飛躍,比大多數人預計得都要好得多。但最吸引人好奇心的是,Helsgaun 到底是如何實現的這個優化?
Helsgaun 向研究界公開了他的完整代碼,以此揭示他成功的秘訣。答案是:沒有秘訣。
他在代碼里,完整列出了所有 148 種情形,分別討論了這些可能性。 他為了寫出正確的代碼,付出了堪比愚公移山的努力。
《案例》
雞兔同籠問題
這個問題源于1500年前的《孫子算經》原文如下:
今有雞兔同籠,上有三十五頭,下有九十四足,問雞兔各幾何?
參考代碼:(此文僅用受眾較廣的 Java 實現,語言可替換,思想最重要!)
💡 溫馨提示:可以左右滑動查看代碼哦~
/*** 窮舉算法思想* 今有雞兔同籠,上有三十五頭,下有九十四足,問雞兔各幾何?* 解法:通過題目可以知道,雞和兔的總數量為0-35只(每個動物都是一個腦袋,這就不用說了吧),* 我們可以假設雞為0,兔子為35,用雞的個數*2+兔子的個數*4就可以得到總的腳的數量,如果等于94,那么便是答案,* 如果不等,則雞的數量+1,兔子數量-1,依次類推,窮舉所有情況直到得到答案*/public static void enumeratingAlgorithm() {int head = 35, foot = 94;int j;for (int i = 0; i <= 35; i++) {//i代表雞的數量j = head - i;//j代表兔子的數量if (i * 2 + j * 4 == foot) {System.out.printf("雞的個數為[ %d ]只,兔子的個數為[ %d ]只。", i, j);return;}}System.out.printf("此題無解!你家雞和兔子有三只腳的吧?");}運行輸出:
雞的個數為[ 23 ]只,兔子的個數為[ 12 ]只。二、遞推
遞推思想跟枚舉思想一樣,都是接近人類思維方式的思想,根據已有的數據和關系,逐步推導而得到結果,通常是根據前面的一些項得到后面的一些項。
遞推思想用圖解來表示可以參見下圖。每一次推導的結果可以作為下一次推導的的開始,這似乎跟迭代、遞歸的思想有點類似,不過遞推的范疇要更廣一些。
遞推算法往往需要用戶知道答案和問題之間的邏輯關系。在許多數學問題中,都有著明確的計算公式可以遵循,因此往往可以采用遞推算法來實現。
《案例》
兔子產仔問題
這個問題癩子13世紀意大利數學家斐波那契的《算盤書》,問題的大意如下:
如果一對兩個月大的兔子以后每一個月都可以生一對小兔子,而一對新生的兔子初剩兩個月后才可以生小兔子。也就是說,1月份出生,3月份才可以產仔。那么假定一年沒有產生兔子死亡事件,問一年后共有多少對兔子?
我們先來逐月分析每月兔子對數。
從上述內容可以看出,從第3個月開始,每個月的兔子總對數等于前兩個月兔子數的總和。相應的計算公式Fn=F(n-1)+F(n-2),這里的n是第n個月,這里初始第1個月的兔子數為F1=1,F2=1。
參考代碼:
/*** 遞推算法* 如果一對兩個月大的兔子以后每一個月都可以生一對小兔子,而一對新生的兔子初剩兩個月后才可以生小兔子。* 那么假定一年沒有產生兔子死亡事件,問一年后共有多少對兔子?* 解法:* 頭兩個月,兔子都是只有一對,第三個月是2對,第四個月是3對,第五個月是5對。。。* 由此可以看出。從第三個月開始,每個月的兔子對數,等于前兩個月之和。* 所以第n個月的兔子對數為 f? = f??? + f???** @param month 月份*/public static int recursiveDeduceAlgorithm(int month) {int f1, f2;if (month == 1 || month == 2) {return 1;}f1 = recursiveDeduceAlgorithm(month - 1);//遞歸調用f2 = recursiveDeduceAlgorithm(month - 2);//遞歸調用return f1 + f2;//根據f???和f???,推導出f? }調用輸入
int month = 12;int num = recursiveDeduceAlgorithm(month);System.out.println("經過 " + month + " 個月,共有 " + num + " 對兔子。");運行輸出:
經過 12 個月,共有 144 對兔子。三、遞歸
說完遞推,我們接著來說說它的兄弟思想——遞歸。兩者都用一個「遞」字,可以看出應該具有一定的相似性?!高f」的理解可以是逐次、逐步。在「遞推」中,是逐次對問題進行推導直到獲得最終解。而在「遞歸」中,則是逐次回歸迭代,直到跳出回歸。
程序調用自身的編程技巧稱為遞歸( recursion)。它通常把一個復雜的問題轉換為與原問題相似的規模較小的問題來求解。
用一句話來形容遞歸算法的實現,就是在函數或者子過程的內部,直接或間接的調用自己算法。
- 直接遞歸 ,在方法中調用自身。
- 間接遞歸,間接的調用一個方法。比如方法a調用方法b,方法b又調用方法a。
使用遞歸,可以是代碼更簡潔清晰,可讀性更好。但是在編寫遞歸方法時,要注意使用 if 語句來在某些情況下強制退出遞歸返回結果。否則,會形成死循環。無法結束,當然也無法得到實際問題的解。
遞歸算法可用下圖表示。遞歸過程可以形象的表示為一種「套娃」過程。
《案例》
求階乘問題
所謂階乘,就是從1到指定數之間的所有自然數相乘的結果。n的階乘為:
n! = n * (n-1) * (n-2) * ······ * 2 * 1
參考代碼:
/*** 遞歸算法思想* 求階乘(factorial)問題* n的階乘為:n! = n * (n-1) * (n-2) * ······ * 2 * 1* 解法,每一項都等于前一項-1,結果也等于之前的結果*前一項-1* 我們這里用int,要注意int的取值范圍,不要超過int的上限。* @param n 求n的階乘* @return n的階乘*/public static int recursiveAlgorithm(int n) {if (n <= 1) {return 1;}return n * recursiveAlgorithm(n - 1);//遞歸調用 }調用輸入:
int n = 8; int result = recursiveAlgorithm(n); System.out.println(n + " 的階乘為: " + result)運行輸出:
8 的階乘為: 40320四、分治
分治,分而治之。
分治算法的核心只有兩步:分和治。
我們可以先從現實中的管理思想理解分治算法,一個大公司從最高級層層劃分,將子任務分配給不同的字部門,進而將大問題拆分,最底層得出最基本的解后,又一層層向上級匯報,直至大BOSS作出最終解。
同理運用在計算機的世界中,分治算法是將一個計算復雜的問題分為規模較小,計算簡單的小問題求解,然后綜合各個小問題,從而得到最終答案。 例如我們常說的歸并排序就是典型的分治思想的應用。
分治思想的圖解可見下圖。通過層層粒度上的劃分,將原問題劃分為更小的子問題,然后再向上依次得到更高粒度的解。先分解,再求解,再合并。
《案例》
歸并排序
參考代碼:(為了便于理解未使用庫中的源碼,自己手寫超詳細注釋!這么用心只想博你一贊🥺)
/*** @description: 歸并排序* 時間復雜度:O(nlogn)* 空間復雜度:O(n)* 穩定排序* 非原地排序* @author: Kevin* @createDate: 2020/2/13* @version: 1.0*/ public class MergeSort {public static void main(String[] args) {int[] arr = {8, 5, 3, 9};userMergeSort(arr);System.out.println("sorted arr:");for (Integer i:arr) {System.out.println(i);}}private static void userMergeSort(int[] arr){if (arr == null || arr.length == 0) {return;}mergeSort(arr,0,arr.length-1);}/*** 將數組 arr[left] --> arr[right] 進行歸并排序* @param arr 要排序的數組* @param left 左邊界* @param right 右邊界*/private static void mergeSort(int[] arr,int left,int right){//終止條件 --> left == right 的時候,就遞歸到只有一個元素,則不用遞歸if (left < right) {// [分]: 將數組一分為二int center = (left+right)/2; // int center = (left+right)>>1; // 位運算寫法// [治]: 將左邊的數組排序(left --> center)mergeSort(arr,left,center);// [治]: 將右邊的數組排序(center+1 --> right)mergeSort(arr,center+1,right);// [合]: 合并兩個有序數組merge(arr, left, center, right);}}/*** 將 arr[left...center] 和 arr[center+1...right] 兩個有序數組合并為一個有序數組* @param arr* @param left* @param center* @param right*/private static void merge(int[] arr, int left, int center, int right) {//先用一個臨時數組把他們合并匯總起來int[] temp = new int[arr.length];int i = left,j = center+1;// 先通過比較將兩個有序數組合并為一個有序數組,結果暫存到 temp 數組for (int k = left;k<=right;k++){// 如果左邊數組 arr[left...center] 中的元素取完[即比較完](i>center),// 則直接復制右邊數組的元素到輔助數組。右邊數組同理if (i>center) { temp[k] = arr[j++]; }else if(j>right) { temp[k] = arr[i++]; }// 合并時,比較兩有序數組值,將小的放入輔助數組中else if(arr[i]<=arr[j]) {temp[k] = arr[i++]; }else {temp[k] = arr[j++]; }}// 再將已經排序好的輔助數組中的值復制到原數組 arr 中for (int k=left;k<=right;k++){arr[k] = temp[k];}} }WOW~ 你已經看完了50%,真的很棒!接下來的50%難度將增大一點點,相信你一定可以看完,并且收獲更大!加油 :)
五、動態規劃
動態規劃是我比較喜歡的一個算法,它就像人生規劃,我們當前所做的抉擇受到之前狀態的影響,我們當下所遇到的問題可能在之前已經有過答案,并且不論過去狀態和決策如何,對之前狀態而言,當下的決策必須構成最優策略。
動態規劃(Dynamic Programming,簡稱DP)與分治思想相似,都是通過組合子問題的解來求解原問題。如上所述,分治方法將問題劃分為互不相交的子問題,遞歸地求解子問題,再將它們的解組合起來,求出原問題的解。不同的是,動態規劃應用于子問題重疊的情況,即不同的子問題具有公共的子子問題(子問題的求解是遞歸進行的,將其劃分為更小的子子問題)。在這種情況下,分治算法會做許多不必要的工作,它會反復地求解那些公共子子問題。而動態規劃對每個子子問題只求解一次,將其解保持在一個表格中,從而無需每次求解一個子子問題時都重新計算,避免了這種不必要的計算工作。
動態規劃通常用來求解最優化問題(optimization problem)。這類問題可以有很多可行解,每個解都有一個值,我們希望尋找具有最優解(最小值或最大值)的解。我們稱這樣的解為問題的一個最優解(an optimal solution),而不是最優解(the optimal solution),因為可能有多個解都達到最優值。
動態規劃的思路如下圖解。動態規劃的開始需要將問題按照一定順序劃分為各個階段,然后確定每個階段的狀態,如圖中節點的 F0 等。然后重點是根據決策的方法來確定狀態轉移方程。也就是需要根據當前階段的狀態確定下一階段的狀態。
在這個過程中,下一狀態的確定往往需要參考之前的狀態。因此需要在每一次狀態轉移的過程中將當前的狀態變量進行記錄(如下圖的備忘錄表格中),方便之后的查找。
動態規劃主要就是用來解決多階段決策的問題,但是實際問題中往往很難有統一的處理方法,必須結合問題的特點來進行算法的設計相應的 G() 函數,這也是動態規劃很難真正掌握的原因。
《案例》
背包問題
有一個可載重量為 W 的背包和 N 件物品,每個物品有重量和價值兩個屬性。其中第i個物品的重量為weight[i],價值為value[i]。現在讓你用這個背包裝物品,最多能裝的價值是多少?
這里我們討論的是0-1背包問題:每類物品最多只能裝一次
參考代碼:
public class Solution {public static void main(String[] args) {int W = 4, N = 3;int[] weight = {2,1,3}, value = {4,2,3};int maxValue = knapsack(W,N,weight,value);System.out.println(maxValue); //6}/*** 0-1背包問題** @param W 背包容量* @param N 物品種類* @param weight 物品重量* @param value 物品價值* @return*/public static int knapsack(int W, int N, int[] weight, int[] value) {// 初始化動態規劃數組,「狀態」有兩個:用二維數組:一維表示可選的物品,二維表示背包的容量int[][] dp = new int[N + 1][W + 1];// 已經有初始值,dp[0][j] = dp[i][0] = 0 (沒有物品或者沒有背包空間的時候,能裝的最大價值就是0)for (int i = 1; i < N + 1; i++) {for (int j = 1; j < W + 1; j++) {if (j - weight[i - 1] < 0) {// 當前背包容量裝不下,只能選擇不裝入背包dp[i][j] = dp[i - 1][j];} else {// 兩種選擇:不裝 或裝入背包,擇優// 如果不裝第 i 個物品:最大價值為 dp[i-1][j](dp[i][j] = dp[i-1][j] + 0)// 如果裝入第 i 個物品:最大價值為 dp[i][j] = dp[i-1][j- weight[i-1]] + value[i-1]// 因為 i 是從 1 開始的,所有 weight 和 value 的取值是 i-1// dp[i-1][j- weight[i-1]]:在裝第 i 個物品前提下,背包能裝的最大價值是多少// + value[i-1]:加上第 i 個物品的價值dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);}}}// 容量為W的背包能裝入物品的最大價值return dp[N][W];} }六、貪心
貪心算法(greedy algorithm)也稱貪婪算法。在對問題求解時,總是做出在當前看來是最好的選擇。 也就是說,不從整體最優上加以考慮,他所做出的是在某種意義上的局部最優解。 貪心算法不是對所有問題都能得到整體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具備無后效性,即某個狀態以前的過程不會影響以后的狀態,只與當前狀態有關。
求解最優化問題的算法通常需要經過一系列的步驟,在每個步驟都面臨著多種選擇。對于許多最優化問題,使用動態規劃算法來求最優解有些殺雞用牛刀來,可以使用更簡單、更高效的貪心算法,它在每一步都作出當時看起來最佳的選擇。也就是說,它總是做出局部最優解,寄希望這樣的選擇能導致全局最優解。
下圖表示的是求解對多條直線的交點。很顯然,下圖中的直線是不存在統一交點的,但是可以通過算法求得統一交點的最優解。若是采用貪心算法,那么在進行迭代時,每一次都會選擇離此時位置最近的直線進行更新。這樣一來,在經過多次迭代后,交點的位置就會在某一片區域無限輪回跳轉。而這片區域也就是能求得出的大致的最優解區域。
《案例》
活動安排
活動安排問題是可以用貪心算法有效求解的一個很好的例子。該問題要求高效地安排一系列爭用某一公共資源的活動。貪心算法提供了一個簡單、漂亮的方法使得盡可能多的活動能兼容地使用公共資源。
設有n個活動的集合e={1,2,…,n},其中每個活動都要求使用同一資源,如演講會場等,而在同一時間內只有一個活動能使用這一資源。每個活動i都有一個要求使用該資源的起始時間si和一個結束時間fi,且si< fi。如果選擇了活動i,則它在半開時間區間[si,fi]內占用資源。若區間[si,fi]與區間[sj,fj]不相交,則稱活動i與活動j是相容的。也就是說,當si≥fj或sj≥fi時,活動i與活動j相容?;顒影才艈栴}就是要在所給的活動集合中選出最大的相容活動子集合。
參考代碼:
public class ArrangeActivity {/*** 活動時間安排*/public static void main(String[] args) {int[] start = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12};int[] end = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};List<Integer> results = arrangeActivity(start, end);for (int i = 0; i < results.size(); i++) {int index = results.get(i);System.out.println("開始時間:" + start[index] + ",結束時間:" + end[index]);}//開始時間:1,結束時間:4//開始時間:5,結束時間:7//開始時間:8,結束時間:11//開始時間:12,結束時間:14}/*** 活動安排** @param s 開始時間* @param e 結束時間* @return*/public static List<Integer> arrangeActivity(int[] s, int[] e) {int total = s.length;int endFlag = e[0];List<Integer> results = new ArrayList<>();results.add(0);for (int i = 0; i < total; i++) {if (s[i] > endFlag) {results.add(i);endFlag = e[i];}}return results;} }七、回溯
回溯算法實際上一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就“回溯”返回,嘗試別的路徑。 回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇并不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。許多復雜的,規模較大的問題都可以使用回溯法,有“通用解題方法”的美稱。
如果你玩過迷宮的話,應該很容易理解回溯思想,我們一般是從起點開始探索路徑,當走到死路時,便退回重新試探,直至找到一條完整的前進路徑。
下面用一張圖表示回溯算法的思想,假設目的是從最X0到達X4,需要對所有節點進行回溯遍歷路徑。那么回溯算法的過程則需要在前進的每一步對所有可能的路徑進行試探。
比方說,X0節點前進的路徑有三條,分別是上中下條的X1。回溯過程的開始,先走上面的X1,然后能夠到達上面 X2,但是這時是一條死路。那么就需要從X2退回到X1,而此時X1的唯一選擇也走不通,所以還需要從X1退回到X0。然后繼續試探中間的X1
《案例》
全排列
給定一個 沒有重復 數字的序列,返回其所有可能的全排列。
例如: [1,2,3]
其全排列為:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
參考代碼:
List<List<Integer>> res = new LinkedList<>();/* 主函數,輸入一組不重復的數字,返回它們的全排列 */ List<List<Integer>> permute(int[] nums) {// 記錄「路徑」LinkedList<Integer> track = new LinkedList<>();backtrack(nums, track);return res; }// 路徑:記錄在 track 中 // 選擇列表:nums 中不存在于 track 的那些元素 // 結束條件:nums 中的元素全都在 track 中出現 void backtrack(int[] nums, LinkedList<Integer> track) {// 觸發結束條件if (track.size() == nums.length) {res.add(new LinkedList(track));return;}for (int i = 0; i < nums.length; i++) {// 排除不合法的選擇if (track.contains(nums[i]))continue;// 做選擇track.add(nums[i]);// 進入下一層決策樹backtrack(nums, track);// 取消選擇track.removeLast();} }八、模擬
許多真實場景下,由于問題規模過大,變量過多等因素,很難將具體的問題抽象出來,也就無法針對抽象問題的特征來進行算法的設計。這個時候,模擬思想或許是最佳的解題策略。
模擬是對真實事物或者過程的虛擬。在編程時為了實現某個功能,可以用語言來模擬那個功能,模擬成功也就相應地表示編程成功。
模擬說起來是一種很玄幻的思想,沒有具體的實現思路,也沒有具體的優化策略。只能說,具體問題具體分析。
那如何用圖解來表示呢?如下圖所示,任意的輸入,經過不規則的處理,得出理想的輸出。
《案例》
猜數字游戲
用計算機實現一個隨機數1到100之間的數字,然后由用戶來猜這個數,根據用戶的猜測數量分別給出不同的提示
參考代碼:
public class GuessNum {public static void main(String[] args) {int aimNum = 0, guessNum = 0, guessCount = 0; // 目標數字,猜的數字,猜的次數int max = 100, min = 1; // 比較數范圍Random rand = new Random();aimNum = rand.nextInt(100) + 1; // 生成[1, 100]的隨機數System.out.printf("aim num: %d\n", aimNum);do {System.out.printf("%d < 目標數 < %d, 輸入你猜的數字:", min, max);Scanner scanner = new Scanner(System.in); guessNum = scanner.nextInt(); //輸入你猜的數字guessCount++; // 統計猜數字的次數if (guessNum > aimNum) {System.out.printf("猜大了\n");max = guessNum; // 刷新最大值}else if (guessNum < aimNum) {System.out.printf("猜小了\n");min = guessNum; // 刷新最小值}} while (guessNum != aimNum);System.out.println("恭喜你~猜對啦!");System.out.printf("共猜了%d次\n", guessCount);if (guessCount <= 5){System.out.println("你也太聰明了吧,這么快就猜出來了!");}else {System.out.println("還需改進哦,相信你下次可以更快的!");}} }輸入&輸出:
九、總結
如果你看到了這里并且理解了以上八大算法思想,一定要夸你根棒!如果沒有看懂不也要氣餒,用心多看幾遍多思考多學習!
另外,學會了算法思想并不一定代表刷題就所向披靡,因為有些題目是將很多思想雜糅在一起,只是角度和側重點不同。以上《案例》也不是只能用一種思想來解答,只是作為代表來體會對應的算法思想。
大多數人作為工程師,雖說不需要天天刷題,也不一定需要寫算法。但是,工程的很多方面都運用著算法思想,如 “Java8中Hashmap使用紅黑樹來實現” 、“Redis底層使用LRU來進做淘汰策略”、“大數據領域很多問題都基于TopK”、“JS原型鏈里使了類似鏈表的成環檢測”、“MySql為什么索引要用B+樹”、“Oracle里的開窗函數如何實現”、“TCP協議的滑動窗口如何控制流量” 等等等等。
在我看來,算法不僅僅是對問題的解決,更重要的是對思維的鍛煉。
最后的最后,希望大家幫忙「點贊」、「在看」、「轉發」三連支持~ 這是我原創的最大動力!
參考
《算法導論》
還不懂這八大算法思想,刷再多題也白搭!https://mp.weixin.qq.com/s/FnZGEJi1D53m77Gy2JXw7Q
常用算法指南(一)基本算法思想 - 柱哥哥的文章 - 知乎 https://zhuanlan.zhihu.com/p/36903717
旅行售貨員問題背后 https://mp.weixin.qq.com/s/xyKJaot4r15CBVTJpteQaA
精彩推薦
我寫了個“女朋友”陪自己打麻將……
《算法撕裂者》02 - 推薦算法
Kevin的算法之路|20201130匯總
總結
以上是生活随笔為你收集整理的图码详解算法|理解八大思想,胜刷百道力扣!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Yii2 Elasticsearch 操
- 下一篇: java wgs84转西安80_java