2 March 2020
LeetCode(106) -- 207, 208
by Jerry Zhang
P207. Course Schedule (Medium)
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, is it possible for you to finish all courses?
The Best Solution
上次做的时候我认为:“这道题本质上就是问一个有向图中,有没有环的问题。”
其实现在来看,需要加一个重要的条件,就是“这个图不一定是连通的”。一般的DFS或者BFS,都是保证整个图是连通的,那遍历起来其实非常简单。但这个题之所以比较难,就是因为这个图有可能不是连通的。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
if (numCourses == 0) {
return true;
}
int[] in = new int[numCourses];
for (int i = 0; i < prerequisites.length; i++) {
in[prerequisites[i][1]]++;
}
boolean[] visited = new boolean[prerequisites.length];
boolean change = true;
while (change) {
change = false;
for (int i = 0; i < prerequisites.length; i++) {
if (!visited[i]) {
if (in[prerequisites[i][0]] == 0 && in[prerequisites[i][1]] != 0) {
visited[i] = true;
in[prerequisites[i][1]]--;
change = true;
}
}
}
}
for (int i = 0; i < numCourses; i++) {
if (in[i] != 0) {
return false;
}
}
return true;
}
}
最优解的算法为以下几步:
- 创建一个与节点数一样长度的int数组,统计每个节点有多少个子节点。
- 创建一个与给出的边的数量一样长度的boolean数组,为了记录哪些边被访问/删除过了。
- 反复地循环/清洗所有的边,只有某一条边没有被访问过,并且这条边的子节点没有子节点了(叶节点),并且这条边的父节点还有子节点(这个节点不是孤立的),那么就把这条边“删除”,访问过,并且父节点的子节点数量减一,并且记录一下这一轮有所改变,下次还要继续循环。这相当于从一棵树的底部不断地向上砍,直到砍成孤立的一个节点或者因为有环没办法再砍为止。
- 重新检查一遍所有节点的子节点数目,如果有大于0的,说明有环。
当然这道题完全也可以用DFS做,每个节点都遍历一次,如果发现有环,就返回false.
P208. Implement Trie (Prefix Tree) (Medium)
Implement a trie with insert, search, and startsWith methods.
Solution
Trie这个数据结构的用途:
- google的自动完成
- 拼写检查
- IP路由前缀检查
- T9文本预测
- 单词游戏
为什么用Trie而不用HashSet呢?
- 有时候,我们有一个前缀,我们想找到所有这个前缀的单词,用hashset就不好使。
- 按字母表顺序遍历所有单词,那hashset也不好用。
- 当单词越来越多时,hashset的时间复杂度可能从O(1)变成O(n)。
用Trie的时间复杂度只有O(m), m是单词的长度。
My Modified Solution
class Trie {
static class TrieNode {
private TrieNode[] children = new TrieNode[26];
private boolean isEnd;
}
private TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode curr = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (curr.children[c - 'a'] == null) {
curr.children[c - 'a'] = new TrieNode();
}
curr = curr.children[c - 'a'];
}
curr.isEnd = true;
}
private TrieNode searchPrefix(String word) {
TrieNode curr = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (curr.children[c - 'a'] == null) {
return null;
}
curr = curr.children[c - 'a'];
}
return curr;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode node = searchPrefix(word);
return node != null && node.isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
return searchPrefix(prefix) != null;
}
}
肯定需要写一个node作为内部类。包括26个children和一个boolean来标记这是不是一个单词。
tags: LeetCode