日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

【简约而不简单:神级代码的小秘密】| 第二章 栈

發(fā)布時(shí)間:2024/1/8 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【简约而不简单:神级代码的小秘密】| 第二章 栈 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

2.1 什么是棧????????

????????有一種藝術(shù)叫做沙畫(huà)。把沙子一層一層的疊在一起,從而形成美麗的圖案。從圖中可以看到,最底層的深藍(lán)色沙子是最先放進(jìn)去的。

?????????繼續(xù)放沙子,我們可以看到,最先放入的藍(lán)色沙子在底部,而最后放入的紫色沙子在頂部。

? ? ? ? 如果把頂部的沙子取出,又是怎樣的效果呢?

? ? ? ? 在此過(guò)程中,紫色沙子是最后放入的,卻是最先取出的,繼續(xù)取沙子,會(huì)觀察到下面的情況:

????????藍(lán)色沙子是最后取出的。只有一個(gè)出口,最先放入的元素(藍(lán)色沙子)最后取出(FIRST IN LAST OUT 先進(jìn)后出),最后放入的元素(紫色沙子)最先取出(LAST IN FIRST OUT 后進(jìn)先出)的方式,這就是本章要講的

? ? ? ? 把沙子放入桶的過(guò)程,叫做入棧,把沙子從桶中取出的過(guò)程,叫做出棧

2.2 棧有什么用

2.2.1 歷史記錄(步驟回退)

? ? ? ? CTRL+Z是最常見(jiàn)的文本處理命令之一。甚至不止出現(xiàn)在文本軟件上,有編輯作用的軟件,例如圖片處理(PS),視頻剪輯(會(huì)聲會(huì)影,PE)軟件,通常也會(huì)有這個(gè)命令。簡(jiǎn)單格式的軟件通常只支持一步回退動(dòng)作,例如windows自帶的txt文本文件notepad.exe。

????????在益智小游戲,比如推箱子,五子棋,圍棋,井字棋中,往往會(huì)有個(gè)撤銷(xiāo)按鈕,這種時(shí)候,棧就可以派上用場(chǎng)了。下圖是井字棋下棋,以及棧操作撤銷(xiāo)的步驟:

?????????從圖中可以看到,撤銷(xiāo)的過(guò)程和下棋的過(guò)程恰好是相反的,符合棧中“先入后出”的規(guī)律。

? ? ? ? 附代碼:

public class Main {public static void main(String[] args){Main solution = new Main();System.out.println("開(kāi)始井字棋游戲!");char[][] cs= new char[3][3];for(int i = 0; i < 3; i++){Arrays.fill(cs[i],'_');}solution.print(cs);Stack<char[][]> stack = new Stack<>();int type = 1;Scanner scanner = new Scanner(System.in);while(!solution.winOrLose(cs)){if(type==1){System.out.println("您現(xiàn)在是 X 棋");}else{System.out.println("您現(xiàn)在是 O 棋");}String s;if(!stack.isEmpty()){System.out.println("您需要進(jìn)行什么操作?1 下棋 其他按鍵 撤銷(xiāo)");s = scanner.nextLine();}else{System.out.println();s = "1";}if("1".equals(s)){System.out.println("請(qǐng)輸入下棋位置,用逗號(hào)分隔:");int[] pos = new int[0];String line = scanner.nextLine();pos = solution.getPos(line);while (pos.length==0){System.out.println("您下棋的位置無(wú)效!請(qǐng)輸入下棋位置,用逗號(hào)分隔:");line = scanner.nextLine();pos = solution.getPos(line);}//壓棧stack.push(solution.copy(cs));cs[pos[0]][pos[1]] = type==1?'X':'O';type = -type;}else {//彈出cs = stack.pop();type = -type;}solution.print(cs);}System.out.println("贏家是"+ solution.winner+"的下棋者");}private char[][] copy(char[][] origin){char[][] copy = new char[3][3];for(int i = 0; i < 3;i++){for(int j = 0; j < 3; j++){copy[i][j] = origin[i][j];}}return copy;}char winner = ' ';private boolean winOrLose(char[][] cs){for(int i = 0; i < 3; i++){if(cs[i][0]!='_'&&cs[i][0]==cs[i][1]&&cs[i][1]==cs[i][2]){winner = cs[i][0];return true;}}for(int i = 0; i < 3; i++){if(cs[0][i]!='_'&&cs[0][i]==cs[1][i]&&cs[1][i]==cs[2][i]){winner = cs[0][i];return true;}}if(cs[0][0]!='_'&&cs[0][0]==cs[1][1] &&cs[1][1]==cs[2][2]){winner = cs[0][0];return true;}if(cs[0][2]!='_'&&cs[0][2]==cs[1][1] &&cs[1][1]==cs[2][0]){winner = cs[0][2];return true;}return false;}private int[] getPos(String str){if(!str.contains(","))return new int[0];String[] strs = str.split(",");if(isDigit(strs[0]) && isDigit(strs[1])){int a = Integer.parseInt(strs[0]);int b = Integer.parseInt(strs[1]);if(a>=0&&a<3&&b>=0&&b<3)return new int[]{a,b};}return new int[0];}private boolean isDigit(String s){try{int t = Integer.parseInt(s);}catch (Exception e){return false;}return true;}private void print(char[][] cs){for(int i = 0; i < 3; i++){for(int j = 0; j < 3;j++){System.out.print(cs[i][j]+" ");}System.out.println();}} }

2.2.2 配對(duì)

? ? ? ? 在使用計(jì)算器,文本,輸入代碼過(guò)程中,如果括號(hào)不匹配,會(huì)立刻給出提示。例如:

????????我們忽略文本中的字母部分,僅保留括號(hào),可得到內(nèi)容: {{([]){(){}}????????

? ? ? ? 已知 ’{‘ 和 ’}‘ 是匹配的, ’[‘ 和 ’]‘ 是匹配的, '(' 和 ’)‘ 是匹配的。

????????在遇到右側(cè)括號(hào)時(shí),如果沒(méi)有足夠的左側(cè)括號(hào)與其進(jìn)行抵消,那說(shuō)明是非法的;在遍歷完所有的括號(hào)之后,如果左側(cè)括號(hào)依然無(wú)法找到與之匹配的右側(cè)括號(hào),那么說(shuō)明左側(cè)括號(hào)是非法的。代碼如下,此處暫不深究,后續(xù)將做進(jìn)一步說(shuō)明:

public class Main {public static void main(String[] args) {Main main = new Main();System.out.println( main.checkCmp("public class Main{\n" +"\tpublic static void main(String[] args){\n" +"\t\tMain solution = new Main();{\n" +"\t}\n" +"}"));//多了一個(gè)左括號(hào)System.out.println( main.checkCmp("public class Main{\n" +"\tpublic static void main(String[] args){\n" +"\t\tMain solution = new Main();\n" +"\t}\n" +"}"));//正確的System.out.println( main.checkCmp("public class Main{\n" +"\tpublic static void main(String[] args){\n" +"\t\tMain solution = new Main();\n" +"\t}\n" +"}}"));//多了一個(gè)右括號(hào)}public boolean checkCmp(String str){if(str == null || str.isEmpty())return true;int n = str.length();Stack<Character> stack = new Stack<>();for(int i = 0; i < n; i++){char c = str.charAt(i);//左括號(hào)入棧if(c == '{' || c == '[' || c == '('){stack.push(c);//右括號(hào)出棧}else if(c == '}' || c == ']' || c ==')'){if(stack.isEmpty())return false;char p = stack.pop();if(c=='}' && p !='{')return false;if(c == ']' && p != '[')return false;if(c == ')' && p != '(')return false;}}return stack.isEmpty();}}

2.2.3 計(jì)算器

? ? ? ? 計(jì)算機(jī)最早的用途就是用于計(jì)算數(shù)值的。以一段簡(jiǎn)單的 為例。當(dāng)遇到左括號(hào)的時(shí)候,把前面的累計(jì)結(jié)果對(duì)數(shù)字棧進(jìn)行入棧 [2] ,符號(hào)棧也進(jìn)行入棧 [x] ,算完括號(hào)的 ?的結(jié)果后,把兩個(gè)棧的結(jié)果彈出拿到 2 和符號(hào) x?,然后用拿到的數(shù)字和符合,和后面計(jì)算過(guò)的數(shù)字進(jìn)行運(yùn)算,得到?。

2.2.4 遞歸????????

????????比如下面這段程序,是一段遞歸。完成了從 start end 的累加過(guò)程。

public class Main {public static void main(String[] args){Main solution = new Main();System.out.println(solution.getSum(1,3));}private int getSum(int start, int end){if(start>end)return dfs(end,start);return dfs(start,end);}private int dfs(int start, int end){if(start == end)return start;return start+dfs(start+1,end);} }

????????下圖,是計(jì)算機(jī)實(shí)際的執(zhí)行過(guò)程:

?2.3?棧的操作

棧頂:棧最頂部的元素叫做 棧頂,指向棧頂元素的指針,叫做 棧頂指針

出棧:把棧頂指針的元素取出的過(guò)程,把棧頂指針向下移動(dòng)的過(guò)程,叫做?出棧

入棧:把元素放入棧頂,并將棧頂指針向下移動(dòng)的過(guò)程,叫做?入棧

查看棧頂元素:查看棧頂指針對(duì)應(yīng)的元素內(nèi)容,這一過(guò)程只查看不進(jìn)行出棧入棧操作。

棧空:棧中沒(méi)有元素,棧頂指針指向 NULL,無(wú)法進(jìn)行出棧操作

棧滿:棧中數(shù)據(jù)已滿,無(wú)法再容納新的元素,無(wú)法進(jìn)行入棧操作

這樣描述呢比較抽象,下面用比較形象的方式再進(jìn)行一次敘述。

2.3.1 概念解讀

? ? ? ? 一串糖葫蘆最上面的那顆,我們下一顆可以吃掉的糖葫蘆,就是棧頂。它的特點(diǎn)是在頂部,是下一顆準(zhǔn)備吃掉的目標(biāo)。

? ? ? ? 最后一顆能吃掉的糖葫蘆就是?棧底

? ? ? ? 吃掉糖葫蘆之前需要用自己銅鈴般水汪汪的大眼睛盯著,防止把糖葫蘆吃到鼻子里。這個(gè)“盯”,就是?棧頂指針?啦!

? ? ? ? 入棧出棧 在本章開(kāi)頭已經(jīng)有了較為詳盡的解釋,此處不再贅述。

? ? ? ? 棧滿 呢,就是剛拿到這串糖葫蘆的時(shí)候,它上面的尖頭已經(jīng)不足以再串一個(gè)新的糖葫蘆了,換句話說(shuō),就是串糖葫蘆的竹簽不夠長(zhǎng)度再串一顆新的糖葫蘆了。

? ? ? ? 棧空,就是把這串糖葫蘆吃完了,沒(méi)有新的糖葫蘆可以吃,也沒(méi)有任何一顆糖葫蘆需要你繼續(xù)盯著。

2.3.2 棧的缺點(diǎn)

  • 操作受限

? ? ? ? 棧只能做出查看 棧頂、入棧、出棧 三種操作,而且僅能對(duì)棧頂元素進(jìn)行操作,實(shí)際能做的非常有限,需要一些特定場(chǎng)景才能起到作用。

  • ?遞歸層次有限????????

????????如圖所示,遞歸調(diào)用不到10000層就發(fā)生了內(nèi)存溢出。

2.4 單調(diào)棧

????????棧內(nèi)的元素按照從棧底到棧頂?shù)捻樞?#xff0c;所有元素滿足單調(diào)遞增或者單調(diào)遞減或單調(diào)非遞增,單調(diào)非遞減時(shí),可以稱(chēng)之為單調(diào)棧

2.4.1 什么時(shí)候可以用單調(diào)棧

? ? ? ? 事實(shí)上,在寫(xiě)這篇文章之前,我也不知道什么是單調(diào)棧。但是既然決定了把單調(diào)棧作為書(shū)寫(xiě)內(nèi)容之一,就有責(zé)任有義務(wù)把這個(gè)東西它什么時(shí)候能用具體的進(jìn)行描述。只有這樣,單調(diào)棧這樣的神級(jí)工具,才能在合適的位置發(fā)揮最大作用。

????????如果一個(gè)問(wèn)題可以轉(zhuǎn)化為,找到(上一個(gè)/下一個(gè))(大于/小于)【等于】當(dāng)前值或者對(duì)應(yīng)的索引,就可以求出答案,那么單調(diào)棧就是你的最佳幫手。

? ? ? ? 如果是上一個(gè),則從前往后遍歷。

? ? ? ? 如果是下一個(gè),則從后往前遍歷。

? ? ? ? 其中等于這個(gè)條件可要可不要。

2.4.2 單調(diào)棧實(shí)踐

1475. 商品折扣后的最終價(jià)格https://leetcode-cn.com/problems/final-prices-with-a-special-discount-in-a-shop/

????????瀏覽了眾多有關(guān)單調(diào)棧的題目,力扣的這一題,感覺(jué)特別合適作為單調(diào)棧的講解題目。

????????為了方便讀者閱讀,本篇文章內(nèi)也引用一下題目?jī)?nèi)容:

給你一個(gè)數(shù)組?prices?,其中?prices[i]?是商店里第?i?件商品的價(jià)格。

商店里正在進(jìn)行促銷(xiāo)活動(dòng),如果你要買(mǎi)第?i?件商品,那么你可以得到與 prices[j] 相等的折扣,其中?j?是滿足?j > i?且?prices[j] <= prices[i]?的?最小下標(biāo)?,如果沒(méi)有滿足條件的?j?,你將沒(méi)有任何折扣。

請(qǐng)你返回一個(gè)數(shù)組,數(shù)組中第?i?個(gè)元素是折扣后你購(gòu)買(mǎi)商品 i?最終需要支付的價(jià)格。

? ? ? ? 首先進(jìn)行一波題目解析:

? ? ? ? 輸入:數(shù)組 prices,每個(gè)元素代表價(jià)格

? ? ? ? 輸出:數(shù)組 ans,每個(gè)元素代表折扣后你購(gòu)買(mǎi)商品 i?最終需要支付的價(jià)格

? ? ? ? 條件:

? ? ? ? 1. 商品可以得到折扣

? ? ? ? 2. 優(yōu)惠價(jià)格是在當(dāng)前索引后面的位置查找到第一個(gè)小于等于它的價(jià)格作為折扣

? ? ? ? 3. 沒(méi)有折扣的情況,折扣為0

? ? ? ? 4. 實(shí)際支付金額=原價(jià)-折扣

? ? ? ? 這題可以轉(zhuǎn)化為:找到下一個(gè)小于等于當(dāng)前價(jià)格的值,用當(dāng)前的價(jià)格找到下一個(gè)比它大的價(jià)格即為答案。

? ? ? ? 思路:

????????1.? 根據(jù)前面給的解題方式,描述時(shí)使用了 下一個(gè),因此從后往前遍歷

? ? ? ? 2.? 應(yīng)為非遞增棧,從后往前遍歷,假設(shè) j1<=j2,且?prices[j1]?的值更小,那么對(duì)于任意一個(gè)比 j1?的索引更前的索引i,必然選擇 j1 的值,那么棧中所有比它大的元素都應(yīng)出棧

喜歡的話,點(diǎn)個(gè)贊吧~!平時(shí)做題,以及筆記內(nèi)容將更新到公眾號(hào)。

關(guān)注公眾號(hào),互相學(xué)習(xí):鈺娘娘知識(shí)匯總

總結(jié)

以上是生活随笔為你收集整理的【简约而不简单:神级代码的小秘密】| 第二章 栈的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。