目錄
- 前言
- 0. *經驗總結
- 0.1 程式員面試金典 P82
- 0.2 Java常用資料結構API
- 0.3 關于延遲移動
- 1. 三合一 [easy]
- 1.1 考慮點
- 1.2 解法
- 1.2.1 三指針法
- 1.2.2 二維數組法(優)
- 1.2.3 一維數組法
- 2. 棧的最小值 [easy]
- 2.1 考慮點
- 2.2 解法
- 2.2.1 循環周遊法
- 2.2.2 主輔棧法(優)
- 3. 堆盤子 [medium]
- 3.1 考慮點
- 3.2 解法
- 3.2.1 單連結清單尋棧法
- 3.2.2 二維連結清單法(優)
- 4. 化棧為隊 [easy]
- 4.1 考慮點
- 4.2 解法
- 4.2.1 雙棧法
- 4.2.2 延遲移動法(優)
- 5. 棧排序 [medium]
- 5.1 考慮點
- 5.2 解法
- 5.2.1 單棧法
- 5.2.2 根據插入值分離棧法(優)
- 6. 動物收容所 [easy]
- 6.1 考慮點
- 6.2 解法
- 6.2.1 單連結清單法
- 6.2.2 雙隊列法(優)
- 最後
本系列筆記主要記錄筆者刷《程式員面試金典》算法的一些想法與經驗總結,按專題分類,主要由兩部分構成:經驗值點和經典題目。其中重點放在經典題目上;
- 棧 - 後進先出(LIFO):
- 棧無法在常數時間複雜度内通路第i個元素。但因為棧不需要在添加和删除時移動元素,可以在常數時間複雜度内完成此操作;
- 對于遞歸算法:有時需要把臨時變量加入到棧中,在回溯時删除;
- 隊列 - 先進先出(FIFO):
- 更新隊列第一個和最後一個節點容易出錯,要再三确認;
- 隊列常用于廣度優先搜尋或緩存的實作;
- 例如,在廣度優先搜尋中,可以使用隊列來存儲需要被處理的節點。每處理一個結點,就把其相鄰節點加入隊列尾部。這樣可以按照發現順序處理節點;
- 需要注意的共同點:
- 要理清下标的棧大小的差別;
- 涉及棧和隊列的題目往往需要編寫好幾個方法,要理清楚前後邏輯;
詳細見以下文章:
Java | 個人總結的Java常用API手冊彙總
- 有些時候我們需要通路棧底或者隊列底的資料,往往需要對資料次序進行反轉操作;
- 我們可以每次都進行兩次反轉,因為要保證回到原來的樣子,這樣會産生大量重複操作;
- 另一種做法是我們先對棧或隊列進行反轉,需要通路棧頂或隊列頂元素時才翻轉回來;
- 第二種方法需要注意翻轉的時機;
- 下面《4. 化棧為隊》和《5. 棧排序》都用到類似的思想;

