先說一個消息,爲了方便互相交流學習,青銅三人行建了個微信羣,感興趣的夥伴可以掃碼加下面的小助手抱你入羣哦!
青銅三人行小助手
每週一題,代碼無敵~這次的主題是「貪心算法」:
青銅三人行——每週一題@驗證棧序列 視頻講解
驗證棧序列
給定 pushed 和 popped 兩個序列,每個序列中的 值都不重複,只有當它們可能是在最初空棧上進行的推入 push 和彈出 pop 操作序列的結果時,返回 true;否則,返回 false 。
示例1:
輸入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
輸出:true
解釋:我們可以按以下順序執行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例2:
輸入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
輸出:false
解釋:1 不能在 2 之前彈出。
提示:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列
棧結構
要做這道題,首先得了解一下什麼是「棧」。爲此書香搬來了維基百科上的解釋:
堆棧(英語:stack)又稱爲棧或堆疊,是計算機科學中的一種抽象數據類型,只允許在有序的線性數據集合的一端(稱爲堆棧頂端,英語:top)進行加入數據(英語:push)和移除數據(英語:pop)的運算。因而按照後進先出(LIFO,
Last In First Out)的原理運作。 棧常與另一種有序的線性數據集合隊列相提並論。 棧常用一維數組或鏈表來實現。
這個定義看起來多,其實也沒什麼大不了的。棧本身就是一個數組或者鏈表,只是人爲定義它獲取數據的方式只能從棧的頂端獲取,因此遵循先進後出,後進先出的規則罷了。
想象棧就是一摞盤子,你只能在最上面放盤子或者拿走盤子。對應起來,棧的操作就有兩個:
- push操作,往棧頂放入一個數據。
- pop操作,從棧頂取走一個數據。
解題思路
回到這道題目,一開始看起來題目有點繞,讓人不知道要做什麼。後來 helen 提議,既然題目要求是考慮在最初空棧上進行的推入 push 和彈出 pop 操作,那麼我們不妨就建立一個空棧嘗試用程序的方式來模擬一遍操作的流程,看看會不會明朗點:
var validateStackSequences = function (pushed, popped) {
const stack = [];
pushed.forEach((ele) => {
stack.push(ele);
stack.pop();
});
return !stack.length;
};
這樣我們就建立了一個棧,並且在按題目中 pushed 數組的順序將元素 push 進棧, 然後再按照同樣的順序 pop 出去。
不過這樣子就跟數學題裏面一邊放水,一邊加水的瘋狂管理員一般,返回的結果肯定爲 true。
回頭再看看題目,發現其實就是在這個一邊增加一邊移出的過程上,添加了一個條件:只能按照 poped 的順序來 pop 數據,看看能不能將 stack 清空 。
再拆解一下目標,就更明確了:
- 按照 pushed 的順序將元素 push 入棧。
- 在 push 的過程中嘗試 pop 元素。
- pop 元素的順序要和 poped 的順序一樣。
要滿足這三個條件,一個方法就是,嘗試在 push 的每一步時,儘可能按照指定順序 pop 出所有的元素。根據這個思路,helen 給出了題解:
var validateStackSequences = function(pushed, popped) {
const stack = [];
let popIndex = 0;
for (const val of pushed) {
stack.push(val);
while (stack.length !== 0 && stack[stack.length - 1] === popped[popIndex]) {
stack.pop();
popIndex++;
}
}
return stack.length === 0;
};
書香的思路一模一樣,只是把代碼寫的更短了點 :
var validateStackSequences = function (pushed, popped) {
const stack = [];
pushed.forEach((ele) => {
stack.push(ele);
while (stack.length && stack[stack.length - 1] === popped[0]) {
stack.pop();
popped.shift();
}
});
return !stack.length;
};
Extra go
對於書香和 helen 這樣的初級選手,通過實現一個棧結構來模擬題目中要求的操作,解出題目,就已經開心地到一邊去玩耍了 ~
但對於追求完美的曾大師來說,push 和 pop 這兩個操作,都非常消耗計算資源。而這種把數據一邊 push 一邊 pop 的瘋狂操作顯然是不能容忍的。
爲此,他寫出了2米長的 go 語言代碼:
func validateStackSequences(pushed []int, popped []int) bool {
if len(popped) == 0 {
return true
}
pushedValues := make(map[int]int) //存儲所有的已經入棧的值和數組索引
left := 0
right := 0
for i:=0;i< len(pushed);i++ {
if pushed[i] == popped[0] {
pushed[i] = -1 // 出棧
left = i-1
right = i+1
break
}else{
pushedValues[pushed[i]] = i
}
}
for j:=1;j<len(popped);j++ {
if _, ok := pushedValues[popped[j]]; ok { // 值已經加入stack了
for left >= 0 {
if pushed[left] == -1 {
left--
}else{
break
}
}
if left < 0 {
left = 0
}
if popped[j] != pushed[left] {
return false
}else{ // 值相等,出棧
pushed[left] = -1
left--
}
}else{ // 值沒有加入stack,繼續往前找
for right < len(popped) {
if popped[j] == pushed[right] { // 找到了
pushed[right] = -1 //出棧
left = right - 1 // 重新賦值left
right ++ // 重新賦值right
break
}else{ // 沒有找到,繼續往前
pushedValues[pushed[right]] = right
right++
}
}
}
}
return true
}
嗯…8 ms 的運行時間…
貪心算法
不知道你有沒有發現,這道題目,在開始的時候看起來比較繞,但是真正實現起來並沒有那麼困難?
其實關鍵點在於「分而治之」,將任務中的每一步拆分開來,並且在每一步時,都儘可能去尋找最優解,再將每一步的最優解達到合起來,看是否能達成目標。
這種思路的算法就稱爲「貪心算法」,它在遇到尋找最優解問題的情況下,能夠提供很大的幫助。
下次見~
三人行