--- tags: 提升程式設計師的面試力 --- # Chapter 3 | Stacks and Queues ## 實作堆疊 Stack 是使用 Last In First Out (LIFO) 後進先出原則的數據結構。 有些題目中,使用堆疊儲存資料比陣列更好。 基本的常見運算有: * isEmpty():檢查堆疊是否為空?若堆疊內無任何元素即返回 true,反之返回 false * push(elements):新增一個或多個元素到堆疊頂部 * pop():移除堆疊頂部元素,同時返回被移除的元素 * peek() 或 top():僅返回堆疊元素,不做任何修改 ![](https://i.imgur.com/sthCLom.png) 相較於 array,stack 只支援 pop 或 push 這類的方法(只能從尾取資料),而不能直接取出中間的項目,目的是要限制開發者的操作,以避免對該資料結構做了不適當的操作。 pop跟push之時間複雜度皆為常數, 即O(1) ### 實作堆疊的範例程式碼 ```java= package Introduction; import java.util.EmptyStackException; public class MyStack<T> { private static class StackNode<T> { private T data; private StackNode<T> next; public StackNode(T data) { this.data = data; } public T getData() { return data; } } private StackNode<T> top; public T pop() { if (top == null) throw new EmptyStackException(); T item = top.getData(); top = top.next; return item; } public void push(T item) { StackNode<T> t = new StackNode<T>(item); t.next = top; top = t; } public T peek() { if (top == null) throw new EmptyStackException(); return top.data; } public boolean isEmpty() { return top == null; } } ``` 堆疊常用於遞迴演算法。 有時在遞迴過程中需要將暫時資料推到堆疊上,然後再回頭移除他們。 堆疊也可以用於迭代的實作遞迴演算法。 ## 面試題目 ### 3.1 一對三:如何以單一陣列實作三個堆疊。 Three in One: Describe how you could use a single array to implement three stacks. ### SOLUTION --- ### Approach 1: Fixed Division 固定分隔 * For stack 1, we will use [0, n/3). * For stack 2, we will use [ n/3 , 2n/3 ) . * For stack 3, we will use [ 2n/3 , n) . ```java= package Q3_01_Three_in_One; import java.util.EmptyStackException; import CtCILibrary.AssortedMethods; public class FixedMultiStack { private int numberOfStacks = 3; private int stackCapacity; private int[] values; private int[] sizes; public FixedMultiStack(int stackSize) { stackCapacity = stackSize; values = new int[stackSize * numberOfStacks]; sizes = new int[numberOfStacks]; } /* Push value onto stack. */ public void push(int stackNum, int value) throws FullStackException { /* Check that we have space for the next element */ if (isFull(stackNum)) { throw new FullStackException(); } /* Increment stack pointer and then update top value. */ sizes[stackNum]++; values[indexOfTop(stackNum)] = value; } /* Pop item from top stack. */ public int pop(int stackNum) { if (isEmpty(stackNum)) { throw new EmptyStackException(); } int topIndex = indexOfTop(stackNum); int value = values[topIndex]; // Get top values[topIndex] = 0; // Clear sizes[stackNum]--; // Shrink return value; } /* Return top element. */ public int peek(int stackNum) { if (isEmpty(stackNum)) { throw new EmptyStackException(); } return values[indexOfTop(stackNum)]; } /* Return if stack is empty. */ public boolean isEmpty(int stackNum) { return sizes[stackNum] == 0; } /* Return if stack is full. */ public boolean isFull(int stackNum) { return sizes[stackNum] == stackCapacity; } /* Returns index of the top of the stack. */ private int indexOfTop(int stackNum) { int offset = stackNum * stackCapacity; int size = sizes[stackNum]; return offset + size - 1; } public int[] getValues() { return values; } public int[] getStackValues(int stackNum) { int[] items = new int[sizes[stackNum]]; for (int i = 0; i < items.length; i++) { items[i] = values[stackNum * stackCapacity + i]; } return items; } public String stackToString(int stackNum) { int[] items = getStackValues(stackNum); return stackNum + ": " + AssortedMethods.arrayToString(items); } } ``` ### 3.2 堆疊最小值: 如何設計除了push 與 pop 外還具有回傳最小元素的 min 函式的堆疊? push、pop、與 min 都應該以 O(1) 時間操作。 Stack Min: How would you design a stack which, in addition to push and pop, has a function min which returns the minimum element? Push, pop and min should all operate in O(1) time. push(5); // stack is {5}, min is 5 push(6); // stack is {6, 5}, min is 5 push(3); // stack is {3, 6, 5}, min is 3 push(7); // stack is {7, 3, 6, 5}, min is 3 pop(); // pops 7. stack is {3, 6, 5}, min is 3 pop(); // pops 3. stack is {6, 5}. min is 5. ### SOLUTION --- * minstack 必須判斷「欲新增的資料」是否有比「目前最上面」還要小。 * 如果有就把「欲新增的資料」push()進minstack,此時該資料會位在Stack的「最上面」,可以保證minstack的「最上面」資料就是datastack中的「最小值」。 ```java= package Q3_02_Stack_Min; import java.util.Stack; public class StackWithMin extends Stack<NodeWithMin> { public void push(int value) { int newMin = Math.min(value, min()); super.push(new NodeWithMin(value, newMin)); } public int min() { if (this.isEmpty()) { return Integer.MAX_VALUE; } else { return peek().min; } } } ``` ```java= package Q3_02_Stack_Min; class NodeWithMin { public int value; public int min; public NodeWithMin(int v, int min){ value = v; this.min = min; } } ``` ```java= package Q3_02_Stack_Min; import java.util.Stack; public class StackWithMin2 extends Stack<Integer> { Stack<Integer> s2; public StackWithMin2() { s2 = new Stack<Integer>(); } public void push(int value){ if (value <= min()) { s2.push(value); } super.push(value); } public Integer pop() { int value = super.pop(); if (value == min()) { s2.pop(); } return value; } public int min() { if (s2.isEmpty()) { return Integer.MAX_VALUE; } else { return s2.peek(); } } } ``` ### 3.3 Stack of Plates: 想像一下,一疊盤子,如果堆得太高,它很可能會倒,因此在現實生活中,如果盤子堆到一定高度,我們就會重新堆新的一疊。 實現一個新的資料結構 SetOfStack 來模擬這樣現象。 SetOfStack 當中包含很多的堆疊,當一個堆疊達到上限的時候,啟用下一個堆疊。 SetOfStack.push 和 SetOfStack.pop 應該和普通堆疊的操作一樣。 ### SOLUTION --- * 每次 push()都必須將元素放到最近使用的一個堆疊中,在這個堆疊已經滿了情況下,那就必須建一個新的堆疊,然後放進堆疊。 * pop()也必須要在最近的一個堆疊上操作,如果最後一個堆疊是空的話,就應該將其移除。 * 可以用Stack of Stacks來解決,當 push 超過 capacity 的時候,追加一個Stack。 * 每次 pop 都從 top Stack 中 pop。 ```java= package Q3_03_Stack_of_Plates; import java.util.ArrayList; import java.util.EmptyStackException; public class SetOfStacks { ArrayList<Stack> stacks = new ArrayList<Stack>(); public int capacity; public SetOfStacks(int capacity) { this.capacity = capacity; } public Stack getLastStack() { if (stacks.size() == 0) { return null; } return stacks.get(stacks.size() - 1); } public void push(int v) { Stack last = getLastStack(); if (last != null && !last.isFull()) { // add to last last.push(v); } else { // must create new stack Stack stack = new Stack(capacity); stack.push(v); stacks.add(stack); } } public int pop() { Stack last = getLastStack(); if (last == null) throw new EmptyStackException(); int v = last.pop(); if (last.size == 0) { stacks.remove(stacks.size() - 1); } return v; } public int popAt(int index) { return leftShift(index, true); } public int leftShift(int index, boolean removeTop) { Stack stack = stacks.get(index); int removed_item; if (removeTop) removed_item = stack.pop(); else removed_item = stack.removeBottom(); if (stack.isEmpty()) { stacks.remove(index); } else if (stacks.size() > index + 1) { int v = leftShift(index + 1, false); stack.push(v); } return removed_item; } public boolean isEmpty() { Stack last = getLastStack(); return last == null || last.isEmpty(); } } ``` #### Follow Up: Implement popAt(int index) 進階: 實現一個函數 popAt(int index),指定在哪個堆疊上執行 pop 操作。 ### SOLUTION --- * 如果在Stack 1 pop了一個元素,那麼就得把 Stack 2的一個元素移到Stack 1裡去,以此類推把Stack 3、4、5 …的都挪動一下。 ```java= package Q3_03_Stack_of_Plates; import java.util.ArrayList; import java.util.EmptyStackException; public class SetOfStacks { ArrayList<Stack> stacks = new ArrayList<Stack>(); public int capacity; public SetOfStacks(int capacity) { this.capacity = capacity; } public Stack getLastStack() { if (stacks.size() == 0) { return null; } return stacks.get(stacks.size() - 1); } public void push(int v) { Stack last = getLastStack(); if (last != null && !last.isFull()) { // add to last last.push(v); } else { // must create new stack Stack stack = new Stack(capacity); stack.push(v); stacks.add(stack); } } public int pop() { Stack last = getLastStack(); if (last == null) throw new EmptyStackException(); int v = last.pop(); if (last.size == 0) { stacks.remove(stacks.size() - 1); } return v; } public int popAt(int index) { return leftShift(index, true); } public int leftShift(int index, boolean removeTop) { Stack stack = stacks.get(index); int removed_item; if (removeTop) removed_item = stack.pop(); else removed_item = stack.removeBottom(); if (stack.isEmpty()) { stacks.remove(index); } else if (stacks.size() > index + 1) { int v = leftShift(index + 1, false); stack.push(v); } return removed_item; } public boolean isEmpty() { Stack last = getLastStack(); return last == null || last.isEmpty(); } } ``` ```java= package Q3_03_Stack_of_Plates; import java.util.EmptyStackException; public class Stack { private int capacity; public Node top; public Node bottom; public int size = 0; public Stack(int capacity) { this.capacity = capacity; } public boolean isFull() { return capacity == size; } public void join(Node above, Node below) { if (below != null) below.above = above; if (above != null) above.below = below; } public boolean push(int v) { if (size >= capacity) return false; size++; Node n = new Node(v); if (size == 1) bottom = n; join(n, top); top = n; return true; } public int pop() { if (top == null) throw new EmptyStackException(); Node t = top; top = top.below; size--; return t.value; } public boolean isEmpty() { return size == 0; } public int removeBottom() { Node b = bottom; bottom = bottom.above; if (bottom != null) bottom.below = null; size--; return b.value; } } ``` 參考資料: https://www.programiz.com/dsa/stack https://alrightchiu.github.io/SecondRound/stack-neng-gou-zai-o1qu-de-zui-xiao-zhi-de-minstack.html