數據結構丨隊列和棧

隊列

先入先出的數據結構

img

在 FIFO 數據結構中,將首先處理添加到隊列中的第一個元素html

如上圖所示,隊列是典型的 FIFO 數據結構。插入(insert)操做也稱做入隊(enqueue),新元素始終被添加在隊列的末尾。 刪除(delete)操做也被稱爲出隊(dequeue)。 你只能移除第一個元素java

示例 - 隊列node

  1. 入隊:您能夠單擊下面的 Enqueue 以查看如何將新元素 6 添加到隊列中。

img

Enqueue

img

  1. 出隊:您能夠單擊下面的 Dequeue 以查看將刪除哪一個元素。

img

Dequeue

img

隊列-實現

爲了實現隊列,咱們可使用動態數組和指向隊列頭部的索引。ios

如上所述,隊列應支持兩種操做:入隊和出隊。入隊會向隊列追加一個新元素,而出隊會刪除第一個元素。 因此咱們須要一個索引來指出起點。c++

這是一個供你參考的實現:git

#include <iostream>
#include <vector>

using namespace std;

class MyQueue {
    private:
        // store elements
        vector<int> data;       
        // a pointer to indicate the start position
        int p_start;            
    public:
        MyQueue() {p_start = 0;}
        /** Insert an element into the queue. Return true if the operation is successful. */
        bool enQueue(int x) {
            data.push_back(x);
            return true;
        }
        /** Delete an element from the queue. Return true if the operation is successful. */
        bool deQueue() {
            if (isEmpty()) {
                return false;
            }
            p_start++;
            return true;
        };
        /** Get the front item from the queue. */
        int Front() {
            return data[p_start];
        };
        /** Checks whether the queue is empty or not. */
        bool isEmpty()  {
            return p_start >= data.size();
        }
};

int main() {
    MyQueue q;
    q.enQueue(5);
    q.enQueue(3);
    if (!q.isEmpty()) {
        cout << q.Front() << endl;
    }
    q.deQueue();
    if (!q.isEmpty()) {
        cout << q.Front() << endl;
    }
    q.deQueue();
    if (!q.isEmpty()) {
        cout << q.Front() << endl;
    }
}

缺點算法

上面的實現很簡單,但在某些狀況下效率很低。 隨着起始指針的移動,浪費了愈來愈多的空間。 當咱們有空間限制時,這將是難以接受的。數組

img

讓咱們考慮一種狀況,即咱們只能分配一個最大長度爲 5 的數組。當咱們只添加少於 5 個元素時,咱們的解決方案頗有效。 例如,若是咱們只調用入隊函數四次後還想要將元素 10 入隊,那麼咱們能夠成功。數據結構

可是咱們不能接受更多的入隊請求,這是合理的,由於如今隊列已經滿了。可是若是咱們將一個元素出隊呢?less

img
實際上,在這種狀況下,咱們應該可以再接受一個元素。

循環隊列

此前,咱們提供了一種簡單但低效的隊列實現。

更有效的方法是使用循環隊列。 具體來講,咱們可使用固定大小的數組兩個指針來指示起始位置和結束位置。 目的是重用咱們以前提到的被浪費的存儲

讓咱們經過一個示例來查看循環隊列的工做原理。 你應該注意咱們入隊出隊元素時使用的策略。

1

仔細檢查動畫,找出咱們用來檢查隊列是仍是滿的策略。

下一個練習,咱們將讓你本身嘗試實現循環隊列,以後會提供給你一個解決方案。

設計循環隊列

設計你的循環隊列實現。 循環隊列是一種線性數據結構,其操做表現基於 FIFO(先進先出)原則而且隊尾被鏈接在隊首以後以造成一個循環。它也被稱爲「環形緩衝器」。

循環隊列的一個好處是咱們能夠利用這個隊列以前用過的空間。在一個普通隊列裏,一旦一個隊列滿了,咱們就不能插入下一個元素,即便在隊列前面仍有空間。可是使用循環隊列,咱們能使用這些空間去存儲新的值。

你的實現應該支持以下操做:

  • MyCircularQueue(k): 構造器,設置隊列長度爲 k 。
  • Front: 從隊首獲取元素。若是隊列爲空,返回 -1 。
  • Rear: 獲取隊尾元素。若是隊列爲空,返回 -1 。
  • enQueue(value): 向循環隊列插入一個元素。若是成功插入則返回真。
  • deQueue(): 從循環隊列中刪除一個元素。若是成功刪除則返回真。
  • isEmpty(): 檢查循環隊列是否爲空。
  • isFull(): 檢查循環隊列是否已滿。

示例:

MyCircularQueue circularQueue = new MycircularQueue(3); // 設置長度爲 3

circularQueue.enQueue(1);  // 返回 true

circularQueue.enQueue(2);  // 返回 true

circularQueue.enQueue(3);  // 返回 true

circularQueue.enQueue(4);  // 返回 false,隊列已滿

circularQueue.Rear();  // 返回 3

circularQueue.isFull();  // 返回 true

circularQueue.deQueue();  // 返回 true

circularQueue.enQueue(4);  // 返回 true

circularQueue.Rear();  // 返回 4

提示:

  • 全部的值都在 0 至 1000 的範圍內;
  • 操做數將在 1 至 1000 的範圍內;
  • 請不要使用內置的隊列庫。
#include <iostream>
#include <vector>
using namespace std;

/// One more space implementation
/// Time Complexity: O(1)
/// Space Complexity: O(n)
class MyCircularQueue
{
private:
    int front, tail;
    vector<int> data;

public:
    MyCircularQueue(int k)
    {
        front = tail = 0;
        data.clear();
        for (int i = 0; i <= k; i++)    //這裏多分配了一個空間
            data.push_back(-1);
    }
    bool enQueue(int value)
    {
        if (isFull())
            return false;
        data[tail] = value;
        tail = (tail + 1) % data.size();
        return true;
    }
    bool deQueue()
    {
        if (isEmpty())
            return false;
        front = (front + 1) % data.size();
        return true;
    }
    int Front(){
        if(isEmpty())
            return -1;
        return data[front];
    }
    int Rear()
    {
        if (isEmpty())
            return -1;
        int index = tail - 1;
        if (index < 0)
            index += data.size();
        return data[index];
    }
    bool isEmpty()
    {
        return front == tail;
    }
    bool isFull()
    {
        return (tail + 1) % data.size() == front;
    }
};

循環隊列-實現

在循環隊列中,咱們使用一個數組和兩個指針(headtail)。 head 表示隊列的起始位置,tail 表示隊列的結束位置。

這裏咱們提供了代碼供你參考:

class MyCircularQueue {
private:
    vector<int> data;
    int head;
    int tail;
    int size;
public:
    /** Initialize your data structure here. Set the size of the queue to be k. */
    MyCircularQueue(int k) {
        data.resize(k);
        head = -1;
        tail = -1;
        size = k;
    }
    
    /** Insert an element into the circular queue. Return true if the operation is successful. */
    bool enQueue(int value) {
        if (isFull()) {
            return false;
        }
        if (isEmpty()) {
            head = 0;   //第一次入隊,下標將從0開始。
        }
        tail = (tail + 1) % size;   //以後只需調整隊尾指針。
        data[tail] = value;
        return true;
    }
    
