LeetCode 棧、隊列、優(yōu)先隊列專題 1:棧和隊列的使用

這一部分,我們開始介紹“棧、隊列、優(yōu)先隊列”。棧和隊列雖然是簡單的數(shù)據(jù)結(jié)構(gòu),但是使用這些簡單的數(shù)據(jù)結(jié)構(gòu)所解決的算法問題不一定簡單。在這一章里,我們將來探索,和棧與隊列相關(guān)的算法問題。

棧和隊列的使用,棧和隊列是兩種基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)。Stack 這個基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)的特點是:后進先出,這一點是非常重要的。下面請看 LeetCode 第 20 題:

例題:LeetCode 第 20 題:有效的括號

傳送門:英文網(wǎng)址:20. Valid Parentheses ,中文網(wǎng)址:20. 有效的括號 。

給定一個只包括 '(',')','{','}''[',']' 的字符串,判斷字符串是否有效。

有效字符串需滿足:

  1. 左括號必須用相同類型的右括號閉合。
  2. 左括號必須以正確的順序閉合。

注意空字符串可被認(rèn)為是有效字符串。

示例 1:

輸入: "()"
輸出: true

示例 2:

輸入: "()[]{}"
輸出: true

示例 3:

輸入: "(]"
輸出: false

示例 4:

輸入: "([)]"
輸出: false

示例 5:

輸入: "{[]}"
輸出: true

分析:典型應(yīng)用,檢查括號匹配,是文本編輯器常見的功能。

注意空字符串可被認(rèn)為是有效字符串。

思路:問題本身非常容易。判斷字符串中的括號匹配是否合法。遍歷一遍這個字符串,使用一個 Stack 作為輔助空間。

一旦遇到左方向的符號,就把這個符號推入棧。

一旦遇到右方向的符號,將棧的棧頂元素出棧,就須要判斷是否對應(yīng)匹配。

Stack 中只是存放左方向的符號:“{”、“[”、“(”,在整個方法返回之前,一定要判斷一下 Stack 是否為空。因為 Stack 有可能出現(xiàn)全是左括號的情況。即:在棧中還有元素的情況下,待檢測的字符串一定是不符合題意的。

我的解答(看起來有些繁瑣):

下面我按照老師的解法,寫了一個不是優(yōu)化了很多的解法:

Java 代碼:

public class Solution {

    public boolean isValid(String s) {
        boolean isValid = false;
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '{' || c == '(' || c == '[') {
                stack.push(c);
            }
            if (c == '}' || c == ')' || c == ']') {
                // 出棧之前,應(yīng)該先檢查一下棧中是否還有元素
                if (stack.isEmpty()) {
                    return isValid;
                }
                Character popElement = stack.pop();
                Character match = null;
                if (c == '}') {
                    match = '{';
                }
                if (c == ']') {
                    match = '[';
                }
                if (c == ')') {
                    match = '(';
                }

                if (popElement != match) {
                    return isValid;
                }
            }
        }
        if (stack.isEmpty()) {
            isValid = true;
        }
        return isValid;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        boolean result = solution.isValid("8{90[s(d)f]44}33");
        System.out.println(result);
    }

}

說明:最后的這一步:

if (stack.isEmpty()) {
    isValid = true;
}

很容易忽略,請留意。

學(xué)到棧的時候,也學(xué)習(xí)了一些經(jīng)典的使用棧解決問題,我們要思考一下為什么使用棧?
使用棧的原因:在一個嵌套的關(guān)系中,通過棧頂元素來獲得最近的那個我們須要處理的元素。

棧頂元素反映了在嵌套的層次關(guān)系中,最近的需要匹配的元素。

Python 代碼:

# 20. 有效的括號
# 給定一個只包括 '(',')','{','}','[',']' 的字符串,判斷字符串是否有效。
class Solution:
    def isValid(self, s):
        """
        :type s: str
        :rtype: bool
        """
        stack = []
        d = ["()", "[]", "{}"]
        for i in range(0, len(s)):
            stack.append(s[i])
            if len(stack) >= 2 and stack[-2] + stack[-1] in d:
                stack.pop()
                stack.pop()
        return len(stack) == 0

練習(xí) 1:LeetCode 第 150 題: 逆波蘭表達式求值

傳送門:150. 逆波蘭表達式求值

根據(jù)逆波蘭表示法,求表達式的值。

有效的運算符包括 +, -, *, / 。每個運算對象可以是整數(shù),也可以是另一個逆波蘭表達式。

說明:

  • 整數(shù)除法只保留整數(shù)部分。
  • 給定逆波蘭表達式總是有效的。換句話說,表達式總會得出有效數(shù)值且不存在除數(shù)為 0 的情況。

示例 1:

輸入: ["2", "1", "+", "3", "*"]
輸出: 9
解釋: ((2 + 1) * 3) = 9

示例 2:

輸入: ["4", "13", "5", "/", "+"]
輸出: 6
解釋: (4 + (13 / 5)) = 6

示例 3:

輸入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
輸出: 22
解釋: 
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

Java 代碼:

public class Solution {

    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
            String pattern = "-?[0-9]+|[\\+\\-\\*/]";
            if (!token.matches(pattern)) {
                throw new RuntimeException("非法的表達式");
            }
            if (token.matches("-?[0-9]+")) {
                int num = Integer.valueOf(token);
                System.out.println(num);
                stack.push(num);
            }
            if (token.matches("[\\+\\-\\*/]")) {
                System.out.println("加減乘除" + token);
                if (stack.size() >= 2) {
                    int num1 = stack.pop();
                    int num2 = stack.pop();
                    int result = 0;
                    switch (token){
                        case "+":
                            result = num2 +num1;
                            break;
                        case "-":
                            result = num2 -num1;
                            break;
                        case "*":
                            result = num2 *num1;
                            break;
                        case "/":
                            result = num2 /num1;
                            break;
                    }
                    stack.push(result);
                }
            }
        }

        return stack.pop();
    }


    public static void main(String[] args) {
        String[] tokens = new String[]{"3", "-4", "+"};

        Solution solution = new Solution();
        int result = solution.evalRPN(tokens);
        System.out.println(result);
    }
}

是有問題的:Time Limit Exceeded 。然后我把上面的兩個 System.out.println() 語句刪除就 A 過了,好神奇,所以做題還是要規(guī)范啊。

練習(xí)2:LeetCode 第 71 題:簡化路徑

傳送門:71. 簡化路徑。

以 Unix 風(fēng)格給出一個文件的絕對路徑,你需要簡化它。或者換句話說,將其轉(zhuǎn)換為規(guī)范路徑。

在 Unix 風(fēng)格的文件系統(tǒng)中,一個點(.)表示當(dāng)前目錄本身;此外,兩個點 (..) 表示將目錄切換到上一級(指向父目錄);兩者都可以是復(fù)雜相對路徑的組成部分。更多信息請參閱: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"

參考了如下的文章:http://blog.csdn.net/u012249528/article/details/46705867

Java 代碼實現(xiàn):

public class Solution {

    public String simplifyPath(String path) {
        String result = "";
        String[] pathList = path.split("/");
        if (pathList.length == 0) {
            return "/";
        }

        Stack<String> stack = new Stack<>();
        for (String p : pathList) {
            if ("".equals(p) || ".".equals(p)) {
                continue;
            }
            if ("..".equals(p)) {
                if (!stack.isEmpty()) {
                    stack.pop();
                }
            } else { // 是正常的路徑字符串的時候,入棧
                stack.push(p);
            }
        }


        // 現(xiàn)在考慮輸出字符串
        while (!stack.isEmpty()) {
            result = "/" + stack.pop() + result;
        }
        if ("".equals(result)) {
            result = "/";
        }
        return result;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();

        String path1 = "/home/";
        String result1 = solution.simplifyPath(path1);
        System.out.println(result1);


        String path2 = "/a/./b/../../c/";
        String result2 = solution.simplifyPath(path2);
        System.out.println(result2);


        String path3 = "/..";
        String result3 = solution.simplifyPath(path3);
        System.out.println(result3);

        String path4 = "/..";
        String result4 = solution.simplifyPath(path4);
        System.out.println(result4);

        String path5 = "/abc/def/.";
        String result5 = solution.simplifyPath(path5);
        System.out.println(result5);
    }
}

(本節(jié)完)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容