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