leetcode algorithm-11
14 Sep 2020
|
|
例题分析,待整理。
Morris遍历算法
比如中序遍历:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
TreeNode *predecessor = nullptr;
while (root != nullptr) {
if (root->left != nullptr) {
// predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
predecessor = root->left;
while (predecessor->right != nullptr && predecessor->right != root) {
predecessor = predecessor->right;
}
// 让 predecessor 的右指针指向 root,继续遍历左子树
if (predecessor->right == nullptr) {
predecessor->right = root;
root = root->left;
}
// 说明左子树已经访问完了,我们需要断开链接
else {
res.push_back(root->val);
predecessor->right = nullptr;
root = root->right;
}
}
// 如果没有左孩子,则直接访问右孩子
else {
res.push_back(root->val);
root = root->right;
}
}
return res;
}
};
并查集+有向图
Redundant Connection II
思路:主要是有向图的话,有两种类型的边要移除,这是需要考虑清楚的。
// 不一定有环出现,也不一定有冲突出现,但只能有一条边被移除而恢复正常,所以冲突的边和环边有一个节点是重合的。
// 另外,冲突的边不可能出现两个不同的节点上,否则不满足只移除一条边的限制。
// 这里是两类,所以一定要判断以下,是冲突还是环,而不是放在一个unionfind里,对无向图是有用的,无向图是只要连接就会归到同一个派别。
struct UnionFind {
vector <int> ancestor;
UnionFind(int n) {
ancestor.resize(n);
for (int i = 0; i < n; ++i) {
ancestor[i] = i;
}
}
int find(int index) {
return index == ancestor[index] ? index : ancestor[index] = find(ancestor[index]);
}
void merge(int u, int v) {
ancestor[find(u)] = find(v);
}
};
class Solution {
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
// 特殊之处在于图是directed graph.
int cntsize = edges.size();
UnionFind uf = UnionFind(cntsize+1);
auto parent = vector<int>(cntsize+1);
for(int i = 1; i < cntsize+1; i++){
parent[i] = i;
}
int conflict = -1;
int cycle = -1;
for(int i = 0; i < cntsize; i++){
int x = edges[i][0], y = edges[i][1];
if(parent[y] != y){
conflict = i;
}else{
parent[y] = x;
if(uf.find(x) == uf.find(y)){
cycle = i;
}else{
uf.merge(x, y);
}
}
}
if(conflict == -1){
return {edges[cycle][0], edges[cycle][1]};
}else{
if(cycle == -1){
return {edges[conflict][0], edges[conflict][1]};
}else{
// 如果存在cycle,最后要移除的边不一定是最后一条形成环的边,因为与其他节点的关系未知,而是冲突边(因为只能保留一条冲突边。)
return {parent[edges[conflict][1]], edges[conflict][1]};
}
}
}
};
链表: Linked List Cycle II
思路:
-
—-a(初始点) —-b(相遇点),fast走的路程(2a+2b)-slow走的路程(a+b)=a+b,所以再走a可以到初始点。
- 现在的slow的位置,和指针head的位置,同时走,可以在初始点相遇。
- (重要)在链表中,长度是通过指针的相遇(快慢指针)表示的,而不是一个特定的数。
链表:Populating Next Right Pointers in Each Node II
思路:
递归是从右侧往左递归,逐步建立连接,寻找下一个节点的过程也是使用递归,这个DFS和BFS一样写的漂亮。
同时,从题目中也可以看到,c++和python类的构造函数逻辑不太相同,一个是重写函数,一个是设置构造函数的默认值。
两种方法同样都是对子结点进行处理,而不是对当前节点进行处理:
DFS:
class Solution {
public:
Node* connect(Node* root) {
// DFS并不是分治的思想,因为需要连接不同树的节点。
// BFS很简单,最主要的是constant extra space.
// 可以从右侧进行BFS建立连接,有点dp的意味
if(!root) return root;
if(root->left){
if(root->right){
root->left->next = root->right;
}
else{
root->left->next = next(root->next);
}
}
if(root->right){
root->right->next = next(root->next);
}
connect(root->right);
connect(root->left);
return root;
}
// 函数名字写的不好,不应该是next,而应该是getnode
Node* next(Node* root){
if(!root) return NULL;
if(root->left) return root->left;
if(root->right) return root->right;
return next(root->next);
// if(root->next) return next(root->next);
// return NULL;
}
};
在链表中,存元素可以在这里做一个head节点,然后用head.next来找到元素,处理方法是很高明的。
BFS:
class Solution:
def connect(self, root: 'Node') -> 'Node':
# 在当前层将下一层的节点连接,高
# 建立了单向连接,所以只能从左到右
left_most = root
while left_most:
cur = left_most
# cur再上一层,pre在下一层
head = pre = Node()
while cur:
if cur.left:
pre.next = cur.left
pre = pre.next
if cur.right:
pre.next = cur.right
pre = pre.next
cur = cur.next
# 怎么换层?
left_most = head.next
return root
二分法:Koko Eating Bananas
Note:int mi = lo + (hi - lo) / 2; 在lo和hi都比较大的时候,会导致越界的问题。
这里面还有划分派别的问题:
如果要给1~k的元素都划分到第1个派别,采用式子$n=(m-1)/k+1$,解决这种时间的问题是最好用的,因为一个小时就算没用完也使用了。
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int H) {
int lo = 1, hi = pow(10, 9);
while (lo < hi) {
int mi = lo + (hi - lo) / 2;
if (!possible(piles, H, mi))
lo = mi + 1;
else
hi = mi;
}
return lo;
}
bool possible(vector<int>& piles, int H, int K) {
int time = 0;
for (int p: piles)
time += (p - 1) / K + 1;
return time <= H;
}
};