Tag Archives: BFS

LeetCode Course Schedule II

210. Course Schedule II 210. Course Schedule II

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, return the ordering of courses you should take to finish all courses.

There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.

Example 1:

Input: 2, [[1,0]] 
Output: [0,1]
Explanation: There are a total of 2 courses to take. To take course 1 you should have finished   
             course 0. So the correct course order is [0,1] .

Example 2:

Input: 4, [[1,0],[2,0],[3,1],[3,2]]
Output: [0,1,2,3] or [0,2,1,3]
Explanation: There are a total of 4 courses to take. To take course 3 you should have finished both     
             courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. 
             So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3] .

Note:

  1. The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input prerequisites.

LeetCode Course Schedule的基础上,要求输出一种拓扑排序结果。
因为已经知道拓扑排序的Kahn解法,即BFS解法,那么输出一种拓扑排序的结果是很简单的事。在BFS的过程中,存储在队列中的点肯定都是入度为0的点,也就是这些课程可以在这一阶段完成,所以我们只需要在每轮BFS的时候,保存一下这一轮的节点就好了。
完整代码如下:

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<pair<int, int> >& prerequisites)
    {
        vector<unordered_set<int> > pre(numCourses), next(numCourses);
        for (const auto& p : prerequisites) {
            pre[p.first].insert(p.second);
            next[p.second].insert(p.first);
        }
        vector<int> ans;
        queue<int> q;
        for (int i = 0; i < numCourses; ++i) {
            if (pre[i].empty())
                q.push(i);
        }
        int edges = prerequisites.size();
        while (!q.empty()) {
            int n = q.size();
            for (int i = 0; i < n; ++i) {
                int cur = q.front();
                ans.push_back(cur);
                q.pop();
                for (const auto& nxt : next[cur]) {
                    pre[nxt].erase(cur);
                    –edges;
                    if (pre[nxt].empty())
                        q.push(nxt);
                }
            }
        }
        if (edges > 0)
            return {};
        else
            return ans;
    }
};

本代码提交AC,用时23MS。

二刷。在上一题的基础上,要求输出拓扑排序的顺序,比之前简单的解法代码,不需要unordered_set:

class Solution {
public:
	vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
		vector<vector<int>> graph(numCourses, vector<int>(numCourses, 0));
		vector<int> indegree(numCourses, 0);
		for (int i = 0; i < prerequisites.size(); ++i) {
			int a = prerequisites[i][0], b = prerequisites[i][1];
			graph[b][a] = 1;
			++indegree[a];
		}
		vector<int> ans;
		while (true) {
			int validid = -1;
			for (int i = 0; i < numCourses; ++i) {
				if (indegree[i] == 0) {
					validid = i;
					--indegree[i];
					break;
				}
			}
			if (validid == -1)break;
			ans.push_back(validid);
			for (int i = 0; i < numCourses; ++i) {
				if (graph[validid][i] == 1) {
					graph[validid][i] = 0;
					--indegree[i];
				}
			}
		}
		if (ans.size() != numCourses)return { };
		return ans;
	}
};

本代码提交AC,用时80MS。

LeetCode Task Scheduler

LeetCode Task Scheduler Given a char array representing tasks CPU need to do. It contains capital letters A to Z where different letters represent different tasks.Tasks could be done without original order. Each task could be done in one interval. For each interval, CPU could finish one task or just be idle. However, there is a non-negative cooling interval n that means between two same tasks, there must be at least n intervals that CPU are doing different tasks or just be idle. You need to return the least number of intervals the CPU will take to finish all the given tasks. Example 1:

Input: tasks = ['A','A','A','B','B','B'], n = 2
Output: 8
Explanation: A -> B -> idle -> A -> B -> idle -> A -> B.
Note:
  1. The number of tasks is in the range [1, 10000].