    /** Delete an element from the circular queue. Return true if the operation is successful. */
    bool deQueue() {
        if (isEmpty()) {
            return false;
        }
        if (head == tail) { //這裏與下面的判斷方式不一樣
            head = -1;
            tail = -1;
            return true;
        }
        head = (head + 1) % size;
        return true;
    }
    
    /** Get the front item from the queue. */
    int Front() {
        if (isEmpty()) {
            return -1;
        }
        return data[head];
    }
    
    /** Get the last item from the queue. */
    int Rear() {
        if (isEmpty()) {
            return -1;
        }
        return data[tail];
    }
    
    /** Checks whether the circular queue is empty or not. */
    bool isEmpty() {
        return head == -1;  //這不是用head == tail判斷
    }
    
    /** Checks whether the circular queue is full or not. */
    bool isFull() {
        return ((tail + 1) % size) == head;
    }
};

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * bool param_1 = obj.enQueue(value);
 * bool param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * bool param_5 = obj.isEmpty();
 * bool param_6 = obj.isFull();
 */

隊列-用法

大多數流行語言都提供內置的隊列庫,所以您無需從新發明輪子。

如前所述,隊列有兩個重要的操做,入隊 enqueue出隊 dequeue。 此外,咱們應該可以得到隊列中的第一個元素,由於應該首先處理它。

下面是使用內置隊列庫及其常見操做的一些示例:

#include <iostream>

int main() {
    // 1. Initialize a queue.
    queue<int> q;
    // 2. Push new element.
    q.push(5);
    q.push(13);
    q.push(8);
    q.push(6);
    // 3. Check if queue is empty.
    if (q.empty()) {
        cout << "Queue is empty!" << endl;
        return 0;
    }
    // 4. Pop an element.
    q.pop();
    // 5. Get the first element.
    cout << "The first element is: " << q.front() << endl;
    // 6. Get the last element.
    cout << "The last element is: " << q.back() << endl;
    // 7. Get the size of the queue.
    cout << "The size is: " << q.size() << endl;
}

咱們在本文以後提供了練習,以幫助你熟悉這些操做。請記住,當你想要按順序處理元素時,使用隊列多是一個很好的選擇。

數據流中的移動平均值

*到了這一題,發現要開會員才能看到題目-_-b,因而我就去百度找題目了。*

Given a stream of integers and a window size, calculate the moving average of all integers in the sliding window.

For example,
MovingAverage m = new MovingAverage(3);
m.next(1) = 1
m.next(10) = (1 + 10) / 2
m.next(3) = (1 + 10 + 3) / 3
m.next(5) = (10 + 3 + 5) / 3

給一個整數流和一個窗口,計算在給定大小的窗口裏的數字的平均值。

解法:隊列queue,用一個queue記錄進入窗口的整數。當流進窗口的整數不足時,計算全部窗口內的數字和返回,當進入窗口的整數多於窗口大小時,移除最早進入窗口的整數,新的整數進入queue,而後計算窗口內的整數和。
#include <iostream>
#include <queue>

using namespace std;

/// Using Queue
/// Time Complexity: O(1)
/// Space Complexity: O(size)
class MovingAverage
{
private:
    queue<int> q;
    int sz, sum;

public:
    MovingAverage(int size)
    {
        sz = size;
        sum = 0;
    }
    double next(int val)
    {
        if (q.size() == sz)
        {
            sum -= q.front();
            q.pop();
        }
        sum += val;
        q.push(val);

        return (double)sum / q.size();
    }
}

隊列和廣度優先搜索

隊列和BFS

廣度優先搜索(BFS)的一個常見應用是找出從根結點到目標結點的最短路徑。在本文中,咱們提供了一個示例來解釋在 BFS 算法中是如何逐步應用隊列的。

示例

這裏咱們提供一個示例來講明如何使用 BFS 來找出根結點 A 和目標結點 G 之間的最短路徑。

1.gif

洞悉

觀看上面的動畫後,讓咱們回答如下問題:

1. 結點的處理順序是什麼?

在第一輪中,咱們處理根結點。在第二輪中,咱們處理根結點旁邊的結點;在第三輪中,咱們處理距根結點兩步的結點;等等等等。

與樹的層序遍歷相似,越是接近根結點的結點將越早地遍歷

若是在第 k 輪中將結點 X 添加到隊列中,則根結點與 X 之間的最短路徑的長度剛好是 k。也就是說,第一次找到目標結點時,你已經處於最短路徑中。

2. 隊列的入隊和出隊順序是什麼?

如上面的動畫所示,咱們首先將根結點排入隊列。而後在每一輪中,咱們逐個處理已經在隊列中的結點,並將全部鄰居添加到隊列中。值得注意的是,新添加的節點不會當即遍歷,而是在下一輪中處理。

結點的處理順序與它們添加到隊列的順序是徹底相同的順序,即先進先出(FIFO)。這就是咱們在 BFS 中使用隊列的緣由。

廣度優先搜索-模板

以前,咱們已經介紹了使用 BFS 的兩個主要方案:遍歷找出最短路徑。一般,這發生在樹或圖中。正如咱們在章節描述中提到的,BFS 也能夠用於更抽象的場景中。

在本文中,咱們將爲你提供一個模板。而後,咱們在本文後提供一些習題供你練習。

在特定問題中執行 BFS 以前肯定結點和邊緣很是重要。一般,結點將是實際結點或是狀態,而邊緣將是實際邊緣或可能的轉換。

模板I

在這裏,咱們爲你提供僞代碼做爲模板:

/**
 * Return the length of the shortest path between root and target node.
 */
