Leedcode算法专题训练(搜索)
BFS
广度优先搜索一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
第一层:
- 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 表示可以经过某个位置,求解从左上角到右下角的最短路径长度。
class Solution {
public static int shortestPathBinaryMatrix(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return -1;
}
// 如果起点就阻塞那就玩完啦
if (grid[0][0]==1){
return -1;
}
//定义 8个方向
int[][] dir = {{1, -1}, {1, 0}, {1, 1}, {0,-1},{0,1},{-1,-1},{-1,0},{-1,1}};
int m = grid.length;
int n = grid[0].length;
//bfs的老套路 来个队列
Queue<int[]> queue = new LinkedList<>();
queue.add(new int[]{0,0}); //把起点扔进去
grid[0][0] = 1; // 把起点标记为阻塞
int path = 1; // 层数
while (!queue.isEmpty()){
int size = queue.size();
while(size-- > 0){
int[] cur = queue.poll();
int x = cur[0];
int y = cur[1];
//能放进队列里的都是为0可以走的(这一点在后面保证了)
// 如果等于终点则返回
if (x == m-1 && y == n-1){ //
return path;
}
//开始八个方向的判断
for (int[] d : dir){
int x1 = x + d[0];
int y1 = y + d[1];
//这里开始过滤
if (x1 < 0 || x1 >= m || y1 < 0||y1>=n || grid[x1][y1]==1){
continue;
}
//把在数组范围内 并且为0不阻塞的放入队列中
queue.add(new int[]{x1,y1});
grid[x1][y1] = 1; // 标记
}
}
path++; //遍历完一层 这时候要 ++啦
}
return -1;
}
}
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 的最短路径。
本题也可以用动态规划求解,在之后动态规划部分中会再次出现。
class Solution {
public int numSquares(int n) {
ArrayList<Integer> square_nums = new ArrayList<Integer>();
for (int i = 1; i * i <= n; ++i) {
square_nums.add(i * i);
}
Set<Integer> queue = new HashSet<Integer>();
queue.add(n);
int level = 0;
while (queue.size() > 0) {
level += 1;
Set<Integer> next_queue = new HashSet<Integer>();
for (Integer remainder : queue) {
for (Integer square : square_nums) {
if (remainder.equals(square)) {
return level;
} else if (remainder < square) {
break;
} else {
next_queue.add(remainder - square);
}
}
}
queue = next_queue;
}
return level;
}
}
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 中。
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if (!wordList.contains(endWord)) {
return 0;
}
// visited修改为boolean数组
boolean[] visited = new boolean[wordList.size()];
int idx = wordList.indexOf(beginWord);
if (idx != -1) {
visited[idx] = true;
}
Queue<String> queue = new LinkedList<>();
queue.add(beginWord);
int count = 0;
while (queue.size() > 0) {
int size = queue.size();
++count;
while (size-- > 0) {
String start = queue.poll();
for (int i = 0; i < wordList.size(); ++i) {
// 通过index判断是否已经访问
if (visited[i]) {
continue;
}
String s = wordList.get(i);
if (!canConvert(start, s)) {
continue;
}
if (s.equals(endWord)) {
return count + 1;
}
visited[i] = true;
queue.offer(s);
}
}
}
return 0;
}
public boolean canConvert(String s1, String s2) {
// 因为题目说了单词长度相同,可以不考虑长度问题
// if (s1.length() != s2.length()) return false;
int count = 0;
for (int i = 0; i < s1.length(); ++i) {
if (s1.charAt(i) != s2.charAt(i)) {
++count;
if (count > 1) {
return false;
}
}
}
return count == 1;
}
}
DFS
广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。
而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。
在程序实现 DFS 时需要考虑以下问题:
- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
1. 查找最大的连通面积
695. Max Area of Island (Medium)
[[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
可以将矩阵表示看成一张有向图。
这俩个题目好典型,要结合在一起学会使用
class Solution {
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]);
}
}
}
3. 好友关系的连通分量数目
547. Friend Circles (Medium)
算法
给定的矩阵可以看成图的邻接矩阵。这样我们的问题可以变成无向图连通块的个数。为了方便理解,考虑如下矩阵:
M= [1 1 0 0 0 0
1 1 0 0 0 0
0 0 1 1 1 0
0 0 1 1 0 0
0 0 1 0 1 0
0 0 0 0 0 1]
题目给出的矩阵实际上是一个邻接矩阵,因此这个题目就抽象成了已知邻接矩阵,求这个图的连通分量个数这样一个问题,就是一个模板题了,直接套模板代码即可。
class Solution {
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)
思路及解法
我们可以使用深度优先搜索实现标记操作。在下面的代码中,我们把标记过的字母 O
修改为字母 T
。
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).
左边和上边是太平洋,右边和下边是大西洋,内部的数字代表海拔,海拔高的地方的水能够流到低的地方,求解水能够流到太平洋和大西洋的所有位置。
class Solution {
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);
}
}
}
Backtracking
Backtracking(回溯)属于 DFS。
- 普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
- 而 Backtracking 主要用于求解 排列组合 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
1. 数字键盘组合
17. Letter Combinations of a Phone Number (Medium)
Input:Digit string "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
class Solution {
private 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,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)
Given "25525511135",
return ["255.255.11.135", "255.255.111.35"].
class Solution {
public List<String> restoreIpAddresses(String s) {
List<String> ans = new ArrayList<>();
if (s == null || s.length() == 0) {
return ans;
}
backtrack(s, ans, 0, new ArrayList<>());
return ans;
}
// pos-当前遍历到 s 字符串中的位置,tmp-当前存放已经确定好的 ip 段的数量
private void backtrack(String s, List<String> ans, int pos, List<String> tmp) {
if (tmp.size() == 4) {
// 如果此时 pos 也刚好遍历完整个 s
if (pos == s.length()) {
// join 用法:例如 [[255],[255],[111],[35]] -> 255.255.111.35
ans.add(String.join(".", tmp));
}
// 否则直接返回
return;
}
// ip 地址每段最多有三个数字
for (int i = 1; i <= 3; i++) {
// 如果当前位置距离 s 末尾小于 3 就不用再分段了,直接跳出循环即可。
if (pos + i > s.length()) {
break;
}
// 将 s 的子串开始分段
String segment = s.substring(pos, pos + i);
int val = Integer.valueOf(segment);
// 剪枝条件:段的起始位置不能为 0,段拆箱成 int 类型的长度不能大于 255
if (segment.startsWith("0") && segment.length() > 1 || (i == 3 && val > 255)) {
continue;
}
// 符合要求就加入到 tmp 中
tmp.add(segment);
// 继续递归遍历下一个位置
backtrack(s, ans, pos + i, tmp);
// 回退到上一个元素,即回溯
tmp.remove(tmp.size() - 1);
}
}
}
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.length());
}
}
}
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.
class Solution {
private 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 i=0;i<m;i++){
for(int j=0;j<n;j++){
if(backtracking(0,i,j,hasVisited,board,word)){
return true;
}
}
}
return false;
}
private boolean backtracking(int curLen,int i, int j, boolean[][]hasVisited,char[][] board,String word){
//记录word的长度,长度相同则认为匹配
if(curLen==word.length()){
return true;
}
if(i<0||i>=m||j<0||j>=n||board[i][j]!=word.charAt(curLen)||hasVisited[i][j]){
return false;
}
hasVisited[i][j]=true;
for(int[] d: direction){
if(backtracking(curLen+1,i+d[0],j+d[1],hasVisited,board,word)){
return true;
}
}
hasVisited[i][j] = false;
return false;
}
}
4. 输出二叉树中所有从根到叶子的路径
257. Binary Tree Paths (Easy)
1
/ \
2 3
\
5
["1->2->5", "1->3"]
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> tree_list=new ArrayList<>();
if(root==null){
return tree_list;
}
String bree="";
backtrace(root ,tree_list,bree);
return tree_list;
}
private void backtrace(TreeNode root ,List<String> tree_list,String bree){
if(root==null){
return;
}
bree+=root.val;
if(root.left==null&&root.right==null){
tree_list.add(bree);
}
else{
bree+="->";
backtrace(root.left,tree_list,bree);
backtrace(root.right,tree_list,bree);;
}
}
}
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]
]
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> output = new ArrayList<Integer>();
for (int num : nums) {
output.add(num);
}
int n = nums.length;
backtrack(n, output, res, 0);
return res;
}
public void backtrack(int n, List<Integer> output, List<List<Integer>> res, int first) {
// 所有数都填完了
if (first == n) {
res.add(new ArrayList<Integer>(output));
}
for (int i = first; i < n; i++) {
// 动态维护数组
Collections.swap(output, first, i);
// 继续递归填下一个数
backtrack(n, output, res, first + 1);
// 撤销操作
Collections.swap(output, first, i);
}
}
}
class Solution {
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. 含有相同元素求排列
47. Permutations II (Medium)
[1,1,2] have the following unique permutations:
[[1,1,2], [1,2,1], [2,1,1]]
数组元素可能含有相同的元素,进行排列时就有可能出现重复的排列,要求重复的排列只返回一个。
在实现上,和 Permutations 不同的是要先排序,然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。
class Solution {
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,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;
}
}
}
7. 组合
77. Combinations (Medium)
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> result =new ArrayList<>();
if(n==0||k==0){
return result;
}
LinkedList<Integer> arr=new LinkedList<>();
backtrcak(n,k,1,arr,result);
return result;
}
//prenum为访问的第几个数字
public void backtrcak(int n,int k,int prenum,LinkedList<Integer> arr,List<List<Integer>> result){
if(arr.size()==k){
result.add(new LinkedList<>(arr));
return;
}
for(int i=prenum;i<=n;i++){
arr.addLast(i);
backtrcak(n,k,i+1,arr,result);
arr.pollLast();
}
}
}
8. 组合求和
39. Combination Sum (Medium)
given candidate set [2, 3, 6, 7] and target 7,
A solution set is:
[[7],[2, 2, 3]]
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<Integer>arr=new ArrayList<>();
List<List<Integer>> result=new ArrayList<>();
if(candidates==null||candidates.length==0){
return result;
}
backtrack(arr,result,candidates,target,0,0);
return result;
}
private void backtrack(List<Integer>arr,List<List<Integer>> result,int[] candidates,int target,int sum,int start){
if(target<sum)return;
if(sum==target){
result.add(new ArrayList<>(arr));
return;
}
for(int i=start;i<candidates.length;i++){
arr.add(candidates[i]);
sum+=candidates[i];
backtrack(arr,result,candidates,target,sum,i);
sum-=candidates[i];
arr.remove(arr.size()-1);
}
}
}
List<List<Integer>> lists = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0 || target < 0) {
return lists;
}
List<Integer> list = new ArrayList<>();
process(0, candidates, target, list);
return lists;
}
private void process(int start, int[] candidates, int target, List<Integer> list) {
//递归的终止条件
if (target < 0) {
return;
}
if (target == 0) {
lists.add(new ArrayList<>(list));
} else {
for (int i = start; i < candidates.length; i++) {
list.add(candidates[i]);
//因为每个数字都可以使用无数次,所以递归还可以从当前元素开始
process(i, candidates, target - candidates[i], list);
list.remove(list.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]
]
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> result =new ArrayList<>();
if(candidates.length==0||candidates==null){
return result;
}
List<Integer>arr =new ArrayList<>();
Arrays.sort(candidates);
backtrack(candidates,arr,result,target,0);
return result;
}
private void backtrack(int[] candidates,List<Integer>arr,List<List<Integer>> result,int target,int start){
if(target==0){
result.add(new ArrayList<>(arr));
return;
}
if(target<0){
return;
}
for(int i=start;i<candidates.length;i++){
if(i>start&&candidates[i]==candidates[i-1]){
continue;
}
arr.add(candidates[i]);
backtrack(candidates,arr,result,target-candidates[i],i+1);
arr.remove(arr.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。
class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>>result=new ArrayList<>();
List<Integer> path=new ArrayList<>();
backtrack(k,n,1,path,result);
return result;
}
private void backtrack(int k,int n, int start,List<Integer>path,List<List<Integer>>result){
if(k==0&&n==0){
result.add(new ArrayList<>(path));
return;
}
if(k==0||n==0){
return ;
}
for(int i=start;i<=9;i++){
path.add(i);
backtrack(k-1,n-i,i+1,path,result);
path.remove(path.size()-1);
}
}
}
11. 子集
78. Subsets (Medium)
找出集合的所有子集,子集不能重复,[1, 2] 和 [2, 1] 这种子集算重复
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result=new ArrayList<>();
List<Integer> arr=new ArrayList<>();
if(nums==null||nums.length==0){
return result;
}
backtrack(0,nums,result,arr);
return result;
}
private void backtrack(int i,int[] nums,List<List<Integer>> result,List<Integer> arr){
result.add(new ArrayList<>(arr));
for(int j=i;j<nums.length;j++){
arr.add(nums[j]);
backtrack(j+1,nums,result,arr);
arr.remove(arr.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],
[]
]
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>>result=new ArrayList<>();
List<Integer>arr=new ArrayList<>();
if(nums.length==0||nums==null){
return result;
}
Arrays.sort(nums);
backtrack(0,nums,result,arr);
return result;
}
private void backtrack(int i,int[] nums,List<List<Integer>> result,List<Integer> arr){
result.add(new ArrayList<>(arr));
for(int j=i;j<nums.length;j++){
if(j>i&&nums[j]==nums[j-1]){
continue;
}
arr.add(nums[j]);
backtrack(j+1,nums,result,arr);
arr.remove(arr.size()-1);
}
}
}
13. 分割字符串使得每个部分都是回文数
131. Palindrome Partitioning (Medium)
For example, given s = "aab",
Return
[
["aa","b"],
["a","a","b"]
]
class Solution {
public List<List<String>> partition(String s) {
List<String>arr=new ArrayList<>();
List<List<String>>result=new ArrayList<>();
if(s==null||s.length()==0){
return result;
}
backtrack(arr,result,s);
return result;
}
private void backtrack(List<String> arr,List<List<String>>result,String s){
if(s.length()==0){
result.add(new ArrayList<>(arr));
return;
}
for(int i=0;i<s.length();i++){
if(isPalindrome(s,0,i)){
arr.add(s.substring(0,i+1));
backtrack(arr,result,s.substring(i+1));
arr.remove(arr.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)
class Solution {
//回溯算法
public boolean solveSudoku(char[][] board) {
return backTrace(board, 0, 0);
}
//注意这里的参数,row表示第几行,col表示第几列。
private boolean backTrace(char[][] board, int row, int col) {
//注意row是从0开始的,当row等于board.length的时候表示数独的
//最后一行全部读遍历完了,说明数独中的值是有效的,直接返回true
if (row == board.length)
return true;
//如果当前行的最后一列也遍历完了,就从下一行的第一列开始。这里的遍历
//顺序是从第1行的第1列一直到最后一列,然后第二行的第一列一直到最后
//一列,然后第三行的……
if (col == board.length)
return backTrace(board, row + 1, 0);
//如果当前位置已经有数字了,就不能再填了,直接到这一行的下一列
if (board[row][col] != '.')
return backTrace(board, row, col + 1);
//如果上面条件都不满足,我们就从1到9种选择一个合适的数字填入到数独中
for (char i = '1'; i <= '9'; i++) {
//判断当前位置[row,col]是否可以放数字i,如果不能放再判断下
//一个能不能放,直到找到能放的为止,如果从1-9都不能放,就会下面
//直接return false
if (!isValid(board, row, col, i))
continue;
//如果能放数字i,就把数字i放进去
board[row][col] = i;
//如果成功就直接返回,不需要再尝试了
if (backTrace(board, row, col))
return true;
//否则就撤销重新选择
board[row][col] = '.';
}
//如果当前位置[row,col]不能放任何数字,直接返回false
return false;
}
//验证当前位置[row,col]是否可以存放字符c
private static boolean isValid(char[][] board, int row, int col, char c) {
for (int i = 0; i < 9; i++) {
//当前列有没有和字符c重复的
if (board[i][col] == c)
return false;
//当前行有没有和字符c重复的
if (board[row][i] == c)
return false;
//当前的单元格内是否有和字符c重复的
if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c)
return false;
}
return true;
}
}
15. N 皇后
51. N-Queens (Hard)
public List<List<String>> solveNQueens(int n) {
char[][] chess = new char[n][n];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
chess[i][j] = '.';
List<List<String>> res = new ArrayList<>();
solve(res, chess, 0);
return res;
}
private void solve(List<List<String>> res, char[][] chess, int row) {
if (row == chess.length) {
res.add(construct(chess));
return;
}
for (int col = 0; col < chess.length; col++) {
if (valid(chess, row, col)) {
chess[row][col] = 'Q';
solve(res, chess, row + 1);
chess[row][col] = '.';
}
}
}
//row表示第几行,col表示第几列
private boolean valid(char[][] chess, int row, int col) {
//判断当前列有没有皇后,因为他是一行一行往下走的,
//我们只需要检查走过的行数即可,通俗一点就是判断当前
//坐标位置的上面有没有皇后
for (int i = 0; i < row; i++) {
if (chess[i][col] == 'Q') {
return false;
}
}
//判断当前坐标的右上角有没有皇后
for (int i = row - 1, j = col + 1; i >= 0 && j < chess.length; i--, j++) {
if (chess[i][j] == 'Q') {
return false;
}
}
//判断当前坐标的左上角有没有皇后
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (chess[i][j] == 'Q') {
return false;
}
}
return true;
}
//把数组转为list
private List<String> construct(char[][] chess) {
List<String> path = new ArrayList<>();
for (int i = 0; i < chess.length; i++) {
path.add(new String(chess[i]));
}
return path;
}
Leedcode算法专题训练(搜索)的更多相关文章
- 算法专题训练 搜索a-T3 Ni骑士(ni)
搞了半天八数码弄不出来就只好来打题解 这道题是在搜索a碰到的(链接: http://pan.baidu.com/s/1jG9rQsQ ) 感觉题目最大亮点就是这英文简写"ni", ...
- Leedcode算法专题训练(树)
递归 一棵树要么是空树,要么有两个指针,每个指针指向一棵树.树是一种递归结构,很多树的问题可以使用递归来处理. 1. 树的高度 104. Maximum Depth of Binary Tree (E ...
- Leedcode算法专题训练(分治法)
归并排序就是一个用分治法的经典例子,这里我用它来举例描述一下上面的步骤: 1.归并排序首先把原问题拆分成2个规模更小的子问题. 2.递归地求解子问题,当子问题规模足够小时,可以一下子解决它.在这个例子 ...
- Leedcode算法专题训练(二分查找)
二分查找实现 非常详细的解释,简单但是细节很重要 https://www.cnblogs.com/kyoner/p/11080078.html 正常实现 Input : [1,2,3,4,5] key ...
- Leedcode算法专题训练(排序)
排序 快速排序 用于求解 Kth Element 问题,也就是第 K 个元素的问题. 可以使用快速排序的 partition() 进行实现.需要先打乱数组,否则最坏情况下时间复杂度为 O(N2). 堆 ...
- Leedcode算法专题训练(贪心)
1. 分配饼干 455. 分发饼干 题目描述:每个孩子都有一个满足度 grid,每个饼干都有一个大小 size,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足.求解最多可以获得满足的孩子数 ...
- Leedcode算法专题训练(双指针)
算法思想 双指针 167. 两数之和 II - 输入有序数组 双指针的典型用法 如果两个指针指向元素的和 sum == target,那么得到要求的结果: 如果 sum > target,移动较 ...
- Leedcode算法专题训练(位运算)
https://www.cnblogs.com/findbetterme/p/10787118.html 看这个就完事了 1. 统计两个数的二进制表示有多少位不同 461. Hamming Dista ...
- Leedcode算法专题训练(数组与矩阵)
1. 把数组中的 0 移到末尾 283. Move Zeroes (Easy) Leetcode / 力扣 class Solution { public void moveZeroes(int[] ...
随机推荐
- NGK数字增益平台中如何分配代币产出
最近很多朋友听说NGK公链的主网和数字增益平台即将上线以后都纷纷表示非常感兴趣,已经基本了解了NGK代币的产出方式,但还是对代币产出分配的问题不是很明确.今天小编就给大家科普一下,NGK代币在NGK数 ...
- jetty的jndi
jetty的jndi和tomcat的用法 tomcat的jndi是内置的,在web.xml文件里直接默认支持的,所有web项目可以直接使用 <resources> <!-- < ...
- winform导出excel
public void AllDataSetToExcel(DataSet ds) { string saveFileName = ""; bool fileSaved = fal ...
- 页面导入导出EXCEL
引用 using Microsoft.Office.Interop.Excel;using System.Reflection;//反射命名空间using System.IO; protected v ...
- ElasticSearcher的安装以及安装过程中出现的问题
先给出参考链接,带安装成功后再进行总结整个过程. 参考链接:https://blog.csdn.net/fjyab/article/details/81101284 java操作ElasticSear ...
- 基于【腾讯云函数】/【GitHub Actions】/【Docker】的每日签到脚本(支持多账号使用)
每日签到集合 基于[腾讯云函数]/[GitHub Actions]/[Docker]的每日签到脚本 支持多账号使用 特别声明: 本仓库发布的脚本及其中涉及的任何解锁和解密分析脚本,仅用于测试和学习研究 ...
- Vue3.0+Electron聊天室|electron跨平台仿QQ客户端|vue3.x聊天应用
基于vue3+electron11跨端仿制QQ桌面应用实战Vue3ElectronQchat. 使用vue3+electron+vuex4+ant-design-vue+v3scroll+v3laye ...
- Google单元测试框架gtest之官方sample笔记2--类型参数测试
gtest 提供了类型参数化测试方案,可以测试不同类型的数据接口,比如模板测试.可以定义参数类型列表,按照列表定义的类型,每个测试case都执行一遍. 本例中,定义了2种计算素数的类,一个是实时计算, ...
- RabbitMQ(三) SpringBoot2.x 集成 RabbitMQ
3-1 RabbitMQ 整合 SpringBoot2.x 生产者发送消息 创建 SpringBoot 项目application.properties 配置 spring.rabbitmq.host ...
- Java并发之ThreadPoolExecutor源码解析(三)
Worker 先前,笔者讲解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在这个方法中工作线程可能创建成功,也可能创建 ...