广度优先搜索模型
BFS() {
1.建立起始步骤,队列初始化
2.遍历队列中的每一种可能,whlie(队列不为空)
通过队头元素带出下一步的所有可能,并且依次入队
判断当前情况是否达成目标:按照目标要求处理逻辑
继续遍历队列中的剩余情况
}
出迷宫
假设有一个迷宫,里面有障碍物,迷宫用二维矩阵表示,标记为O的地方表示可以通过,标记为1的地方表示障碍物,不能通过。现在给一个迷宫出口,让你判断是否可以从入口进来之后,走出迷宫,每次可以向任意方向走。
假设是一个10*10的迷宫,入口在(1,1)的位置,出口在(8,10)的位置,通过(1,1)一步可以走到的位置有两个(1,2),(2,1)·但是这两个点并不是出口,需要继续通过这两个位置进一步搜索,假设现在在(1,2),下一次一步可以到达的新的位置为(1,3),(2.2)。而通过(2,1)可以一步到达的新的位置为(2,2),(3,1),但是这里(2,2)是重复的,所以每一个点在走的过程中需要标记是否已经走过了。
两步之后,还没没有走到出口,这时候需要通过新加入的点再去探索下一步能走到哪些新的点上,重复这个过程,直到走到出口为止。
- 代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description: If you don't work hard, you will a loser.
* User: Listen-Y.
* Date: 2020-09-22
* Time: 21:34
*/
public class SolutionBreadth {
static class Node {
public int x;
public int y;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
}
/**
* 迷宫问题
*/
public static void main(String[] args) {
int sr, sc, endR, endC;
Scanner scanner = new Scanner(System.in);
System.out.print("输入迷宫起点与终点:");
sr = scanner.nextByte();
sc = scanner.nextByte();
endR = scanner.nextByte();
endC = scanner.nextByte();
int[][] gird = {
{
0,1,0,0},
{
0,0,0,1},
{
0,1,0,0},
{
0,0,1,0}};
System.out.println(DFS(gird, sr, sc, endR, endC));
}
private static int[][] next = {
{
0, 1}, {
0, -1}, {
1, 0}, {
-1, 0}};
private static boolean DFS(int[][] gird, int sr, int sc, int endR, int endC) {
int row = gird.length;
if (row == 0) {
return false;
}
int col = gird[0].length;
//需要一个used保存访问过得点
boolean[][] used = new boolean[row][col];
//需要一个队列保存需要遍历的点
Queue<Node> queue = new LinkedList<>();
queue.offer(new Node(sr, sc));
//标记起点已被使用
used[sr][sc] = true;
//只要队列不为空就说明还有机会到达终点
while (!queue.isEmpty()) {
//查看起点的四周, 看哪个方向可以走
for (int i = 0; i < 4; i++) {
int newX = queue.peek().x + next[i][0];
int newY = queue.peek().y + next[i][1];
//判断边界
if (newX < 0 || newX >= row || newY < 0 || newY >= col) {
continue;
}
//如果此时位置无障碍, 并且未被访问就入队列
if (gird[newX][newY] == 0 && !used[newX][newY]) {
queue.offer(new Node(newX, newY));
//并标记这点被访问过
used[newX][newY] = true;
}
//如果此时已经是终点就结束方法
if (newX == endR && newY == endC) {
return true;
}
}
//否则就出队列判断下一个点
queue.poll();
}
return false;
}
}
员工的重要性
- 题目描述
给定一个保存员工信息的数据结构,它包含了员工唯一的id,重要度 和 直系下属的id。
比如,员工1是员工2的领导,员工2是员工3的领导。他们相应的重要度为15, 10, 5。那么员工1的数据结构是[1, 15, [2]],员工2的数据结构是[2, 10, [3]],员工3的数据结构是[3, 5, []]。注意虽然员工3也是员工1的一个下属,但是由于并不是直系下属,因此没有体现在员工1的数据结构中。
现在输入一个公司的所有员工信息,以及单个员工id,返回这个员工和他所有下属的重要度之和。
示例 1:
输入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
输出: 11
解释:
员工1自身的重要度是5,他有两个直系下属2和3,而且2和3的重要度均为3。因此员工1的总重要度是 5 + 3 + 3 = 11。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/employee-importance
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
/*
// Definition for Employee.
class Employee {
public int id;
public int importance;
public List<Integer> subordinates;
};
*/
class Solution {
public int getImportance(List<Employee> employees, int id) {
//将所有员工保存在map中
Map<Integer, Employee> map = new HashMap<>();
for (Employee em : employees) {
map.put(em.id, em);
}
//需要返回的重要性
int importance = 0;
//创建一个队列保存下属员工
Queue<Employee> queue = new LinkedList<>();
queue.offer(map.get(id));
while (!queue.isEmpty()) {
//获得当前有队列里有几个下属员工
int size = queue.size();
while (size-- > 0) {
//获取下属
Employee employ = queue.poll();
importance += employ.importance;
//判断当前员工还有没有下属 如果有就加到队列中
if (employ.subordinates != null) {
for (int curId : employ.subordinates) {
queue.offer(map.get(curId));
}
}
}
}
return importance;
}
}
N叉树的遍历
- 题目描述
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树 :
返回其层序遍历:
[
[1],
[3,2,4],
[5,6]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public List<List<Integer>> levelOrder(Node root) {
//创建一个队列保存每一层的节点数据
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
List<List<Integer>> ret = new ArrayList<>();
if (root == null) return ret;
while (!queue.isEmpty()) {
//获取当前层的结点个数
int size = queue.size();
List<Integer> list = new ArrayList<>();
while (size-- > 0) {
//将该层所有结点的数据保存在链表中
Node cur = queue.poll();
list.add(cur.val);
//如果当前结点下一层还有结点就入队列
if (cur.children != null) {
for (Node node : cur.children) {
queue.offer(node);
}
}
}
ret.add(list);
}
return ret;
}
}
腐烂的橘子
- 题目描述
在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotting-oranges
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
class Solution {
static class Node {
public int x;
public int y;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
}
private int[][] next = {
{
1, 0}, {
-1, 0}, {
0, 1}, {
0, -1}};
public int orangesRotting(int[][] grid) {
int row = grid.length;
if (row == 0) return 0;
int col = grid[0].length;
//需要一个队列保存坏的橘子
Queue<Node> queue = new LinkedList<>();
//遍历找到坏的橘子
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] == 2) {
queue.offer(new Node(i, j));
}
}
}
int time = 0;
//如果此时有坏的橘子就进行处理感染
while (!queue.isEmpty()) {
//用一个数据判断是否进行了感染
boolean ok = false;
int size = queue.size();
//进行一分钟的感染
while (size-- > 0) {
Node cur = queue.poll();
//遍历该橘子的四周
for (int i = 0; i < 4; i++) {
int newR = cur.x + next[i][0];
int newC = cur.y + next[i][1];
//判断边界
if (newR < 0 || newR >= row || newC < 0 || newC >= col) {
continue;
}
//判断如果当前是新鲜橘子就进行感染
if (grid[newR][newC] == 1) {
grid[newR][newC] = 2;
ok = true;
//并将感染后的橘子放到队列中
queue.offer(new Node(newR, newC));
}
}
}
//如果有被感染
if (ok) {
time++;
}
}
//检查如果还有没被感染的橘子就返回-1
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] == 1) {
return -1;
}
}
}
return time;
}
}
单词接龙
- 题目描述
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
//将所有单词放到hash表中 便于查询
Set<String> dict = new HashSet<>();
for (String word : wordList) {
dict.add(word);
}
//判断此时字典是否有这个endWord
if (!dict.contains(endWord)) {
return 0;
}
//使用一个set去保存访问过的单词
Set<String> used = new HashSet<>();
used.add(beginWord);
//使用一个队列去保存替换一次的单词
Queue<String> queue = new LinkedList<>();
queue.offer(beginWord);
//计数器
int step = 1;
while (!queue.isEmpty()) {
//取出一次变化后的说有单词
int size = queue.size();
while (size-- > 0) {
String curWord = queue.poll();
//对这个单词进行每一个位置的每一个字符的替换
for (int i = 0; i < curWord.length(); i++) {
StringBuilder builder = new StringBuilder(curWord);
for (char ch = 'a'; ch <= 'z'; ch++) {
builder.setCharAt(i, ch);
String newWord = builder.toString();
//如果此时就是endWord就结束
if (newWord.equals(endWord)) {
return step + 1;
}
//判断这个新单词是否在字典中 没有被访问过
if (dict.contains(newWord) && !used.contains(newWord)) {
//否则入队列
queue.offer(newWord);
used.add(newWord);
}
}
}
}
//完成所有size表示进行了一次改变
step++;
}
//如果到这还没有返回说明不能扎到
return 0;
}
}
打开转盘锁
- 题目描述
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/open-the-lock
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
class Solution {
public int openLock(String[] deadends, String target) {
//用一个hash去保存dead便于查找
Set<String> deadSet = new HashSet<>();
for (String str : deadends) {
deadSet.add(str);
}
//如果0000是死亡数字那么永远有达不到
if (deadSet.contains("0000")) {
return -1;
}
//如果当前就是0000 就返回0
if (target.equals("0000")) {
return 0;
}
//用一个队列去保存此时的转盘上的数字
Queue<String> queue = new LinkedList<>();
queue.offer("0000");
//计数器
int step = 0;
//用一个set保存访问过得密码
Set<String> used = new HashSet<>();
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
String curStr = queue.poll();
//进行一次的拨盘
for (int i = 0; i < 4; i++) {
//一次波动有俩种可能
char newOne;
char newTwo;
//对curStr当前的字符是0/9要进行特殊处理
if (curStr.charAt(i) == '0' || curStr.charAt(i) == '9') {
if (curStr.charAt(i) == '0') {
newOne = '1';
newTwo = '9';
} else {
newOne = '0';
newTwo = '8';
}
} else {
newOne = (char) (curStr.charAt(i) + 1);
newTwo = (char) (curStr.charAt(i) - 1);
}
//将拨好的单个数字进行与其他三个字符想组合
StringBuilder oneBuilder = new StringBuilder(curStr);
StringBuilder twoBuilder = new StringBuilder(curStr);
oneBuilder.setCharAt(i, newOne);
twoBuilder.setCharAt(i, newTwo);
String oneStr = oneBuilder.toString();
String twoStr = twoBuilder.toString();
//对此时这俩个字符串进行判断
// 如果已经是target就返回操作步数
if (oneStr.equals(target) || twoStr.equals(target)) {
return step + 1;
}
//分别对俩个字符串进行判断是否为锁死密码和是否已经遍历过
if (!deadSet.contains(oneStr) && !used.contains(oneStr)) {
queue.offer(oneStr);
used.add(oneStr);
}
if (!deadSet.contains(twoStr) && !used.contains(twoStr)) {
queue.offer(twoStr);
used.add(twoStr);
}
}
}
step++;
}
return -1;
}
}