int BFS(Node root, Node target) {
    Queue<Node> queue;  // store all nodes which are waiting to be processed
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    // BFS
    while (queue is not empty) {
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
                add next to queue;
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}
  1. 如代碼所示,在每一輪中,隊列中的結點是等待處理的結點
  2. 在每一個更外一層的 while 循環以後,咱們距離根結點更遠一步。變量 step 指示從根結點到咱們正在訪問的當前結點的距離。

模板 II

有時,確保咱們永遠不會訪問一個結點兩次很重要。不然,咱們可能陷入無限循環。若是是這樣,咱們能夠在上面的代碼中添加一個哈希集來解決這個問題。這是修改後的僞代碼:

/**
 * Return the length of the shortest path between root and target node.
 */
int BFS(Node root, Node target) {
    Queue<Node> queue;  // store all nodes which are waiting to be processed
    Set<Node> used;     // store all the used nodes
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    add root to used;
    // BFS
    while (queue is not empty) {
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
                if (next is not in used) {
                    add next to queue;
                    add next to used;
                }
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}

有兩種狀況你不須要使用哈希集:

  1. 你徹底肯定沒有循環,例如,在樹遍歷中;
  2. 你確實但願屢次將結點添加到隊列中。

牆與門

轉載自http://www.javashuo.com/article/p-acdhjjaq-bn.html

You are given a m x n 2D grid initialized with these three possible values.

  1. -1 - A wall or an obstacle.
  2. 0 - A gate.
  3. INF - Infinity means an empty room. We use the value 231 - 1 = 2147483647 to represent INF as you may assume that the distance to a gate is less than 2147483647.

Fill each empty room with the distance to its nearest gate. If it is impossible to reach a gate, it should be filled with INF.

For example, given the 2D grid:

INF  -1  0  INF
INF INF INF  -1
INF  -1 INF  -1
  0  -1 INF INF

After running your function, the 2D grid should be:

3  -1   0   1
  2   2   1  -1
  1  -1   2  -1
  0  -1   3   4

這道題相似一種迷宮問題,規定了-1表示牆,0表示門,讓求每一個點到門的最近的曼哈頓距離,這其實相似於求距離場Distance Map的問題,那麼咱們先考慮用DFS來解,思路是,咱們搜索0的位置,每找到一個0,以其周圍四個相鄰點爲起點,開始DFS遍歷,並帶入深度值1,若是遇到的值大於當前深度值,咱們將位置值賦爲當前深度值,並對當前點的四個相鄰點開始DFS遍歷,注意此時深度值須要加1,這樣遍歷完成後,全部的位置就被正確地更新了,參見代碼以下:

解法一:

class Solution {
public:
    void wallsAndGates(vector<vector<int>>& rooms) {
        for (int i = 0; i < rooms.size(); ++i) {
            for (int j = 0; j < rooms[i].size(); ++j) {
                if (rooms[i][j] == 0) dfs(rooms, i, j, 0);
            }
        }
    }
    void dfs(vector<vector<int>>& rooms, int i, int j, int val) {
        if (i < 0 || i >= rooms.size() || j < 0 || j >= rooms[i].size() || rooms[i][j] < val) return;
        rooms[i][j] = val;
        dfs(rooms, i + 1, j, val + 1);
        dfs(rooms, i - 1, j, val + 1);
        dfs(rooms, i, j + 1, val + 1);
        dfs(rooms, i, j - 1, val + 1);
    }
};

那麼下面咱們再來看BFS的解法,須要藉助queue,咱們首先把門的位置都排入queue中,而後開始循環,對於門位置的四個相鄰點,咱們判斷其是否在矩陣範圍內,而且位置值是否大於上一位置的值加1,若是知足這些條件,咱們將當前位置賦爲上一位置加1,並將次位置排入queue中,這樣等queue中的元素遍歷完了,全部位置的值就被正確地更新了,參見代碼以下:

解法二:

class Solution {
public:
    void wallsAndGates(vector<vector<int>>& rooms) {
        queue<pair<int, int>> q;
        vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
        for (int i = 0; i < rooms.size(); ++i) {
            for (int j = 0; j < rooms[i].size(); ++j) {
                if (rooms[i][j] == 0) q.push({i, j});   
            }
        }
        while (!q.empty()) {
            int i = q.front().first, j = q.front().second; q.pop();
            for (int k = 0; k < dirs.size(); ++k) {
                int x = i + dirs[k][0], y = j + dirs[k][1];
                if (x < 0 || x >= rooms.size() || y < 0 || y >= rooms[0].size() || rooms[x][y] < rooms[i][j] + 1) continue;
                rooms[x][y] = rooms[i][j] + 1;
                q.push({x, y});
            }
        }
    }
};

島嶼數量

給定一個由 '1'(陸地)和 '0'(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,而且它是經過水平方向或垂直方向上相鄰的陸地鏈接而成的。你能夠假設網格的四個邊均被水包圍。

示例 1:

輸入:
11110
11010
11000
00000

輸出: 1

示例 2:

輸入:
11000
11000
00100
00011

輸出: 3

思路:只需判斷陸地有沒有跟已發現的島嶼相鄰,若是沒有相鄰,則是新的島嶼。

/// Source : https://leetcode.com/problems/number-of-islands/description/
/// Author : liuyubobobo
/// Time   : 2018-08-25

#include <iostream>
#include <vector>
#include <cassert>
#include <queue>

using namespace std;

/// Floodfill - BFS
/// Time Complexity: O(n*m)
/// Space Complexity: O(n*m)
class Solution {

private:
    int d[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};   //分別表示上、右、下、左
    int m, n;

public:
    int numIslands(vector<vector<char>>& grid) {

        m = grid.size();
        if(m == 0)
            return 0;
        n = grid[0].size();
        if(n == 0)
            return 0;

        vector<vector<bool>> visited(m, vector<bool>(n, false));

        int res = 0;
        for(int i = 0 ; i < m ; i ++)
            for(int j = 0 ; j < n ; j ++)
                if(grid[i][j] == '1' && !visited[i][j]){
                    bfs(grid, i, j, visited);
                    res ++;
                }
        return res;
    }

private:
    void bfs(vector<vector<char>>& grid, int x, int y, vector<vector<bool>>& visited){

        queue<pair<int, int>> q;
        q.push(make_pair(x, y));
        visited[x][y] = true;
        while(!q.empty()){
            int curx = q.front().first;
            int cury = q.front().second;
            q.pop();

            for(int i = 0; i < 4; i ++){
                int newX = curx + d[i][0];
                int newY = cury + d[i][1];
                if(inArea(newX, newY) && !visited[newX][newY] && grid[newX][newY] == '1'){
                    q.push(make_pair(newX, newY));
                    visited[newX][newY] = true;
                }
            }
        }

        return;
    }

    bool inArea(int x, int y){
        return x >= 0 && x < m && y >= 0 && y < n;
    }
};


int main() {

    vector<vector<char>> grid1 = {
            {'1','1','1','1','0'},
            {'1','1','0','1','0'},
            {'1','1','0','0','0'},
            {'0','0','0','0','0'}
    };
    cout << Solution().numIslands(grid1) << endl;
    // 1

    // ---

    vector<vector<char>> grid2 = {
            {'1','1','0','0','0'},
            {'1','1','0','0','0'},
            {'0','0','1','0','0'},
            {'0','0','0','1','1'}
    };
    cout << Solution().numIslands(grid2) << endl;
    // 3

    return 0;
}

打開轉盤鎖

你有一個帶有四個圓形撥輪的轉盤鎖。每一個撥輪都有10個數字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每一個撥輪能夠自由旋轉:例如把 '9' 變爲 '0''0' 變爲 '9' 。每次旋轉都只能旋轉一個撥輪的一位數字。

鎖的初始數字爲 '0000' ,一個表明四個撥輪的數字的字符串。

列表 deadends 包含了一組死亡數字,一旦撥輪的數字和列表裏的任何一個元素相同,這個鎖將會被永久鎖定,沒法再被旋轉。

字符串 target 表明能夠解鎖的數字,你須要給出最小的旋轉次數,若是不管如何不能解鎖,返回 -1。

示例 1:

輸入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
輸出:6
解釋:
可能的移動序列爲 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 這樣的序列是不能解鎖的,
由於當撥動到 "0102" 時這個鎖就會被鎖定。

示例 2:

輸入: deadends = ["8888"], target = "0009"
輸出:1
解釋:
把最後一位反向旋轉一次便可 "0000" -> "0009"。

示例 3:

輸入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
輸出:-1
解釋:
沒法旋轉到目標數字且不被鎖定。

示例 4:

輸入: deadends = ["0000"], target = "8888"
輸出:-1

提示:

  1. 死亡列表 deadends 的長度範圍爲 [1, 500]
  2. 目標數字 target 不會在 deadends 之中。
  3. 每一個 deadendstarget 中的字符串的數字會在 10,000 個可能的狀況 '0000''9999' 中產生。

思路:等價於八領域的迷宮問題,並繞過死鎖區域。字符和數字的轉換經過加 '0' 實現

/// Source : https://leetcode.com/problems/open-the-lock/description/
/// Author : liuyubobobo
/// Time   : 2017-12-23

#include <iostream>
#include <vector>
#include <set>
#include <queue>
#include <cassert>

using namespace std;

/// BFS
/// Time Complexity: O(charset^N)
/// Space Complexity: O(charset^N)
class Solution {
public:
    int openLock(vector<string>& deadends, string target) {

        set<string> dead;
        for(string s: deadends)
            dead.insert(s);

        if(dead.find(target) != dead.end() || dead.find("0000") != dead.end())
            return -1;

        set<string> visited;
        queue<pair<string, int>> q;
        q.push(make_pair("0000", 0));
        visited.insert("0000");
        while(!q.empty()){
            string cur = q.front().first;
            int step = q.front().second;
            q.pop();

            vector<string> next = getNext(cur, dead);
            for(string next_s: next)
                if(visited.find(next_s) == visited.end()){
                    if(next_s == target)
                        return step + 1;

                    visited.insert(next_s);
                    q.push(make_pair(next_s, step + 1));
                }
        }
        return -1;
    }

private:
    vector<string> getNext(const string& s, const set<string>& dead){
        vector<string> res;
        assert(s.size() == 4);
        for(int i = 0 ; i < 4 ; i ++){
            int num = s[i] - '0';

            int d = num + 1;
            if(d > 9) d = 0;
            string t = s;
            t[i] = ('0' + d);
            if(dead.find(t) == dead.end())
                res.push_back(t);

            d = num - 1;
            if(d < 0) d = 9;
            t = s;
            t[i] = ('0' + d);
            if(dead.find(t) == dead.end())
                res.push_back(t);
        }
        return res;
    }
};


int main() {

    vector<string> dead1 = {"0201","0101","0102","1212","2002"};
    string target1 = "0202";
    cout << Solution().openLock(dead1, target1) << endl;

    vector<string> dead2 = {"8888"};
    string target2 = "0009";
    cout << Solution().openLock(dead2, target2) << endl;

    vector<string> dead3 = {"8887","8889","8878","8898","8788","8988","7888","9888"};
    string target3 = "8888";
    cout << Solution().openLock(dead3, target3) << endl;

    vector<string> dead4 = {"1002","1220","0122","0112","0121"};
    string target4 = "1200";
    cout << Solution().openLock(dead4, target4) << endl;

    return 0;
}

徹底平方數

給定正整數 n,找到若干個徹底平方數(好比 1, 4, 9, 16, ...)使得它們的和等於 n。你須要讓組成和的徹底平方數的個數最少。

示例 1:

輸入: n = 12
輸出: 3 
解釋: 12 = 4 + 4 + 4.

示例 2:

輸入: n = 13
輸出: 2
解釋: 13 = 4 + 9.

思路:該問題可轉化爲求圖的無權最短路徑,即正整數n到0間是否存在一條最短路徑,路徑上節點數目最少。所以採用BFS,率先抵達0的路徑即爲所求。此外,重複出現的節點,可不加入圖中。以下圖中第一個7若能到達0,則以後再出現的7也能抵達0,而且第一次出現的7所歷經的層數必定比以後的7要少,無需重複計算。

1558862153945

/// Source : https://leetcode.com/problems/perfect-squares/description/
/// Author : liuyubobobo
/// Time   : 2017-11-17

#include <iostream>
#include <vector>
#include <queue>
#include <stdexcept>

using namespace std;

/// BFS
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {
public:
    int numSquares(int n) {

        if(n == 0)
            return 0;

        queue<pair<int, int>> q;
        q.push(make_pair(n, 0));

        vector<bool> visited(n + 1, false);
        visited[n] = true;

        while(!q.empty()){
            int num = q.front().first;
            int step = q.front().second;
            q.pop();

            for(int i = 1; num - i * i >= 0; i ++){
                int a = num - i * i;
                if(!visited[a]){
                    if(a == 0) return step + 1;
                    q.push(make_pair(a, step + 1));
                    visited[a] = true;
                }
            }
        }

        throw invalid_argument("No Solution.");
    }
};

int main() {

    cout << Solution().numSquares(12) << endl;
    cout << Solution().numSquares(13) << endl;

    return 0;
}

後入先出的數據結構

img

在 LIFO 數據結構中,將首先處理添加到隊列中的最新元素

與隊列不一樣,棧是一個 LIFO 數據結構。一般,插入操做在棧中被稱做入棧 push 。與隊列相似,老是在堆棧的末尾添加一個新元素。可是,刪除操做,退棧 pop ,將始終刪除隊列中相對於它的最後一個元素

示例 - 棧

  1. 入棧:你能夠單擊下面的 Push 按鈕查看如何將新元素 6 添加到棧中。

  2. 退棧:你能夠單擊下面的 Pop 按鈕查看當你從棧中彈出一個元素時將移除哪一個元素。

1558972652135

1558972704497

實現 - 棧

#include <iostream>

class MyStack {
    private:
        vector<int> data;               // store elements
    public:
        /** Insert an element into the stack. */
        void push(int x) {
            data.push_back(x);
        }
        /** Checks whether the queue is empty or not. */
        bool isEmpty() {
            return data.empty();
        }
        /** Get the top item from the queue. */
        int top() {
            return data.back();
        }
        /** Delete an element from the queue. Return true if the operation is successful. */
        bool pop() {
            if (isEmpty()) {
                return false;
            }
            data.pop_back();
            return true;
        }
};

int main() {
    MyStack s;
    s.push(1);
    s.push(2);
    s.push(3);
    for (int i = 0; i < 4; ++i) {
        if (!s.isEmpty()) {
            cout << s.top() << endl;
        }
        cout << (s.pop() ? "true" : "false") << endl;
    }
}

棧-用法

大多數流行的語言都提供了內置的棧庫,所以你沒必要從新發明輪子。除了初始化,咱們還須要知道如何使用兩個最重要的操做:入棧退棧。除此以外,你應該可以從棧中得到頂部元素。下面是一些供你參考的代碼示例:

#include <iostream>

int main() {
    // 1. Initialize a stack.
    stack<int> s;
    // 2. Push new element.
    s.push(5);
    s.push(13);
    s.push(8);
    s.push(6);
    // 3. Check if stack is empty.
    if (s.empty()) {
        cout << "Stack is empty!" << endl;
        return 0;
    }
    // 4. Pop an element.
    s.pop();
    // 5. Get the top element.
    cout << "The top element is: " << s.top() << endl;
    // 6. Get the size of the stack.
    cout << "The size is: " << s.size() << endl;
}

從如今開始,咱們可使用內置的棧庫來更方便地解決問題。 讓咱們從一個有趣的問題(最小棧)開始,幫助你複習有用的操做。 而後咱們將看一些經典的棧問題。 當你想首先處理最後一個元素時,棧將是最合適的數據結構。

最小棧

設計一個支持 push,pop,top 操做,並能在常數時間內檢索到最小元素的棧。

  • push(x) -- 將元素 x 推入棧中。
  • pop() -- 刪除棧頂的元素。
  • top() -- 獲取棧頂元素。
  • getMin() -- 檢索棧中的最小元素。

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

思路:有兩個棧:最小棧和普通棧。最小棧的元素個數和普通棧同樣,最小棧中的元素爲普通棧對應位置前全部元素的最小值。

class MinStack
{
private:
    stack<int> normalStack;
    stack<int> minStack;

public:
    MinStack()
    {
        while (!normalStack.empty())
            normalStack.pop();
        while (!minStack.empty())
            minStack.pop();
    }
    void push(int x)
    {
        normalStack.push(x);
        if (minStack.empty())
            minStack.push(x);
        else
            minStack.push(min(minStack.top(), x));
    }
    int pop()
    {
        assert(normalStack.size() > 0);

        int v = normalStack.top();
        normalStack.pop();
        minStack.pop();

        return v;
    }
    int top()
    {
        assert(normalStack.size() > 0);
        return normalStack.top();
    }
    int getMin()
    {
        assert(normalStack.size() > 0);
        return minStack.top();
    }
};

有效的括號

給定一個只包括 '('')''{''}''['']' 的字符串,判斷字符串是否有效。

有效字符串需知足:

  1. 左括號必須用相同類型的右括號閉合。
  2. 左括號必須以正確的順序閉合。

注意空字符串可被認爲是有效字符串。

示例 1:

輸入: "()"
輸出: true

示例 2:

輸入: "()[]{}"
輸出: true

示例 3:

輸入: "(]"
輸出: false

示例 4:

輸入: "([)]"
輸出: false

示例 5:

輸入: "{[]}"
輸出: true
#include <iostream>
#include <stack>
#include <cassert>

using namespace std;

// Using Stack
// Time Complexity: O(n)
// Space Complexity: O(n)
class Solution
{
public:
    bool isValid(string s)
    {
        stack<char> stack;
        for (int i = 0; i < s.size(); i++)
            if (s[i] == '(' || s[i] == '{' || s[i] == '[')
                stack.push(s[i]);
            else
            {
                if (stack.size() == 0)
                    return false;

                char c = stack.top();
                stack.pop();

                char match;
                if (s[i] == ')')
                    match = '(';
                else if (s[i] == ']')
                    match = '[';
                else
                {
                    assert(s[i] == '}');
                    match = '{';
                }
                if (c != match)
                    return false;
            }
        if (stack.size() != 0)
            return false;
        return true;
    }
};

void printBool(bool res){
    cout << (res? "True" : "False") << endl;
}

int main() {

    printBool(Solution().isValid("()"));
    printBool(Solution().isValid("()[]{}"));
    printBool(Solution().isValid("(]"));
    printBool(Solution().isValid("([)]"));

    return 0;
}

每日溫度

根據每日 氣溫 列表,請從新生成一個列表,對應位置的輸入是你須要再等待多久溫度纔會升高的天數。若是以後都不會升高,請輸入 0 來代替。

例如,給定一個列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的輸出應該是 [1, 1, 4, 2, 1, 1, 0, 0]

提示:氣溫 列表長度的範圍是 [1, 30000]。每一個氣溫的值的都是 [30, 100] 範圍內的整數。

思路:從後往前遍歷每一個節點,用一個棧stack來存放值比自身大 且 離本身最近的節點下標

/// Source : https://leetcode.com/problems/daily-temperatures/description/
/// Author : liuyubobobo
/// Time   : 2018-08-28

#include <iostream>
#include <vector>
#include <stack>

using namespace std;


/// Using Stack
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {

        vector<int> res(temperatures.size(), 0);
        stack<int> stack;
        for(int i = temperatures.size() - 1; i >= 0 ; i --){
            while(!stack.empty() && temperatures[stack.top()] <= temperatures[i])
                stack.pop();

            if(!stack.empty())
                res[i] = stack.top() - i;
            stack.push(i);
        }

        return res;
    }
};


void printVec(const vector<int>& vec){
    for(int e: vec)
        cout << e << " ";
    cout << endl;
}

int main() {

    vector<int> vec = {73, 74, 75, 71, 69, 72, 76, 73};
    printVec(Solution().dailyTemperatures(vec));
    // 1 1 4 2 1 1 0 0

    return 0;
}

逆波蘭表達式求值

根據逆波蘭表示法,求表達式的值。

有效的運算符包括 +, -, *, / 。每一個運算對象能夠是整數,也能夠是另外一個逆波蘭表達式。

說明:

  • 整數除法只保留整數部分。
  • 給定逆波蘭表達式老是有效的。換句話說,表達式總會得出有效數值且不存在除數爲 0 的狀況。

示例 1:

輸入: ["2", "1", "+", "3", "*"]
輸出: 9
解釋: ((2 + 1) * 3) = 9

示例 2:

輸入: ["4", "13", "5", "/", "+"]
輸出: 6
解釋: (4 + (13 / 5)) = 6

示例 3:

輸入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
輸出: 22
解釋: 
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
#include <iostream>
#include <stack>
#include <vector>

using namespace std;

/// Two stacks
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution
{
public:
    int evalRPN(vector<string> &tokens)
    {
        stack<int> nums;
        stack<char> ops;
        for(const string& s: tokens){
            if(s == "+" || s=="-" || s=="*" || s=="/"){
                int a = nums.top();
                nums.pop();
                int b = nums.top();
                nums.pop();

                if(s == "+")
                    nums.push(b + a);
                else if(s == "-")
                    nums.push(b- a);
                else if(s=="*")
                    nums.push(b*a);
                else if(s=="/")
                    nums.push(b/a);
            }
            else
                nums.push(atoi(s.c_str()));
        }
        return nums.top();
    }
};

int main(){
    return 0;
}

棧和深度優先搜索

棧和DFS

與 BFS 相似,深度優先搜索(DFS)也可用於查找從根結點到目標結點的路徑。在本文中,咱們提供了示例來解釋 DFS 是如何工做的以及棧是如何逐步幫助 DFS 工做的。

示例

咱們來看一個例子吧。咱們但願經過 DFS 找出從根結點 A 到目標結點 G 的路徑。

1

洞悉

觀看上面的動畫後,讓咱們回答如下問題:

1. 結點的處理順序是什麼?

在上面的例子中,咱們從根結點 A 開始。首先,咱們選擇結點 B 的路徑,並進行回溯,直到咱們到達結點 E,咱們沒法更進一步深刻。而後咱們回溯到 A 並選擇第二條路徑到結點 C 。從 C 開始,咱們嘗試第一條路徑到 E可是 E 已被訪問過。因此咱們回到 C 並嘗試從另外一條路徑到 F。最後,咱們找到了 G

總的來講,在咱們到達最深的結點以後,咱們會回溯並嘗試另外一條路徑。

所以,你在 DFS 中找到的第一條路徑並不老是最短的路徑。例如,在上面的例子中,咱們成功找出了路徑 A-> C-> F-> G 並中止了 DFS。但這不是從 AG 的最短路徑。

2. 棧的入棧和退棧順序是什麼?

如上面的動畫所示,咱們首先將根結點推入到棧中;而後咱們嘗試第一個鄰居 B 並將結點 B 推入到棧中;等等等等。當咱們到達最深的結點 E 時,咱們須要回溯。當咱們回溯時,咱們將從棧中彈出最深的結點,這其實是推入到棧中的最後一個結點

結點的處理順序是徹底相反的順序,就像它們被添加到棧中同樣,它是後進先出(LIFO)。這就是咱們在 DFS 中使用棧的緣由。

DFS - 模板I

正如咱們在本章的描述中提到的,在大多數狀況下,咱們在能使用 BFS 時也可使用 DFS。可是有一個重要的區別:遍歷順序

與 BFS 不一樣,更早訪問的結點可能不是更靠近根結點的結點。所以,你在 DFS 中找到的第一條路徑可能不是最短路徑

在本文中,咱們將爲你提供一個 DFS 的遞歸模板,並向你展現棧是如何幫助這個過程的。在這篇文章以後,咱們會提供一些練習給你們練習。

模板-遞歸

有兩種實現 DFS 的方法。第一種方法是進行遞歸,這一點你可能已經很熟悉了。這裏咱們提供了一個模板做爲參考:

/*
 * Return true if there is a path from cur to target.
 */
boolean DFS(Node cur, Node target, Set<Node> visited) {
    return true if cur is target;
    for (next : each neighbor of cur) {
        if (next is not in visited) {
            add next to visted;
            return true if DFS(next, target, visited) == true;
        }
    }
    return false;
}

示例

讓咱們看一個例子。咱們但願在下圖中找到結點 0 和結點 3 之間的路徑。咱們還會在每次調用期間顯示棧的狀態。

img

在每一個堆棧元素中,都有一個整數 cur,一個整數 target,一個對訪問過的數組的引用和一個對數組邊界的引用,這些正是咱們在 DFS 函數中的參數。咱們只在上面的棧中顯示 cur

每一個元素都須要固定的空間。棧的大小正好是 DFS 的深度。所以,在最壞的狀況下,維護系統棧須要 O(h),其中 h 是 DFS 的最大深度。在計算空間複雜度時,永遠不要忘記考慮系統棧。

在上面的模板中,咱們在找到第一條路徑時中止。

若是你想找到最短路徑呢?

提示:再添加一個參數來指示你已經找到的最短路徑。

島嶼數量

給定一個由 '1'(陸地)和 '0'(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,而且它是經過水平方向或垂直方向上相鄰的陸地鏈接而成的。你能夠假設網格的四個邊均被水包圍。

示例 1:

輸入:
11110
11010
11000
00000

輸出: 1

示例 2:

輸入:
11000
11000
00100
00011

輸出: 3
#include <iostream>
#include <vector>
#include <cassert>

using namespace std;

/// Floodfill - DFS
/// Recursion implementation
///
/// Time Complexity: O(n*m)
/// Space Complexity: O(n*m)
class Solution
{
private:
    int d[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    int m, n;
    vector<vector<bool>> visited;

    bool inArea(int x, int y)
    {
        return x >= 0 && x < m && y >= 0 && y < n;
    }

    void dfs(vector<vector<char>> &grid, int x, int y)
    {
        visited[x][y] = true;
        for (int i = 0; i < 4; i++)
        {
            int newx = x + d[i][0];
            int newy = y + d[i][1];
            if (inArea(newx, newy) && !visited[newx][newy] && grid[newx][newy] == '1')
                dfs(grid, newx, newy);
        }
        return ;
    }
    public:
        int numIslands(vector<vector<char>>& grid){
            m = grid.size();
            if(m == 0)
                return 0;
            n = grid[0].size();
            if(n == 0)
                return 0;
            for(int i=0; i<m; i++)
                visited.push_back(vector<bool>(n, false));
            
            int res = 0;
            for(int i=0; i<m; i++)
                for(int j=0; j<n; j++)
                    if(grid[i][j]=='1' && !visited[i][j]){
                        dfs(grid, i,j);
                        res++;
                    }
            return res;
        }
};

int main() {

    char g1[4][5] = {
            {'1','1','1','1','0'},
            {'1','1','0','1','0'},
            {'1','1','0','0','0'},
            {'0','0','0','0','0'}
    };
    vector<vector<char>> grid1;
    for(int i = 0 ; i < 4 ; i ++)
        grid1.push_back( vector<char>(g1[i], g1[i] + sizeof( g1[i])/sizeof(char)));

    cout << Solution().numIslands(grid1) << endl;
    // 1

    // ---

    char g2[4][5] = {
            {'1','1','0','0','0'},
            {'1','1','0','0','0'},
            {'0','0','1','0','0'},
            {'0','0','0','1','1'}
    };
    vector<vector<char>> grid2;
    for(int i = 0 ; i < 4 ; i ++)
        grid2.push_back(vector<char>(g2[i], g2[i] + sizeof( g2[i])/sizeof(char)));

    cout << Solution().numIslands(grid2) << endl;
    // 2

    return 0;
}

克隆圖

給定無向連通圖中一個節點的引用,返回該圖的深拷貝(克隆)。圖中的每一個節點都包含它的值 valInt) 和其鄰居的列表(list[Node])。

示例:

img

輸入:
{"$id":"1","neighbors":[{"$id":"2","neighbors":[{"$ref":"1"},{"$id":"3","neighbors":[{"$ref":"2"},{"$id":"4","neighbors":[{"$ref":"3"},{"$ref":"1"}],"val":4}],"val":3}],"val":2},{"$ref":"4"}],"val":1}

解釋:
節點 1 的值是 1,它有兩個鄰居:節點 2 和 4 。
節點 2 的值是 2,它有兩個鄰居:節點 1 和 3 。
節點 3 的值是 3,它有兩個鄰居:節點 2 和 4 。
節點 4 的值是 4,它有兩個鄰居:節點 1 和 3 。

提示:

  1. 節點數介於 1 到 100 之間。
  2. 無向圖是一個簡單圖,這意味着圖中沒有重複的邊,也沒有自環。
  3. 因爲圖是無向的,若是節點 p 是節點 q 的鄰居,那麼節點 q 也必須是節點 p 的鄰居。
  4. 必須將給定節點的拷貝做爲對克隆圖的引用返回。
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

struct UndirectedGraphNode
{
    int label;
    vector<UndirectedGraphNode *> neighbors;
    UndirectedGraphNode(int x) : label(x){};
};

/// DFS
/// Time Complexity: O(V+E)
/// Space Complexity: O(V)
class Solution
{
public:
    UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node)
    {
        if (node == NULL)
            return NULL;

        unordered_map<UndirectedGraphNode *, UndirectedGraphNode *> nodeMap;
        return dfs(node, nodeMap);
    }

private:
    UndirectedGraphNode *dfs(UndirectedGraphNode *node,
                             unordered_map<UndirectedGraphNode *, UndirectedGraphNode *> &nodeMap)
    {
        if (nodeMap.count(node))
            return nodeMap[node];

        nodeMap[node] = new UndirectedGraphNode(node->label);
        for (UndirectedGraphNode *next : node->neighbors)
            nodeMap[node]->neighbors.push_back(dfs(next, nodeMap));
        return nodeMap[node];
    }
};

目標和

給定一個非負整數數組,a1, a2, ..., an, 和一個目標數,S。如今你有兩個符號 +-。對於數組中的任意一個整數,你均可以從 +-中選擇一個符號添加在前面。

返回可使最終數組和爲目標數 S 的全部添加符號的方法數。

示例 1:

輸入: nums: [1, 1, 1, 1, 1], S: 3
輸出: 5
解釋: 

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5種方法讓最終目標和爲3。

注意:

  1. 數組的長度不會超過20,而且數組中的值全爲正數。
  2. 初始的數組的和不會超過1000。
  3. 保證返回的最終結果爲32位整數。
#include <iostream>
#include <vector>
#include <stack>

using namespace std;

/// Backtracking
/// Time Complexity: O(2^n)
/// Space Complexity: O(n)
class SolutionA
{
public:
    int findTargetSumWays(vector<int> &nums, int S)
    {
        return dfs(nums, 0, 0, S);
    }
private:
    int dfs(const vector<int>& nums, int index, int res, int S){
        if(index == nums.size())
            return res==S;
        int ret = 0;
        ret += dfs(nums, index+1, res-nums[index], S);
        ret += dfs(nums, index+1, res+nums[index],S);
        return ret;
    }
};

/// Backtracking
/// Using Stack - Non-recursion solution
///
/// Time Complexity: O(2^n)
/// Space Complexity: O(2^n)
class SolutionB
{
public:
    int findTargetSumWays(vector<int> &nums, int S)
    {
        stack<int> indexStack, sumStack;
        indexStack.push(0);
        sumStack.push(0);
        int res = 0, index, sum;
        while (!indexStack.empty())
        {
            index = indexStack.top();
            sum = sumStack.top();
            indexStack.pop();
            sumStack.pop();

            if (index + 1 == nums.size())
                res += (sum + nums[index] == S) + (sum - nums[index] == S);
            else
            {
                indexStack.push(index + 1);
                sumStack.push(sum + nums[index]);
                indexStack.push(index + 1);
                sumStack.push(sum - nums[index]);
            }
        }
        return res;
    }
};
int main()
{

    vector<int> nums = {18, 1};
    cout << SolutionA().findTargetSumWays(nums, 3) << endl;

    return 0;
}

DFS - 模板II

遞歸解決方案的優勢是它更容易實現。 可是,存在一個很大的缺點:若是遞歸的深度過高,你將遭受堆棧溢出。 在這種狀況下,您可能會但願使用 BFS,或使用顯式棧實現 DFS。

這裏咱們提供了一個使用顯式棧的模板:

/*
 * Return true if there is a path from cur to target.
 */
boolean DFS(int root, int target) {
    Set<Node> visited;
    Stack<Node> s;
    add root to s;
    while (s is not empty) {
        Node cur = the top element in s;
        return true if cur is target;
        for (Node next : the neighbors of cur) {
            if (next is not in visited) {
                add next to s;
                add next to visited;
            }
        }
        remove cur from s;
    }
    return false;
}

該邏輯與遞歸解決方案徹底相同。 但咱們使用 while 循環和來模擬遞歸期間的系統調用棧。 手動運行幾個示例確定會幫助你更好地理解它。

二叉樹的中序遍歷

給定一個二叉樹,返回它的中序 遍歷。

示例:

輸入: [1,null,2,3]
   1
    \
     2
    /
   3

輸出: [1,3,2]

進階: 遞歸算法很簡單,你能夠經過迭代算法完成嗎?

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x):val(x), left(NULL), right(NULL){}
};