CPU需要处理一系列任务,限制条件是两个相同的任务被处理的时间至少需要间隔n时刻,问CPU最少需要多长时间能处理完所有任务。 比赛没做出来,参考讨论区。 根据题意,两个任务被处理至少需要间隔n时刻,所以我们可以认为CPU处理一批任务的循环周期是n+1,比如0时刻处理了任务’A’,则至少要到n+1时刻才能再次处理任务’A’,中间间隔了n时刻。 假设数量最多的任务的数量是k,则我们至少需要k个周期才能把这个任务处理完。为了让CPU处理的空闲时间更少,我们在一个周期内尽量让CPU处理的任务更丰富。所以想象一下,我们有k个桶,相当于k个周期,每个周期,我们把频率最高的任务拿出来,分发到这最多k个桶中。如果所有不同任务都分发完了还没有填满一个桶,则说明该桶内(周期内)CPU需要空闲等待。 比如样例中,最高频的任务是A和B,都是3,所以我们至少需要3个桶。每个桶的容量是n+1=3,相当于相同任务的距离是3。每次我们把A和B扔到不同的桶中,前两个桶各有一个空闲等待,第三个桶因为结束了,所以不需等待。 因为每次都需要取出最高频的任务去分发,所以用一个优先队列来实时更新频率排名。 完整代码如下: [cpp] class Solution { public: int leastInterval(vector<char>& tasks, int n) { map<char, int> count; for (const auto& c : tasks)++count[c]; priority_queue<pair<int, char>> pq; for (const auto& p : count)pq.push({ p.second,p.first }); int cycle = n + 1, time = 0; while (!pq.empty()) { vector<pair<int, char>> tmp; int tsk = 0; for (int i = 0; i < cycle; ++i) { if (!pq.empty()) { tmp.push_back(pq.top()); pq.pop(); ++tsk; } } for (auto& t : tmp) { if (–t.first) { pq.push(t); } } time += pq.empty() ? tsk : cycle; } return time; } }; [/cpp] 本代码提交AC,用时95MS。]]>

LeetCode Add One Row to Tree

LeetCode Add One Row to Tree Given the root of a binary tree, then value v and depth d, you need to add a row of nodes with value v at the given depth d. The root node is at depth 1. The adding rule is: given a positive integer depth d, for each NOT null tree nodes N in depth d-1, create two tree nodes with value v as N's left subtree root and right subtree root. And N's original left subtree should be the left subtree of the new left subtree root, its original right subtree should be the right subtree of the new right subtree root. If depth d is 1 that means there is no depth d-1 at all, then create a tree node with value v as the new root of the whole original tree, and the original tree is the new root’s left subtree. Example 1:

Input:
A binary tree as following:
       4
     /   \
    2     6
   / \   /
  3   1 5
v = 1
d = 2
Output:
       4
      / \
     1   1
    /     \
   2       6
  / \     /
 3   1   5
Example 2:
Input:
A binary tree as following:
      4
     /
    2
   / \
  3   1
v = 1
d = 3
Output:
      4
     /
    2
   / \
  1   1
 /     \
3       1
Note:
  1. The given d is in range [1, maximum depth of the given tree + 1].
  2. The given binary tree has at least one tree node.

在二叉树深度为d的那一层增加一行值全为v的节点,如果d=1的话,新增一个值为v的根节点,原来的树作为新根节点的左子树。 简单题,层次遍历到深度为d-1时,对d-1层的所有节点,都增加值为v的左右孩子,如果d-1层的节点原来有左右孩子,则原来的左右孩子接到新加入的v的节点上,作为左右孩子。 完整代码如下: [cpp] class Solution { public: TreeNode* addOneRow(TreeNode* root, int v, int d) { if (d == 1) { TreeNode* newRoot = new TreeNode(v); newRoot->left = root; return newRoot; } queue<TreeNode*> tree; tree.push(root); int depth = 0; while (!tree.empty()) { ++depth; int n = tree.size(); if (depth == d – 1) { for (int i = 0; i < n; ++i) { TreeNode* p = tree.front(); tree.pop(); if (p == NULL)continue; TreeNode* l = new TreeNode(v); l->left = p->left; p->left = l; TreeNode* r = new TreeNode(v); r->right = p->right; p->right = r; } break; } else { for (int i = 0; i < n; ++i) { TreeNode* p = tree.front(); tree.pop(); if (p == NULL)continue; tree.push(p->left); tree.push(p->right); } } } return root; } }; [/cpp] 本代码提交AC,用时16MS。]]>

LeetCode Lowest Common Ancestor of a Binary Tree

