模版--来源lanbulandong
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径) return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
子集问题
1.无重复元素的数组所有子集
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
int n = nums.length;
if(n == 0 )return res;
List<Integer> list = new ArrayList<>();
dfs(nums , 0 , list);
return res;
}
void dfs(int[] nums , int start , List<Integer> list){
res.add(new ArrayList<>(list));
for(int i = start ; i < nums.length ; i++){
list.add(nums[i]);
dfs(nums , i+1 , list);
list.remove(list.size()-1);
}
}
}
2.有重复元素的数组所有子集
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
int n = nums.length;
if(n == 0) return res;
Arrays.sort(nums);
List<Integer> list = new ArrayList<>();
dfs(nums , 0 , list);
return res;
}
void dfs(int[] nums , int start , List<Integer> list){
res.add(new ArrayList<>(list));
for(int i = start ; i < nums.length ; i++){
if(i > start && nums[i] == nums[i-1] ) continue;
list.add(nums[i]);
dfs(nums , i+1 , list);
list.remove(list.size()-1);
}
}
}
全排列问题
1.无重复数字的全排列
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
if(nums.length == 0) return res;
List<Integer> list = new ArrayList<>();
boolean[] isv = new boolean[nums.length]; //全排列,每个数字只能用一次,用一个布尔数组来确定是否被访问过
dfs(nums , list , isv);
return res;
}
//由于每次全排列都是从数组开始到数组结尾,所以不需要传入start
void dfs(int[] nums ,List<Integer> list , boolean[] isv){
if(list.size() == nums.length){ //满足结束条件
res.add(new ArrayList<>(list));
return;
}
for(int i = 0 ; i < nums.length ; i++){
if(isv[i]) continue; //如果已经选择了就continue
list.add(nums[i]); //做选择
isv[i] = true;
dfs(nums , list , isv);
list.remove(list.size()-1); //撤销
isv[i] = false;
}
}
}
2.有重复数字的全排列
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
List<Integer> list = new ArrayList<>();
int n = nums.length;
if(n == 0) return res;
Arrays.sort(nums); //要先排序
boolean[] isv = new boolean[n];
dfs(nums , list , isv);
return res;
}
void dfs(int[] nums , List<Integer> list , boolean[] isv){
if(list.size() == nums.length){
res.add(new ArrayList<>(list));
return;
}
for(int i = 0 ; i < nums.length ; i++){
if(i > 0 && nums[i] == nums[i-1] && isv[i-1]) continue;
if(isv[i]) continue;
list.add(nums[i]);
isv[i] = true;
dfs(nums , list , isv);
list.remove(list.size()-1);
isv[i] = false;
}
}
}
3.二叉树的所有路径列表
class Solution {
List<String> res = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
if(root == null) return res;
StringBuilder sb = new StringBuilder();
dfs(root , sb);
return res;
}
void dfs(TreeNode root , StringBuilder sb){
if(root == null) return;
int n = sb.length();
sb.append(root.val);
sb.append("->");
//满足条件,撤销 -> 后加入结果集
if(root.left == null && root.right == null){
sb.deleteCharAt(sb.length()-1);
sb.deleteCharAt(sb.length()-1);
res.add(new String(sb));
}
dfs(root.left , sb);
dfs(root.right , sb);
sb.delete(n,sb.length()); //撤销选择,将这回合加入的全部删除
}
}
4.字符串的全排列
class Solution {
public String[] permutation(String s) {
if(s == null || s.length() == 0) return new String[0];
List<String> list = new ArrayList<>();
char[] chars = s.toCharArray();
int n = chars.length;
Arrays.sort(chars);
boolean[] isv = new boolean[n];
StringBuilder sb = new StringBuilder();
dfs( sb , chars , isv ,list);
String[] res = new String[list.size()];
for(int i = 0 ; i < res.length ; i++){
res[i] = list.get(i);
}
return res;
}
void dfs( StringBuilder sb , char[] chars , boolean[] isv , List<String> list){
if(sb.length() == chars.length){
list.add(new String(sb));
}
for(int i = 0 ; i < chars.length ; i++){
if(isv[i]) continue;
if(i > 0 && chars[i] == chars[i-1] && isv[i-1]) continue;
sb.append(chars[i]);
isv[i] = true;
dfs(sb , chars , isv , list);
sb.deleteCharAt(sb.length()-1);
isv[i] = false;
}
}
}
5.第k个排列
class Solution {
String res = "";
int count = 1;
public String getPermutation(int n, int k) {
int[] nums = new int[n];
for(int i = 0 ; i < n ; i++) nums[i] = i+1;
boolean[] isv = new boolean[n];
dfs(nums , k , "" , isv);
return res;
}
void dfs(int[] nums , int k , String s , boolean[] isv){
if(res != "") return;
if(s.length() == nums.length){
if(count == k) res = s;
else count++;
}
for(int i = 0 ; i < nums.length ; i++){
if(isv[i]) continue;
s = s + nums[i];
isv[i] = true;
dfs(nums , k , s , isv);
s = s.substring(0 , s.length()-1);
isv[i] = false;
}
}
}
组合问题
k个数组合
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
List<Integer> list = new ArrayList<>();
dfs(n , 1 , k , list);
return res;
}
void dfs(int n , int start ,int k , List<Integer> list){
if(list.size() == k){
res.add(new ArrayList<>(list));
return;
}
//如果 n = 7, k = 4,从 55 开始搜索就已经没有意义了,这是因为:即使把 5 选上,后面的数只有 6 和 7,一共就 3 个候选数,凑不出 4 个数的组合
//例如:n = 6 ,k = 4。
//path.size() == 1 的时候,接下来要选择 33 个数,搜索起点最大是 44,最后一个被选的组合是 [4, 5, 6];
//path.size() == 2 的时候,接下来要选择 22 个数,搜索起点最大是 55,最后一个被选的组合是 [5, 6];
//path.size() == 3 的时候,接下来要选择 11 个数,搜索起点最大是 66,最后一个被选的组合是 [6]
//搜索起点的上界 + 接下来要选择的元素个数 - 1 = n
//i <= n - (k - list.size()) + 1 优化
// i <= n 也可以,就是效率慢一点
for(int i = start ; i <= n - (k - list.size()) + 1; i++){
list.add(i);
dfs(n , i+1 , k , list);
list.remove(list.size()-1);
}
}
}
组合总和
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] nums, int target) {
int n = nums.length;
if(n == 0) return res;
List<Integer> list = new ArrayList<>();
dfs(nums , 0 , target , list);
return res;
}
void dfs(int[] nums , int start , int target, List<Integer> list){
if(target < 0) return;
if(target == 0){
res.add(new ArrayList<>(list));
return;
}
for(int i = start ; i < nums.length ; i++){
list.add(nums[i]);
dfs(nums, i , target- nums[i] , list); //因为每个数字可以重复选取,所以i不变
list.remove(list.size()-1);
}
}
}
组合总和2
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] nums, int target) {
int n = nums.length;
if(n == 0) return res;
List<Integer> list = new ArrayList<>();
Arrays.sort(nums);
dfs(nums , 0 , target , list );
return res;
}
void dfs(int[] nums , int start , int target , List<Integer> list ){
if(target < 0) return;
if(target == 0){
res.add(new ArrayList<>(list));
return;
}
for(int i = start ; i < nums.length ; i++){
if(i > start && nums[i] == nums[i-1]) continue;
list.add(nums[i]);
dfs(nums , i+1 , target-nums[i] , list );
list.remove(list.size()-1);
}
}
}
N皇后问题
1.返回所有方案
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
//1.初始化棋盘,全部填 '.'
List<char[]> board = new ArrayList<>();
for(int i = 0 ; i < n ; i++){
char[] arr = new char[n];
Arrays.fill(arr , '.');
board.add(arr);
}
dfs(board , 0); //从第0行开始
return res;
}
void dfs(List<char[]> board , int row){
if(row == board.size()){ //结束条件
res.add(transform(board));
return;
}
int n = board.size();
for(int col = 0 ; col < n ; col++){ //遍历每一列
if(!isValid(board , row ,col)) continue; //如果无效则返回
board.get(row)[col] = 'Q'; //做选择
dfs(board , row+1); //下一行决定
board.get(row)[col] = '.'; //撤销当前选择
}
}
//检查在 row,col 位置放Q是否有冲突
boolean isValid(List<char[]> board , int row , int col){
int n = board.size();
for(int i = 0 ; i < n ;i++){ // 检查列是否有皇后互相冲突
if(board.get(i)[col] == 'Q') return false;
}
for(int i = row-1 , j = col + 1 ; i >= 0 && j < n ; i--,j++){ //检查右上角
if(board.get(i)[j] == 'Q') return false;
}
for(int i = row-1 , j = col - 1 ; i >= 0 && j >= 0 ; i--,j--){ //检查左上方
if(board.get(i)[j] == 'Q') return false;
}
return true;
}
//格式转换函数
List<String> transform(List<char[]> board){
List<String> newBoard = new ArrayList<>();
for(char[] row : board) newBoard.add(new String(row));
return newBoard;
}
}
2.返回方案的种类数
class Solution {
int res = 0;
public int totalNQueens(int n) {
List<char[]> board = new ArrayList<>();
for(int i = 0 ; i < n ;i++){
char[] arr = new char[n];
Arrays.fill(arr , '.');
board.add(arr);
}
dfs(board , 0);
return res;
}
void dfs(List<char[]> board , int row){
if(row == board.size()) res++;
int n = board.size();
for(int col = 0 ; col < n; col++){
if(!isValid(board , row , col)) continue;
board.get(row)[col] = 'Q';
dfs(board , row + 1);
board.get(row)[col] = '.';
}
}
boolean isValid(List<char[]> board , int row ,int col){
int n = board.size();
for(int i = 0 ; i < n ; i++){
if(board.get(i)[col] == 'Q') return false;
}
for(int i = row-1 , j = col+1 ; i >= 0 && j < n ; i--,j++){
if(board.get(i)[j] == 'Q') return false;
}
for(int i = row-1 , j = col-1 ; i >= 0 && j >=0 ; i--,j--){
if(board.get(i)[j] == 'Q') return false;
}
return true;
}
}
数独问题
1.判断是不是有效的数独
class Solution {
public boolean isValidSudoku(char[][] board) {
for(int i = 0 ; i < board.length ; i++){
for(int j = 0 ; j < board.length ; j++){
if(board[i][j] == '.') continue;
if(!isValid(board , i , j)) return false;
}
}
return true;
}
private boolean isValid(char[][] board, int row, int col) {
char c = board[row][col];
for(int i = 0 ; i < 9 ; i++){
if(board[i][col] == c && i != row) return false; //排除自身的情况
if(board[row][i] == c && i != col) return false;
if(board[3 * (row/3) +i/3][ 3 *(col/3) + i%3] == c
&& 3 * (row/3) +i/3 != row
&& 3 *(col/3) + i%3 != col) return false;
}
return true;
}
}
2.解数独
class Solution {
public void solveSudoku(char[][] board) {
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;
}
}
括号生成问题
1. 生成所有的括号组合
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if(n == 0) return res;
String s = "";
dfs(n , n ,s); //左括号和右括号都为n
return res;
}
void dfs(int l , int r , String s){
if(l == 0 && r == 0){
res.add(new String(s));
return;
}
if(l > 0) dfs(l-1 , r , s+"("); //如果左括号大于0,拼接左括号
if(r > l) dfs(l , r-1 , s+")"); //如果右括号大于左括号个数,拼接右括号
}
}
矩阵搜索问题(岛屿问题)
1.单词搜索
class Solution {
public boolean exist(char[][] board, String word) {
int row = board.length;
if(row == 0) return false;
int col = board[0].length;
boolean[][] isv = new boolean[row][col];
for(int i = 0 ; i < row ; i++){
for(int j = 0 ; j < col ; j++){
if(board[i][j] == word.charAt(0) && dfs(board , 0 , i , j , word , isv)) return true;
}
}
return false;
}
boolean dfs(char[][] board , int index , int i , int j , String word , boolean[][] isv){
if(index == word.length()) return true;
if(i >= board.length || i < 0 || j >= board[0].length || j < 0) return false;
if(isv[i][j] || word.charAt(index) != board[i][j]) return false;
isv[i][j] = true;
if(dfs(board , index+1 , i+1 , j ,word , isv)
|| dfs(board , index+1 , i-1 , j , word , isv)
|| dfs(board , index+1 , i , j+1 , word , isv)
|| dfs(board , index+1 , i , j-1 ,word , isv)) return true;
isv[i][j] = false;
return false;
}
}
2.岛屿数量
class Solution {
public int numIslands(char[][] grid) {
int row = grid.length;
if(row == 0) return 0;
int col = grid[0].length;
int res = 0;
for(int i = 0 ; i < row ; i++){
for(int j = 0 ; j < col ; j++){
if(grid[i][j] == '1'){
dfs(i , j , grid);
res++;
}
}
}
return res;
}
void dfs(int i , int j , char[][] grid){
if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) return;
if(grid[i][j] != '1') return;
grid[i][j] = '0'; //将相邻的 1 全部变成0
dfs(i+1 , j , grid);
dfs(i-1 , j , grid);
dfs(i , j+1 , grid);
dfs(i , j-1 , grid);
}
}
3.岛屿的最大面积
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int row = grid.length;
if(row == 0) return 0;
int col = grid[0].length;
int res = 0;
for(int i = 0 ; i < row ; i++){
for(int j = 0 ; j < col ; j++){
if(grid[i][j] == 1){
res = Math.max(res , dfs(grid , i , j));
}
}
}
return res;
}
int dfs(int[][] grid , int i , int j){
if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) return 0;
if(grid[i][j] != 1) return 0;
int count = 1;
grid[i][j] = 2;
count += dfs(grid , i+1 , j);
count += dfs(grid , i , j+1);
count += dfs(grid , i-1 , j);
count += dfs(grid , i , j-1);
return count;
}
}
4. 统计封闭岛屿的数目
(leetcode-cn.com/problems/nu…)
class Solution {
// 0 表示陆地
// 1 表示水域
// 2 表示已经计算过了
public int closedIsland(int[][] 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] == 0){
if(dfs(i ,j ,grid)) // 如果是封闭岛屿
res++;
}
}
}
return res;
}
//判断是不是封闭岛屿
boolean dfs(int i , int j ,int[][] grid){
//封闭岛屿
if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length ) return false;
if(grid[i][j] != 0) return true;
grid[i][j] = 2;
boolean down = dfs(i+1,j,grid);
boolean up = dfs(i-1,j,grid);
boolean left = dfs(i,j+1,grid);
boolean right = dfs(i,j-1,grid);
if(up && down && left && right){
return true;
}
return false;
}
}
5.统计岛屿的周长
class Solution {
public int islandPerimeter(int[][] grid) {
int row = grid.length;
if(row == 0) return 0;
int col = grid[0].length;
int res = 0;
for(int i = 0 ; i < row ; i++){
for(int j = 0 ; j < col ; j++){
if(grid[i][j] == 1) {
res += 4;
if(i > 0 && grid[i-1][j] == 1) res -= 2;
if(j > 0 && grid[i][j-1] == 1) res -= 2;
}
}
}
return res;
}
}
其他经典问题
1. 电话号码的字母组合
class Solution {
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
if(digits.length() == 0) return res;
Map<Character , char[]> map = new HashMap<>();
map.put('2' , new char[]{'a' , 'b' ,'c'});
map.put('3' , new char[]{'d' , 'e' ,'f'});
map.put('4' , new char[]{'g' , 'h' ,'i'});
map.put('5' , new char[]{'j' , 'k' ,'l'});
map.put('6' , new char[]{'m' , 'n' ,'o'});
map.put('7' , new char[]{'p' , 'q' ,'r','s'});
map.put('8' , new char[]{'t' , 'u' ,'v'});
map.put('9' , new char[]{'w' , 'x' ,'y','z'});
StringBuilder sb = new StringBuilder();
dfs(digits , 0, sb , res , map);
return res;
}
void dfs(String digits ,int start ,StringBuilder sb , List<String> res ,Map<Character , char[]> map){
//递归终止条件
if(start == digits.length()){
res.add(new String(sb));
return;
}
char[] chars = map.get(digits.charAt(start));
for(int i = 0 ; i < chars.length ; i++){
sb.append(chars[i]);
dfs(digits , start+1 , sb , res , map);
sb.deleteCharAt(sb.length()-1);
}
}
}
2. 分割回文串
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> partition(String s) {
int n = s.length();
List<String> list = new ArrayList<>();
dfs(s , 0 , list);
return res;
}
void dfs(String s , int start , List<String> list){
if (start == s.length()){
res.add(new ArrayList<>(list));
return;
}
for(int i = start ; i < s.length() ; i++){
String str = s.substring(start , i+1);
if(isHuiWen(str)){
list.add(str);
dfs(s , i+1 , list);
list.remove(list.size()-1);
}
}
}
boolean isHuiWen(String s){
int n = s.length();
if(n == 1) return true;
int l = 0 , r = n-1;
while(l < r){
if(s.charAt(l) != s.charAt(r)) return false;
l++;
r--;
}
return true;
}
}
3. 判断是不是累加数
class Solution {
public boolean isAdditiveNumber(String num) {
int n = num.length();
if(n < 3) return false;
List<Long> list = new ArrayList<>();
return dfs(num , 0 ,list);
}
//int 可能会超数据范围,所以用long
boolean dfs(String num , int start , List<Long> list){
if(start == num.length() && list.size() > 2) return true;
for(int i = start ; i < num.length() ; i++){
String tem = num.substring(start , i+1);
if(tem.charAt(0) == '0' && tem.length() > 1) return false; //剪枝:不以0开头
if(tem.length() > num.length()/2) return false; //剪枝
int size = list.size();
if(size < 2 || Long.parseLong(tem) == list.get(size-1) + list.get(size-2)){
list.add(Long.parseLong(tem));
if(dfs(num , i+1 , list)) return true;
list.remove(list.size()-1);
}
}
return false;
}
}
4. 恢复IP地址
class Solution {
public List<String> restoreIpAddresses(String s) {
List<String> res = new ArrayList<>();
int n = s.length();
if(n == 0) return res;
List<String> segment = new ArrayList<>();
dfs(s , 0 ,segment, res);
return res;
}
void dfs(String s , int start , List<String> segment , List<String> res){
if(start == s.length()){
if(segment.size() == 4){
StringBuilder sb = new StringBuilder();
for(String tem : segment) sb.append(tem + ".");
sb.deleteCharAt(sb.length()-1);
res.add(new String(sb));
}
}
if(segment.size() > 4) return; //剪枝,大于4个分段就直接返回
for(int i = start ; i < s.length() && i < start+3 ; i++){
String tem = s.substring(start , i+1);
if(tem.charAt(0) == '0' && tem.length() > 1) continue;
int num = Integer.parseInt(tem);
if(num <= 255 && num >= 0){
segment.add(tem);
dfs(s,i+1,segment ,res);
segment.remove(segment.size()-1);
}
}
}
}