- 注意看提示
,說明三個棧在數組中排列是123123123;0<=stackNum<=2
- 可以試着跟面試官談談擴充問題,如:
- 當運作棧塊空間大小靈活可變時,要進行彈性分割,該怎麼實作;
- 可以将數組設計成環形,最後一個棧可能從數組尾處開始,環繞到數組起始處;
- 方法實作起來比較複雜,可以試着提供僞代碼或其中部分代碼;
class TripleInOne {
int[] stack;
int stackSize;
int t0;
int t1;
int t2;
public TripleInOne(int stackSize) {
stack = new int[stackSize*3];
int t0 = -3;
int t1 = -2;
int t2 = -1;
this.stackSize = stackSize;
this.t0 = t0;
this.t1 = t1;
this.t2 = t2;
}
public void push(int stackNum, int value) {
if( stackNum == 0 ){
this.t0 = pushVal( t0, value );
} else if( stackNum == 1 ){
this.t1 = pushVal( t1, value);
} else if( stackNum == 2){
this.t2 = pushVal( t2, value);
}
}
public int pushVal( int top, int value ){
if( top + 3 < stackSize*3 ){
top += 3;
stack[top] = value;
}
return top;
}
public int pop(int stackNum) {
int val = peek(stackNum);
if( val != -1 ){
if( stackNum == 0 ){
stack[t0] = -1;
t0 -= 3;
} else if( stackNum == 1 ){
stack[t1] = -1;
t1 -= 3;
} else if( stackNum == 2){
stack[t2] = -1;
t2 -= 3;
}
return val;
}
return -1;
}
public int peek(int stackNum) {
if( stackNum == 0 && t0 >= 0 ){
return stack[t0];
} else if( stackNum == 1 && t1 >= 1){
return stack[t1];
} else if( stackNum == 2 && t2 >= 2){
return stack[t2];
}
return -1;
}
public boolean isEmpty(int stackNum) {
int value = peek(stackNum);
if( value == -1){
return true;
}
return false;
}
}
- 執行時間:34.80%;記憶體消耗:44.07%;
- 定義三個指針,分别指向三個隊列在數組的索引;
class TripleInOne {
int N = 3;
// 3 * n 的數組,每一行代表一個棧
int[][] data;
// 記錄每個棧「待插入」位置
int[] locations;
public TripleInOne(int stackSize) {
data = new int[N][stackSize];
locations = new int[N];
}
public void push(int stackNum, int value) {
int[] stk = data[stackNum];
int loc = locations[stackNum];
if (loc < stk.length) {
stk[loc] = value;
locations[stackNum]++;
}
}
public int pop(int stackNum) {
int[] stk = data[stackNum];
int loc = locations[stackNum];
if (loc > 0) {
int val = stk[loc - 1];
locations[stackNum]--;
return val;
} else {
return -1;
}
}
public int peek(int stackNum) {
int[] stk = data[stackNum];
int loc = locations[stackNum];
if (loc > 0) {
return stk[loc - 1];
} else {
return -1;
}
}
public boolean isEmpty(int stackNum) {
return locations[stackNum] == 0;
}
}
- 執行時間:97.06%;記憶體消耗:69.49%;
- 時間複雜度:所有的操作均為 O(1)。
- 空間複雜度:O(k*n)。k 為我們需要實作的棧的個數,n 為棧的容量;
- 建立一個二維數組 datadata ;二維數組的每一行代表一個棧,同時使用一個locationslocations 記錄每個棧「待插入」的下标;
class TripleInOne {
int N = 3;
int[] data;
int[] locations;
int size;
public TripleInOne(int stackSize) {
size = stackSize;
data = new int[size * N];
locations = new int[N];
for (int i = 0; i < N; i++) {
locations[i] = i * size;
}
}
public void push(int stackNum, int value) {
int idx = locations[stackNum];
if (idx < (stackNum + 1) * size) {
data[idx] = value;
locations[stackNum]++;
}
}
public int pop(int stackNum) {
int idx = locations[stackNum];
if (idx > stackNum * size) {
locations[stackNum]--;
return data[idx - 1];
} else {
return -1;
}
}
public int peek(int stackNum) {
int idx = locations[stackNum];
if (idx > stackNum * size) {
return data[idx - 1];
} else {
return -1;
}
}
public boolean isEmpty(int stackNum) {
return locations[stackNum] == stackNum * size;
}
}
- 執行時間:97.06%;記憶體消耗:44.07%;
- 把加入一個元素看成一種狀态,每種狀态都有對應最小值,這樣得到解法2;
class MinStack {
Stack<Integer> stack;
Stack<Integer> minStack;
/** initialize your data structure here. */
public MinStack() {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minStack = new Stack<>();
this.stack = stack;
this.minStack = minStack;
}
public void push(int x) {
stack.add(x);
if( minStack.isEmpty() ){
minStack.add(x);
return;
}
Stack<Integer> cache = new Stack<>();
boolean isMatter = false;
while( !isMatter ){
if( !minStack.isEmpty() && minStack.peek() < x ){
cache.add( minStack.pop() );
} else {
minStack.add(x);
isMatter = true;
}
}
while( !cache.isEmpty() ){
minStack.add(cache.pop());
}
}
public void pop() {
if( stack.isEmpty() ){
return;
}
int topNum = stack.pop();
Stack<Integer> cache = new Stack<>();
boolean isMatter = false;
while( !isMatter ){
if( minStack.peek() == topNum ){
minStack.pop();
isMatter = true;
} else {
cache.add(minStack.pop());
}
}
while( !cache.isEmpty() ){
minStack.add(cache.pop());
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
- 執行時間:5.02%;記憶體消耗:5.18%;
- 不推薦用此方法,不滿足O(1)的時間複雜度;
class MinStack {
Deque<Integer> xStack;
Deque<Integer> minStack;
public MinStack() {
xStack = new LinkedList<Integer>();
minStack = new LinkedList<Integer>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int x) {
xStack.push(x);
minStack.push(Math.min(minStack.peek(), x));
}
public void pop() {
xStack.pop();
minStack.pop();
}
public int top() {
return xStack.peek();
}
public int getMin() {
return minStack.peek();
}
}
- 執行時間:91.63%;記憶體消耗:58.64%;
-
建立兩個棧,一個棧是主棧 stackstack,另一個是輔助棧 minStackminStack,用于存放對應主棧不同時期的最小值;
、
- push是往最後一個棧中放資料,而不是從前周遊找到空位;
- 注意讨論當傳入cap=0時的情況;
- 可以跟面試官讨論push的兩種情況:
- 第一種是:往最後一個棧中放資料(下訴解法);
- 第二種是:從前周遊找到空位(需要推出棧n+1的棧底元素到棧n,循環往複到最後一個棧);
- 前者優點是能很大程度上改善時間複雜度,後者适用一些要求所有棧(除最後一個)都填滿的場景;
class StackOfPlates {
int cap;
List<Stack<Integer>> list;
public StackOfPlates(int cap) {
List<Stack<Integer>> list = new ArrayList<>();
this.cap = cap;
this.list = list;
}
public void push(int val) {
if( cap == 0 ){
return;
}
Stack<Integer> stack;
if( list.isEmpty() ){
stack = new Stack<Integer>();
list.add(stack);
} else {
stack = list.get(list.size() - 1);
if( stack.size() >= cap ){
stack = new Stack<Integer>();
list.add(stack);
}
}
if( stack.size() < cap ){
stack.add(val);
}
}
public int pop() {
if( cap == 0 ){
return -1;
}
if( list.isEmpty() ){
return -1;
}
Stack<Integer> stack = list.get(list.size() - 1);
int result = stack.pop();
if(stack.isEmpty()){
list.remove( list.size() - 1 );
}
return result;
}
public int popAt(int index) {
if( cap == 0 ){
return -1;
}
if( index > list.size()-1 || index < 0 || list.isEmpty() ){
return -1;
}
Stack<Integer> stack = list.get(index);
int result = stack.pop();
if(stack.isEmpty()){
list.remove( index );
}
return result;
}
}
- 執行時間:60.44%;記憶體消耗:43.48%;
class StackOfPlates {
private LinkedList<LinkedList<Integer>> setOfStacks;
private int cap;
public StackOfPlates(int cap) {
this.setOfStacks = new LinkedList<>();
this.cap = cap;
}
private boolean setIsEmpty() {
return setOfStacks.isEmpty();
}
private boolean lastStackIsFUll() {
if (setIsEmpty()) {
return true;
}
return setOfStacks.getLast().size()>=cap;
}
private boolean lastStackIsEmpty() {
return setOfStacks.getLast().isEmpty();
}
public void push(int val) {
if (cap <= 0) {
return;
}
if (setIsEmpty() || lastStackIsFUll()) {
setOfStacks.addLast(new LinkedList<>());
}
setOfStacks.getLast().addLast(val);
}
public int pop() {
int val = -1;
if (setIsEmpty()) {
return val;
}
val = setOfStacks.getLast().removeLast();
if (lastStackIsEmpty()) {
setOfStacks.removeLast();
}
return val;
}
public int popAt(int index) {
int val = -1;
if (setIsEmpty() ||setOfStacks.size()-1<index ) {
return val;
}
val = setOfStacks.get(index).removeLast();
if (setOfStacks.get(index).isEmpty()) {
setOfStacks.remove(index);
}
return val;
}
}
- 執行時間:96.23%;記憶體消耗:60.59%;
- 使用二維的連結清單存儲,每次目前棧滿了就新增;
- 下訴第一種解法會存在大量重複操作,可以使用第二種延遲移動的方法,在有必要時才反轉次序;
class MyQueue {
Stack<Integer> stack;
Stack<Integer> cache;
/** Initialize your data structure here. */
public MyQueue() {
Stack<Integer> stack = new Stack<>();
Stack<Integer> cache = new Stack<>();
this.stack = stack;
this.cache = cache;
}
/** Push element x to the back of queue. */
public void push(int x) {
stack.add(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if(stack.isEmpty()){
return -1;
}
while( !stack.isEmpty() ){
cache.add(stack.pop());
}
int result = cache.pop();
while( !cache.isEmpty() ){
stack.add(cache.pop());
}
return result;
}
/** Get the front element. */
public int peek() {
if(stack.isEmpty()){
return -1;
}
while( !stack.isEmpty() ){
cache.add(stack.pop());
}
int result = cache.peek();
while( !cache.isEmpty() ){
stack.add(cache.pop());
}
return result;
}
/** Returns whether the queue is empty. */
public boolean empty() {
return stack.isEmpty();
}
}
- 執行時間:81.59%;記憶體消耗:53.96%;
class MyQueue {
Deque<Integer> inStack;
Deque<Integer> outStack;
public MyQueue() {
inStack = new LinkedList<Integer>();
outStack = new LinkedList<Integer>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if (outStack.isEmpty()) {
in2out();
}
return outStack.pop();
}
public int peek() {
if (outStack.isEmpty()) {
in2out();
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
private void in2out() {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
- 執行時間:100.00%;記憶體消耗:35.19%;
- 隻有進行pop()和peek()操作,并且輸出棧為空時才需要翻轉次序;
- 當有必要反轉次序時才移動元素,使用者進行連續pop()操作時不需要反轉次序;
- 需要注意細節;
- 可以考慮延遲移動;
class SortedStack {
Stack<Integer> stack;
Stack<Integer> cache;
public SortedStack() {
Stack<Integer> stack = new Stack<>();
Stack<Integer> cache = new Stack<>();
this.stack = stack;
this.cache = cache;
}
public void push(int val) {
if( stack.isEmpty() ){
stack.add(val);
return;
}
if( stack.peek() > val ){
stack.add(val);
} else {
cache.add(stack.pop());
stack.add(val);
stack.add(cache.pop());
}
}
public void pop() {
if( stack.isEmpty() ){
return;
}
stack.pop();
int val;
if( !stack.isEmpty() ){
val = stack.peek(); //忘記peek
} else {
return;
}
while(!stack.isEmpty()){
if( stack.peek() >= val ){
cache.add(stack.pop());
} else {
val = stack.peek();
}
}
boolean isEliminate = false;
while( !cache.isEmpty() ){
if( cache.peek() == val && !isEliminate ){
cache.pop();
isEliminate = true; //忘記上鎖
} else {
stack.add( cache.pop() );
}
}
stack.add(val);
}
public int peek() {
if(stack.isEmpty()){
return -1;
}
return stack.peek();
}
public boolean isEmpty() {
return stack.isEmpty();
}
}
- 執行時間:5.04%;記憶體消耗:70.80%;
- 這裡的單棧指每次操作後資料都存儲在一個棧,另一個棧隻做輔助作用,可以用其他資料結構代替;
class SortedStack {
//原始棧
Deque<Integer> stack = new LinkedList<>();
//輔助棧
Deque<Integer> tmp = new LinkedList<>();
public SortedStack() {
}
public void push(int val) {
int max = stack.isEmpty() ? Integer.MAX_VALUE : stack.peek();
int min = tmp.isEmpty() ? Integer.MIN_VALUE : tmp.peek();
//比較原始棧與輔助棧棧頂值,使其滿足 輔助棧 <= val <= 原始棧
while(true){
if(val > max){
tmp.push(stack.pop());
max = stack.isEmpty() ? Integer.MAX_VALUE : stack.peek();
} else if(val < min){
stack.push(tmp.pop());
min = tmp.isEmpty() ? Integer.MIN_VALUE : tmp.peek();
} else{
stack.push(val);
break;
}
}
}
public void pop() {
//将輔助棧元素push回原始棧
while (!tmp.isEmpty()){
stack.push(tmp.pop());
}
if (!stack.isEmpty())
stack.pop();
}
public int peek() {
//将輔助棧元素push回原始棧
while (!tmp.isEmpty()){
stack.push(tmp.pop());
}
return stack.isEmpty() ? -1 : stack.peek();
}
public boolean isEmpty() {
return stack.isEmpty() && tmp.isEmpty();
}
}
- 執行時間:99.65%;記憶體消耗:81.59%;
- push()操作後資料可以分布在兩個棧中,隻有pop()或peek()操作時才考慮将儲存值比較小的棧歸位;
- 可以問問面試官允許使用多少個連結清單(或其他資料結構);
- 這個問題有多種解法,如果使用一個隊列,對
操作簡單,而dequeueAny()
和dequeueCat()
則需要周遊整個隊列,降低執行效率;(對應解法一)dequeueDog()
- 另一種解法是貓狗各自建立一個隊列,放進包裝類裡,包裝類有個時間戳屬性,用來标記進入時間,在執行
操作時隻需要檢視兩個隊列的的首部,比較時間戳,傳回較老那位;(對應解法二)dequeueAny()
class AnimalShelf {
List<String> list;
public AnimalShelf() {
List<String> list = new ArrayList<>();
this.list = list;
}
public void enqueue(int[] animal) {
if( animal[0] < 0 || animal[1] < 0 || animal[1] > 2){
return;
}
String animalStr = String.valueOf(animal[0]) + String.valueOf(animal[1]);
list.add(animalStr);
}
public int[] dequeueAny() {
if( list.isEmpty() ){
return new int[]{-1, -1};
}
String result = list.get(0);
list.remove(0);
int num = Integer.parseInt(result);
return new int[]{num/10, num%10};
}
public int[] dequeueDog() {
if( list.isEmpty() ){
return new int[]{-1, -1};
}
for( int i = 0; i < list.size(); i++){
int num = Integer.parseInt( list.get(i) );
if( num%10 == 1 ){
list.remove(i);
return new int[]{num/10, num%10};
}
}
return new int[]{-1, -1};
}
public int[] dequeueCat() {
if( list.isEmpty() ){
return new int[]{-1, -1};
}
for( int i = 0; i < list.size(); i++){
int num = Integer.parseInt( list.get(i) );
if( num%10 == 0 ){
list.remove(i);
return new int[]{num/10, num%10};
}
}
return new int[]{-1, -1};
}
}
- 執行時間:17.07%;記憶體消耗:97.72%;
class AnimalShelf {
LinkedList<int[]> queueCat;
LinkedList<int[]> queueDog;
public AnimalShelf() {
queueCat = new LinkedList<>();
queueDog = new LinkedList<>();
}
public void enqueue(int[] animal) {
// 判斷種類後入隊
if (animal[1] == 0) {
queueCat.addLast(animal);
} else if (animal[1] == 1) {
queueDog.addLast(animal);
}
}
public int[] dequeueAny() {
// 取出cat的隊首,判空則直接傳回
int[] headCat;
if (!queueCat.isEmpty()) {
headCat = queueCat.getFirst();
} else if (!queueDog.isEmpty()) {
return queueDog.removeFirst();
} else {
return new int[]{-1,-1};
}
// 取出dog的隊首,判空則直接傳回
int[] headDog;
if (!queueDog.isEmpty()) {
headDog = queueDog.getFirst();
} else {
return queueCat.removeFirst();
}
// 比較後傳回
if (headCat[0]<=headDog[0]) {
return queueCat.removeFirst();
} else {
return queueDog.removeFirst();
}
}
public int[] dequeueDog() {
if (!queueDog.isEmpty()) {
return queueDog.removeFirst();
} else {
return new int[]{-1,-1};
}
}
public int[] dequeueCat() {
if (!queueCat.isEmpty()) {
return queueCat.removeFirst();
} else {
return new int[]{-1,-1};
}
}
}
- 執行時間:98.86;記憶體消耗:29.43%;
- 建立兩個隊列,分别存儲貓和狗。dequeCat和dequeDog分别取對應的隊首;
新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公衆号,會分享一些更日常的東西!
如需轉載,請标注出處!