LeetCode Lowest Common Ancestor of a Binary Tree Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4
For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
给定一棵二叉树和两个节点p,q,问p和q的最近公共祖先节点是哪个。LCA问题。 思路:要找p和q的最近公共祖先,很直观的方法是找到从根节点分别到p和q的路径,然后把其中一条路径(比如根到p)上的点hash成path1,从另一条路径(比如根到q)的底端(q)往根节点走,把走过的每个点去path1中找,如果找到,则就是他们的LCA,因为这是距离q最近、且在path1中的点。 但是怎样找到根到p和q的路径呢。这里换一种思路,定义如下新的数据结构,par表示该节点的父节点,根节点的父节点为-1。 [cpp] struct MyNode { TreeNode* node; int par; MyNode(TreeNode* n_, int p_) :node(n_), par(p_) {}; }; [/cpp] 先整体把树层次遍历一遍,成为一个保存MyNode的数组,记录每个点的父节点在数组中的下标。在遍历的同时,记录下p和q节点的下标。这样通过下标往前递归就可以找到p和q到根节点的路径了。 找到路径之后,对其中一条路径hash,另一条路径在该hash中找。代码如下: [cpp] class Solution { private: struct MyNode { TreeNode* node; int par; MyNode(TreeNode* n_, int p_) :node(n_), par(p_) {}; }; public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { vector<MyNode> nodes; int i = 0, j = 0; queue<MyNode> tree; tree.push({ root,-1 }); while (!tree.empty()) { MyNode cur = tree.front(); tree.pop(); if (cur.node == NULL) continue; if (cur.node == p)i = nodes.size(); if (cur.node == q)j = nodes.size(); tree.push({ cur.node->left,nodes.size() }); tree.push({ cur.node->right,nodes.size() }); nodes.push_back(cur); } if (i == j)return nodes[i].node; unordered_set<int> path1; while (i != -1) { path1.insert(i); i = nodes[i].par; } while (j != -1) { if (path1.find(j) != path1.end()) { return nodes[j].node; } j = nodes[j].par; } return NULL; } }; [/cpp] 本代码提交AC,用时19MS。 讨论区还有一种递归的解法,很有意思。我们在以root为根的树中找p和q的LCA,如果root等于p或q其中之一,说明p或q就是他们的LCA。否则分别在左子树和右子树中递归的找LCA,如果发现在左右子树中找到的LCA都不为null,说明他们p和q分别位于root的两个子树,类似样例中的5和1,则root就是他们的LCA。如果某个子树返回的LCA为空,说明p和q都不在这个子树中,则先遇到谁,谁就是LCA,比如样例中的5和4,都不在3的右子树,在递归3的左子树的时候,先遇到了5,所以5是LCA。 完整代码如下: [cpp] class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (root == NULL || root == p || root == q)return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); if (left != NULL&&right != NULL)return root; return left != NULL ? left : right; } }; [/cpp] 本代码提交AC,用时23MS。]]>

LeetCode Integer Replacement

LeetCode Integer Replacement Given a positive integer n and you can do operations as follow:

  1. If n is even, replace n with n/2.
  2. If n is odd, you can replace n with either n + 1 or n - 1.
What is the minimum number of replacements needed for n to become 1? Example 1:
Input:
8
Output:
3
Explanation:
8 -> 4 -> 2 -> 1
Example 2:
Input:
7
Output:
4
Explanation:
7 -> 8 -> 4 -> 2 -> 1
or
7 -> 6 -> 3 -> 2 -> 1

给定一个数n,不断对n进行如下两种操作中的一种
  1. 如果n是偶数,把n变成n/2
  2. 如果n是奇数,把n变成n+1或者n-1
问要把n变为1,需要的最少的变换次数是多少。 为啥我首先想到的是BFS。。。这不就相当于每个点根据情况可以走不同的方向,问走到终点1需要的最少步数吗?很像BFS呀。代码如下: [cpp] class Solution { private: struct P { long long val; int step; P(long long v_, int s_) :val(v_), step(s_) {}; }; public: int integerReplacement(int n) { P p(n, 0); queue<P> qp; qp.push(p); while (!qp.empty()) { P f = qp.front(); qp.pop(); if (f.val == 1)return f.step; else if (f.val & 1) { qp.push(P(f.val + 1, f.step + 1)); qp.push(P(f.val – 1, f.step + 1)); } else { qp.push(P(f.val / 2, f.step + 1)); } } return 0; } }; [/cpp] 本代码提交AC,用时6MS,只击败5%的人。。。 看讨论区,用位运算求解。要想快速的到达1,则n的二进制中0越多越好,因为0越多,后续越有可能用n/2快速的去掉一位。所以如果n是奇数时,我们判断一下n+1和n-1哪个包含的0越多,就走哪步。 但是每次都需要求n+1和n-1中1的个数,复杂度有点高。 其实,我们只需要关注n的最后两个二进制位即可。任何一个数的二进制尾数最后两位可能有四种情况,00、01、10、11,如果n是偶数,即以00或10结尾,则显然n/2能快速的减少一位。但是如果是01或者11呢。如果是01,则减1会变成00,如果加1,会变成10。显然,减1之后多出一个0,而加1之后0的个数没变,所以如果是以01结尾,则减1更优。 如果末尾是11,则减1变成10,加1变成100,显然加1更优。 有一个例外是,如果n就等于3,即二进制为11,则11→10→1比11→100→10→1更优,即当n==3时,减1更好。 完整代码如下: [cpp] class Solution { public: int integerReplacement(int n) { if (n == INT_MAX)return 32; int ans = 0; while (n > 1) { if ((n & 1) == 0) n >>= 1; else if (n == 3 || ((n >> 1) & 1) == 0)–n; else ++n; ++ans; } return ans; } }; [/cpp] 本代码提交AC,用时3MS。]]>

