栈
栈与深度优先搜索
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;
}
但是递归深度太深的话会出现栈溢出,所以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;
}
二叉树的中序遍历
递归实现
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> res;
vector<int> inorderTraversal(TreeNode* root) {
if (root == NULL) return res;
dfs(root);
return res;
}
void dfs(TreeNode* root) {
if (root -> left != NULL) dfs(root->left);
res.push_back(root->val);
if (root -> right != NULL) dfs(root->right);
return;
}
};
栈实现
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> res;
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> nodestack;
TreeNode* now = root;
while (!nodestack.empty() || now != NULL) {
while (now != NULL) {
nodestack.push(now);
now = now -> left;
}
now = nodestack.top();
nodestack.pop();
res.push_back(now->val);
now = now->right;
}
return res;
}
};
这个题难度还是有的,关键在于要先一路到最左边到底,然后弹出最左最深的节点之后再将该节点的右儿子入栈,右边全部出栈后会出去直接到上面的 未输出节点,这个时候因为now是null的状态,所以不会重复访问左边的儿子,会先输出自己然后再去遍历右子树,
队列
队列与广度优先搜索
广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径。
在图中搜索与根节点与目标节点的最短路径可以使用队列,因为算法如下
- 第一轮将根节点的相邻节点依次入队
- 处理队头,将其未见过的相邻界节点入队,然后出队
- 处理完第一轮的节点之后++k开始第二轮相同的操作
- 只要队头发现了目标节点,当前的k就是目标节点
/**
* 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循环配合for循环上面的size完成了
有时,确保我们永远
不会访问一个结点两次
很重要。否则,我们可能陷入无限循环。如果是这样,我们可以在上面的代码中添加一个哈希集来解决这个问题。这是修改后的伪代码
/**
* 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’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
本质:求连通块的个数
搜索一个岛屿就是搜索与一个1相邻的所有1,一个典型的广度优先搜索
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
if (grid.empty() || grid[0].empty()) return 0;
int raws = grid.size(), cols = grid[0].size(), islands = 0;
queue<int> q;
set<int> visited;
for (int i = 0; i < raws; ++i) {
for (int j = 0; j < cols; ++j) {
int n = i * cols + j;
if (grid[i][j] == '0' || visited.count(n)) continue;
q.push(n);
visited.insert(n);
while (!q.empty()) {
int size = q.size();
for (int k = 0; k < size; ++k) {
int nn = q.front();
int ii = nn / cols, jj = nn % cols;
if (ii > 0 && grid[ii - 1][jj] == '1' && !visited.count(nn - cols)) {
q.push(nn - cols);
visited.insert(nn - cols);
}
if (ii < raws - 1 && grid[ii + 1][jj] == '1' && !visited.count(nn+cols)) {
q.push(nn + cols);
visited.insert(nn + cols);
}
if (jj < cols - 1 && grid[ii][jj + 1] == '1' && !visited.count(nn+1)) {
q.push(nn + 1);
visited.insert(nn + 1);
}
if (jj > 0 && grid[ii][jj - 1] == '1' && !visited.count(nn - 1)) {
q.push(nn - 1);
visited.insert(nn - 1);
}
q.pop();
}
}
++islands;
}
}
return islands;
}
};
打开转盘锁
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
典型的广度优先搜索寻找最短路径的方法
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
queue<string> q;
set<string> visited;
for (auto s : deadends) visited.insert(s);//这里没想到其他的能规避deadends的方法
string root("0000");
if (visited.count(root)) return -1;
q.push(root);
visited.insert(root);
int step = -1;
while (!q.empty()) {
++step;
int size = q.size();
for (int i = 0; i < size; ++i) {
string now = q.front();
if (target == now) return step;
for (int j = 0; j < 4; ++j) {
string s1 = now, s2 = now;
++s1[j];--s2[j];
if (s1[j] > '9')s1[j] = '0';
if (s2[j] < '0') s2[j] = '9';
if (!visited.count(s1)) {
q.push(s1);
visited.insert(s1);
}
if (!visited.count(s2)) {
q.push(s2);
visited.insert(s2);
}
}
q.pop();
}
}
return -1;
}
};
完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
堪称传统BFS也是一个寻找最短路径的题,不过需要把那颗树画出来
class Solution {
public:
int numSquares(int n) {
queue<int> q;
set<int> visited;
q.push(n);
visited.insert(n);
int step = -1;
while (!q.empty()) {
++step;
int size = q.size();
for (int i = 0; i < size; ++i) {
int now = q.front();
if (now == 0) return step;
int j = 1;
while (1) {
int res = now - j*j;
if (res >= 0 && !visited.count(res)) {
q.push(res);
visited.insert(res);
}
else if (res < 0) break;
++j;
}
q.pop();
}
}
return step;
}
};
发现,不同的BFS不同点主要在
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPJUmB3k-1597225224565)(E:\SJTUONE\OneDrive - sjtu.edu.cn\Note\算法与数据结构\2. 线性结构.assets\image-20200803131525360.png)]
但是实际上可以使用set来简化这个过程
class Solution {
public:
int numSquares(int n) {
set<int> q;
q.insert(n);
int step = -1;
while (q.size() > 0) {
++step;
int size = q.size();
set<int> newQ;
for (auto now : q) {
if (now == 0) return step;
int res, j = 1;
while (1) {
res = now - j*j;
if (res < 0) break;
newQ.insert(res);
++j;
}
}
q = newQ;
}
return step;
}
};
仍然超时…
原来超时的原因就在于访问set/插入set,使用布尔数组代替它就可以了
class Solution {
public:
int numSquares(int n) {
queue<int> q;
vector<bool> visited(n+1,false);
q.push(n);
// visited.insert(n);
visited[n] = true;
int step = -1;
while (!q.empty()) {
++step;
int size = q.size();
for (int i = 0; i < size; ++i) {
int now = q.front();
if (now == 0) return step;
int j = 1;
while (1) {
int res = now - j*j;
if (res >= 0 && !visited[res]) {
q.push(res);
visited[res] = true;
}
else if (res < 0) break;
++j;
}
q.pop();
}
}
return step;
}
};
链表
实现
class MyLinkedList {
public:
/** Initialize your data structure here. */
MyLinkedList() {
head = NULL;
length = 0;
}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
int get(int index) {
if (index < 0 || index >= length) return -1;
Node* n = head;
for (int i = 0; i < index; ++i) {
n = n->next;
}
return n->val;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
void addAtHead(int val) {
Node* newHead = new Node(val);
newHead -> next = head;
head = newHead;
++length;
}
/** Append a node of value val to the last element of the linked list. */
void addAtTail(int val) {
Node* newTail = new Node(val);
Node* p = head;
while (p->next != NULL) p = p->next;
p->next = newTail;
++length;
}
/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
void addAtIndex(int index, int val) {
Node* n = head, *prev = head;
if (index == 0) {
addAtHead(val);
return;
}
if (index > length) return;
if (index == length) {
addAtTail(val);
return;
}
for (int i = 0; i < index; ++i) {
prev = n;
n = n->next;
}
Node* newNode = new Node(val);
newNode->next = n;
prev->next = newNode;
++length;
}
/** Delete the index-th node in the linked list, if the index is valid. */
void deleteAtIndex(int index) {
Node* n = head, *prev = head;
if (index == 0) head = head->next;
else if (index >= length) return;
else {
for (int i = 0; i < index; ++i) {
prev = n;
n = n->next;
}
prev->next = n->next;
}
--length;
}
private:
struct Node {
int val;
Node* next;
//如果没有无参数的默认构造函数会不会报错
Node(int x):val(x), next(NULL){}
};
Node* head;
int length;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
双指针在链表中的妙用
环形链表
比较基础的快慢指针,快指针依次两步,慢指针一次一步,设环长m,那么当他们相差m的时候就相遇了
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
//O(1)内存就是快慢指针法
//其他方法是哈希表的方法?出现重复访问的节点就有环
ListNode* slow = head, * fast = head;
while (fast != NULL) {
slow = slow -> next;
fast = fast->next;
if (fast != NULL) fast = fast->next;
else return false;
if (fast == slow) return true;
}
return false;
}
};
head离相遇点的距离一定是M的倍数
环形链表2
本题需要找出环的入口 略微麻烦了些,这次的双指针是一样快的。因为head与相遇点的距离的m的倍数。那么现在一个从head出发,另一个从相遇点出发,那么他们一定是同时在进入环的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (head == NULL) return NULL;
ListNode* slow = head, * fast = head, *encounter = head;
while (fast != NULL) {
slow = slow -> next;
fast = fast->next;
if (fast == NULL || fast->next == NULL) return NULL;
else fast = fast->next;
if (fast == slow) {
encounter = fast;
break;
}
}
ListNode* fisrt = head, *second = encounter;
while (fisrt != second) {
fisrt = fisrt->next;
second = second->next;
}
return fisrt;
// 没有用到双指针
// if (head == NULL) return NULL;
// ListNode *go = head;
// int maxLen = 0;
// while (1) {
// if (go->next == go) return go;
// ListNode* p = head;
// int len = 0;
// while (p != go) {
// p = p->next;
// ++len;
// }
// if (len < maxLen) {
// return go;
// }
// else if (p == NULL || p->next == NULL) {
// return NULL;
// }
// else {
// maxLen = len;
// go = go -> next;
// }
// }
}
};
相交链表
走你走过的路,最终还会遇到你/或许永远不会
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL) return NULL;
ListNode* a = headA, *b = headB, *lastA = headA, *lastB = headA;
int time = 0;
while (a != b) {
if (time == 2 && lastA != lastB) return NULL;
if (a->next == NULL){
lastA = a;
a = headB;
++time;
}
else a = a->next;
if (b->next == NULL){
lastB = b;
b = headA;
++time;
}
else b = b->next;
}
return a;
//不满足O(N)复杂度
// ListNode* a = headA;
// while(a != NULL) {
// ListNode* b = headB;
// while (b != NULL) {
// if (a == b) return a;
// b = b->next;
// }
// a=a->next;
// }
// return NULL;
}
};
删除链表的倒数第N个节点
挡板
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL) return NULL;
ListNode* a = headA, *b = headB, *lastA = headA, *lastB = headA;
int time = 0;
while (a != b) {
if (time == 2 && lastA != lastB) return NULL;
if (a->next == NULL){
lastA = a;
a = headB;
++time;
}
else a = a->next;
if (b->next == NULL){
lastB = b;
b = headA;
++time;
}
else b = b->next;
}
return a;
//不满足O(N)复杂度
// ListNode* a = headA;
// while(a != NULL) {
// ListNode* b = headB;
// while (b != NULL) {
// if (a == b) return a;
// b = b->next;
// }
// a=a->next;
// }
// return NULL;
}
};
经典问题
反转链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL) return NULL;
ListNode* cur = head, *pre = NULL;
while (cur != NULL) {
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
//这个思路与双指针迭代思路差不多,但是直接改方向会简单一点吧
//上面的方法调用next数量2n, 而下面的调用次数未4n 所以虽然复杂度相同,但是开销也是不同的
// ListNode* p = head->next, *newHead = head;
// while (p != NULL) {
// head->next = p->next;
// p->next = newHead;
// newHead = p;
// p = head->next;
// }
// return newHead;
}
};