BFS相关算法题
完全平方数
给定正整数 n,找到若干个完全平方数(比如
1, 4, 9, 16, ...
)使得它们的和等于 n。让其组成和的完全平方数的个数最少。
>输入:n = 12 >输出:3 >解释:12 = 4 + 4 + 4 >输入:n = 13 >输出:2 >解释:13 = 4 + 9
public int numSquares(int n) {
List<Integer> squares = generateSquares(n);
Queue<Integer> queue = new LinkedList<>();
boolean[] marked = new boolean[n + 1]; // 其实感觉是为了剪枝,也可以set标记
queue.add(n);
marked[n] = true; //
int level = 0; //
while (!queue.isEmpty()) {
int size = queue.size();
level++;
while (size-- > 0) {
int cur = queue.poll();
for (int s : squares) {
int next = cur - s;
if (next < 0) {
break;
}
if (next == 0) {
return level;
}
if (marked[next]) {
continue; // 剪
}
marked[next] = true;
queue.add(next);
}
}
}
return n;
}
/**
* 生成小于 n 的平方数序列
* @return 1,4,9,...
*/
private List<Integer> generateSquares(int n) {
List<Integer> squares = new ArrayList<>();
int square = 1;
int diff = 3;
while (square <= n) {
squares.add(square);
square += diff;
diff += 2;
}
return squares;
}
2. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
>输入:root = [3,9,20,null,null,15,7] >输出:2 >输入:root = [2,null,3,null,4,null,5,null,6] >输出:5
class Solution {
public int minDepth(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
// 树不需要标记哦
queue.add(root);
int depth = 1; // 根根
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
if (node.left == null && node.right == null)
return depth;
if (node.left != null)
queue.add(node.left);
if (node.right != null)
queue.add(node.right);
}
depth++;
}
return depth;
}
}
3. 打开转盘锁
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,’0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
>输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202" >输出:6 >解释: >可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。 >注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的, >因为当拨动到 "0102" 时这个锁就会被锁定。
>输入: deadends = ["8888"], target = "0009" >输出:1 >解释: >把最后一位反向旋转一次即可 "0000" -> "0009"。
>输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888" >输出:-1 >解释: >无法旋转到目标数字且不被锁定。
class Solution {
public int openLock(String[] deadends, String target) {
// 这里将dead和marked放在一起
Set<String> dead = new HashSet<>();
for (String s : deadends)
dead.add(s);
// queue
Queue<String> queue = new LinkedList<>();
Set<String> marked = new HashSet<>();
queue.add("0000");
marked.add("0000");
int cnt = 0;
// luoji
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
String cur = queue.poll();
if (dead.contains(cur))
continue;
if (cur.equals(target))
return cnt;
for (int i = 0; i < 4; i++) {
String up = plusOne(cur, i);
if (!marked.contains(up)) {
queue.add(up);
marked.add(up);
}
String down = minusOne(cur, i);
if (!marked.contains(down)) {
queue.add(down);
marked.add(down);
}
}
}
cnt++;
}
return -1;
}
public String plusOne(String s, int j) {
char[] ch = s.toCharArray();
if (ch[j] == '9')
ch[j] = '0';
else
ch[j] += 1;
return new String(ch);
}
public String minusOne(String s, int j) {
char[] ch = s.toCharArray();
if (ch[j] == '0')
ch[j] = '9';
else
ch[j] -= 1;
return new String(ch);
}
}
4. 地图分析
你现在手里有一份大小为 N x N 的 网格 grid,上面的每个 单元格 都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的。
我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。
如果网格上只有陆地或者海洋,请返回 -1。
>输入:[[1,0,1],[0,0,0],[1,0,1]] >输出:2 >解释: >海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。 >即最中间那个方格。
>输入:[[1,0,0],[0,0,0],[0,0,0]] >输出:4 >解释: >海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最大,最大距离为 4。 >即最右下角的方格。
class Solution {
public int maxDistance(int[][] grid) {
int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
Queue<Pair<Integer, Integer>> q = new LinkedList<>();
int m = grid.length, n = grid[0].length;
// 先把所有的陆地都入队
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1)
q.add(new Pair<>(i, j));
}
}
// 判断是否都是陆地 或者没有陆地
if (q.size() == m * n || q.isEmpty())
return -1;
// 从各个陆地开始,一圈一圈的遍历海洋,最后遍历到的海洋就是离陆地最远的海洋。
int step = 0;
Pair<Integer, Integer> p = null;
while (!q.isEmpty()) {
int size = q.size();
while (size-- > 0) {
p = q.poll();
int x = p.getKey(), y = p.getValue();
// 取出队列的元素,将其四周的海洋入队。
for (int[] d : dir) {
int newX = x + d[0];
int newY = y + d[1];
if (newX < 0 || newX >= m || newY < 0 || newY >= n || grid[newX][newY] != 0) {
continue;
}
grid[newX][newY] = 1; // 标记
q.add(new Pair<>(newX, newY));
}
}
if (q.size() > 0)
step++;
}
return step;
}
}
public int maxDistance(int[][] grid) {
int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
Queue<Pair<Integer, Integer>> q = new LinkedList<>();
int m = grid.length, n = grid[0].length;
// 先把所有的陆地都入队
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1)
q.add(new Pair<>(i, j));
}
}
// 判断是否都是陆地 或者没有陆地
if (q.size() == m + n || q.isEmpty())
return -1;
// 从各个陆地开始,一圈一圈的遍历海洋,最后遍历到的海洋就是离陆地最远的海洋。
Pair<Integer, Integer> p = null;
while (!q.isEmpty()) {
p = q.poll();
int x = p.getKey(), y = p.getValue();
// 取出队列的元素,将其四周的海洋入队。
for (int[] d : dir) {
int newX = x + d[0];
int newY = y + d[1];
if (newX < 0 || newX >= m || newY < 0 || newY >= n || grid[newX][newY] != 0) {
continue;
}
grid[newX][newY] = grid[x][y] + 1; // 省略了标记, 要不然要加标记并且加个变量
q.add(new Pair<>(newX, newY));
}
}
return grid[p.getKey()][p.getValue()] - 1;
}
5. 腐烂的橘子
在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。
>输入:[[2,1,1],[1,1,0],[0,1,1]] >输出:4 >输入:[[2,1,1],[0,1,1],[1,0,1]] >输出:-1 >解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个正向上
class Solution {
public int orangesRotting(int[][] grid) {
// 俺就不判断了,直接上
int[][] dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
Queue<Pair<Integer, Integer>> q = new LinkedList<>();
int m = grid.length, n = grid[0].length;
int cnt = 0; // 表示新鲜的橘子
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1)
cnt++; // 新鲜橘子计数
else if (grid[i][j] == 2)
q.add(new Pair<>(i, j)); // 腐烂橘子的坐标
}
}
if (cnt == 0 || q.size() == m * n)
return 0;
int step = 0; // 轮数
while (cnt > 0 && !q.isEmpty()){
int size = q.size();
while (size-- > 0) {
Pair<Integer, Integer> p = q.poll();
int x = p.getKey(), y = p.getValue();
for (int[] d : dir) {
int newX = x + d[0];
int newY = y + d[1];
if (newX < 0 || newX >= m || newY < 0 || newY >= n) {
continue;
}
if (grid[newX][newY] == 1) {
grid[newX][newY] = 2;
q.add(new Pair<>(newX, newY));
cnt--;
}
}
}
step++;
}
return cnt > 0 ? -1 : step;
}
}
6. 被围绕的区域
给你一个
m x n
的矩阵board
,由若干字符'X'
和'O'
,找到所有被'X'
围绕的区域,并将这些区域里所有的'O'
用'X'
填充。
>输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]] >输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]] >解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
>输入:board = [["X"]] >输出:[["X"]]
import java.util.LinkedList;
import java.util.Queue;
class Solution {
public void solve(char[][] board) {
if (board == null || board.length == 0)
return;
int[][] dir = {{0,1},{0,-1},{1,0},{-1,0}};
int m = board.length, n = board[0].length;
Queue<Pair<Integer, Integer>> q = new LinkedList<>();
// 找到边缘的O
// 边缘两列
for (int i = 0; i < m; i++) {
if (board[i][0] == 'O') {
q.add(new Pair<>(i, 0));
board[i][0] = 'T';
}
if (board[i][n - 1] == 'O') {
q.add(new Pair<>(i, n - 1));
board[i][n - 1] = 'T';
}
}
// 上下两列
for (int i = 0; i < n; i++) {
if (board[0][i] == 'O') {
q.add(new Pair<>(0, i));
board[0][i] = 'T';
}
if (board[m - 1][i] == 'O') {
q.add(new Pair<>(m - 1, i));
board[m - 1][i] = 'T';
}
}
// bfs 搜索
while (!q.isEmpty()) {
int size = q.size();
while (size-- > 0) {
Pair<Integer, Integer> p = q.poll();
int x = p.getKey(), y = p.getValue();
for (int[] d : dir) {
int nx = x + d[0];
int ny = y + d[1];
if (nx < 0 || nx >= m || ny < 0 || ny >= n)
continue;
if (board[nx][ny] == 'O'){
q.add(new Pair<>(nx, ny));
board[nx][ny] = 'T';
}
}
}
}
// 标记
// 再走全部走一遍
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 遇见T标记O
if (board[i][j] == 'T') {
board[i][j] = 'O';
// 遇见O标记X
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
}
7. 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
>输入:coins = [1, 2, 5], amount = 11 >输出:3 >解释:11 = 5 + 5 + 1
>输入:coins = [2], amount = 3 >输出:-1
>输入:coins = [1], amount = 0 >输出:0
>输入:coins = [1], amount = 2 >输出:2
import java.util.*;
public class Solution {
public int coinChange(int[] coins, int amount) {
if (amount == 0)
return 0;
// bfs
Queue<Integer> q = new LinkedList<>();
Set<Integer> marked = new HashSet<>();
q.add(amount);
marked.add(amount);
Arrays.sort(coins);
int cnt = 0;
while (!q.isEmpty()) {
int size = q.size();
cnt++;
while (size-- > 0) {
int cur = q.poll();
for (int coin : coins) {
int next = cur - coin;
if (next < 0)
break;
if (next == 0)
return cnt;
if (marked.contains(next))
continue;
q.add(next);
marked.add(next);
}
}
}
return -1;
}
}
8. 岛屿数量
给你一个由
'1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
>输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] >] >输出:1
>输入:grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] >] >输出:3
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
int[][] dir = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
int m, n;
public int numIslands(char[][] grid) {
this.m = grid.length;
if (m == 0)
return 0;
this.n = grid[0].length;
boolean[][] marked = new boolean[m][n];
int cnt = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (!marked[i][j] && grid[i][j] == '1') {
cnt++;
// bfs
bfs(grid, marked, i, j);
}
}
}
return cnt;
}
private void bfs(char[][] grid, boolean[][] marked, int i, int j) {
Queue<Pair<Integer, Integer>> q = new LinkedList<>();
q.add(new Pair<>(i, j));
marked[i][j] = true;
while (!q.isEmpty()) {
int size = q.size();
while (size-- > 0) {
Pair<Integer, Integer> p = q.poll();
int x = p.getKey(), y = p.getValue();
for (int[] d : dir) {
int nx = x + d[0];
int ny = y + d[1];
if (nx < 0 || nx >= m || ny < 0 || ny >= n || marked[nx][ny])
continue;
if (grid[nx][ny] == '1') {
q.add(new Pair<>(nx, ny));
marked[nx][ny] = true;
}
}
}
}
}
}
9. 单词接龙
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。
>输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] >输出:5 >解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
>输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] >输出:0 >解释:endWord "cog" 不在字典中,所以无法进行转换。
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if (!wordList.contains(endWord))
return 0;
boolean[] marked = new boolean[wordList.size()]; // 可以set
//检验是否存在beginWord,如果存在,就置为访问过了,没必要访问
int idx = wordList.indexOf(beginWord);
if (idx != -1)
marked[idx] = true;
Queue<String> q = new LinkedList<>();
q.add(beginWord);
int cnt = 0;
while (!q.isEmpty()) {
int size = q.size();
cnt++;
while (size-- > 0) {
String start = q.poll();
for (int i = 0; i < wordList.size(); i++) {
// 访问过了
if (marked[i])
continue;
String s = wordList.get(i);
//不满足和当前只差一个字符不同,跳过,访问下一个
if (!isConnect(start, s))
continue;
//和endWord匹配上了,进行返回,因为是bfs,所以找到了直接返回就是最短的
if (s.equals(endWord))
return cnt+1;
q.add(s);
marked[i] = true;
}
}
}
return 0;
}
private boolean isConnect(String s1, String s2) {
int diffCnt = 0;
for (int i = 0; i < s1.length() && diffCnt <= 1; i++) {
if (s1.charAt(i) != s2.charAt(i)) {
diffCnt++;
}
}
return diffCnt == 1;
}
}
本博客所有文章除特别声明外,大部分为学习心得,欢迎与博主联系讨论