// Recursive
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class SolutionA{
public:
    vector<int> inorderTraversal(TreeNode* root){
        vector<int> res;
        __inorderTraversal(root, res);
        return res;
    }
private:
    void __inorderTraversal(TreeNode* node, vector<int> & res){
        if(node){
            __inorderTraversal(node->left, res);
            res.push_back(node->val);
            __inorderTraversal(node->right, res);
        }
    }
};

// Classic Non-Recursive algorithm for inorder traversal
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class SolutionB{
public:
    vector<int> inorderTraversal(TreeNode* root){
        vector<int> res;
        if(root == NULL)
            return res;
        stack<TreeNode*> stack;
        TreeNode* cur = root;
        while(cur != NULL || !stack.empty()){
            while(cur != NULL){
                stack.push(cur);
                cur = cur->left;
            }
            cur = stack.top();
            stack.pop();
            res.push_back(cur->val);
            cur = cur->right;
        }
        return res;
    }
};

int main(){
    return 0;
}

小結

用棧實現隊列

使用棧實現隊列的下列操做:

  • push(x) -- 將一個元素放入隊列的尾部。
  • pop() -- 從隊列首部移除元素。
  • peek() -- 返回隊列首部的元素。
  • empty() -- 返回隊列是否爲空。

示例:

MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);  
queue.peek();  // 返回 1
queue.pop();   // 返回 1
queue.empty(); // 返回 false