LeetCode Course Schedule

207. Course Schedule

There are a total of numCourses courses you have to take, labeled from 0 to numCourses-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0, and to take course 0 you should
             also have finished course 1. So it is impossible.

Constraints:

  • The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  • You may assume that there are no duplicate edges in the input prerequisites.
  • 1 <= numCourses <= 10^5

排课表问题,一看就是拓扑排序的题。
之前介绍过,拓扑排序有两种解法,一种是类似BFS的Kahn算法,另一种是DFS算法,具体可以参考维基百科的伪代码
解法1:Kahn算法。基本思想,首先把所有入度为0的点放到集合S中(表示这些课没有先修课程,可以首先完成),然后不断从S中取点x,把x指向的所有的点y的边(x,y)删掉,如果删掉之后,y的入度也变为0了,把y也加入到S中。当S为空时,如果图中还有边没删掉,说明形成环了,否则是一个DAG。
想象一下图中的一个环,如同时有边(x,y)和(y,x),则x和y因为都有入度,说明都不可能加入到集合S中,所以这两条边永远都删不掉。
Kahn的算法很好理解,代码类似于BFS,如下:

class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int> >& prerequisites)
    {
        int numEdges = prerequisites.size();
        vector<set<int> > pre(numCourses, set<int>()), next(numCourses, set<int>());
        for (const auto& p : prerequisites) {
            pre[p.first].insert(p.second);
            next[p.second].insert(p.first);
        }
        queue<int> Q;
        for (int i = 0; i < numCourses; ++i) {
            if (pre[i].size() == 0)
                Q.push(i);
        }
        while (!Q.empty()) {
            int node = Q.front();
            Q.pop();
            for (const auto& n : next[node]) {
                pre[n].erase(node);
                –numEdges;
                if (pre[n].size() == 0)
                    Q.push(n);
            }
        }
        return numEdges == 0;
    }
};

本代码提交AC,用时15MS。
解法2:DFS算法。还可以用DFS求解,基本思想是,维护两个访问标记数组,visited[i]表示全局是否被访问数组,相当于维基百科中的permanently标记,onpath[i]表示当前DFS路径是否被访问数组,相当于维基百科中的temporarily标记。
对于所有visited[i]没访问的点i,我们DFS它。把从i开始DFS到的点标记为onpath[j],如果在DFS的过程中访问到了onpath[k]被标记过的点,说明这条路形成了环,返回false,不是DAG。否则,如果visited[node]==1,说明node是DFS之前的点时被访问过,而之前的DFS返回了true,说明从node DFS的结果是不会形成环的,可以直接返回true。否则如果visited[node]==0,我们从node继续DFS,具体步骤是,首先标记onpath[node]=1,然后把node指向的所有点都DFS一遍,如果都没发现换,则永久标记visited[node]=1,说明从node这个点DFS是没问题的,不会形成环的;同时复位onpath[node]=0,以便下次DFS使用。
完整代码如下,实现方法完全参考维基百科中的DFS版本。

class Solution {
private:
    bool dfs(vector<int>& visited, vector<int>& onpath, vector<vector<int> >& graph, int node)
    {
        if (onpath[node] == 1)
            return false;
        if (visited[node] == 0) {
            onpath[node] = 1;
            for (int i = 0; i < graph.size(); ++i) {
                if (graph[node][i] == 1) {
                    if (!dfs(visited, onpath, graph, i))
                        return false;
                }
            }
            visited[node] = 1;
            onpath[node] = 0;
            return true;
        }
        return true;
    }

public:
    bool canFinish(int numCourses, vector<pair<int, int> >& prerequisites)
    {
        vector<vector<int> > graph(numCourses, vector<int>(numCourses, 0));
        for (const auto& p : prerequisites)
            graph[p.second][p.first] = 1;
        vector<int> visited(numCourses, 0), onpath(numCourses, 0); // visited==permanently; onpath=temporarily
        for (int i = 0; i < numCourses; ++i) {
            if (visited[i] == 0) {
                if (!dfs(visited, onpath, graph, i))
                    return false;
            }
        }
        return true;
    }
};

本代码提交AC,用时62MS,比Kahn慢好多。

二刷。上述解法也太复杂了,直接循环把入度为0的删掉就行了,完整代码如下:

