写在前面:该系列按照标签进行刷题,参考 leetcode标签
广度优先搜索 BFS
1091.二进制矩阵中的最短路径
在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1)。
一条从左上角到右下角、长度为 k 的畅通路径,由满足下述条件的单元格 C_1, C_2, ..., C_k 组成:
-
相邻单元格
C_i
和C_{i+1}
在八个方向之一上连通(此时,C_i
和C_{i+1}
不同且共享边或角) -
C_1
位于(0, 0)
(即,值为grid[0][0]
) -
C_k
位于(N-1, N-1)
(即,值为grid[N-1][N-1]
) -
如果
C_i
位于(r, c)
,则grid[r][c]
为空(即,grid[r][c] == 0
)
返回这条从左上角到右下角的最短畅通路径的长度。如果不存在这样的路径,返回 -1 。
看到最短路径,可以考虑 BFS,利用 grid 表达当前点的路径。
BFS:
class Solution {
private int[][] dist={{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
public int shortestPathBinaryMatrix(int[][] grid) {
// 10:03
// 从左上角到右下角,多种路径选择其中一条
int rows=grid.length;
int cols=grid[0].length;
if(grid[0][0]==1 || grid[rows-1][cols-1]==1) return -1;
Queue<int []> queue=new LinkedList<>();
int fir[]={0,0};//起点
grid[0][0]=1;//利用 grid 记录距离
queue.offer(fir);
// grid[rows-1][cols-1]==0 代表还没有走到终点
while(!queue.isEmpty() && grid[rows-1][cols-1]==0){
int temp[]=queue.poll();
int row=temp[0];
int col=temp[1];
for(int i=0;i<8;i++){
int newx=row+dist[i][0];
int newy=col+dist[i][1];
if(newx <0 || newx>=rows || newy <0|| newy >=cols) continue;
if(grid[newx][newy]==0){//判断新点是否已经到达
grid[newx][newy]=grid[row][col]+1;
queue.offer(new int[]{newx,newy});
}
}
}
// 若最后末尾点为0,证明没有路径可以到达,否则返回末尾点的值即路径
return grid[rows-1][cols-1]==0 ? -1:grid[rows-1][cols-1];
}
}
279.完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
//示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
//示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
BFS:相当于构建一个树。第一层是n,第二层是n减去每个完全平方数的结果,第三层是第二层减之后的结果,哪一层先出现0,层数即个数。
动态规划:状态转移方程:
dp[i]=min(dp[i],dp[i-j*j]+1)
,类似硬币问题。
1.BFS:
class Solution {
public int numSquares(int n) {
// 10:44
// 1 4 9 16 25
List<Integer> squares=square(n);
Queue<Integer> queue=new LinkedList<>();
boolean flag[]=new boolean[n+1];
queue.offer(n);
flag[n]=true;
int num=0;
// 对n减完全平方数,当结果为0时即所需个数
while(!queue.isEmpty()){
int size=queue.size();
num++;
while(size-- >0){
int cur=queue.poll();
for(int s:squares){
int res=cur-s;
if(res==0) return num;
if(res<0) break;
if(flag[res]) continue;//若结果值已访问则进行下一次遍历
flag[res]=true;
queue.offer(res);
}
}
}
return num;
}
//生成小于n的完全平方数列表
public List<Integer> square(int n){
List<Integer> res=new ArrayList<>();
int square=1;
int count=1;
while(square<=n){
res.add(square);
count++;
square=count*count;
}
return res;
}
}
2.动态规划:
class Solution {
public int numSquares(int n) {
// 10:44
// DP :状态转移方程:dp[i]=min(dp[i],dp[i-j*j]+1);
if(n<1) return 0;
int[] dp=new int[n+1];
for(int i=1;i<n+1;i++){
dp[i]=i;//最坏情况,全部由1组成
for(int j=1;i-j*j>=0;j++){
dp[i]=Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
}
127.单词接龙
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
-
每次转换只能改变一个字母。
-
转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回 0。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
//示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
//示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: 0
解释: endWord "cog" 不在字典中,所以无法进行转换。
BFS:重点是找到只差一个单词的字符串,然后每次遍历一个字符串,找寻和它相差一个的字符串,判断是否是目标字符串。
1.BFS:
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// 11:35
int L = beginWord.length();
// 存储通用字符和字典中与其对应的字符串列表:d*g->{dog,dig}
Map<String, List<String>> allComboDict = new HashMap<>();
wordList.forEach(
word -> {
for (int i = 0; i < L; i++) {
String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);
// 找到字典中通用字符对应的字符列表,若为空则创建一个新列表
List<String> transformations = allComboDict.getOrDefault(newWord, new ArrayList<>());
transformations.add(word);
allComboDict.put(newWord, transformations);
}
});
// pair 存储字符串与转换到该字符串所需的次数
Queue<Pair<String, Integer>> Q = new LinkedList<>();
Q.add(new Pair(beginWord, 1));
// 访问标志
Map<String, Boolean> visited = new HashMap<>();
visited.put(beginWord, true);
// BFS
while (!Q.isEmpty()) {
Pair<String, Integer> node = Q.remove();
String word = node.getKey();
int level = node.getValue();
for (int i = 0; i < L; i++) {
//当前字符串的通用字符形式
String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);
// 从map中获得字典中该通用字符对应的字符串列表
for (String adjacentWord : allComboDict.getOrDefault(newWord, new ArrayList<>())) {
//如果找到字符串对应目标字符串,则返回层数+1
if (adjacentWord.equals(endWord)) {
return level + 1;
}
// 如果不等则判断是否已访问,未访问则置为访问,同时加入队列
if (!visited.containsKey(adjacentWord)) {
visited.put(adjacentWord, true);
Q.add(new Pair(adjacentWord, level + 1));
}
}
}
}
return 0;
}
}