說明:

  • 你只能使用標準的棧操做 -- 也就是隻有 push to top, peek/pop from top, size, 和 is empty 操做是合法的。
  • 你所使用的語言也許不支持棧。你可使用 list 或者 deque(雙端隊列)來模擬一個棧,只要是標準的棧操做便可。
  • 假設全部操做都是有效的 (例如,一個空的隊列不會調用 pop 或者 peek 操做)。

用隊列實現棧

使用隊列實現棧的下列操做:

  • push(x) -- 元素 x 入棧
  • pop() -- 移除棧頂元素
  • top() -- 獲取棧頂元素
  • empty() -- 返回棧是否爲空

注意:

  • 你只能使用隊列的基本操做-- 也就是 push to back, peek/pop from front, size, 和 is empty 這些操做是合法的。
  • 你所使用的語言也許不支持隊列。 你可使用 list 或者 deque(雙端隊列)來模擬一個隊列 , 只要是標準的隊列操做便可。
  • 你能夠假設全部操做都是有效的(例如, 對一個空的棧不會調用 pop 或者 top 操做)。
#include <iostream>
#include <queue>
#include <cassert>

using namespace std;

/// Naive Implementation
/// Time Complexity: init: O(1)
///                  push: O(1)
///                  pop: O(n)
///                  top: O(n)
///                  empty: O(1)
/// Space Complexity: O(n)
class MyStack{
private:
    queue<int> q;
public:
    MyStack(){}
    