class Solution {
public:
	bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
		vector<vector<int>> graph(numCourses, vector<int>(numCourses, 0));
		vector<int> indegree(numCourses, 0);
		for (int i = 0; i < prerequisites.size(); ++i) {
			graph[prerequisites[i][1]][prerequisites[i][0]] = 1;
			++indegree[prerequisites[i][0]];
		}
		int finish = 0;
		while (true) {
			bool found = false;
			for (int i = 0; i < numCourses; ++i) {
				if (indegree[i] == 0) {
					--indegree[i];
					++finish;
					found = true;
					for (int j = 0; j < numCourses; ++j) {
						if (graph[i][j] == 1) {
							graph[i][j] = 0;
							--indegree[j];
						}
					}
				}
			}
			if (!found)break;
		}
		return finish == numCourses;
	}
};

本代码提交AC,用时76MS。

LeetCode Pacific Atlantic Water Flow

LeetCode Pacific Atlantic Water Flow Given an m x n matrix of non-negative integers representing the height of each unit cell in a continent, the “Pacific ocean” touches the left and top edges of the matrix and the “Atlantic ocean” touches the right and bottom edges. Water can only flow in four directions (up, down, left, or right) from a cell to another one with height equal or lower. Find the list of grid coordinates where water can flow to both the Pacific and Atlantic ocean. Note:

  1. The order of returned grid coordinates does not matter.
  2. Both m and n are less than 150.
Example:
Given the following 5x5 matrix:
  Pacific ~   ~   ~   ~   ~
       ~  1   2   2   3  (5) *
       ~  3   2   3  (4) (4) *
       ~  2   4  (5)  3   1  *
       ~ (6) (7)  1   4   5  *
       ~ (5)  1   1   2   4  *
          *   *   *   *   * Atlantic
Return:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix).

