題目匯總:https://leetcode-cn.com/tag/stack/
20. 有效的括號簡單
42. 接雨水困難[?]
71. 簡化路徑中等
84. 柱狀圖中最大的矩形困難※※※
85. 最大矩形困難※※※
94. 二叉樹的中序遍歷中等[?]
103. 二叉樹的鋸齒形層次遍歷中等[?]
144. 二叉樹的前序遍歷中等
145. 二叉樹的后序遍歷困難
150. 逆波蘭表達式求值中等[?]
20. 有效的括號簡單
給定一個只包括
'(',')','{','}','[',']'的字符串,判斷字符串是否有效。
有效字符串需滿足:
- 左括號必須用相同類型的右括號閉合。
- 左括號必須以正確的順序閉合。
注意空字符串可被認為是有效字符串。
示例 1:
輸入: "()"
輸出: true
示例 2:
輸入: "()[]{}"
輸出: true
示例 3:
輸入: "(]"
輸出: false
思路:棧
class Solution {//執(zhí)行用時:2 ms, 在所有 Java 提交中擊敗了81.59%的用戶
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for(int i=0;i<s.length();i++){
char c = s.charAt(i);
if(c=='(' || c=='{' || c=='['){
stack.push(c);//遇到左括號就入棧
}
else{
if(stack.isEmpty()){
return false;
}
char popChar = stack.pop();
if((c==')'&&popChar!='(')||(c=='}'&&popChar!='{')||(c==']'&&popChar!='['))
return false;
}
}
return stack.isEmpty();
}
}
class Solution {//執(zhí)行用時:1 ms, 在所有 Java 提交中擊敗了98.76%的用戶
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for(char c:s.toCharArray()){
if(c == '(') stack.push(')');
else if(c == '[') stack.push(']');
else if(c == '{') stack.push('}');
else if(stack.isEmpty() || c != stack.pop()) return false;
}
return stack.isEmpty();
}
}
42. 接雨水困難
精選題解的幾種解法都超級棒
給定 n 個非負整數(shù)表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之后能接多少雨水。
上面是由數(shù)組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。
輸入: [0,1,0,2,1,0,1,3,2,1,2,1],輸出: 6
思路一:暴力法,時間復雜度O(n*n)
對于數(shù)組中的每個元素,下雨后水能達到的最高位置,等于兩邊最大高度的較小值減去當前高度的值。為了找到最大值每次都要向左和向右掃描一次。
class Solution {
public int trap(int[] height) {
int sum = 0;
for(int i=1;i<height.length-1;i++){
int max_left = 0;
int max_right = 0;
//左邊最高列
for(int j=i;j>=0;j--){
if(height[j]>max_left)
max_left = height[j];
}
//右邊最高列
for(int j=i;j<height.length;j++){
if(height[j]>max_right)
max_right = height[j];
}
int min = Math.min(max_left,max_right);
//只有較小的一列大于當前列的高度才會有水
if (min > height[i])
sum += (min - height[i]);
}
return sum;
}
}
思路二:動態(tài)規(guī)劃,時間復雜度O(n)
在按列求解的暴力法中,對于每一列,我們求它左邊最高的墻和右邊最高的墻,都是重新遍歷一遍所有高度。為了降低時間復雜度,定義兩個數(shù)組,max_left [i] 代表第 i 列左邊最高的墻的高度,max_right[i] 代表第 i 列右邊最高的墻的高度。
class Solution {
public int trap(int[] height) {
int sum = 0;
int[] max_left = new int[height.length];
int[] max_right = new int[height.length];
for(int i = 1; i < height.length - 1; i++){
max_left[i] = Math.max(max_left[i-1],height[i-1]);
}
for(int i = height.length - 2; i >= 0; i--){
max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
}
for (int i = 1; i < height.length - 1; i++) {
int min = Math.min(max_left[i],max_right[i]);
//只有較小的一列大于當前列的高度才會有水
if(min>height[i])
sum += (min - height[i]);
}
return sum;
}
}
71. 簡化路徑中等
以 Unix 風格給出一個文件的絕對路徑,你需要簡化它?;蛘邠Q句話說,將其轉(zhuǎn)換為規(guī)范路徑。
在 Unix 風格的文件系統(tǒng)中,一個點(.)表示當前目錄本身;此外,兩個點 (..) 表示將目錄切換到上一級(指向父目錄);兩者都可以是復雜相對路徑的組成部分。更多信息請參閱:Linux / Unix中的絕對路徑 vs 相對路徑
請注意,返回的規(guī)范路徑必須始終以斜杠/開頭,并且兩個目錄名之間必須只有一個斜杠/。最后一個目錄名(如果存在)不能以/結(jié)尾。此外,規(guī)范路徑必須是表示絕對路徑的最短字符串。
示例 1:
輸入:"/home/"
輸出:"/home"
解釋:注意,最后一個目錄名后面沒有斜杠。
示例 2:
輸入:"/../"
輸出:"/"
解釋:從根目錄向上一級是不可行的,因為根是你可以到達的最高級。
示例 3:
輸入:"/home//foo/"
輸出:"/home/foo"
解釋:在規(guī)范路徑中,多個連續(xù)斜杠需要用一個斜杠替換。
示例 4:
輸入:"/a/./b/../../c/"
輸出:"/c"
示例 5:
輸入:"/a/../../b/../c//.//"
輸出:"/c"
示例 6:
輸入:"/a//b////c/d//././/.."
輸出:"/a/b/c"
思路:棧
84. 柱狀圖中最大的矩形困難※※※
給定 n 個非負整數(shù),用來表示柱狀圖中各個柱子的高度。每個柱子彼此相鄰,且寬度為 1 。求在該柱狀圖中,能夠勾勒出來的矩形的最大面積。
以上是柱狀圖的示例,其中每個柱子的寬度為 1,給定的高度為[2,1,5,6,2,3]。
圖中陰影部分為所能勾勒出的最大矩形面積,其面積為10個單位。
輸入: [2,1,5,6,2,3]
輸出: 10
思路:單調(diào)棧
看了以下幾篇文章
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/84-by-ikaruga/很好的解釋了為什么使用單調(diào)棧
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/xiang-jie-dan-diao-zhan-bi-xu-miao-dong-by-sweetie/
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/zhao-liang-bian-di-yi-ge-xiao-yu-ta-de-zhi-by-powc/
遍歷每根柱子,以當前柱子 i 的高度作為矩形的高,那么矩形的寬度邊界即為向左找到第一個高度小于當前柱體 i 的柱體,向右找到第一個高度小于當前柱體 i 的柱體。
對于每個柱子我們都如上計算一遍以當前柱子作為高的矩形面積,最終比較出最大的矩形面積即可。
class Solution {//執(zhí)行用時 :15 ms, 在所有 Java 提交中擊敗了53.09%的用戶
public int largestRectangleArea(int[] heights) {
int[] tmp = new int[heights.length + 2];
//在柱體數(shù)組的頭和尾加了兩個高度為 0 的柱體
//將height中從0開始的,長度為height.length的元素復制到temp從1開始的位置上
System.arraycopy(heights, 0, tmp, 1, heights.length);
Stack<Integer> stack = new Stack <>();
stack.push(-1);
int maxarea = 0;
for (int i = 0; i < tmp.length; ++i) {
while (stack.peek() != -1 && tmp[stack.peek()] >= tmp[i]){
maxarea = Math.max(maxarea,tmp[stack.pop()]*(i-stack.peek()-1));
}
stack.push(i);
}
return maxarea;
}
}
85. 最大矩形困難※※※
給定一個僅包含 0 和 1 的二維二進制矩陣,找出只包含 1 的最大矩形,并返回其面積。
輸入:
[
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
]
輸出: 6
class Solution {
public int maximalRectangle(char[][] matrix) {//執(zhí)行用時 :10 ms, 在所有 Java 提交中擊敗了66.16%的用戶
if (matrix.length == 0) {
return 0;
}
int[] heights = new int[matrix[0].length];
int maxArea = 0;
for (int row = 0; row < matrix.length; row++) {
//遍歷每一列,更新高度
for (int col = 0; col < matrix[0].length; col++) {
if (matrix[row][col] == '1') {
heights[col] += 1;
} else {
heights[col] = 0;
}
}
//調(diào)用上一題的解法,更新函數(shù)
maxArea = Math.max(maxArea, largestRectangleArea(heights));
}
return maxArea;
}
public int largestRectangleArea(int[] heights) {
int maxArea = 0;
Stack<Integer> stack = new Stack<>();
int p = 0;
while (p < heights.length) {
//??杖霔? if (stack.isEmpty()) {
stack.push(p);
p++;
} else {
int top = stack.peek();
//當前高度大于棧頂,入棧
if (heights[p] >= heights[top]) {
stack.push(p);
p++;
} else {
//保存棧頂高度
int height = heights[stack.pop()];
//左邊第一個小于當前柱子的下標
int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
//右邊第一個小于當前柱子的下標
int RightLessMin = p;
//計算面積
int area = (RightLessMin - leftLessMin - 1) * height;
maxArea = Math.max(area, maxArea);
}
}
}
while (!stack.isEmpty()) {
//保存棧頂高度
int height = heights[stack.pop()];
//左邊第一個小于當前柱子的下標
int leftLessMin = stack.isEmpty() ? -1 : stack.peek();
//右邊沒有小于當前高度的柱子,所以賦值為數(shù)組的長度便于計算
int RightLessMin = heights.length;
int area = (RightLessMin - leftLessMin - 1) * height;
maxArea = Math.max(area, maxArea);
}
return maxArea;
}
}
94. 二叉樹的中序遍歷中等
給定一個二叉樹,返回它的中序遍歷。
進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?
思路:迭代+棧
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {//執(zhí)行用時 :1 ms, 在所有 Java 提交中擊敗了49.48%的用戶
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(root != null || stack.size() > 0){
if(root != null){
//不斷往左子樹方向走,每走一次就將當前節(jié)點保存到棧中,這是模擬遞歸的調(diào)用
stack.push(root);
root = root.left;
}else{
//當前節(jié)點為空,說明左邊走到頭了,從棧中彈出節(jié)點并保存,然后轉(zhuǎn)向右邊節(jié)點,繼續(xù)上面整個過程
TreeNode temp = stack.pop();//pop()函數(shù)返回棧頂?shù)脑兀⑶覍⒃摋m斣爻鰲? res.add(temp.val);
root = temp.right;
}
}
return res;
}
}
144. 二叉樹的前序遍歷中等
給定一個二叉樹,返回它的 *前序 *遍歷。
進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?
思路:迭代+棧
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {//執(zhí)行用時:0 ms, 在所有 Java 提交中擊敗了100.00%的用戶
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null)
return res;
Deque<TreeNode> stack = new ArrayDeque<>(); // 創(chuàng)建一個棧,官方推薦利用Deque
stack.push(root);//根節(jié)點入棧
while (!stack.isEmpty()){
// 1.節(jié)點出棧,訪問節(jié)點, 加入ArrayList中
TreeNode node = stack.poll();
res.add(node.val);
//之所以先加入右孩子再加入左孩子是利用棧后入先出的原則
// 2.若右孩子節(jié)點非空, 則將右孩子入棧
if(node.right != null)
stack.push(node.right);
// 3.若左孩子節(jié)點非空, 則將左孩子入棧
if(node.left != null)
stack.push(node.left);
}
return res;
}
}
145. 二叉樹的后序遍歷困難
給定一個二叉樹,返回它的 后序遍歷。
示例:
輸入: [1,null,2,3]
輸出: [3,2,1]
進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?
思路:迭代+棧
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {執(zhí)行用時:1 ms, 在所有 Java 提交中擊敗了56.90%的用戶
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null)
return res;
Deque<TreeNode> stack = new ArrayDeque<>(); // 創(chuàng)建一個棧,官方推薦利用Deque
stack.push(root);//根節(jié)點入棧
while (!stack.isEmpty()){
// 1.節(jié)點出棧,保存節(jié)點值
TreeNode node = stack.poll();
res.add(node.val);
// 2.左孩子節(jié)點入棧
if(node.left != null)
stack.push(node.left);
// 3.右孩子節(jié)點入棧
if(node.right != null)
stack.push(node.right);
}
Collections.reverse(res);//再逆序訪問節(jié)點
return res;
}
}
150. 逆波蘭表達式求值中等
根據(jù) 逆波蘭表示法,求表達式的值。
有效的運算符包括+,-,*,/。每個運算對象可以是整數(shù),也可以是另一個逆波蘭表達式。
說明:
整數(shù)除法只保留整數(shù)部分。
給定逆波蘭表達式總是有效的。換句話說,表達式總會得出有效數(shù)值且不存在除數(shù)為 0 的情況。
示例 :
輸入: ["2", "1", "+", "3", " * "]
輸出: 9
解釋: 該算式轉(zhuǎn)化為常見的中綴算術表達式為:((2 + 1) * 3) = 9
逆波蘭表達式:是一種后綴表達式,所謂后綴就是指算符寫在后面。
平常使用的算式則是一種中綴表達式,如( 1 + 2 ) * ( 3 + 4 )。
該算式的逆波蘭表達式寫法為( ( 1 2 + ) ( 3 4 + ) * )。
逆波蘭表達式主要有以下兩個優(yōu)點:
去掉括號后表達式無歧義,上式即便寫成1 2 + 3 4 + *也可以依據(jù)次序計算出正確結(jié)果。
適合用棧操作運算:遇到數(shù)字則入棧;遇到算符則取出棧頂兩個數(shù)字進行計算,并將結(jié)果壓入棧中。
思路:棧
class Solution {//執(zhí)行用時:6 ms, 在所有 Java 提交中擊敗了89.88%的用戶
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
int num1,num2;
for(String s:tokens){
switch(s){
case "+":
num2 = stack.pop();
num1 = stack.pop();
stack.push(num1 + num2);
break;
case "-":
num2 = stack.pop();
num1 = stack.pop();
stack.push(num1 - num2);
break;
case "*":
num2 = stack.pop();
num1 = stack.pop();
stack.push(num1 * num2);
break;
case "/":
num2 = stack.pop();
num1 = stack.pop();
stack.push(num1 / num2);
break;
default:
stack.push(Integer.valueOf(s));
break;
}
}
return stack.pop();
}
}




