Java入门算法(数据结构篇)丨蓄力计划
本專欄已參加蓄力計劃,感謝讀者支持
往期文章
一. Java入門算法(貪心篇)丨蓄力計劃
二. Java入門算法(暴力篇)丨蓄力計劃
三. Java入門算法(排序篇)丨蓄力計劃
四. Java入門算法(遞歸篇)丨蓄力計劃
五. Java入門算法(雙指針篇)丨蓄力計劃
六. Java入門算法(數據結構篇)丨蓄力計劃
七. Java入門算法(滑動窗口篇)丨蓄力計劃
你好,我是Ayingzz,Ayi是我的名字,ing進行時代表我很有動力,zz提醒我按時睡覺 ~
- 篇幅短小精悍,適合初學者反復咀嚼:此專欄的文章并不是一系列大而全的整理文章,而是一系列簡明扼要的算法入門講解文章,篇幅短而內容精,有利于初學者針對一種或多種算法快速入門。
- 例題簡單易懂,讓你印象深刻:引入精選LeetCode簡易算法例題,通過生動形象的講解對其思路進行簡明剖析,更容易上手并掌握。
- 涉及算法種類廣:雙指針、遞歸、排序、貪心、分治、動態規劃、滑動窗口、DFS...各類基礎算法收攬其中。
為什么要學算法?
對于所有的Problems-Solving的過程都可以理解為算法,程序員對算法或多或少都有著一些復雜的情感,為什么一定要學算法?
- "程序 = 數據結構 + 算法"。這個公式相信已經耳濡目染,目前在各大廠的面試里,對基礎算法的考察的比重逐年增加,只寫會某種語言的工程代碼顯然并不太夠,大部分面試官會優先考慮掌握算法的面試者。在現實開發里,僅使用一些簡單的算法就可以快讀優化各種繁雜的工程代碼,降低時間復雜度與工程運行速度,提升用戶體驗。
- 對算法的熱愛。作為程序員或多或少對算法都有著某種情感上的執著與偏愛,如果你還是學生,想參與各類的競賽,那么入門算法即是數學建模、軟件開發、算法等各類競賽的敲門磚,選手的動力就是對算法的追求與熱愛,類似的有ACM、藍橋杯、傳智杯等。
專欄思路和內容大綱
基礎部分:
進階部分:
適宜人群
- 對算法感興趣的初學者
- 想加強算法基本功的讀者
數據結構篇
- 往期文章
- 為什么要學算法?
- 專欄思路和內容大綱
- 適宜人群
- 寫在前面
- 本篇內容
- MinStack(輔助棧)
- 溫度(單調棧)
- 卡片(HashMap)
- 分糖果(HashSet)
- 推薦練習
- 股票價格跨度
- 存在重復元素
寫在前面
我們都知道 “程序 = 數據結構 + 算法”,開發常用的數據結構有:數組、棧、鏈表、隊列、樹、圖、堆、哈希表等,算法題需要用來輔助解題的數據結構也不在少數, 棧、哈希表(散列表)這兩個結構在算法題中用得特別多,先來簡單看看他們是什么:
巧用數據結構能夠幫助我們從另一個角度快速解題,下面講解具體的應用。
本篇內容
MinStack(輔助棧)
LeetCode題目描述:155.最小棧(Easy)
設計一個支持 push ,pop ,top 操作,并能在常數時間內檢索到最小元素的棧。
- push(x) —— 將元素 x 推入棧中。
- pop() —— 刪除棧頂的元素。
- top() —— 獲取棧頂元素。
- getMin() —— 檢索棧中的最小元素。
示例
輸入: ["MinStack","push","push","push","getMin","pop","top","getMin"] [[],[-2],[0],[-3],[],[],[],[]]輸出: [null,null,null,null,-3,null,0,-2]解釋: MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.getMin(); --> 返回 -2.若沒有getMin()方法,這道題就是簡單的實現一個棧的幾個基本的操作:壓棧、彈棧、獲取棧頂元素。所以關鍵是怎么實現getMin()獲取棧中最小值的這個功能,方法是有的,而且還不少:
- 遍歷,搜索最小值
- 排序,獲取排序后的棧頂元素
實際上這兩種方法的速度都太慢了,AC時間在300ms~600ms之間,所以有沒有更快一點的方法呢?有。
- 設置輔助棧,與原始棧同步壓棧、彈棧
在這一題中,輔助棧的作用是記錄最小元素,將元素一起壓進原始棧和輔助棧時,需要注意的是輔助棧壓棧的方式是不同的。每次將要壓棧(push)的元素與輔助棧的棧頂元素比較,取值較小的那一個加入輔助棧頂,這樣就可以在輔助棧的棧頂一直記錄著原始棧中值最小的那一個元素(細節)。
輔助棧的實現:
public class MinStack {Deque<Integer> stack, minstack;/** initialize your data structure here. */public MinStack() {stack = new ArrayDeque<Integer>();// 輔助棧minstack = new ArrayDeque<Integer>();}public void push(int val) {stack.addFirst(val);// 將要push的元素與輔助棧頂元素比較,取值小的那一個加入輔助棧頂minstack.addFirst(minstack.isEmpty() ? val : Math.min(minstack.peekFirst(), val));}public void pop() {stack.removeFirst();// 同步pop,保持兩個棧大小一樣minstack.removeFirst();}public int top() {return stack.peekFirst();}public int getMin() {// 取輔助棧頂元素即可return minstack.getFirst();} }當用到getMin()方法時,直接返回輔助棧的棧頂元素,它即是目前原始棧中的最小元素。借助輔助棧的方式實現最小棧,無疑是高效的,AC的時間在9ms左右,快了近50倍。
溫度(單調棧)
LeetCode題目描述:739.每日溫度(Middle)
請根據每日 氣溫 列表,重新生成一個列表。對應位置的輸出為:要想觀測到更高的氣溫,至少需要等待的天數。如果氣溫在這之后都不會升高,請在該位置用 0 來代替。
示例: 給定一個列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的輸出應該是 [1, 1, 4, 2, 1, 1, 0, 0]。
- 提示:氣溫 列表長度的范圍是 [1, 30000]。每個氣溫的值的均為華氏度,都是在 [30, 100] 范圍內的整數。
本題可以使用暴力快慢指針解法,時間復雜度較高,不作介紹。
單調棧,意思是在解題過程中維護一個棧,使其中元素一直保持單調性。
在這一題,因為題目中求的是至少需要等待的天數,而不是溫度差,固應將原數組下標作為元素依次加入棧中,代表天數 i 。接下來,我們將下一個即將加入棧的元素 i 與當前棧頂元素作判斷:
按照這些步驟,可以驚喜地發現我們的這些操作一直都是滿足了棧內元素的單調性,固稱之為單調棧。最后依然存在棧里的元素,代表著在數組中找不到下一個更高溫度的天數,題目要求在返回數組總用0代替,因為初始化已為0,不作處理(細節)。
單調棧的使用:
class Solution {public int[] dailyTemperatures(int[] T) {Deque<Integer> stack = new ArrayDeque<>();int[] res = new int[T.length];// 維護一個單調棧for (int i = 0; i < T.length; ++i) {// 棧不空 且 第i天的溫度比棧頂天數的溫度大while (!stack.isEmpty() && T[stack.peek()] < T[i]) {// 彈棧、計算相差天數儲存到返回數組int idx = stack.pop();res[idx] = i - idx;}// 第i天壓棧stack.push(i);}return res;} }.
卡片(HashMap)
先補充說明一下HashMap與最后一題里HashSet的異同,HashMap保存的是一對對key-value鍵值對,HashSet保存的是單一無重復的對象,它的底層封裝了一個HashMap,但把value置為了常量,只對傳進來的key對象做操作。
卡片這一題是第十二屆藍橋杯省賽中的題目。
最笨拙的辦法,就是用代碼模擬這一個拼卡片的過程,笨拙但有效。我們將0~9這些卡片作為Key,與2021作為初始的Value表示卡片剩余的數量組成鍵值對,依次存進HashMap中。
開始模擬,用 i 不斷自增,作為我們當前拼的數字,每一次將它的位數分解,去HashMap里找對應的Value值,若Value值不為0,那么就表示這種卡片還有,有我們就用,用了之后對應的就是那一種卡片的Value值 - 1。當檢測到某一個Value值變為了 0,意味著組成當前拼的這一個數字的某一個位數的數字卡片沒有了,所以拼不了了,但是 i 已經自增,所以 i - 1 就是我們的答案(細節)。
HashMap的使用:
import java.util.*;public class Main {public static void main(String[] args) {Map<Integer, Integer> map = new HashMap<Integer, Integer>();// put key 0~9 到哈希表里for (int i = 0; i < 10; ++i) {map.put(i, 2021);}// 暴力,從1開始自增for (int i = 1; ; ++i) {int n = i;// 取拼成 i 所需的每一個數字 mwhile (n > 0) {int m = n % 10;if (map.get(m) > 0) {// 只要m卡片還有,就用,用完后對應value值 - 1map.put(m, map.get(m) - 1);}else{// 當哈希表中某個 Value 變為 0 時,輸出System.out.println(i - 1);return;}n /= 10;}}} }(運行結果:3181)
分糖果(HashSet)
LeetCode題目描述:575. 分糖果(Easy)
給定一個偶數長度的數組,其中不同的數字代表著不同種類的糖果,每一個數字代表一個糖果。你需要把這些糖果平均分給一個弟弟和一個妹妹。返回妹妹可以獲得的最大糖果的種類數。
示例
輸入: candies = [1,1,2,2,3,3] 輸出: 3 解析: 一共有三種種類的糖果,每一種都有兩個。 最優分配方案:妹妹獲得[1,2,3],弟弟也獲得[1,2,3]。這樣使妹妹獲得糖果的種類數最多-----------------------------------------------------------------------------------輸入: candies = [1,1,2,3] 輸出: 2 解析: 妹妹獲得糖果[2,3],弟弟獲得糖果[1,1],妹妹有兩種不同的糖果,弟弟只有一種。 這樣使得妹妹可以獲得的糖果種類數最多。偶數長度,表示可以平均分。
不同數字代表不同種類的糖果,每個數字代表一個糖果,假設數字不重復,糖果的種類就是數組的長度,但題目需要將糖果平均分給弟弟和妹妹,所以妹妹實際獲得的糖果最多只有 n / 2;假設糖果的種類遠小于糖果的數量,也就是數組中有一些糖果是重復的,此時應將計算出的實際種類數與數組長度的一半做比較,妹妹最多可以獲得的糖果種類就是兩者中的較小者。
計算種類數,我們用HashSet(不允許加入重復的鍵)來實現,只需將數組中所有元素依次加入Set,不管有沒有重復,最終得到的Set集合里都不會存在重復的key,固Set的大小Size即是我們要求的種類數。將其與數組長度的一半取小者返回即可。
HashSet的使用:
public class Solution {public int distributeCandies(int[] candies) {HashSet < Integer > set = new HashSet < > ();// Set中不允許含有重復元素for (int candy: candies) {set.add(candy);}// 最后Set里就是所有不同種類的糖果,注意弟弟妹妹需要平均分return Math.min(set.size(), candies.length / 2);} }推薦練習
\
股票價格跨度
- 編寫一個 StockSpanner 類,它收集某些股票的每日報價,并返回該股票當日價格的跨度。
- 今天股票價格的跨度被定義為股票價格小于或等于今天價格的最大連續日數(從今天開始往回數,包括今天)。
存在重復元素
- 給定一個整數數組,判斷是否存在重復元素。
- 如果存在一值在數組中出現至少兩次,函數返回 true 。如果數組中每個元素都不相同,則返回 false 。
/
題目來源:https://leetcode-cn.com/
棧動圖來源:https://www.jianshu.com/p/603f919b2693
本專欄持續更新,預計7月結束,感謝讀者的支持
總結
以上是生活随笔為你收集整理的Java入门算法(数据结构篇)丨蓄力计划的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三星s10充电器功率(三星智能手机)
- 下一篇: Java入门算法(滑动窗口篇)丨蓄力计划