给定一个矩阵,每个元素表示一个方格中水柱的高度,左上角是太平洋,右下角是大西洋,问哪些坐标的水柱既能流向太平洋,又能流向大西洋。注意每个水柱能流向和它同样高或者比它第的地方。 一看就是DFS或者BFS的题。 拍脑袋解法。对于每个坐标,我们DFS看能否分别到达太平洋或者大西洋。代码如下: [cpp] vector<vector<int>> dirs = { { 0,-1 },{ -1,0 },{ 0,1 },{ 1,0 } }; // 0:left, 1:up, 2:right, 3:down class Solution4 { private: int m, n; bool isOk(int x, int y) { return x >= 0 && x < m&&y >= 0 && y < n; } // type=0:Pacific; 1:Atlantic bool dfs(vector<vector<int>>& matrix, vector<vector<int>>& visited, int x, int y, const int& type) { for (int i = 0; i < dirs.size(); ++i) { int xx = x + dirs[i][0], yy = y + dirs[i][1]; if (isOk(xx, yy) && visited[xx][yy] == 0 && matrix[x][y] >= matrix[xx][yy]) { visited[xx][yy] = 1; if (dfs(matrix, visited, xx, yy, type)) { visited[xx][yy] = 0; return true; } visited[xx][yy] = 0; } else if ((type == 0 && (xx < 0 || yy < 0)) || (type == 1 && (xx >= m || yy >= n))) { return true; } } return false; } public: vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty())return{}; m = matrix.size(), n = matrix[0].size(); vector<vector<int>> visited(m, vector<int>(n, 0)); vector<pair<int, int>> ans; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { visited[i][j] = 1; if (dfs(matrix,visited,i,j,0)&&dfs(matrix,visited,i,j,1)) ans.push_back(pair<int, int>(i, j)); visited[i][j] = 0; } } return ans; } }; [/cpp] 本代码提交AC,用时702MS。 这种DFS的解法重复计算非常多,比如(a,b)这个点DFS时会经过(a-1,b-1),而(a-1,b-1)之前其实已经DFS过了。可以在DFS的过程中记录下经过的点的DFS状态,比如在DFS(a,b)时,走到(a-1,b-1),先查状态,发现(a-1,b-1)到不了大西洋,那现在这条路就没必要继续DFS下去了;相反,如果查状态发现(a-1,b-1)能到大西洋,则后续不需要再DFS其他路径了。 保存状态的DFS版本如下: [cpp] vector<vector<int>> dirs = { { 0,-1 },{ -1,0 },{ 0,1 },{ 1,0 } }; // 0:left, 1:up, 2:right, 3:down // memory version class Solution3 { private: int m, n; bool isOk(int x, int y) { return x >= 0 && x < m&&y >= 0 && y < n; } bool dfs(vector<vector<int>>& matrix, vector<vector<int>>& visited, int x, int y, const int& type, vector<vector<vector<int>>>& mem) { for (int i = 0; i < dirs.size(); ++i) { int xx = x + dirs[i][0], yy = y + dirs[i][1]; if (isOk(xx, yy) && visited[xx][yy] == 0 && matrix[x][y] >= matrix[xx][yy]) { if (mem[xx][yy][type] == 1) { mem[x][y][type] = 1; return true; } else if (mem[xx][yy][type] == 0)continue; visited[xx][yy] = 1; if (dfs(matrix, visited, xx, yy, type, mem)) { mem[x][y][type] = 1; visited[xx][yy] = 0; return true; } visited[xx][yy] = 0; } else if ((type == 0 && (xx < 0 || yy < 0)) || (type == 1 && (xx >= m || yy >= n))) { mem[x][y][type] = 1; return true; } } mem[x][y][type] = 0; return false; } public: vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty())return{}; m = matrix.size(), n = matrix[0].size(); vector<vector<int>> visited(m, vector<int>(n, 0)); vector<vector<vector<int>>> mem(m, vector<vector<int>>(n, vector<int>(2, -1))); vector<pair<int, int>> ans; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { visited[i][j] = 1; if (mem[i][j][0] == -1) dfs(matrix, visited, i, j, 0, mem); if (mem[i][j][1] == -1) dfs(matrix, visited, i, j, 1, mem); visited[i][j] = 0; if (mem[i][j][0] == 1 && mem[i][j][1] == 1) ans.push_back(pair<int, int>(i, j)); } } return ans; } }; [/cpp] 无奈,在第112/113这个数据上WA了,但是这个数据太大了,面对的是汪洋大海,调试太费劲,暂时没fix bug。 看了下讨论区,发现主流解法是从海洋开始BFS,分别从大西洋和太平洋BFS,记录下两个海洋分别能访问到的点,最后如果某个点同时被两个海洋访问到了,则说明该点既能到达大西洋,也能到达太平洋。 代码如下: [cpp] vector<vector<int>> dirs = { { 0,-1 },{ -1,0 },{ 0,1 },{ 1,0 } }; // 0:left, 1:up, 2:right, 3:down class Solution { private: int m, n; bool isOk(int x, int y) { return x >= 0 && x < m&&y >= 0 && y < n; } void bfs(vector<vector<int>>& matrix, queue<pair<int, int>>& Q, vector<vector<int>>& visited) { while (!Q.empty()) { auto f = Q.front(); Q.pop(); visited[f.first][f.second] = 1; for (int i = 0; i < dirs.size(); ++i) { int x = f.first + dirs[i][0], y = f.second + dirs[i][1]; if (isOk(x, y) && visited[x][y] == 0 && matrix[f.first][f.second] <= matrix[x][y]) { Q.push(pair<int, int>(x, y)); } } } } public: vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty())return{}; m = matrix.size(), n = matrix[0].size(); vector<vector<int>> pacific(m, vector<int>(n, 0)), atlantic(m, vector<int>(n, 0)); queue<pair<int, int>> pQ, aQ; for (int i = 0; i < m; ++i) { // vertical pQ.push(pair<int, int>(i, 0)); aQ.push(pair<int, int>(i, n – 1)); } for (int i = 0; i < n; ++i) { // horizontal pQ.push(pair<int, int>(0, i)); aQ.push(pair<int, int>(m – 1, i)); } bfs(matrix, pQ, pacific); bfs(matrix, aQ, atlantic); vector<pair<int, int>> ans; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (pacific[i][j] == 1 && atlantic[i][j] == 1) ans.push_back(pair<int, int>(i, j)); } } return ans; } }; [/cpp] 本代码提交AC,用时减少到45MS。 对于类似的搜索题目,如果是求单个点能否达到目的地,则从起始点或者目的点开始BFS/DFS的效果都是一样的。但是如果要求所有能到达目的点的点,则从目的点开始BFS就能一次性找到这些点,而从每个起始点开始BFS/DFS就非常费劲了。]]>

LeetCode Kill Process

LeetCode Kill Process Given n processes, each process has a unique PID (process id) and its PPID (parent process id). Each process only has one parent process, but may have one or more children processes. This is just like a tree structure. Only one process has PPID that is 0, which means this process has no parent process. All the PIDs will be distinct positive integers. We use two list of integers to represent a list of processes, where the first list contains PID for each process and the second list contains the corresponding PPID. Now given the two lists, and a PID representing a process you want to kill, return a list of PIDs of processes that will be killed in the end. You should assume that when a process is killed, all its children processes will be killed. No order is required for the final answer. Example 1:

Input:
pid =  [1, 3, 10, 5]
ppid = [3, 0, 5, 3]
kill = 5
Output: [5,10]
Explanation:
           3
         /   \
        1     5
             /
            10
Kill 5 will also kill 10.
Note:
  1. The given kill id is guaranteed to be one of the given PIDs.
  2. n >= 1.