    void push(int x){
        q.push(x);
    }
    int pop(){
        assert(!empty());

        queue<int> q2;
        while(q.size() > 1){
            q2.push(q.front());
            q.pop();
        }
        int ret = q.front();
        q.pop();

        while(!q2.empty()){
            q.push(q2.front());
            q2.pop();
        }
        return ret;
    }
    int top(){
        assert(!empty());
        
        queue<int> q2;
        while(q.size() > 1){
            q2.push(q.front());
            q.pop();
        }
        int ret = q.front();
        q2.push(ret);
        q.pop();

        while(!q2.empty()){
            q.push(q2.front());
            q2.pop();
        }
        return ret;
    }
    bool empty(){
        return q.empty();
    }
};

字符串解碼

給定一個通過編碼的字符串,返回它解碼後的字符串。

編碼規則爲: k[encoded_string],表示其中方括號內部的 encoded_string 正好重複 k 次。注意 k 保證爲正整數。

你能夠認爲輸入字符串老是有效的;輸入字符串中沒有額外的空格,且輸入的方括號老是符合格式要求的。

此外,你能夠認爲原始數據不包含數字,全部的數字只表示重複的次數 k ,例如不會出現像 3a2[4] 的輸入。

示例:

s = "3[a]2[bc]", 返回 "aaabcbc".
s = "3[a2[c]]", 返回 "accaccacc".
s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".
#include <iostream>
#include <cassert>
#include <vector>

