Leetcode 搜索(BFS DFS 回溯)
基础部分
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 。
示例 1:
输入:[[0,1],[1,0]]
输出:2
示例 2:
输入:[[0,0,0],[1,1,0],[1,1,0]]
输出:4
提示:
1 <= grid.length == grid[0].length <= 100
grid[i][j]
为0
或1
import java.util.ArrayDeque;
import java.util.Queue;
class Solution {
public int shortestPathBinaryMatrix(int[][] grid) {
if (grid[0][0] == 1 || grid[grid.length-1][grid[0].length-1] == 1) return -1;
Queue<int[]> queue = new ArrayDeque<>();
queue.add(new int[]{0,0});
grid[0][0] = 1;
int res = 0;
while (!queue.isEmpty()){
res++;
int len = queue.size();
while (len-- > 0){
int[] zuobiao = queue.poll();
int i = zuobiao[0];
int j = zuobiao[1];
if(i==grid.length-1 && j==grid[0].length-1) return res;
if (i > 0 && grid[i-1][j] == 0){ //上
queue.add(new int[]{i-1,j});
grid[i-1][j] = 1;
}
if (i > 0 && j > 0 && grid[i-1][j-1] == 0){ //左上
queue.add(new int[]{i-1,j-1});
grid[i-1][j-1] = 1;
}
if (i > 0 && j < grid[0].length-1 && grid[i-1][j+1] == 0){ //右上
queue.add(new int[]{i-1,j+1});
grid[i-1][j+1] = 1;
}
if (i < grid.length-1 && grid[i+1][j] == 0){ //下
queue.add(new int[]{i+1,j});
grid[i+1][j] = 1;
}
if (i < grid.length-1 && j > 0 && grid[i+1][j-1] == 0){ //左下
queue.add(new int[]{i+1,j-1});
grid[i+1][j-1] = 1;
}
if (i < grid.length-1 && j < grid[0].length-1 && grid[i+1][j+1] == 0){ //右下
queue.add(new int[]{i+1,j+1});
grid[i+1][j+1] = 1;
}
if (j > 0 && grid[i][j-1] == 0){ //左
queue.add(new int[]{i,j-1});
grid[i][j-1] = 1;
}
if (j < grid[0].length-1 && grid[i][j+1] == 0){ //右
queue.add(new int[]{i,j+1});
grid[i][j+1] = 1;
}
}
}
return -1;
}
}
279. 完全平方数
中等
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
class Solution { //很意外:12 ms, 在所有 Java 提交中击败了94.28%的用户
public static int numSquares(int n) {
List<Integer> arr = new ArrayList<>();
Queue<Integer> queue = new ArrayDeque<>();
for (int i = 1; ; i++){
long a = i*i;
if (a == n) return 1;
if (a > n) break;
arr.add((int) a);
queue.add((int) a);
}
if (ij(arr,n)) return 2;
int res = 0;
while (!queue.isEmpty()){
res++;
int size = queue.size();
while (size-- > 0){
int num = queue.poll();
if (num == n) return res;
if (ij(arr,n-num)) return res+2;
else {
for (Integer i : arr) {
queue.add(i+num);
}
}
}
}
return 1;
}
private static boolean ij(List<Integer> arr, int n) {
//双指针查找,答案基本不会大于4,所以这样省不少时间
int i = 0;
int j = arr.size() - 1;
while (i <= j){
int x = arr.get(i);
int y = arr.get(j);
if (x + y == n) return true;
if (x + y > n) j--;
else i++;
}
return false;
}
}
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" 不在字典中,所以无法进行转换。
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if (!wordList.contains(endWord)) return 0;
Queue<String> queue = new ArrayDeque<>();
queue.add(beginWord);
int res = 1;
while (!queue.isEmpty()){
int size = queue.size();
res++;
while (size-- > 0){
String str = queue.poll();
if (canTran(str,endWord)) return res;
for (int i = wordList.size()-1; i >= 0; i--) {
if (canTran(str,wordList.get(i))){
queue.add(wordList.get(i));
wordList.remove(i);
}
}
}
}
return 0;
}
private boolean canTran(String s1,String s2){
boolean can = true;
for (int i = 0; i < s1.length(); i++) {
if (s1.charAt(i)!=s2.charAt(i)){
if (can) can = false;
else return false;
}
}
return true;
}
}
DFS
695. 岛屿的最大面积
中等
给定一个包含了一些 0
和 1
的非空二维数组 grid
。
一个 岛屿 是由一些相邻的 1
(代表土地) 构成的组合,这里的「相邻」要求两个 1
必须在水平或者竖直方向上相邻。你可以假设 grid
的四个边缘都被 0
(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0
。)
示例 1:
[[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]]
对于上面这个给定矩阵应返回 6
。注意答案不应该是 11
,因为岛屿只能包含水平或垂直的四个方向的 1
。
示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0
。
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int row = grid.length;
int column = grid[0].length;
int res = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
if (grid[i][j] == 1) {
Stack<int[]> stack = new Stack<>();
stack.push(new int[]{i,j});
int S = 0;
while (!stack.isEmpty()){
int[] point = stack.pop();
int x = point[0];
int y = point[1];
if (grid[x][y] == 1) {
S++;
grid[x][y] = 0;
}else continue;
if (x > 0 && grid[x-1][y] == 1) stack.push(new int[]{x-1,y});
if (x < row-1 && grid[x+1][y] == 1) stack.push(new int[]{x+1,y});
if (y > 0 && grid[x][y-1] == 1) stack.push(new int[]{x,y-1});
if (y < column-1 && grid[x][y+1] == 1) stack.push(new int[]{x,y+1});
}
res = res > S ? res : S;
}
}
}
return res;
}
}
200. 岛屿数量
中等
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
[
['1','1','1','1','0'],
['1','1','0','1','0'],
['1','1','0','0','0'],
['0','0','0','0','0']
]
输出: 1
示例 2:
输入:
[
['1','1','0','0','0'],
['1','1','0','0','0'],
['0','0','1','0','0'],
['0','0','0','1','1']
]
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
class Solution {
public int numIslands(char[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1'){
res++;
dfs(i,j,grid);
}
}
}
return res;
}
private void dfs(int i, int j, char[][] grid) {
grid[i][j] = '0';
if (i > 0 && grid[i-1][j] == '1') dfs(i-1,j,grid);
if (i < grid.length-1 && grid[i+1][j] == '1') dfs(i+1,j,grid);
if (j > 0 && grid[i][j-1] == '1') dfs(i,j-1,grid);
if (j < grid[0].length-1 && grid[i][j+1] == '1') dfs(i,j+1,grid);
}
}
547. 朋友圈
中等
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:
- N 在[1,200]的范围内。
- 对于所有学生,有M[i][i] = 1。
- 如果有M[i][j] = 1,则有M[j][i] = 1。
class Solution {
public int findCircleNum(int[][] M) {
int n = M.length;
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (M[i][j] == 1){
M[i][j] = 0;
M[j][i] = 0;
res++;
dfs(j,M);
}
}
}
return res;
}
private void dfs(int j, int[][] M) {
for (int i = 0; i < M.length; i++) {
if (M[i][j] == 1) {
M[i][j] = 0;
M[j][i] = 0;
dfs(i,M);
}
}
}
}
130. 被围绕的区域
中等
给定一个二维的矩阵,包含 'X'
和 'O'
(字母 O)。
找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。
示例:
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'
。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
class Solution {
List<int[]> list = new ArrayList<>();
boolean[][] visited;
public void solve(char[][] board) {
if (board.length == 0) return;
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 (board[i][j] == 'X' || visited[i][j]) continue;
dfs(i,j,board);
boolean flag = true;
for (int[] ints : list) {
if (ints[0] == 0 || ints[0] == board.length-1 || ints[1] == 0 || ints[1] == board[0].length-1){
flag = false;
break;
}
}
if (flag){
for (int[] ints : list) {
board[ints[0]][ints[1]] = 'X';
}
}
list.clear();
}
}
}
private void dfs(int i, int j, char[][] board) {
if (visited[i][j]) return;
visited[i][j] = true;
list.add(new int[]{i,j});
if (i > 0 && board[i-1][j] == 'O') dfs(i-1,j,board);
if (i < board.length-1 && board[i+1][j] == 'O') dfs(i+1,j,board);
if (j > 0 && board[i][j-1] == 'O') dfs(i,j-1,board);
if (j < board[0].length-1 && board[i][j+1] == 'O') dfs(i,j+1,board);
}
}
417. 太平洋大西洋水流问题
中等
给定一个 m x n
的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
提示:
- 输出坐标的顺序不重要
- m 和 n 都小于150
示例:
给定下面的 5x5 矩阵:
太平洋 ~ ~ ~ ~ ~
~ 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 *
* * * * * 大西洋
返回:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).
public class Solution {
private static int[][] dires = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
private int m, n;
private int[][] matrix;
public List<List<Integer>> pacificAtlantic(int[][] matrix) {
List<List<Integer>> res = new ArrayList<>();
m = matrix.length;
if (m == 0) return res;
n = matrix[0].length;
if (n == 0) return res;
this.matrix = matrix;
boolean[][] canReachP = new boolean[m][n];
boolean[][] canReachA = new boolean[m][n];
for (int i = 0; i < n; i++) {
dfs(0, i, canReachP);
dfs(m - 1, i, canReachA);
}
for (int i = 0; i < m; i++) {
dfs(i, 0, canReachP);
dfs(i, n - 1, canReachA);
}
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(canReachA[i][j] && canReachP[i][j]){
List<Integer> temp = new ArrayList<>();
temp.add(i);
temp.add(j);
res.add(temp);
}
}
}
return res;
}
//换一种思路,从边界往里面走,只能走到比自己更高或者等高的地方。
//边界能走到的地方,就是能流入对应海洋的地方。
private void dfs(int x, int y, boolean[][] canReach) {
canReach[x][y] = true;
for (int i = 0; i < 4; i++) {
int newX = x + dires[i][0];
int newY = y + dires[i][1];
if (isIn(newX, newY) && matrix[x][y] <= matrix[newX][newY] && !canReach[newX][newY]) {
dfs(newX, newY, canReach);
}
}
}
private boolean isIn(int x, int y) {
return x >= 0 && x < m && y >= 0 && y < n;
}
}
回溯法
17. 电话号码的字母组合
中等
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
class Solution {
String[] strings = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
List<String> result = new ArrayList<>();
public List<String> letterCombinations(String digits) {
char[] ds = digits.toCharArray();
if (ds.length == 0) return result;
StringBuilder sb = new StringBuilder();
backtrack(sb,ds,0);
return result;
}
private void backtrack(StringBuilder sb, char[] ds, int index) {
if (sb.length() == ds.length){
result.add(sb.toString());return;
}
char[] chs = strings[ds[index] - '0'].toCharArray();
for (int i = 0; i < chs.length; i++) {
sb.append(chs[i]);
backtrack(sb,ds,index+1);
sb.deleteCharAt(sb.length()-1);
}
}
}
93. 复原IP地址
中等
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 '.'
分隔。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
class Solution {
List<String> result = new ArrayList<>();
int len; //一直能复用
public List<String> restoreIpAddresses(String s) {
len = s.length();
if (len <= 12) helper(s,0,"",0);
return result;
}
private void helper(String ip, int index, String restore, int count) {
if (count == 4 && index == len) result.add(restore);
if (count >= 4) return;
for (int i = 1; i < 4; i++) {
if (index + i > len) break;
String s = ip.substring(index,index+i);
if (i > 1 && s.charAt(0) == '0' || Integer.parseInt(s) > 255)
break; //i即s的长度
helper(ip, index+i, restore + s + (count < 3 ? "." : "" ), count+1);
}
}
}
79. 单词搜索
中等
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
给定 word = "ABCCED", 返回 true
给定 word = "SEE", 返回 true
给定 word = "ABCB", 返回 false
提示:
board
和word
中只包含大写和小写英文字母。1 <= board.length <= 200
1 <= board[i].length <= 200
1 <= word.length <= 10^3
class Solution {
int m;
int n;
int[][] directions = {{-1,0},{1,0},{0,-1},{0,1}};
boolean res = false;
public boolean exist(char[][] board, String word) {
this.m = board.length;
this.n = board[0].length;
if (word.length() > m * n) return false;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (word.charAt(0) == board[i][j]) backtrack(board,visited,word,i,j,0);
if (res) break;
}
}
return res;
}
private void backtrack(char[][] board,boolean[][] visited,String word,int i,int j,int index) {
if (!visited[i][j] && board[i][j] == word.charAt(index)){
if (index == word.length()-1) {
res = true;return;
}
visited[i][j] = true;
for (int k = 0; k < 4; k++) {
int x = i + directions[k][0];
int y = j + directions[k][1];
if (isIn(x,y) && !visited[x][y] && board[x][y] == word.charAt(index+1)){
backtrack(board,visited,word,x,y,index+1);
if (res) return; //有满足的了,赶紧结束递归
}
}
visited[i][j] = false; //回溯
}
}
private boolean isIn(int i,int j){
return !(i < 0 || j < 0 || i >= m || j >= n);
}
}
257. 二叉树的所有路径
简单
给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
输入:
1
/ \
2 3
\
5
输出: ["1->2->5", "1->3"]
解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
class Solution {
List<String> result = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
if (root == null) return result;
StringBuilder sb = new StringBuilder();
helper(root,sb);
return result;
}
private void helper(TreeNode root, StringBuilder sb) {
if (root == null) return;
String str = "" + root.val + "->";
sb.append(str);
if (root.left == null && root.right == null)
result.add(sb.toString().substring(0,sb.length()-2));
else {
helper(root.left,sb);
helper(root.right,sb);
}
sb.delete(sb.length()-str.length(),sb.length());
}
}
46. 全排列
中等
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution {
List<List<Integer>> result = new LinkedList<>();
List<Integer> restore = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
helper(nums);
return result;
}
private void helper(int[] nums) {
if (nums.length == restore.size()) {
result.add(new LinkedList<>(restore)); //必须要初始化※
//result.add(restore); //错的
return;
}
for (int num : nums) {
if (restore.contains(num)) continue;
restore.add(num);
helper(nums);
restore.remove(restore.size() - 1);
}
}
}
47. 全排列 II
中等
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
class Solution {
Set<List<Integer>> result = new HashSet<>();
List<Integer> restore = new LinkedList<>();
Set<Integer> set = new HashSet<>();
public List<List<Integer>> permuteUnique(int[] nums) {
helper(nums);
return new ArrayList<>(result);
}
private void helper(int[] nums) {
if (nums.length == restore.size()) {
result.add(new LinkedList<>(restore));
return;
}
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
if (set.contains(i)) continue;
restore.add(num);
set.add(i);
helper(nums);
restore.remove(restore.size() - 1);
set.remove(i);
}
}
}
77. 组合
中等
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> restore = new ArrayList<>();
Set<Integer> set = new HashSet<>();
public List<List<Integer>> combine(int n, int k) {
helper(n,k,0);
return result;
}
private void helper(int n, int k, int count) {
if (count == k){
result.add(new ArrayList<>(restore));
return;
}
for (int i = 1; i <= n; i++) {
if (set.contains(i) || count > 0 && restore.get(count-1) > i) continue;
restore.add(i);
set.add(i);
helper(n,k,count+1);
restore.remove(count);
set.remove(i);
}
}
}
39. 组合总和
中等
给定一个无重复元素的数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的数字可以无限制重复被选取。
说明:
- 所有数字(包括
target
)都是正整数。 - 解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate
中的每个元素都是独一无二的。1 <= target <= 500
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> restore = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
helper(candidates,target);
return result;
}
private void helper(int[] candidates, int target) {
if (target == 0) {
result.add(new ArrayList<>(restore));
return;
}
for (int i : candidates) {
if (target-i < 0) break;
if (!restore.isEmpty() && i < restore.get(restore.size()-1)) continue;
restore.add(i);
helper(candidates, target - i);
restore.remove(restore.size()-1);
}
}
}
40. 组合总和 II
中等
给定一个数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用一次。
说明:
- 所有数字(包括目标数)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
class Solution {
Set<List<Integer>> result = new HashSet<>();
List<Integer> restore = new ArrayList<>();
Set<Integer> set = new HashSet<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
helper(candidates,target);
return new ArrayList<>(result);
}
private void helper(int[] candidates, int target) {
if (target == 0) {
result.add(new ArrayList<>(restore));
return;
}
for (int i = 0; i < candidates.length; i++) {
int num = candidates[i];
if (target - num < 0) break;
if (set.contains(i) || !restore.isEmpty() && num < restore.get(restore.size() - 1))continue;
restore.add(num);
set.add(i);
helper(candidates, target - num);
restore.remove(restore.size() - 1);
set.remove(i);
}
}
}
216. 组合总和 III
中等
找出所有相加之和为 n 的 k* 个数的组合。*组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> restore = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
helper(n,k,0);
return result;
}
private void helper(int n, int k, int count) {
if (count == k && n == 0) result.add(new ArrayList<>(restore));
if (count >= k) return;
int i = restore.isEmpty() ? 1 : restore.get(restore.size()-1)+1;
for (; i <= 9; i++) {
restore.add(i);
helper(n-i,k,count+1);
restore.remove(restore.size()-1);
}
}
}
78. 子集
中等
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> restore = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
Arrays.sort(nums);
helper(nums);
return result;
}
private void helper(int[] nums) {
result.add(new ArrayList<>(restore));
if (restore.size() == nums.length) return;
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
if (!restore.isEmpty() && restore.get(restore.size()-1) >= num) continue;
restore.add(num);
helper(nums);
restore.remove(restore.size()-1);
}
}
}
90. 子集 II
中等
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> restore = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
help(nums, 0);
return result;
}
private void help(int[] nums, int index) {
if (!result.contains(restore)) result.add(new ArrayList<>(restore));
if (index == nums.length) return;
for (int i = index; i < nums.length; i++) {
restore.add(nums[i]);
help(nums, i+1);
restore.remove(restore.size()-1);
}
}
}
131. 分割回文串
中等
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab"
输出:
[
["aa","b"],
["a","a","b"]
]
class Solution {
List<List<String>> result = new ArrayList<>();
List<String> restore = new ArrayList<>();
public List<List<String>> partition(String s) {
helper(s,0,s.length());
return result;
}
private void helper(String s,int index, int end) {
if (index == end) {
result.add(new ArrayList<>(restore));
return;
}
for (int i = index+1; i <= end; i++) {
String substring = s.substring(index, i);
if (isPalindrome(substring)){
restore.add(substring);
helper(s,i,end);
restore.remove(restore.size()-1);
}
}
}
private boolean isPalindrome(String str){
int len = str.length();
for (int i = 0; i < len/2; i++) {
if (str.charAt(i) != str.charAt(len-i-1))
return false;
}
return true;
}
}
37. 解数独
困难
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
空白格用 '.'
表示。
一个数独。
答案被标成红色。
Note:
- 给定的数独序列只包含数字
1-9
和字符'.'
。 - 你可以假设给定的数独只有唯一解。
- 给定数独永远是
9x9
形式的。
class Solution {
Set<Character>[] sets1 = new HashSet[9]; //横向
Set<Character>[] sets2 = new HashSet[9]; //竖向
Set<Character>[] sets3 = new HashSet[9]; //块
public Solution() {
for (int i = 0; i < 9; i++) {
sets1[i] = new HashSet<>();
sets2[i] = new HashSet<>();
sets3[i] = new HashSet<>();
}
}
public void solveSudoku(char[][] board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') continue;
sets1[i].add(board[i][j]);
sets2[j].add(board[i][j]);
sets3[i/3*3 + j/3].add(board[i][j]);
}
}
helper(board,0,0);
}
private boolean helper(char[][] board,int i,int j) {
if (j == 9){
i++;
j = 0;
if (i == 9) return true; //全部填完了
}
if (board[i][j] == '.') {
int block = i/3*3 + j/3;
for (int num = 1; num <= 9; num++) {
char ch = (char)('0'+num);
if (sets1[i].contains(ch) || sets2[j].contains(ch) || sets3[block].contains(ch)) continue;
board[i][j] = ch;
sets1[i].add(ch);
sets2[j].add(ch);
sets3[block].add(ch);
if (helper(board,i,j+1)) return true; //填对了,后边就不用变回'.'了
board[i][j] = '.';
sets1[i].remove(ch);
sets2[j].remove(ch);
sets3[block].remove(ch);
}
}else {
return helper(board, i, j+1);
}
return false; //没找到能匹配的数
}
}
51. N皇后
困难
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
class Solution {
List<List<String>> result = new ArrayList<>();
char[][] board;
public List<List<String>> solveNQueens(int n) {
board = new char[n][n];
init(board);
helper(0,n);
return result;
}
private void helper(int rowIndex, int colIndex) {
if (rowIndex == board.length) {
result.add(new ArrayList<>(generate(board)));
return;
}
for (int i = 0; i < colIndex; i++) {
if (canfill(rowIndex,i)){
board[rowIndex][i] = 'Q';
helper(rowIndex+1, colIndex);
board[rowIndex][i] = '.';
}
}
}
private boolean canfill(int x, int y) {
for (int i = x-1,j = y;i >=0;i--)
if (board[i][j] == 'Q') return false;
for (int i = x-1,j = y-1;i >=0 && j >= 0;i--,j--)
if (board[i][j] == 'Q') return false;
for (int i = x-1,j = y+1;i >=0 && j <= board.length-1;i--,j++)
if (board[i][j] == 'Q') return false;
return true;
}
private List<String> generate(char[][] board) {
List<String> rows = new ArrayList<>();
for (int i = 0; i < board.length; i++)
rows.add(new String(board[i]));
return rows;
}
private void init(char[][] board) {
for (int i = 0; i < board.length; i++)
for (int j = 0; j < board.length; j++)
board[i][j] = '.';
}
}
频率排序
广度:913,815,407,1036,864,199,505,847,200,994,773,863,743
深度:546,679,711,928,394,329,199,834,505,124,488,200,839,364,863,695,753,743,694
回溯:411,93,46,10,37,51,44,22