深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。
本篇和树与图篇有重合。实在不好切分。
最短用bfs,可达用dfs
BFS
模板:
void BFS()
{
定义队列;
定义备忘录,用于记录已经访问的位置;
判断边界条件,是否能直接返回结果的。
将起始位置加入到队列中,同时更新备忘录。
while (队列不为空) {
获取当前队列中的元素个数。
for (元素个数) {
取出一个位置节点。
判断是否到达终点位置。
获取它对应的下一个所有的节点。
条件判断,过滤掉不符合条件的位置。
新位置重新加入队列。
}
}
}
作者:hank-36
链接:https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/solution/biao-zhun-de-bfsjie-fa-duo-lian-xi-jiu-hui-zhang-w/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

广度优先搜索一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
第一层:
- 0 -> {6,2,1,5}
第二层:
- 6 -> {4}
- 2 -> {}
- 1 -> {}
- 5 -> {3}
第三层:
- 4 -> {}
- 3 -> {}
每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di <= dj。利用这个结论,可以求解最短路径等 最优解 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径,无权图是指从一个节点到另一个节点的代价都记为 1。
在程序实现 BFS 时需要考虑以下问题:
- 队列:用来存储每一轮遍历得到的节点;
- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
1. 计算在网格中从原点到特定点的最短路径长度
1091. Shortest Path in Binary Matrix(Medium)
[[1,1,0,1],
[1,0,1,0],
[1,1,1,1],
[1,0,1,1]]
题目描述:0 表示可以经过某个位置,求解从左上角到右下角的最短路径长度。
public class shortestPathBinaryMatrix {
private static int[][] directions = {{0,1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {-1, -1}, {-1, 0}, {-1, 1}};
private int row, col;
public int shortestPathBinaryMatrix(int[][] grid) {
row = grid.length;
col = grid[0].length;
if(grid[0][0] == 1 || grid[row - 1][col - 1] == 1) return -1;
Queue<int[]> pos = new LinkedList<>();
grid[0][0] = 1; // 直接用grid[i][j]记录从起点到这个点的最短路径长。按照题意 起点也有长度1
pos.add(new int[]{0,0});
while(!pos.isEmpty() && grid[row - 1][col - 1] == 0){ // 求最短路径 使用BFS
int[] xy = pos.remove();
int preLength = grid[xy[0]][xy[1]]; // 当前点的路径长度
for(int i = 0; i < 8; i++){
int newX = xy[0] + directions[i][0];
int newY = xy[1] + directions[i][1];
if(inGrid(newX, newY) && grid[newX][newY] == 0){
pos.add(new int[]{newX, newY});
grid[newX][newY] = preLength + 1; // 下一个点的路径长度要+1
}
}
}
return grid[row - 1][col - 1] == 0 ? -1 : grid[row - 1][col - 1]; // 如果最后终点的值还是0,说明没有到达
}
private boolean inGrid(int x, int y){
return x >= 0 && x < row && y >= 0 && y < col;
}
}
2. 组成整数的最小平方数数量
279. Perfect Squares (Medium)
For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.
- 可以将每个整数看成图中的一个节点,如果两个整数之差为一个平方数,那么这两个整数所在的节点就有一条边。要求解最小的平方数数量,就是求解从节点 n 到节点 0 的最短路径。
本题也可以用动态规划和贪心求解,在之后动态规划部分中会再次出现。
BFS
- bfs中每一层记录的是起始到当前点的这一段运算结果,而不光是当前层上的结果。比如上一题,计算每层都需要更新起始到当前点的路径和。
- 每个中间状态都会存在队列中 。
public int numSquares(int n) {
List<Integer> squares = generateSquares(n);
Queue<Integer> queue = new LinkedList<>();
boolean[] marked = new boolean[n + 1];
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 level;
}
/**
* 生成小于 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;
}
3. 最短单词路径
127. Word Ladder (Medium)
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
题目描述:找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。
- 搜索问题一般分两种,一种是有路径,一种是需要自己构建路径的,这题需要自己构建路径。
- 遇到最短路径,我建议你用bfs
- 关键点:
- 构建图
- memo
- 遇到终点则返回
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
wordList.add(beginWord);
int N = wordList.size();
int start = N - 1;
int end = 0;
while (end < N && !wordList.get(end).equals(endWord)) {
end++;
}
if (end == N) {
return 0;
}
List<Integer>[] graphic = buildGraphic(wordList);
return getShortestPath(graphic, start, end);
}
private List<Integer>[] buildGraphic(List<String> wordList) {
int N = wordList.size();
//关键在于数据结构的选取。
List<Integer>[] graphic = new List[N];
for (int i = 0; i < N; i++) {
graphic[i] = new ArrayList<>();
for (int j = 0; j < N; j++) {
if (isConnect(wordList.get(i), wordList.get(j))) {
graphic[i].add(j);
}
}
}
return graphic;
}
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;
}
private int getShortestPath(List<Integer>[] graphic, int start, int end) {
Queue<Integer> queue = new LinkedList<>();
boolean[] marked = new boolean[graphic.length];
queue.add(start);
//记忆
marked[start] = true;
int path = 1;
while (!queue.isEmpty()) {
int size = queue.size();
path++;
while (size-- > 0) {
int cur = queue.poll();
for (int next : graphic[cur]) {
if (next == end) {
return path;
}
if (marked[next]) {
continue;
}
marked[next] = true;
queue.add(next);
}
}
}
return 0;
}
DFS

广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。
而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。
在程序实现 DFS 时需要考虑以下问题:
- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
1. 查找最大的连通面积
695. Max Area of Island (Medium)
- 走过了就清0,不用memo
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
private int m, n;
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public int maxAreaOfIsland(int[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
m = grid.length;
n = grid[0].length;
int maxArea = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
maxArea = Math.max(maxArea, dfs(grid, i, j));
}
}
return maxArea;
}
private int dfs(int[][] grid, int r, int c) {
if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) {
return 0;
}
grid[r][c] = 0;
int area = 1;
for (int[] d : direction) {
area += dfs(grid, r + d[0], c + d[1]);
}
return area;
}
2. 矩阵中的连通分量数目(岛屿数量)
200. Number of Islands (Medium)
Input:
11000
11000
00100
00011
Output: 3
法1:先遍历矩阵,然后dfs一个点的所有引申点,全置为0
- 感染算法, 可以是dfs也可以是bfs
比较精妙的一点是在判断结束条件时在本层判断是否越界,不在上一层判断,简单了很多
if(i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0')
class Solution {
public int numIslands(char[][] grid) {
if(grid.length == 0) return 0;
int res = 0;
int col = grid[0].length;
int row = grid.length;
int[][] dir = new int[][]{{-1,0},{1,0},{0,-1},{0,1}};
boolean[][] used = new boolean[row][col];
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if(grid[i][j]=='1'){
dfs(grid,i,j);
res++;
}
}
}
return res;
}
private void dfs(char[][] grid, int i, int j) {
if(i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0'){
return;
}
grid[i][j] = '0';
dfs(grid,i-1,j);
dfs(grid,i+1,j);
dfs(grid,i,j-1);
dfs(grid,i,j+1);
}
}
可以将矩阵表示看成一张有向图。
private int m, n;
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
m = grid.length;
n = grid[0].length;
int islandsNum = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] != '0') {
dfs(grid, i, j);
islandsNum++;
}
}
}
return islandsNum;
}
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') {
return;
}
grid[i][j] = '0';
for (int[] d : direction) {
dfs(grid, i + d[0], j + d[1]);
}
}
bfs
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(grid[i][j] == '1'){
bfs(grid, i, j);
count++;
}
}
}
return count;
}
private void bfs(char[][] grid, int i, int j){
Queue<int[]> list = new LinkedList<>();
list.add(new int[] { i, j });
while(!list.isEmpty()){
int[] cur = list.remove();
i = cur[0]; j = cur[1];
if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') {
grid[i][j] = '0';
list.add(new int[] { i + 1, j });
list.add(new int[] { i - 1, j });
list.add(new int[] { i, j + 1 });
list.add(new int[] { i, j - 1 });
}
}
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/number-of-islands/solution/number-of-islands-shen-du-you-xian-bian-li-dfs-or-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2.1 岛屿周长
这个只是和dfs很像,实际上没有搜索的过程,只有遍历,然后找右和下的邻居,有邻居减2
class Solution {
public int islandPerimeter(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int res = 0;
int[] dx = new int[]{0, 1};
int[] dy = new int[]{1, 0};
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) continue;
res+=4;
for (int k = 0; k < 2; k++) {
int nx = dx[k] + i;
int ny = dy[k] + j;
if (nx < m && ny < n && grid[nx][ny] == 1) res-=2;
}
}
}
return res;
}
}
3. 好友关系的连通分量数目
547. Friend Circles (Medium)
Input:
[[1,1,0],
[1,1,0],
[0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
The 2nd student himself is in a friend circle. So return 2.
题目描述:好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。
private int n;
public int findCircleNum(int[][] M) {
n = M.length;
int circleNum = 0;
boolean[] hasVisited = new boolean[n];
for (int i = 0; i < n; i++) {
if (!hasVisited[i]) {
dfs(M, i, hasVisited);
circleNum++;
}
}
return circleNum;
}
private void dfs(int[][] M, int i, boolean[] hasVisited) {
hasVisited[i] = true;
for (int k = 0; k < n; k++) {
if (M[i][k] == 1 && !hasVisited[k]) {
dfs(M, k, hasVisited);
}
}
}
4. 填充封闭区域
130. Surrounded Regions (Medium)
For example,
X X X X
X O O X
X X O X
X O X X
After running your function, the board should be:
X X X X
X X X X
X X X X
X O X X
题目描述:使被 ‘X’ 包围的 ‘O’ 转换为 ‘X’。
先填充最外侧,剩下的就是里侧了。
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
private int m, n;
public void solve(char[][] board) {
if (board == null || board.length == 0) {
return;
}
m = board.length;
n = board[0].length;
for (int i = 0; i < m; i++) {
dfs(board, i, 0);
dfs(board, i, n - 1);
}
for (int i = 0; i < n; i++) {
dfs(board, 0, i);
dfs(board, m - 1, i);
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'T') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
private void dfs(char[][] board, int r, int c) {
if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') {
return;
}
board[r][c] = 'T';
for (int[] d : direction) {
dfs(board, r + d[0], c + d[1]);
}
}
5. 能到达的太平洋和大西洋的区域
417. Pacific Atlantic Water Flow (Medium)
Given the following 5x5 matrix:
Pacific ~ ~ ~ ~ ~
~ 1 2 2 3 (5) *
~ 3 2 3 (4) (4) *
~ 2 4 (5) 3 1 *
~ (6) (7) 1 4 5 *
~ (5) 1 1 2 4 *
* * * * * Atlantic
Return:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix).
左边和上边是太平洋,右边和下边是大西洋,内部的数字代表海拔,海拔高的地方的水能够流到低的地方,求解水能够流到太平洋和大西洋的所有位置。
private int m, n;
private int[][] matrix;
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public List<List<Integer>> pacificAtlantic(int[][] matrix) {
List<List<Integer>> ret = new ArrayList<>();
if (matrix == null || matrix.length == 0) {
return ret;
}
m = matrix.length;
n = matrix[0].length;
this.matrix = matrix;
boolean[][] canReachP = new boolean[m][n];
boolean[][] canReachA = new boolean[m][n];
for (int i = 0; i < m; i++) {
dfs(i, 0, canReachP);
dfs(i, n - 1, canReachA);
}
for (int i = 0; i < n; i++) {
dfs(0, i, canReachP);
dfs(m - 1, i, canReachA);
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (canReachP[i][j] && canReachA[i][j]) {
ret.add(Arrays.asList(i, j));
}
}
}
return ret;
}
private void dfs(int r, int c, boolean[][] canReach) {
if (canReach[r][c]) {
return;
}
canReach[r][c] = true;
for (int[] d : direction) {
int nextR = d[0] + r;
int nextC = d[1] + c;
if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n
|| matrix[r][c] > matrix[nextR][nextC]) {
continue;
}
dfs(nextR, nextC, canReach);
}
}
6.被围绕的区域
//非常直观的dfs,一直搜索即可。
class Solution {
int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public void solve(char[][] board) {
if (board == null || board.length == 0 || board[0] == null || board[0].length == 0) return;
int row = board.length;
int col = board[0].length;
for (int j = 0; j < col; j++) {
// 第一行
if (board[0][j] == 'O') dfs(0, j, board, row, col);
// 最后一行
if (board[row - 1][j] == 'O') dfs(row - 1, j, board, row, col);
}
for (int i = 0; i < row; i++) {
// 第一列
if (board[i][0] == 'O') dfs(i, 0, board, row, col);
// 最后一列
if (board[i][col - 1] == 'O') dfs(i, col - 1, board, row, col);
}
// 转变
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (board[i][j] == 'O') board[i][j] = 'X';
if (board[i][j] == 'B') board[i][j] = 'O';
}
}
}
private void dfs(int i, int j, char[][] board, int row, int col) {
board[i][j] = 'B';
for (int[] dir : dirs) {
int tmp_i = dir[0] + i;
int tmp_j = dir[1] + j;
if (tmp_i < 0 || tmp_i >= row || tmp_j < 0 || tmp_j >= col || board[tmp_i][tmp_j] != 'O') continue;
dfs(tmp_i, tmp_j, board, row, col);
}
}
}
单词接龙
class Solution {
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
List<List<String>> res = new ArrayList<>();
Deque<Node> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
List<Node> endlist = new LinkedList<>();
queue.add(new Node(beginWord,null));
while(!queue.isEmpty()){
int size = queue.size();
for (int k = 0; k < size; k++) {
Node cur = queue.poll();
String w = cur.word;
visited.add(w);
if(w.equals(endWord)){
endlist.add(cur);
continue;
}
//在字典中寻找并放入路径中
for (int i = 0; i < w.length(); i++) {
StringBuilder next = new StringBuilder(w);
for (int j = 0; j < 26; j++) {
char c = (char)('a' + j);
next.setCharAt(i,c);
String temp = next.toString();
if(wordList.contains(temp) && !visited.contains(temp)){
queue.add(new Node(temp,cur));
}
}
next.setCharAt(i,w.charAt(i)); //改回来
}
}
if(endlist.size() > 0){
break; //有数据了再添加就不是最短的了
}
}
for (Node end : endlist){
res.add(getNode(end));
}
return res;
}
private List<String> getNode(Node n) {
Deque<String> res = new LinkedList<>();
while(n != null){
res.addFirst(n.word);
n = n.pre;
}
return new ArrayList<>(res);
}
class Node{
String word;
Node pre;
Node(String word,Node pre){
this.pre = pre;
this.word = word;
}
}
}
矩阵中的最长递增路径
class Solution {
public int longestIncreasingPath(int[][] matrix) {
if(matrix == null || matrix.length == 0) return 0;
int m = matrix.length;
int n = matrix[0].length;
int[][] memo = new int[m][n];
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
res = Math.max(res,dfs(matrix, i, j, memo, new boolean[m][n]));
}
}
return res;
}
private int dfs(int[][] matrix, int i, int j, int[][] memo, boolean[][] visited) {
if (memo[i][j] != 0){
return memo[i][j];
}
int m = matrix.length;
int n = matrix[0].length;
int[] dx = new int[]{0,0,1,-1};
int[] dy = new int[]{-1,1,0,0};
visited[i][j] = true;
int t = 0;
for (int k = 0; k < 4; k++) {
int ni = dx[k] + i;
int nj = dy[k] + j;
if(ni >= 0 && ni < m && nj >= 0 && nj < n && !visited[ni][nj] && matrix[ni][nj] > matrix[i][j]){
t = Math.max(t, dfs(matrix, ni, nj, memo,visited));
}
}
memo[i][j] = t+1;
visited[i][j] = false;
return t + 1;
}
}
字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
理解dfs或者栈的一个非常好的题
- 使用栈求解
class Solution {
public String decodeString(String s) {
StringBuilder res = new StringBuilder();
int multi = 0;
LinkedList<Integer> stack_multi = new LinkedList<>();
LinkedList<String> stack_res = new LinkedList<>();
for(Character c : s.toCharArray()) {
if(c == '[') {
stack_multi.addLast(multi);
stack_res.addLast(res.toString());
multi = 0;
res = new StringBuilder();
}
else if(c == ']') {
StringBuilder tmp = new StringBuilder();
int cur_multi = stack_multi.removeLast();
for(int i = 0; i < cur_multi; i++) tmp.append(res);
res = new StringBuilder(stack_res.removeLast() + tmp);
}
else if(c >= '0' && c <= '9') multi = multi * 10 + Integer.parseInt(c + "");
else res.append(c);
}
return res.toString();
}
}
-
使用结构栈(关键在于当前节点有前一个的指针)
https://www.bilibili.com/video/BV1uf4y127kx
- 使用dfs递归
class Solution {
public String decodeString(String s) {
return dfs(s, 0)[0];
}
private String[] dfs(String s, int i) {
StringBuilder res = new StringBuilder();
int multi = 0;
while(i < s.length()) {
if(s.charAt(i) >= '0' && s.charAt(i) <= '9')
multi = multi * 10 + Integer.parseInt(String.valueOf(s.charAt(i)));
else if(s.charAt(i) == '[') {
String[] tmp = dfs(s, i + 1);
i = Integer.parseInt(tmp[0]);
while(multi > 0) {
res.append(tmp[1]);
multi--;
}
}
else if(s.charAt(i) == ']')
return new String[] { String.valueOf(i), res.toString() };
else
res.append(String.valueOf(s.charAt(i)));
i++;
}
return new String[] { res.toString() };
}
}
我一开始的思路:自顶而下
class Solution {
public String decodeString(String s) {
StringBuilder ans = new StringBuilder();
for (int i = 0; i < s.length();) {
if (!Character.isDigit(s.charAt(i))) {
ans.append(s.charAt(i));
i++;
} else {
int k = 0;
while (Character.isDigit(s.charAt(i))) {
k = k * 10 + s.charAt(i) - '0';
i++;
}
//跳过左括号
int j = i + 1;
//剩余括号数量
int sum = 1;
while (sum > 0) {
if(s.charAt(j)=='[') sum++;
if(s.charAt(j)==']') sum--;
j++;
}
//此时j是最后一个括号的下一位
//递归处理括号内的
String r = decodeString(s.substring(i + 1, j - 1));
for (int i1 = 0; i1 < k; i1++) {
ans.append(r);
}
i = j;
}
}
return ans.toString();
}
}
- 我的错误解法
本想着遇到数字就找出数字后面的括号中的内容,直接乘
package leetcode;
import java.util.Arrays;
public class n394 {
public String decodeString(String s) {
return helper(s, 1);
}
private String helper(String sb, int i) {
String res = "";
if(i < 1 ) return "";
int l = 0;
int r = 0;
for (int j = 0; j < sb.length(); j++) {
if(Character.isDigit(sb.charAt(i))){
l = i + 1;
}else if (sb.charAt(i) == ']'){
r = j;
break;
}
}
//如果没有括号,不执行这个
if(r == 0){
res = sb;
}{
int t = sb.charAt(l - 1);
res = sb.substring(0, l) + helper(sb.substring(l + 1, r), t)
+ sb.substring(r + 1);
}
for (int j = 0; j < i; j++) {
res += res;
}
return res;
}
}
克隆图
给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值
val
(int
) 和其邻居的列表(list[Node]
)。
- 使用map记录已将查找过的节点
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> neighbors;
public Node() {
val = 0;
neighbors = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
neighbors = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
}
*/
class Solution {
Map<Node, Node> map = new HashMap<>();
public Node cloneGraph(Node node) {
if(node == null) return node;
if (map.containsKey(node)){
return map.get(node);
}
Node newNode = new Node(node.val);
map.put(node,newNode);
for(Node nbr : node.neighbors){
newNode.neighbors.add(cloneGraph(nbr));
}
return newNode;
}
}
括号生成
模板呐
class Solution {
public List<String> generateParenthesis(int n) {
List<String> res = new LinkedList<>();
dfs(res,new StringBuilder(),0, 2 * n);
return res;
}
private void dfs(List<String> res, StringBuilder temp, int ind, int n) {
if(ind == n){
if(isvalud(temp)){
res.add(temp.toString());
}
return;
}
char[] c = new char[]{'(', ')'};
for (int i = 0; i < 2; i++) {
temp.append(c[i]);
dfs(res, temp, ind+1, n);
temp.setLength(temp.length() - 1);
}
}
private boolean isvalud(StringBuilder str) {
int balance = 0;
for (int i = 0; i < str.length(); i++) {
char t = str.charAt(i);
if (t == '(') balance++;
else balance--;
if (balance < 0) return false;
}
return (balance == 0);
}
}
回溯问题
模板:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
作者:labuladong
链接:https://leetcode-cn.com/problems/n-queens/solution/hui-su-suan-fa-xiang-jie-by-labuladong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Backtracking(回溯)属于 DFS。
- 普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
- 而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
还要注意:
- 某一个路径走到终点后有的可能会用set对结果去重
- 可能会在半路剪枝
1. 数字键盘组合
17. Letter Combinations of a Phone Number (Medium)

Input:Digit string "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
List<String> combinations = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return combinations;
}
doCombination(new StringBuilder(), combinations, digits);
return combinations;
}
private void doCombination(StringBuilder prefix, List<String> combinations, final String digits) {
if (prefix.length() == digits.length()) {
combinations.add(prefix.toString());
return;
}
int curDigits = digits.charAt(prefix.length()) - '0';
String letters = KEYS[curDigits];
for (char c : letters.toCharArray()) {
prefix.append(c); // 添加
doCombination(prefix, combinations, digits);
prefix.deleteCharAt(prefix.length() - 1); // 删除
}
}
2. IP 地址划分
93. Restore IP Addresses(Medium)
- 典型回溯,如果类别路径搜索问题的话,本题路径长度固定为4,路径问题是搜索方向,这个是搜索长度为0, 1, 2的子串
- 很多细节,比如怎么加点,有效性校验
Given "25525511135",
return ["255.255.11.135", "255.255.111.35"].
public List<String> restoreIpAddresses(String s) {
List<String> addresses = new ArrayList<>();
StringBuilder tempAddress = new StringBuilder();
doRestore(0, tempAddress, addresses, s);
return addresses;
}
private void doRestore(int k, StringBuilder tempAddress, List<String> addresses, String s) {
if (k == 4 || s.length() == 0) {
if (k == 4 && s.length() == 0) {
addresses.add(tempAddress.toString());
}
return;
}
for (int i = 0; i < s.length() && i <= 2; i++) {
if (i != 0 && s.charAt(0) == '0') {
break;
}
String part = s.substring(0, i + 1);
if (Integer.valueOf(part) <= 255) {
if (tempAddress.length() != 0) {
part = "." + part;
}
tempAddress.append(part);
doRestore(k + 1, tempAddress, addresses, s.substring(i + 1));
tempAddress.delete(tempAddress.length() - part.length(), tempAddress.leng
th());
}
}
}
非常非常优美的四层循环算法
- leading zero问题的很常见的解法:
public List<String> restoreIpAddresses(String s) {
List<String> ret = new ArrayList<>();
StringBuilder ip = new StringBuilder();
for(int a = 1 ; a < 4 ; ++ a)
for(int b = 1 ; b < 4 ; ++ b)
for(int c = 1 ; c < 4 ; ++ c)
for(int d = 1 ; d < 4 ; ++ d)
{
if(a + b + c + d == s.length() )
{
int n1 = Integer.parseInt(s.substring(0, a));
int n2 = Integer.parseInt(s.substring(a, a+b));
int n3 = Integer.parseInt(s.substring(a+b, a+b+c));
int n4 = Integer.parseInt(s.substring(a+b+c));
if(n1 <= 255 && n2 <= 255 && n3 <= 255 && n4 <= 255)
{
ip.append(n1).append('.').append(n2)
.append('.').append(n3).append('.').append(n4);
if(ip.length() == s.length() + 3) ret.add(ip.toString());
ip.delete(0, ip.length());
}
}
}
return ret;
}
作者:reals
链接:https://leetcode-cn.com/problems/restore-ip-addresses/solution/ke-neng-shi-zui-you-mei-de-bao-li-jie-fa-liao-by-u/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3. 在矩阵中寻找字符串
79. Word Search (Medium)
For example,
Given board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
word = "ABCCED", -> returns true,
word = "SEE", -> returns true,
word = "ABCB", -> returns false.
private final static int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
private int m;
private int n;
public boolean exist(char[][] board, String word) {
if (word == null || word.length() == 0) {
return true;
}
if (board == null || board.length == 0 || board[0].length == 0) {
return false;
}
m = board.length;
n = board[0].length;
boolean[][] hasVisited = new boolean[m][n];
for (int r = 0; r < m; r++) {
for (int c = 0; c < n; c++) {
if (backtracking(0, r, c, hasVisited, board, word)) {
return true;
}
}
}
return false;
}
private boolean backtracking(int curLen, int r, int c, boolean[][] visited, final char[][] board, final String word) {
if (curLen == word.length()) {
return true;
}
if (r < 0 || r >= m || c < 0 || c >= n
|| board[r][c] != word.charAt(curLen) || visited[r][c]) {
return false;
}
visited[r][c] = true;
for (int[] d : direction) {
if (backtracking(curLen + 1, r + d[0], c + d[1], visited, board, word)) {
return true;
}
}
visited[r][c] = false;
return false;
}
4. 输出二叉树中所有从根到叶子的路径
257. Binary Tree Paths (Easy)
1
/ \
2 3
\
5
["1->2->5", "1->3"]
public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new ArrayList<>();
if (root == null) {
return paths;
}
List<Integer> values = new ArrayList<>();
backtracking(root, values, paths);
return paths;
}
private void backtracking(TreeNode node, List<Integer> values, List<String> paths) {
if (node == null) {
return;
}
values.add(node.val);
if (isLeaf(node)) {
paths.add(buildPath(values));
} else {
backtracking(node.left, values, paths);
backtracking(node.right, values, paths);
}
values.remove(values.size() - 1);
}
private boolean isLeaf(TreeNode node) {
return node.left == null && node.right == null;
}
private String buildPath(List<Integer> values) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < values.size(); i++) {
str.append(values.get(i));
if (i != values.size() - 1) {
str.append("->");
}
}
return str.toString();
}
5. 普通全排列
46. Permutations (Medium)
[1,2,3] have the following permutations:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> permutes = new ArrayList<>();
List<Integer> permuteList = new ArrayList<>();
boolean[] hasVisited = new boolean[nums.length];
backtracking(permuteList, permutes, hasVisited, nums);
return permutes;
}
private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, final int[] nums) {
if (permuteList.size() == nums.length) {
permutes.add(new ArrayList<>(permuteList)); // 重新构造一个 List
return;
}
for (int i = 0; i < visited.length; i++) {
if (visited[i]) {
continue;
}
visited[i] = true;
permuteList.add(nums[i]);
backtracking(permuteList, permutes, visited, nums);
permuteList.remove(permuteList.size() - 1);
visited[i] = false;
}
}
6. 含有相同元素求排列
防止重复的方法:
- 使用一个map
- 先排序,然后在循环的时候进行判断nums[i] == nums[i - 1]
47. Permutations II (Medium)
[1,1,2] have the following unique permutations:
[[1,1,2], [1,2,1], [2,1,1]]
数组元素可能含有相同的元素,进行排列时就有可能出现重复的排列,要求重复的排列只返回一个。
在实现上,和 Permutations 不同的是要先排序,然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> permutes = new ArrayList<>();
List<Integer> permuteList = new ArrayList<>();
Arrays.sort(nums); // 排序
boolean[] hasVisited = new boolean[nums.length];
backtracking(permuteList, permutes, hasVisited, nums);
return permutes;
}
private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, final int[] nums) {
if (permuteList.size() == nums.length) {
permutes.add(new ArrayList<>(permuteList));
return;
}
for (int i = 0; i < visited.length; i++) {
if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
continue; // 防止重复
}
if (visited[i]){
continue;
}
visited[i] = true;
permuteList.add(nums[i]);
backtracking(permuteList, permutes, visited, nums);
permuteList.remove(permuteList.size() - 1);
visited[i] = false;
}
}
集合的所有子集
https://www.nowcoder.com/questionTerminal/c333d551eb6243e0b4d92e37a06fbfc9
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> res=new ArrayList<>();
//用回溯法找出所有子集然后按子集大小排序
public ArrayList<ArrayList<Integer>> subsets(int[] S) {
if(S==null||S.length<0)
return res;
ArrayList<Integer> list=new ArrayList<>();
Arrays.sort(S);
Findsubset(S,0,list);
Collections.sort(res, new Comparator<ArrayList<Integer>>(){
public int compare(ArrayList<Integer> list1, ArrayList<Integer> list2){
if(list1.size() > list2.size()){
return 1;
}
else if(list1.size() < list2.size()){
return -1;
}
else{
return 0;
}
}
});
return res;
}
public void Findsubset(int[] S,int start,ArrayList<Integer> list){
res.add(new ArrayList<Integer>(list));
for(int i=start;i<S.length;i++){
list.add(S[i]);
Findsubset(S,i+1,list);
list.remove(list.size()-1);
}
}
}
第k个排列
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
主要在于规律:每次都能找到第一个字符是什么
class Solution {
public String getPermutation(int n, int k) {
int[] factorials = new int[n];
List<Integer> nums = new ArrayList() {{add(1);}};
factorials[0] = 1;
for(int i = 1; i < n; ++i) {
// generate factorial system bases 0!, 1!, ..., (n - 1)!
factorials[i] = factorials[i - 1] * i;
// generate nums 1, 2, ..., n
nums.add(i + 1);
}
// fit k in the interval 0 ... (n! - 1)
--k;
// compute factorial representation of k
StringBuilder sb = new StringBuilder();
for (int i = n - 1; i > -1; --i) {
int idx = k / factorials[i];
//或者 k -= idx * factorials[i];
k %= factorials[i];
sb.append(nums.get(idx));
nums.remove(idx);
}
return sb.toString();
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/permutation-sequence/solution/di-k-ge-pai-lie-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
7. 组合
77. Combinations (Medium)
If n = 4 and k = 2, a solution is:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> combinations = new ArrayList<>();
List<Integer> combineList = new ArrayList<>();
backtracking(combineList, combinations, 1, k, n);
return combinations;
}
private void backtracking(List<Integer> combineList, List<List<Integer>> combinations, int start, int k, final int n) {
if (k == 0) {
combinations.add(new ArrayList<>(combineList));
return;
}
for (int i = start; i <= n - k + 1; i++) { // 剪枝
combineList.add(i);
backtracking(combineList, combinations, i + 1, k - 1, n);
combineList.remove(combineList.size() - 1);
}
}
8. 组合求和
39. Combination Sum (Medium)
given candidate set [2, 3, 6, 7] and target 7,
A solution set is:
[[7],[2, 2, 3]]
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> combinations = new ArrayList<>();
backtracking(new ArrayList<>(), combinations, 0, target, candidates);
return combinations;
}
private void backtracking(List<Integer> tempCombination, List<List<Integer>> combinations,
int start, int target, final int[] candidates) {
if (target == 0) {
combinations.add(new ArrayList<>(tempCombination));
return;
}
for (int i = start; i < candidates.length; i++) {
if (candidates[i] <= target) {
tempCombination.add(candidates[i]);
backtracking(tempCombination, combinations, i, target - candidates[i], candidates);
tempCombination.remove(tempCombination.size() - 1);
}
}
}
9. 含有相同元素的组合求和
40. Combination Sum II (Medium)
For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8,
A solution set is:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> combinations = new ArrayList<>();
Arrays.sort(candidates);
backtracking(new ArrayList<>(), combinations, new boolean[candidates.length], 0, target, candidates);
return combinations;
}
private void backtracking(List<Integer> tempCombination, List<List<Integer>> combinations,
boolean[] hasVisited, int start, int target, final int[] candidates) {
if (target == 0) {
combinations.add(new ArrayList<>(tempCombination));
return;
}
for (int i = start; i < candidates.length; i++) {
if (i != 0 && candidates[i] == candidates[i - 1] && !hasVisited[i - 1]) {
continue;
}
if (candidates[i] <= target) {
tempCombination.add(candidates[i]);
hasVisited[i] = true;
backtracking(tempCombination, combinations, hasVisited, i + 1, target - candidates[i], candidates);
hasVisited[i] = false;
tempCombination.remove(tempCombination.size() - 1);
}
}
}
10. 1-9 数字的组合求和
216. Combination Sum III (Medium)
Input: k = 3, n = 9
Output:
[[1,2,6], [1,3,5], [2,3,4]]
从 1-9 数字中选出 k 个数不重复的数,使得它们的和为 n。
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> combinations = new ArrayList<>();
List<Integer> path = new ArrayList<>();
backtracking(k, n, 1, path, combinations);
return combinations;
}
private void backtracking(int k, int n, int start,
List<Integer> tempCombination, List<List<Integer>> combinations) {
if (k == 0 && n == 0) {
combinations.add(new ArrayList<>(tempCombination));
return;
}
if (k == 0 || n == 0) {
return;
}
for (int i = start; i <= 9; i++) {
tempCombination.add(i);
backtracking(k - 1, n - i, i + 1, tempCombination, combinations);
tempCombination.remove(tempCombination.size() - 1);
}
}
11. 子集
78. Subsets (Medium)
找出集合的所有子集,子集不能重复,[1, 2] 和 [2, 1] 这种子集算重复
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> subsets = new ArrayList<>();
List<Integer> tempSubset = new ArrayList<>();
for (int size = 0; size <= nums.length; size++) {
backtracking(0, tempSubset, subsets, size, nums); // 不同的子集大小
}
return subsets;
}
private void backtracking(int start, List<Integer> tempSubset, List<List<Integer>> subsets,
final int size, final int[] nums) {
if (tempSubset.size() == size) {
subsets.add(new ArrayList<>(tempSubset));
return;
}
for (int i = start; i < nums.length; i++) {
tempSubset.add(nums[i]);
backtracking(i + 1, tempSubset, subsets, size, nums);
tempSubset.remove(tempSubset.size() - 1);
}
}
12. 含有相同元素求子集
90. Subsets II (Medium)
For example,
If nums = [1,2,2], a solution is:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> subsets = new ArrayList<>();
List<Integer> tempSubset = new ArrayList<>();
boolean[] hasVisited = new boolean[nums.length];
for (int size = 0; size <= nums.length; size++) {
backtracking(0, tempSubset, subsets, hasVisited, size, nums); // 不同的子集大小
}
return subsets;
}
private void backtracking(int start, List<Integer> tempSubset, List<List<Integer>> subsets, boolean[] hasVisited,
final int size, final int[] nums) {
if (tempSubset.size() == size) {
subsets.add(new ArrayList<>(tempSubset));
return;
}
for (int i = start; i < nums.length; i++) {
if (i != 0 && nums[i] == nums[i - 1] && !hasVisited[i - 1]) {
continue;
}
tempSubset.add(nums[i]);
hasVisited[i] = true;
backtracking(i + 1, tempSubset, subsets, hasVisited, size, nums);
hasVisited[i] = false;
tempSubset.remove(tempSubset.size() - 1);
}
}
13. 分割字符串使得每个部分都是回文数
131. Palindrome Partitioning (Medium)
For example, given s = "aab",
Return
[
["aa","b"],
["a","a","b"]
]
public List<List<String>> partition(String s) {
List<List<String>> partitions = new ArrayList<>();
List<String> tempPartition = new ArrayList<>();
doPartition(s, partitions, tempPartition);
return partitions;
}
private void doPartition(String s, List<List<String>> partitions, List<String> tempPartition) {
if (s.length() == 0) {
partitions.add(new ArrayList<>(tempPartition));
return;
}
for (int i = 0; i < s.length(); i++) {
if (isPalindrome(s, 0, i)) {
tempPartition.add(s.substring(0, i + 1));
doPartition(s.substring(i + 1), partitions, tempPartition);
tempPartition.remove(tempPartition.size() - 1);
}
}
}
private boolean isPalindrome(String s, int begin, int end) {
while (begin < end) {
if (s.charAt(begin++) != s.charAt(end--)) {
return false;
}
}
return true;
}
14. 数独
37. Sudoku Solver (Hard)

