球球速刷LC之數據結構--棧

棧的特性是先入後出,棧的主要題型包括常規棧和單調棧。
常規棧應用
簡化路徑
使用棧緩存當前到達的路徑,遇到"…"彈出棧頂,返回上級目錄。注意對最終棧爲空處理

    string simplifyPath(string path) {
        if(path.empty()) return "";
        
        string curr_path;
        stack<string>st;
        for(int i=0;i<path.size();++i){
        //跳過 /符號
            if(path[i]=='/'){
                continue;
            }
            //記錄當前層級目錄名稱
            curr_path+=path[i];
            //當前層級目錄結束
            if(i+1==path.size ()|| path[i+1]=='/'){
                if(curr_path.empty()==false){
                    if(curr_path==".."){ //返回當前父目錄,彈出棧頂
                       if(st.empty()==false) st.pop();                        
                    }else if(curr_path != "."){ //過濾.符號
                        st.push(curr_path);
                    }
                }
                curr_path.clear();
            }
        }
        
        if(st.empty())return "/"; //棧爲空代表根目錄
        
        string ret;
        while(st.empty()==false){  //將棧內目錄名稱使用 / 符號連接返回
            ret="/"+st.top()+ret;
            st.pop();
        }
        return ret;
    }

計算波蘭表達式
依次遍歷表達式,如果是數字,則直接入棧,如果是操作符,則從棧內彈出左右操作數,並進行符號操作後,將操作結果入棧。注意,彈出操作數時,彈出的第一個是右操作數,第二個是左操作數

int do_operator(char c , int lNum,int rNum)
{
    switch(c)
    {
        case '*':return lNum*rNum;
        case '+':return lNum+rNum;
        case '-':return lNum-rNum;
        case '/':return lNum/rNum;
    }
    return 0;
}

bool isOperator(string s)
{
    if(s == "*" || s=="+" ||s == "-" || s=="/") return true;
    return false;
}

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<string> s;
        
        for(int i = 0 ; i < tokens.size() ; ++i)
        {
            if(isOperator(tokens[i]))
            {
                //彈出兩個操作數
                int rNum = stoi(s.top());
                s.pop();
                int lNum = stoi(s.top());
                s.pop();
                
                int result = do_operator(tokens[i][0],lNum,rNum);
                s.push(to_string(result));                
                //計算結果
                //將計算結果入棧
            }
            else
            {
                s.push(tokens[i]);
            }
        }
        
        if(!s.empty()) return stoi(s.top());
        
        return 0;
    }
};

使用棧模擬隊列
使用隊列模擬棧
代碼略

去除重複字母
主要思路是,如果要讓最終字符串最小,如果後面的字符b比前面的字符a小,則應該儘可能刪除b,把a放到前面。
因此使用棧保存最終結果,遍歷原始字符串A依次進棧內:當前字符A與棧頂元素存在3種大小關係
1.如果當前元素A比top 元素小
< a > 棧內已經存在字符A,則跳過當前元素 ,記錄的剩餘A的數目-1
< b > 棧內沒有A存在

              < i > 剩餘top元素的數目>0 即A後面還有top存在,彈出top,繼續判斷A與彈出後棧的新top元素的關係
              < ii > 剩餘top元素數目==0.即後面沒有top元素,則A入棧 A剩餘數目-1,棧內A字符個數+1

2.如果當前元素A>top元素
棧內已有A,跳過,否則A入棧 ;剩餘A數目-1

3.當前A == top 元素 ,跳過A 剩餘A數目-1;
因此需要記錄棧內每種字符的數量,以及剩餘字符串內每種字符的數量

class Solution {
public:
    string removeDuplicateLetters(string s) {
        vector<char>st;        
        //入棧前        
        int left_count[26]; //用於記錄原是字符串內每種字符數量
        int stack_count[26];//用於記錄棧內每種字符的數量
        //初始化爲0
        for(int i=0;i<26;++i){
            left_count[i]=0; //用於記錄各個字符剩餘的數量
            stack_count[i]=0;//用於記錄棧內各個字符的數量
        }
        //初始化原始字符串內每種字符數量
        for(auto i:s){
            left_count[i-'a']++; //初始化剩餘字符的對應數量
        }
        
        for(int index=0;index<s.size();){
            char a=s[index];
            bool toNext=true;
            //棧爲空,直接入棧
            if(st.empty()){
                st.push_back(a);
                //更新當前字符在棧內以及剩餘數量
                --left_count[a-'a']; 
                ++stack_count[a-'a'];
            }else{
                auto top = st.back();
                //<1>當前元素與棧頂元素相同,直接跳過,並剩餘數量-1
                if(a == top){
                    --left_count[a-'a'];
                  //<2>當前元素大於棧頂元素
                }else if(a>top){ 
                    //棧內不存在當前元素,則該字符入棧,否則直接忽略該字符
                    if(stack_count[a-'a']== 0){
                         st.push_back(a);
                        ++stack_count[a-'a'];
                    }
                    --left_count[a-'a'];
                }else{ //當前元素小於棧頂元素,分類討論
                    //棧內已有該字符,直接忽略該字符
                    if(stack_count[a-'a']>0){
                      --left_count[a-'a'];
                    }else{
                        //由於把當前元素放到當前top字符前面去,可以減少最終字符串大小
                        //因此如果剩餘字符裏面還有top,就把當前top彈出
                        //剩餘字符裏沒有top了
                        if(left_count[top-'a'] == 0){
                             st.push_back(a);
                             --left_count[a-'a'];
                             ++stack_count[a-'a'];
                        }else{
                            //剩餘字符裏還有top,也就是top可以彈出
                            st.pop_back();
                            --stack_count[top-'a'];
                            //注意,此時a繼續與下一個top比較,並不是直接入棧
                            toNext=false;
                        }
                    }
                }
            }
            if(toNext)++index;                
        }
        string ret;
        for(auto i:st) ret.push_back(i);
        
        return ret;        
    }
};

