隊列
先入先出的數據結構
在 FIFO 數據結構中,將首先處理添加到隊列中的第一個元素
。
如上圖所示,隊列是典型的 FIFO 數據結構。插入(insert)操作也稱作入隊(enqueue),新元素始終被添加在隊列的末尾
。 刪除(delete)操作也被稱為出隊(dequeue)。 你只能移除第一個元素
。
示例 - 隊列
- 入隊:您可以單擊下面的
Enqueue
以查看如何將新元素 6 添加到隊列中。
- 出隊:您可以單擊下面的
Dequeue
以查看將刪除哪個元素。
隊列-實現
為了實現隊列,我們可以使用動態數組和指向隊列頭部的索引。
如上所述,隊列應支持兩種操作:入隊和出隊。入隊會向隊列追加一個新元素,而出隊會刪除第一個元素。 所以我們需要一個索引來指出起點。
這是一個供你參考的實現:
#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;
}
}
缺點
上面的實現很簡單,但在某些情況下效率很低。 隨着起始指針的移動,浪費了越來越多的空間。 當我們有空間限制時,這將是難以接受的。
讓我們考慮一種情況,即我們只能分配一個最大長度為 5 的數組。當我們只添加少於 5 個元素時,我們的解決方案很有效。 例如,如果我們只調用入隊函數四次后還想要將元素 10 入隊,那么我們可以成功。
但是我們不能接受更多的入隊請求,這是合理的,因為現在隊列已經滿了。但是如果我們將一個元素出隊呢?
實際上,在這種情況下,我們應該能夠再接受一個元素。
循環隊列
此前,我們提供了一種簡單但低效的隊列實現。
更有效的方法是使用循環隊列。 具體來說,我們可以使用固定大小的數組
和兩個指針
來指示起始位置和結束位置。 目的是重用
我們之前提到的被浪費的存儲
。
讓我們通過一個示例來查看循環隊列的工作原理。 你應該注意我們入隊
或出隊
元素時使用的策略。
仔細檢查動畫,找出我們用來檢查隊列是空
還是滿
的策略。
下一個練習,我們將讓你自己嘗試實現循環隊列,之后會提供給你一個解決方案。
設計循環隊列
設計你的循環隊列實現。 循環隊列是一種線性數據結構,其操作表現基於 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;
}
};
循環隊列-實現
在循環隊列中,我們使用一個數組
和兩個指針(head
和 tail
)。 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. 結點的處理順序是什么?
在第一輪中,我們處理根結點。在第二輪中,我們處理根結點旁邊的結點;在第三輪中,我們處理距根結點兩步的結點;等等等等。
與樹的層序遍歷類似,越是接近根結點的結點將越早地遍歷
。
如果在第 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
}
- 如代碼所示,在每一輪中,隊列中的結點是
等待處理的結點
。 - 在每個更外一層的
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
}
有兩種情況你不需要使用哈希集:
- 你完全確定沒有循環,例如,在樹遍歷中;
- 你確實希望多次將結點添加到隊列中。
牆與門
You are given a m x n 2D grid initialized with these three possible values.
-1
- A wall or an obstacle.0
- A gate.INF
- Infinity means an empty room. We use the value231 - 1 = 2147483647
to representINF
as you may assume that the distance to a gate is less than2147483647
.
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
提示:
- 死亡列表
deadends
的長度范圍為[1, 500]
。 - 目標數字
target
不會在deadends
之中。 - 每個
deadends
和target
中的字符串的數字會在 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要少,無需重復計算。
/// 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;
}
棧
后入先出的數據結構
在 LIFO 數據結構中,將首先處理添加到隊列
中的最新元素
。
與隊列不同,棧是一個 LIFO 數據結構。通常,插入操作在棧中被稱作入棧 push
。與隊列類似,總是在堆棧的末尾添加一個新元素
。但是,刪除操作,退棧 pop
,將始終刪除
隊列中相對於它的最后一個元素
。
示例 - 棧
-
入棧:你可以單擊下面的
Push
按鈕查看如何將新元素 6 添加到棧中。 -
退棧:你可以單擊下面的
Pop
按鈕查看當你從棧中彈出一個元素時將移除哪個元素。
實現 - 棧
#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:
輸入: "()"
輸出: 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. 結點的處理順序是什么?
在上面的例子中,我們從根結點 A
開始。首先,我們選擇結點 B
的路徑,並進行回溯,直到我們到達結點 E
,我們無法更進一步深入。然后我們回溯到 A
並選擇第二條路徑到結點 C
。從 C
開始,我們嘗試第一條路徑到 E
但是 E
已被訪問過。所以我們回到 C
並嘗試從另一條路徑到 F
。最后,我們找到了 G
。
總的來說,在我們到達最深的
結點之后,我們只
會回溯並嘗試另一條路徑。
因此,你在 DFS 中找到的第一條路徑並不總是最短的路徑。例如,在上面的例子中,我們成功找出了路徑
A-> C-> F-> G
並停止了 DFS。但這不是從A
到G
的最短路徑。
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 之間的路徑。我們還會在每次調用期間顯示棧的狀態。
在每個堆棧元素中,都有一個整數 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;
}
克隆圖
給定無向連通圖中一個節點的引用,返回該圖的深拷貝(克隆)。圖中的每個節點都包含它的值 val
(Int
) 和其鄰居的列表(list[Node]
)。
示例:
輸入:
{"$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 到 100 之間。
- 無向圖是一個簡單圖,這意味着圖中沒有重復的邊,也沒有自環。
- 由於圖是無向的,如果節點 p 是節點 q 的鄰居,那么節點 q 也必須是節點 p 的鄰居。
- 必須將給定節點的拷貝作為對克隆圖的引用返回。
#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。
注意:
- 數組的長度不會超過20,並且數組中的值全為正數。
- 初始的數組的和不會超過1000。
- 保證返回的最終結果為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 ,例如不會出現像 3a
或 2[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,
因為它不是在上下左右四個方向上與初始點相連的像素點。
注意:
image
和image[0]
的長度在范圍[1, 50]
內。- 給出的初始點將滿足
0 <= sr < image.length
和0 <= 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
注意:
- 給定矩陣的元素個數不超過 10000。
- 給定矩陣中至少有一個元素是 0。
- 矩陣中的元素只在四個方向上相鄰: 上、下、左、右。
#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 <= rooms.length <= 1000
0 <= rooms[i].length <= 1000
- 所有房間中的鑰匙數量總計不超過
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;
}
};