private boolean[][] rowsUsed = new boolean[9][10];
private boolean[][] colsUsed = new boolean[9][10];
private boolean[][] cubesUsed = new boolean[9][10];
private char[][] board;
public void solveSudoku(char[][] board) {
this.board = board;
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
continue;
}
int num = board[i][j] - '0';
rowsUsed[i][num] = true;
colsUsed[j][num] = true;
cubesUsed[cubeNum(i, j)][num] = true;
}
backtracking(0, 0);
}
private boolean backtracking(int row, int col) {
while (row < 9 && board[row][col] != '.') {
row = col == 8 ? row + 1 : row;
col = col == 8 ? 0 : col + 1;
}
if (row == 9) {
return true;
}
for (int num = 1; num <= 9; num++) {
if (rowsUsed[row][num] || colsUsed[col][num] || cubesUsed[cubeNum(row, col)][num]) {
continue;
}
rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = true;
board[row][col] = (char) (num + '0');
if (backtracking(row, col)) {
return true;
}
board[row][col] = '.';
rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = false;
}
return false;
}
private int cubeNum(int i, int j) {
int r = i / 3;
int c = j / 3;
return r * 3 + c;
}
15. N 皇后
51. N-Queens (Hard)

在 n*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,求所有的 n 皇后的解。
一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。
45 度对角线标记数组的长度为 2 * n - 1,通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。

