994. 腐烂的橘子
BFS (广度优先搜索)可以看成是层序遍历。从某个结点出发,BFS 首先遍历到距离为 1 的结点,然后是距离为 2、3、4…… 的结点。因此,BFS 可以用来求最短路径问题。BFS 先搜索到的结点,一定是距离最近的结点。
题目要求:返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。实际上就是求腐烂橘子到所有新鲜橘子的最短路径。那么这道题就可以使用 BFS。
class Solution {
public int orangesRotting(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
// 统计新鲜橘子的数量
int count = 0;
for(int i=0; i<n; i++){
for(int j=0; j<m; j++){
if(grid[i][j]==1){
count++;
}else if(grid[i][j]==2){
// 多源头,以所有腐烂的橘子为初始起点,并将它们加入队列
queue.add(new int[]{i,j});
}
}
}
// 统计层数,也就是最短路径,即本题的最短分钟数
int round=0;
while(count>0 && !queue.isEmpty()){
round++;
// 多源头,需要关注这些点的扩散状态
int k = queue.size();
for(int i=0; i<k; i++){
int[] orange = queue.poll();
int r = orange[0];
int c = orange[1];
// 保证取值在[0,m), [0,n)中
if(r-1>=0 && grid[r-1][c]==1){
// 不要忽略将其置为腐烂橘子,避免重复计算
grid[r-1][c]=2;
count--;
queue.add(new int[]{r-1,c});
}
if(r+1<n && grid[r+1][c]==1){
grid[r+1][c]=2;
count--;
queue.add(new int[]{r+1,c});
}
if(c-1>=0 && grid[r][c-1]==1){
grid[r][c-1]=2;
count--;
queue.add(new int[]{r,c-1});
}
if(c+1<m && grid[r][c+1]==1){
grid[r][c+1]=2;
count--;
queue.add(new int[]{r,c+1});
}
}
}
if(count>0){
return -1;
}else{
return round;
}
}
}
207. 课程表
考查 拓扑排序:
1.找到入度为0的点,入队
2.从图中删掉入度为0的点,直至全部被删掉。
3.依次删掉的顺序即为拓扑排序的结果。
若课程 a 存在前置课程 b 的话,我们添加一条从 b 到 a 的有向边,同时统计所有点的入度。
当处理完所有的 g[i] 后,将所有的入度为 0 的课程(含义为没有前置课程要求的科目)进行入队操作,跑一遍「拓扑排序」,若所有课程都能顺利出队,说明所有课程都能使完成。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> adjacency = new ArrayList<>();
for(int i=0; i<numCourses; i++){
adjacency.add(new ArrayList<>());
}
int[] flags = new int[numCourses];
for(int[] cp : prerequisites){
adjacency.get(cp[1]).add(cp[0]);
}
for(int i=0; i<numCourses; i++){
if(!dfs(adjacency,flags,i)) return false;
}
return true;
}
public boolean dfs(List<List<Integer>> adjacency, int[] flags, int i){
// 说明在本轮 DFS 搜索中节点 i 被第 2 次访问,即 课程安排图有环 ,直接返回 False
if(flags[i]==1) return false;
// 说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 True。
if(flags[i]==-1) return true;
// 标记其被本轮 DFS 访问过
flags[i]=1;
// 递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 False
for(Integer j : adjacency.get(i)){
if(!dfs(adjacency,flags, j)) return false;
}
// 当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 −1并返回 True
flags[i]=-1;
return true;
}
}
208. 实现 Trie (前缀树)
假定每个节点都有26个孩子节点(因为都是小写字母),将字符串中的字符插入到对应的位置。
还有一个点要明确:节点的值仅仅表示从根节点到本节点的路径构成的字符串是否有效而已。
分两步:
- 首先看表示字符串的路径是否存在
- 其次看该路径的终点处的节点是否有效
class Trie {
// 前缀树的数据结构
class TreeNode{
boolean val;
TreeNode[] children = new TreeNode[26];
}
private TreeNode root;
public Trie() {
root = new TreeNode();
}
public void insert(String word) {
TreeNode p = root;
for(char c : word.toCharArray()){
int i = c - 'a';
// 初始化孩子节点
if(p.children[i]==null) p.children[i]=new TreeNode();
// 将p节点下移
p=p.children[i];
}
// 将字符串最后一个字符置为有效
p.val = true;
}
public boolean search(String word) {
TreeNode p = root;
for(char c : word.toCharArray()){
int i = c - 'a';
if(p.children[i]==null) return false;
p=p.children[i];
}
// 路径存在,直接返回该路径的终点处的节点的有效性
return p.val;
}
public boolean startsWith(String prefix) {
TreeNode p = root;
for(char c : prefix.toCharArray()){
int i = c - 'a';
if(p.children[i]==null) return false;
p=p.children[i];
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/