using namespace std;

/// Using Stack - non recursion algorithm
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution
{
public:
    string decodeString(string s)
    {
        vector<string> stack;
        vector<int> nums;

        stack.push_back("");
        for (int i = 0; i < s.size();)
            if (isalpha(s[i]))
                stack.back() += s[i++];
            else if (isdigit(s[i]))
            {
                int j;
                for (j = i + 1; j < s.size() && isdigit(s[j]); j++)
                    ;
                nums.push_back(atoi(s.substr(i, j - i).c_str()));
                stack.push_back("");
                assert(s[j] == '[');
                i = j + 1;
            }
            else
            {
                assert(s[i] == ']');
                string tres = stack.back();
                stack.pop_back();

                int num = nums.back();
                nums.pop_back();

                while (num--)
                    stack.back() += tres;
                i++;
            }
        return stack.back();
    }
};

int main()
{

    string s1 = "3[a]2[bc]";
    cout << Solution().decodeString(s1) << endl;
    // "aaabcbc"

    string s2 = "3[a2[c]]";
    cout << Solution().decodeString(s2) << endl;
    // "accaccacc"

    string s3 = "2[abc]3[cd]ef";
    cout << Solution().decodeString(s3) << endl;
    // "abcabccdcdcdef"

    return 0;
}