檢查是否爲先序遍歷序列
這個方法簡單的說就是利用棧訪問過程中不斷的砍掉葉子節點。最後看看能不能全部砍掉。只剩下一個NULL,也就是#符號
以例子一爲例,:”9,3,4,#,#,1,#,#,2,#,6,#,#” 遇到x # #也就是葉子節點的時候,就把它變爲 #
模擬一遍過程:
9,3,4,#,# => 9,3,# 繼續讀
9,3,#,1,#,# => 9,3,#,# => 9,# 繼續讀
9,#2,#,6,#,# => 9,#,2,#,# => 9,#,# => #

    bool  isValidSerialization(string preorder) {
        vector<string>st;
        string curr_node;
        for(int i=0;i<preorder.size();++i){
            if(preorder[i]==',') continue; //逗號分割字段
                        
            curr_node+=preorder[i];
            if(i+1==preorder.size() || preorder[i+1]==','){ //當前字段分割完畢
               st.push_back(curr_node);
                //檢查是否有葉子節點,有的話持續刪除葉子節點   
               while(st.size()>=3 && st[st.size()-1]=="#" && st[st.size()-2]=="#" && st[st.size()-3] !="#"){            
                    st.pop_back();
                    st.pop_back();
                    st[st.size()-1]="#";
              }
              curr_node.clear();
            }
        }        
        if(st.size()==1 && st.back()=="#"){
            return true;
        }
        return false;
    }
};

層次列表迭代器
此題的思路是,如果某列表嵌套多層列表,直到最後一層純數字列表才能訪問,即最深的最先訪問到,因此使用棧。此外,對於同一層元素,第一個元素先於最後一個元素訪問,因此對於同一層列表需要倒序入棧,即最後一個元素最先入棧。

class NestedIterator {
    stack<NestedInteger>s;
public:
    NestedIterator(vector<NestedInteger> &nestedList) {
        //初始化時將列表倒序入棧
        for(int i=nestedList.size()-1;i>=0;--i){
            s.push(nestedList[i]);
        }
    }

    int _next =0;
    int next() {
        return _next;
    }

    bool hasNext() {
         while(!s.empty()){
            if(s.top().isInteger()){
               //純數字元素,則直接訪問
                int ret = s.top().getInteger();
                s.pop();
                _next = ret;
                return true;
            }else{
                //元素依然爲列表,則繼續將該列表展開,並倒序入棧
                auto top = s.top();
                s.pop();
                auto nestedList = top.getList();
                for(int i=nestedList.size()-1;i>=0;--i){
                    s.push(nestedList[i]);
                }
            }
        }
        return false;
    }
};

解碼字符串
遞歸是最直接思路


 public String decodeString2(String s) {
        if (s.length() == 0)
            return "";

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c >= '0' && c <= '9') {
                // 解析次數
                int digitStart = i++;
                while (s.charAt(i) >= '0' && s.charAt(i) <= '9')
                    i++;
                int num = Integer.parseInt(s.substring(digitStart, i));

                // 找到對應的右括號
                int strStart = i+1; // number must be followed by '['
                int count = 1; 
                i++;
                while (count != 0) {
                    if (s.charAt(i) == '[')
                        count++;
                    else if (s.charAt(i) == ']')
                        count--;
                    i++;
                }
                i--; 

                // 取子字符串
                String subStr = s.substring(strStart, i);

                // 將子字符串解碼
                String decodeStr = decodeString(subStr);

                // 將解碼的結果拼接到當前的字符串後面
                for (int j = 0; j < num; j++) {
                    sb.append(decodeStr);
                }

            } else {
                // 添加首元素
                sb.append(c);
            }

        }

        return sb.toString();
    }

刪除K個數字
類似於刪除字符串重複字母,使得最小題目。此處也是使用棧緩存最終數字,如果當前數字i比棧頂數字小,則在刪除指標k>0情況下,應該儘量刪除當前棧頂元素,使得小數字儘量進位。

class Solution {
public:
    string removeKdigits(string num, int k) {
        string ret;
        
        for(auto c:num){
            while(k && ret.size() && c<ret.back()){//只要還存在刪減指標,並且當前數小於前綴末尾,不斷刪除,從而使得當前小數字c前移
                ret.pop_back();
                --k;
            }
            if(!(ret.empty()&&c=='0'))  ret.push_back(c);//不存在前綴0
        }
        
        //確保所有刪除指標用完
        while(ret.size() && (k--)){
            ret.pop_back();
        }
        return ret.empty()?"0":ret;        
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章