给定一个父子进程的对应关系,现在要kill掉某个进程id,那么id的子进程,孙进程也会被kill掉,问最终被kill掉的进程有哪些。 简单题。先使用map把父子进程的对应关系存起来,因为是一个父亲可能有多个孩子进程,所以要用multimap。然后用bfs类似于树的层次遍历,把所有孩子进程遍历一遍并存到结果数组中。 代码如下,刚好看了C++ primer的unordered_multimap的范围查找,用equal_range很方便。 [cpp] class Solution { public: vector<int> killProcess(vector<int>& pid, vector<int>& ppid, int kill) { unordered_multimap<int, int> umm; for (int i = 0; i < pid.size(); ++i) { umm.insert(pair<int, int>(ppid[i], pid[i])); } vector<int> ans; queue<int> q; q.push(kill); while (!q.empty()) { int id = q.front(); q.pop(); ans.push_back(id); auto r = umm.equal_range(id); for (auto i = r.first; i != r.second; ++i)q.push(i->second); } return ans; } }; [/cpp] 本代码提交AC,用时166MS。]]>

LeetCode Friend Circles

LeetCode Friend Circles There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a direct friend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends. Given a N*N matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are directfriends with each other, otherwise not. And you have to output the total number of friend circles among all the students. Example 1:

Input:
[[1,1,0],
 [1,1,0],
 [0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
The 2nd student himself is in a friend circle. So return 2.
Example 2:
Input:
[[1,1,0],
 [1,1,1],
 [0,1,1]]
Output: 1
Explanation:The 0th and 1st students are direct friends, the 1st and 2nd students are direct friends,
so the 0th and 2nd students are indirect friends. All of them are in the same friend circle, so return 1.
Note:
  1. N is in range [1,200].
  2. M[i][i] = 1 for all students.
  3. If M[i][j] = 1, then M[j][i] = 1.

给定一个矩阵,M[i][j]=1表示i和j有直接关系,如果i和j有直接关系,j和k有直接关系,则i和k有间接关系。问这个矩阵共有多少个关系网。 简单题,有两种解法。第一种解法是DFS或者BFS,每次把能搜索到的点标记为一个新的关系网,直到所有点都属于一个关系网。但是无论DFS还是BFS,复杂度都比较高。 这种题应该条件反射想到并查集,只要M[i][j]=1,则把i和j union起来,最后看一下有多少个代表就好了。这种解法非常简单,代码如下: [cpp] class Solution { private: int n; vector<int> parent; void init() { parent.resize(n); for (int i = 0; i < n; ++i) { parent[i] = i; } } int find_set(int x) { if (parent[x] != x) parent[x] = find_set(parent[x]); return parent[x]; } void union_set(int u, int v) { parent[find_set(u)] = find_set(v); } public: int findCircleNum(vector<vector<int>>& M) { n = M.size(); init(); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (M[i][j] == 1) { union_set(i, j); } } } set<int> circles; for (int i = 0; i < n; ++i)circles.insert(find_set(i)); // 注意最后还要find一下找到代表 return circles.size(); } }; [/cpp] 本代码提交AC,用时19MS。]]>

hihoCoder 1519-逃离迷宫II

hihoCoder 1519-逃离迷宫II

#1519 : 逃离迷宫II

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

小Hi被坏女巫抓进里一间有N x M个格子组成的矩阵迷宫。 有些格子是小Hi可以经过的,我们用’.’表示;有些格子上有障碍物小Hi不能经过,我们用’#’表示。小Hi的起始位置用’S’表示,他需要到达用’T’表示的格子才能逃离迷宫。 麻烦的是小Hi被坏女巫施了魔法,他只能选择上下左右某一个方向,沿着这个方向一直走,直到遇到障碍物或者迷宫边界才能改变方向。新的方向可以是上下左右四个方向之一。之后他还是只能沿着新的方向一直走直到再次遇到障碍物或者迷宫边界…… 小Hi想知道他最少改变几次方向才能逃离这个迷宫。

输入

第一行包含两个整数N和M。  (1 <= N, M <= 500) 以下N行每行M个字符,代表迷宫。

输出

一个整数代表答案。如果小Hi没法逃离迷宫,输出-1。
样例输入
5 5
S.#.T
.....
.....
.....
.....
样例输出
2

给定一个迷宫,S表示起点,T表示终点,#表示障碍物。行走的时候,只能沿着直线走,直到遇到障碍物或者碰壁,问从S到T,最少需要改变多少次方向。 我一开始是用DFS做的,每次朝一个方向DFS下去,但是因为每次DFS时走了一长条直线,所以回溯的时候需要把之前走过的直线复位。代码如下: [cpp] #include<iostream> #include<vector> #include<climits> #include<algorithm> #include<set> #include<unordered_set> #include<unordered_map> #include<cmath> using namespace std; const int MAXN = 505; int n, m; int startx, starty, endx, endy; int ans = INT_MAX; vector<vector<char>> maze(MAXN, vector<char>(MAXN, ‘0’)); vector<vector<char>> visited(MAXN, vector<char>(MAXN, ‘0’)); vector<vector<int>> dirs{ {-1,0},{1,0},{0,-1},{0,1} }; //up,down,left,right vector<vector<int>> opdirs{ { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } }; // 反方向 bool ok(int x, int y) { if (x >= 0 && x < n&&y >= 0 && y < m&&maze[x][y] != ‘#’)return true; return false; } void dfs(int sx,int sy, int step) { for (int i = 0; i < 4; ++i) { int newx = sx + dirs[i][0], newy = sy + dirs[i][1]; if (ok(newx, newy) && visited[newx][newy] == ‘0’) { visited[newx][newy] = ‘1’; bool done = false; while (ok(newx, newy)) { visited[newx][newy] = ‘1’; if (newx == endx&&newy == endy) { done = true; break; } newx = newx + dirs[i][0], newy = newy + dirs[i][1]; } if (done) { ans = min(ans, step); } else { dfs(newx + opdirs[i][0], newy + opdirs[i][1], step + 1); // 往回走一步再递归 } while (!(newx == sx&&newy == sy)) { if (ok(newx, newy))visited[newx][newy] = ‘0’; // 复位 newx = newx + opdirs[i][0], newy = newy + opdirs[i][1]; } } } } int main() { //freopen("input.txt", "r", stdin); scanf("%d %d", &n, &m); char c; for (int i = 0; i < n; ++i) { scanf("%c", &c); for (int j = 0; j < m; ++j) { scanf("%c", &maze[i][j]); if (maze[i][j] == ‘S’) { startx = i; starty = j; } else if (maze[i][j] == ‘T’) { endx = i; endy = j; } } } visited[startx][starty] = ‘1’; dfs(startx, starty, 0); if (ans == INT_MAX)printf("-1\n"); else printf("%d\n", ans); return 0; } [/cpp] 本代码提交TLE,很明显会TLE,因为DFS必须把所有方案都走一遍才能得到最小值。 比赛的时候脑子短路,这种搜索问题要求最短XX的肯定用BFS比DFS快呀,因为第一次BFS到的方案肯定就是最优方案呀。只不过这题判断距离用的是转弯次数,以前都是算走过的步数。 对于这题,用BFS也是一样的,BFS的下一个点肯定就是走直线走到无法再走的那个拐角点(cornor)。不过为了防止死循环,要把以前走过的拐角都记录下来,只走那些以前没走过的拐点。 代码如下: [cpp] #include<iostream> #include<vector> #include<climits> #include<algorithm> #include<set> #include<unordered_set> #include<unordered_map> #include<cmath> #include<queue> using namespace std; const int MAXN = 505; int n, m; int startx, starty, endx, endy; typedef struct P { int x, y, step; P(int x_, int y_, int step_) :x(x_), y(y_), step(step_) {}; }; vector<vector<char>> maze(MAXN, vector<char>(MAXN, ‘0’)); vector<vector<int>> dirs{ { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } }; //up,down,left,right vector<vector<int>> opdirs{ { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } }; // 反方向 set<int> points; bool ok(int x, int y) { if (x >= 0 && x < n&&y >= 0 && y < m&&maze[x][y] != ‘#’)return true; return false; } int id(int x, int y) { return x*n + y; } int bfs() { queue<P> q; P p(startx, starty, 0); q.push(p); while (!q.empty()) { p = q.front(); q.pop(); points.insert(id(p.x, p.y)); for (int i = 0; i < dirs.size(); ++i) { int newx = p.x + dirs[i][0], newy = p.y + dirs[i][1]; while (ok(newx, newy)) { if (newx == endx&&newy == endy) { return p.step; } newx = newx + dirs[i][0], newy = newy + dirs[i][1]; } int cornorx = newx + opdirs[i][0], cornory = newy + opdirs[i][1]; // 往回走一步 if (points.find(id(cornorx, cornory)) == points.end()) { P tmp(cornorx, cornory, p.step + 1); q.push(tmp); } } } return -1; } int main() { //freopen("input.txt", "r", stdin); scanf("%d %d", &n, &m); char c; for (int i = 0; i < n; ++i) { scanf("%c", &c); for (int j = 0; j < m; ++j) { scanf("%c", &maze[i][j]); if (maze[i][j] == ‘S’) { startx = i; starty = j; } else if (maze[i][j] == ‘T’) { endx = i; endy = j; } } } printf("%d\n", bfs()); return 0; } [/cpp] 本代码提交AC,用时718MS。]]>