圖像渲染

有一幅以二維整數數組表示的圖畫,每個整數表示該圖畫的像素值大小,數值在 0 到 65535 之間。

給你一個座標 (sr, sc) 表示圖像渲染開始的像素值(行 ,列)和一個新的顏色值 newColor,讓你從新上色這幅圖像。

爲了完成上色工做,從初始座標開始,記錄初始座標的上下左右四個方向上像素值與初始座標相同的相連像素點,接着再記錄這四個方向上符合條件的像素點與他們對應四個方向上像素值與初始座標相同的相連像素點,……,重複該過程。將全部有記錄的像素點的顏色值改成新的顏色值。

最後返回通過上色渲染後的圖像。

示例 1:

輸入: 
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
輸出: [[2,2,2],[2,2,0],[2,0,1]]
解析: 
在圖像的正中間,(座標(sr,sc)=(1,1)),
在路徑上全部符合條件的像素點的顏色都被更改爲2。
注意,右下角的像素沒有更改成2,
由於它不是在上下左右四個方向上與初始點相連的像素點。

注意:

  • imageimage[0] 的長度在範圍 [1, 50] 內。
  • 給出的初始點將知足 0 <= sr < image.length0 <= sc < image[0].length
  • image[i][j]newColor 表示的顏色值在範圍 [0, 65535]內。
#include <iostream>
#include <vector>
#include <stack>

using namespace std;

/// DFS - Using Stack
///
/// Time Complexity: O(n*m)
/// Space Complexity: O(n*m)
class Solution
{
private:
    int d[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    int n, m;

public:
    vector<vector<int>> floodFill(vector<vector<int>> &image,
                                  int sr, int sc, int newColor)
    {
        n = image.size();
        m = image[0].size();
        int oldColor = image[sr][sc];

        vector<vector<bool>> visited(n, vector<bool>(m, false));
        stack<pair<int, int>> stack;
        stack.push(make_pair(sr, sc));
        visited[sr][sc] = true;
        while (!stack.empty())
        {
            int x = stack.top().first, y = stack.top().second;
            stack.pop();

            image[x][y] = newColor;
            for (int i = 0; i < 4; i++)
            {
                int newX = x + d[i][0], newY = y + d[i][1];
                if (inArea(newX, newY) && !visited[newX][newY] && image[newX][newY] == oldColor)
                {
                    visited[newX][newY] = true;
                    stack.push(make_pair(newX, newY));
                }
            }
        }
        return image;
    }

private:
    bool inArea(int x, int y)
    {
        return x >= 0 && x < n && y >= 0 && y < m;
    }
};

void printImage(const vector<vector<int>> & image){
    for(vector<int> row:image){
        for(int pixel: row)
            cout << pixel<<"\t";
        cout << endl;
    }
}

int main(){
    vector<vector<int>> image = {{1,1,1}, {1,1,0}, {1,0,1}};
    printImage(Solution().floodFill(image, 1, 1, 2));
    return 0;
}

01矩陣

給定一個由 0 和 1 組成的矩陣,找出每一個元素到最近的 0 的距離。

兩個相鄰元素間的距離爲 1 。

示例 1:
輸入:

0 0 0
0 1 0
0 0 0

輸出:

0 0 0
0 1 0
0 0 0

示例 2:
輸入:

0 0 0
0 1 0
1 1 1

輸出:

0 0 0
0 1 0
1 2 1

注意:

  1. 給定矩陣的元素個數不超過 10000。
  2. 給定矩陣中至少有一個元素是 0。
  3. 矩陣中的元素只在四個方向上相鄰: 上、下、左、右。
#include <iostream>
#include <vector>
#include <queue>
#include <cassert>

using namespace std;

/// BFS
/// Put all zero position in queue and only make one pass BFS :-)
///
/// Time Complexity: O(m*n)
/// Space Complexity: O(m*n)
class Solution
{
private:
    const int d[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    int m, n;

public:
    vector<vector<int>> updateMatrix(vector<vector<int>> &matrix)
    {
        if (matrix.size() == 0 || matrix[0].size() == 0)
            return matrix;

        m = matrix.size();
        n = matrix[0].size();

        queue<int> q;
        vector<vector<int>> res(m, vector<int>(n, INT_MAX));
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if (matrix[i][j] == 0)
                {
                    res[i][j] = 0;
                    q.push(i * n + j);
                }
        bfs(matrix, q, res);
        return res;
    }

    void bfs(const vector<vector<int>> &matrix, queue<int> &q,
             vector<vector<int>> &res)
    {
        while (!q.empty())
        {
            int x = q.front() / n;
            int y = q.front() % n;
            q.pop();

            for (int k = 0; k < 4; k++)
            {
                int newX = x + d[k][0], newY = y + d[k][1];
                if (inArea(newX, newY) && res[x][y] + 1 < res[newX][newY])
                {
                    res[newX][newY] = res[x][y] + 1;
                    q.push(newX * n + newY);
                }
            }
        }
    }

    bool inArea(int x, int y)
    {
        return x >= 0 && x < m && y >= 0 && y < n;
    }
};

鑰匙和房間

N 個房間,開始時你位於 0 號房間。每一個房間有不一樣的號碼:0,1,2,...,N-1,而且房間裏可能有一些鑰匙能使你進入下一個房間。

在形式上,對於每一個房間 i 都有一個鑰匙列表 rooms[i],每一個鑰匙 rooms[i][j][0,1,...,N-1] 中的一個整數表示,其中 N = rooms.length。 鑰匙 rooms[i][j] = v 能夠打開編號爲 v 的房間。

最初,除 0 號房間外的其他全部房間都被鎖住。

你能夠自由地在房間之間來回走動。

若是能進入每一個房間返回 true,不然返回 false

示例 1:

輸入: [[1],[2],[3],[]]
輸出: true
解釋:  
咱們從 0 號房間開始,拿到鑰匙 1。
以後咱們去 1 號房間,拿到鑰匙 2。
而後咱們去 2 號房間,拿到鑰匙 3。
最後咱們去了 3 號房間。
因爲咱們可以進入每一個房間,咱們返回 true。

示例 2:

輸入:[[1,3],[3,0,1],[2],[0]]
輸出:false
解釋:咱們不能進入 2 號房間。

提示:

  1. 1 <= rooms.length <= 1000
  2. 0 <= rooms[i].length <= 1000
  3. 全部房間中的鑰匙數量總計不超過 3000
class Solution{
public:
    bool canVisitAllRooms(vector<vector<int>>& rooms){
        int V = rooms.size();
        vector<bool> visited(V, false);
        return dfs(rooms, 0, visited) == V;
    }
private:
    int dfs(const vector<vector<int>>& rooms, int v, vector<bool>& visited){
        visited[v] = true;

        int res = 1;
        for(int next: rooms[v])
            if(!visited[next])
                res += dfs(rooms, next, visited);
        return res;
    }
};
相關文章
相關標籤/搜索