135 度对角线标记数组的长度也是 2 * n - 1,(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。

比较难的在于对角线,主妇都要进行标记
class Solution {
// 定义列和主副对角线
private boolean[] col;
private boolean[] dia1;
private boolean[] dia2;
private List<List<String>> ans = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
col = new boolean[n];
// 对角线个数为 2 * n - 1
dia1 = new boolean[2 * n - 1];
dia2 = new boolean[2 * n - 1];
// 定义每行的元素个数
List<Integer> row = new LinkedList<>();
// 回溯寻找符合要求的每组解
putQueen(n, 0, row);
return ans;
}
// index 代表当前访问的行数,最多到 n; row 用来存放满足题意的一种情况
private void putQueen(int n, int index, List<Integer> row) {
// 如果遍历到了最后一行,即代表已经找出一组解出来
if (index == n) {
// 将找到的一组解转化为棋盘格的形式后再放入 ans
ans.add(changeBoard(n, row));
return;
}
// 遍历当前 index 行的所有位置(从前往后依次遍历)
for (int i = 0; i < n; i++) {
// index + i 表示横纵坐标相加
// index - i + n - 1 表示横纵坐标之差(同一个副对角线上的横 - 纵是一样的,然后加n-1为了全部变成正数)
// 如果当前位置元素与他同一列,同一主副对角线上没有重复元素
if (!col[i] && !dia1[index + i] && !dia2[index - i + n - 1]) {
// 则该位置即皇后放置的位置,放入 row 存储位置信息,并标记为 true
row.add(i);
// 此时在该元素所在的列和主副对角线上就不能在遍历找到其他元素了
// 即标记为 true
col[i] = true;
dia1[index + i] = true;
dia2[index - i + n - 1] = true;
// 接着递归寻找下一行
putQueen(n, index + 1, row);
// 遍历完后再退出标记
col[i] = false;
dia1[index + i] = false;
dia2[index - i + n - 1] = false;
// 回退上一格子(回溯)
row.remove(row.size() - 1);
}
}
return;
}
// 将找到的一组解转化为棋盘格形式存储
private List<String> changeBoard(int n, List<Integer> row) {
List<String> tmp = new ArrayList<>();
for (int i = 0; i < n; i++) {
char[] ch = new char[n];
// 初始化 ch 中所有位置元素为 ‘.’
Arrays.fill(ch, '.');
// 将 row 中已经确定下来的 Queen 位置改为 ‘Q’
ch[row.get(i)] = 'Q';
// 然后放入 tmp 中
tmp.add(new String(ch));
}
return tmp;
}
}
作者:Jasion_han
链接:https://leetcode-cn.com/problems/n-queens/solution/2020050151hardhui-su-fa-xun-zhao-nhuang-hou-zhen-s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
16.单词接龙 II
//下面做法超时 (深度优先 )
class Solution {
List<List<String>> res = new ArrayList<>();
int minP = Integer.MAX_VALUE;
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
//使用回溯,
helper(beginWord, endWord,wordList,new ArrayList<String>(),new boolean[wordList.size()]);
for(int i = 0; i < res.size(); i++){
if(res.get(i).size() > minP){
res.remove(i);
i--;
}
}
return res;
}
private void helper(String beginWord, String endWord, List<String> wordList, ArrayList<String> temp,boolean[] used) {
//使用回溯,
temp.add(beginWord);
if(beginWord.equals(endWord)){
minP = Math.min(minP, temp.size());
res.add(new ArrayList<>(temp));
return;
}
for(int i = 0; i < wordList.size();i++){
if(used[i] == false && canChange(beginWord, wordList.get(i))){
used[i] = true;
helper(wordList.get(i), endWord,wordList,temp,used);
temp.remove(temp.size()-1);
used[i] = false;
}
}
}
private boolean canChange(String str1, String str2){
//判断可以转换
int cnt = 0;
for(int i = 0; i < str1.length(); i++){
if(str1.charAt(i) != str2.charAt(i)){
cnt++;
}
}
return cnt==1;
}
}
//bfs
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
// 结果集
List<List<String>> res = new ArrayList<>();
Set<String> distSet = new HashSet<>(wordList);
// 字典中不包含目标单词
if (!distSet.contains(endWord)) {
return res;
}
// 已经访问过的单词集合:只找最短路径,所以之前出现过的单词不用出现在下一层
Set<String> visited = new HashSet<>();
// 累积每一层的结果队列
Queue<List<String>> queue= new LinkedList<>();
List<String> list = new ArrayList<>(Arrays.asList(beginWord));
queue.add(list);
visited.add(beginWord);
// 是否到达符合条件的层:如果该层添加的某一单词符合目标单词,则说明截止该层的所有解为最短路径,停止循环
boolean flag = false;
while (!queue.isEmpty() && !flag) {
// 上一层的结果队列
int size = queue.size();
// 该层添加的所有元素:每层必须在所有结果都添加完新的单词之后,再将这些单词统一添加到已使用单词集合
// 如果直接添加到 visited 中,会导致该层本次结果添加之后的相同添加行为失败
// 如:该层遇到目标单词,有两条路径都可以遇到,但是先到达的将该单词添加进 visited 中,会导致第二条路径无法添加
Set<String> subVisited = new HashSet<>();
for (int i = 0; i < size; i++) {
List<String> path = queue.poll();
// 获取该路径上一层的单词
String word = path.get(path.size() - 1);
char[] chars = word.toCharArray();
// 寻找该单词的下一个符合条件的单词
for (int j = 0; j < chars.length; j++) {
char temp = chars[j];
for (char ch = 'a'; ch <= 'z'; ch++) {
chars[j] = ch;
if (temp == ch) {
continue;
}
String str = new String(chars);
// 符合条件:在 wordList 中 && 之前的层没有使用过
if (distSet.contains(str) && !visited.contains(str)) {
// 生成新的路径
List<String> pathList = new ArrayList<>(path);
pathList.add(str);
// 如果该单词是目标单词:将该路径添加到结果集中,查询截止到该层
if (str.equals(endWord)) {
flag = true;
res.add(pathList);
}
// 将该路径添加到该层队列中
queue.add(pathList);
// 将该单词添加到该层已访问的单词集合中
subVisited.add(str);
}
}
chars[j] = temp;
}
}
// 将该层所有访问的单词添加到总的已访问集合中
visited.addAll(subVisited);
}
return res;
}
作者:leo-309
链接:https://leetcode-cn.com/problems/word-ladder-ii/solution/java-duo-jie-fa-bfs-shuang-xiang-bfsdfssi-lu-fen-x/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
17.单词拆分
下面的做法错误,比如
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QEg9kwks-1593873087594)(/home/scl/.config/Typora/typora-user-images/image-20200523105837451.png)]
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> set = new HashSet<>(wordDict);
StringBuilder sb = new StringBuilder();
boolean flag = false;
for(int i = 0; i < s.length(); i++){
sb.append(s.charAt(i));
if(set.contains(sb.toString())){
flag = true;
sb = new StringBuilder();
}else{
flag = false;
}
}
return flag;
}
}
暴力回溯
对于每个子串,看是否存在.超时
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
return helper(s,wordDict,0);
}
private boolean helper(String s, List<String> wordDict, int start){
if(start > s.length()-1) return true;
for(int i = start; i < s.length(); i++){
if(wordDict.contains(s.substring(start, i+1)) && helper(s, wordDict,i+1)){
return true;
}
}
return false;
}
}
记忆化回溯
在先前的方法中,我们看到许多函数调用都是冗余的,也就是我们会对相同的字符串调用多次回溯函数。为了避免这种情况,我们可以使用记忆化的方法,其中一个 memomemo 数组会被用来保存子问题的结果。每当访问到已经访问过的后缀串,直接用 memomemo 数组中的值返回而不需要继续调用函数。
通过记忆化,许多冗余的子问题可以极大被优化,回溯树得到了剪枝,因此极大减小了时间复杂度。
很关键的一点:new Boolean[s.length],没赋值前是null; boolean[] 会自动赋值false;
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
return helper(s,wordDict,0,new Boolean[s.length]);
}
private boolean helper(String s, List<String> wordDict, int start,Boolean[] state){
if(start > s.length()-1) return true;
if(state[start] != null) return state[start];
for(int i = start; i < s.length(); i++){
if(wordDict.contains(s.substring(start, i+1)) && helper(s, wordDict,i+1,state)){
return state[start] = true;
}
}
return state[start] = false;
}
}
使用宽度优先搜索
每次将start存进去,用一个数记录是否访问过了.
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Deque<Integer> q = new LinkedList<Integer>();
q.add(0);
boolean[] visited = new boolean[s.length()];
while(!q.isEmpty()){
int start = q.poll();
if(visited[start] == false){
for(int end = start; end < s.length(); end++){
if(wordDict.contains(s.substring(start,end+1))){
q.add(end+1);
if(end == s.length()-1){
return true;
}
}
}
visited[start] = true;
}
}
return false;
}
}
18.单词拆分 II
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NgMDNJIL-1593873087596)(/home/scl/.config/Typora/typora-user-images/image-20200529115550446.png)]
1.记忆化回溯,有返回值
2.每次递归字符串是当前的字符串, 第一次调用helper产生结果,helper继续递归下去.
class Solution {
public List<String> wordBreak(String s, List<String> wordDict) {
int max_len = 0;
for (String word : wordDict) max_len = Math.max(max_len, word.length());
return helper(s, max_len, wordDict, new HashMap<String, LinkedList<String>>());
}
private List<String> helper(String s, int max_len, List<String> wordDict, HashMap<String, LinkedList<String>> cache) {
if (cache.containsKey(s)) return cache.get(s);
LinkedList<String> res = new LinkedList<>();
if (s.length() == 0) {
res.add("");
return res;
}
for (int i = 0; i < s.length(); i++) {
if (i < max_len && wordDict.contains(s.substring(0, i + 1))) {
for (String tmp : helper(s.substring(i + 1), max_len, wordDict, cache))
res.add(s.substring(0, i + 1) + (tmp.isEmpty() ? "" : " ") + tmp);
}
}
cache.put(s, res);
return res;
}
}
作者:powcai
链接:https://leetcode-cn.com/problems/word-break-ii/solution/dong-tai-gui-hua-zi-ding-xiang-xia-by-powcai/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
216. 组合总和 III
全排列问题
常见技巧:
0.辅助函数的参数设置:
- 一般都有一个结果集,一个暂时结果集,记录是否访问过的数组或起始遍历的index
1.防止重复访问的方法:
- 使用一个boolean
- 在helper函数中传递起始位置
2.防止结果集中出现重复值:
- 使用一个set,在最后一次递归中判断是否重复
容易出错
最后一次将暂时结果集放入结果集中一定主要要new一个新的暂时结果集。详见[1]处代码
class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> res = new ArrayList<>();
helper(res, k, n, 0,new ArrayList<>(), new boolean[10]);
return res;
}
private void helper(List<List<Integer>> res, int k, int n, int tempCnt, ArrayList<Integer> temp,boolean[] used) {
if(n == 0){
if(tempCnt==k){
res.add(new ArrayList<>(temp));//[1]
return;
}
}
if(tempCnt>=k) return;
//回溯的模板,如果不用记忆数组可以使用start来记录接下来访问的起始位置。
for (int i = 1; i <= 9 && !used[i]; i++) {
used[i] = true;
temp.add(i);
helper(res, k, n-i, tempCnt+1, temp, used);
used[i] = false;
temp.remove(temp.size()-1);
}
}
}
直接使用回溯法进行递归调用,会超时
class Solution {
public boolean canWinNim(int n) {
if (n <= 3) return true;
return !canWinNim(n-1) || !canWinNim(n-2) || !canWinNim(n-3);
}
}
换个顺序超时解决了,但大数还是内存溢出.
class Solution {
public boolean canWinNim(int n) {
if (n <= 3) return true;
return !canWinNim(n-3) || !canWinNim(n-2) || !canWinNim(n-1);
}
}
使用一个map还不行
class Solution {
//使用map存储已经使用过的
HashMap<Integer,Boolean> map = new HashMap<>();
public boolean canWinNim(int n) {
if (map.containsKey(n)) return map.get(n);
if (n <= 3) return true;
return !canWinNim(n-3) || !canWinNim(n-2) || !canWinNim(n-1);
}
}
实际上
public boolean canWinNim(int n) {
return n % 4 != 0;
}
LeetCode 60. Permutation Sequence 第k个排列
单词搜索
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
board =
[
[‘A’,‘B’,‘C’,‘E’],
[‘S’,‘F’,‘C’,‘S’],
[‘A’,‘D’,‘E’,‘E’]
]
给定 word = “ABCCED”, 返回 true
给定 word = “SEE”, 返回 true
给定 word = “ABCB”, 返回 false
回溯法
class Solution {
public boolean exist(char[][] board, String word) {
boolean[][] visited = new boolean[board.length][board[0].length];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (word.charAt(0) == board[i][j] && backtrack(i, j, 0, word, visited, board)) return true;
}
}
return false;
}
private boolean backtrack(int i, int j, int idx, String word, boolean[][] visited, char[][] board) {
if (idx == word.length()) return true;
if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word.charAt(idx) || visited[i][j])
return false;
visited[i][j] = true;
if (backtrack(i + 1, j, idx + 1, word, visited, board) || backtrack(i - 1, j, idx + 1, word, visited, board) || backtrack(i, j + 1, idx + 1, word, visited, board) || backtrack(i, j - 1, idx + 1, word, visited, board))
return true;
visited[i][j] = false; // 回溯
return false;
}
}
作者:powcai
链接:https://leetcode-cn.com/problems/word-search/solution/hui-su-dfs-by-powcai/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
单词搜索二
212. 单词搜索 II
给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例:
输入:
words = [“oath”,“pea”,“eat”,“rain”] and board =
[
[‘o’,‘a’,‘a’,‘n’],
[‘e’,‘t’,‘a’,‘e’],
[‘i’,‘h’,‘k’,‘r’],
[‘i’,‘f’,‘l’,‘v’]
]
输出: [“eat”,“oath”]
class Solution {
public List<String> findWords(char[][] board, String[] words) {
Trie root = new Trie();
int m = board.length;
int n = board[0].length;
for (int i = 0; i < words.length; i++) {
insert(root, words[i]);
}
ArrayList<String> res = new ArrayList<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
dfs(board,0,0, root.children[board[i][j] - 'a'], res, new StringBuilder(), new boolean[m][n]);
}
}
return res;
}
public void dfs(char[][] board, int x, int y, Trie root, List<String> res, StringBuilder temp, boolean[][] visited){
if (root == null ) return;
int m = board.length;
int n = board[0].length;
temp.append(board[x][y]);
if(root.isWord) {
res.add(temp.toString());
root.isWord = false;
}
visited[x][y] = true;
int[] dx = new int[]{0, 0, -1, 1};
int[] dy = new int[]{-1, 1, 0, 0};
for (int i = 0; i < 4; i++) {
int nx = dx[i] + x;
int ny = dy[i] + y;
if(nx >= 0 && ny >= 0 && nx < m && ny < n && !visited[nx][ny]){
int idx = board[nx][ny] - 'a';
dfs(board, nx, ny, root.children[idx], res, temp, visited);
}
}
temp.setLength(temp.length() - 1);
visited[x][y] = false;
}
public void insert(Trie root, String str){
Trie cur = root;
for (int i = 0; i < str.length(); i++) {
int t = str.charAt(i) - 'a';
if(cur.children[t] == null){
cur.children[t] = new Trie();
}
cur = cur.children[t];
}
//终点位置的isWord设为true
cur.isWord = true;
}
class Trie{
Trie[] children;
boolean isWord;
Trie(){
children = new Trie[26];
isWord = false;
}
}
}
被围绕的区域
130. 被围绕的区域
还是用dfs模板,这次的细节在于
- 分析题意,使用dfs的关键在于邻居之间的传播关系,本题的传播关系在与是否被淹没与邻居的是否被淹没有关.
- 从边缘部位开始dfs,遇到不可以被淹没的都进行标记
class Solution {
public void solve(char[][] board) {
if (board == null || board.length == 0) return;
int m = board.length;
int n = board[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 从边缘o开始搜索
boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1;
if (isEdge && board[i][j] == 'O') {
dfs(board, i, j);
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'O') {
board[i][j] = 'X';
}
if (board[i][j] == '#') {
board[i][j] = 'O';
}
}
}
}
public void dfs(char[][] board, int i, int j) {
if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') {
// board[i][j] == '#' 说明已经搜索过了.
return;
}
board[i][j] = '#';
dfs(board, i - 1, j); // 上
dfs(board, i + 1, j); // 下
dfs(board, i, j - 1); // 左
dfs(board, i, j + 1); // 右
}
}
作者:Ac_pipe
链接:https://leetcode-cn.com/problems/surrounded-regions/solution/bfsdi-gui-dfsfei-di-gui-dfsbing-cha-ji-by-ac_pipe/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
矩阵中的最长递增路径
我用下面的这种经典的dfs但是超时,因为遍历过的还会去遍历,所以最好的方法是使用原来的数组做dp记录.
class Solution {
public int longestIncreasingPath(int[][] matrix) {
if(matrix == null || matrix.length == 0) return 0;
int m = matrix.length;
int n = matrix[0].length;
int[] res = new int[]{0};
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
dfs(matrix, i, j, res, 0);
}
}
return res[0] + 1;
}
private void dfs(int[][] matrix, int i, int j, int[] res, int curLen) {
res[0] = Math.max(res[0], curLen);
int m = matrix.length;
int n = matrix[0].length;
int[] dx = new int[]{1, -1, 0, 0};
int[] dy = new int[]{0, 0, 1, -1};
int t = matrix[i][j];
// 深度优先
for (int k = 0; k < 4; k++) {
int nx = dx[k] + i;
int ny = dy[k] + j;
if(nx >=0 && nx < m && ny >= 0 && ny < n && matrix[nx][ny] > t){
dfs(matrix, nx, ny, res, curLen + 1);
}
}
}
}
正确的: