标签2树、回溯法/DFS与分治

注意分治和快排:分治是函数名(left,mid);函数名(mid+ 1,right)

快排是函数名(left,index - 1);函数名(index+ 1 ,right),比如数组是nums,把nums[index]作为哨兵。

标签2回溯法与分治

面试题12. 矩阵中的路径

解法:回溯法,路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子,然后回溯。

要是不会就问我。

hasCore()函数的参数的意义:printlength表示即将判断word.charAt(printlength)是否等于board[row]col],要是等于printlength ++,继续回溯。因此开始判断要是printllength == word.length,那么判断结束,匹配完成。

Arrays.fill(marked, false);需注意fill方法只能填充一维数组,不能填充二维数组

            if(! result){
                -- printlength;
                //marked矩阵表示每个格子是否进入路径,因为(row,col)附近的四个点都不能被加入路径,故回溯,printlength--
                marked[row][col] = false;
            }

上面这个if语句有没有都行,建议没有

public class MatrixPath {
	public boolean exist(char[][] board, String word) {
		if(word == null || word.length() == 0){
			return true;
		}
		if(board == null || board.length == 0 || (board.length == 1 && board[0].length == 0)){
			return false;
		}
		int rows = board.length;
		int cols = board[0].length;
		boolean[][] marked = new boolean[rows][cols];
		
		//printlength标识找寻到word的第几个字母,即将判断word.charAt(printlength)是否出现在矩阵中
		//起点可以是矩阵中的任何一个点,因此用二重循环
		int printlength = 0;
		for(int i = 0; i < rows;i ++){
			for(int j = 0;j < cols;j ++){
				if(hasPathCore(board, word, marked, rows, cols, i, j, printlength)){
					return true;
				}
			}
		}
		return false;
    }
	private boolean hasPathCore(char[][] board, String word,boolean[][] marked,int rows,int cols,int row,int col,int printlength){
		//如果printlength为word.length()那么判断结束
		if(printlength == word.length()){
			return true;
		}
		boolean result = false;
		if(row < rows && row >= 0 && col < cols && col >= 0 && board[row][col] == word.charAt(printlength) && !marked[row][col]){
			++ printlength;
			marked[row][col] = true;
			//继续探索(row,col)附近的四个点
			result = hasPathCore(board, word, marked, rows, cols, row + 1, col, printlength)
					|| hasPathCore(board, word, marked, rows, cols, row - 1, col, printlength)
					|| hasPathCore(board, word, marked, rows, cols, row, col + 1, printlength)
					|| hasPathCore(board, word, marked, rows, cols, row, col - 1, printlength);
			//如果row,col)附近的四个点没有找到,那么应该回溯,并把marked[row][col]标记为false
			if(! result){
				-- printlength;
				//marked矩阵表示每个格子是否进入路径,因为(row,col)附近的四个点都不能被加入路径,故回溯,printlength--
				marked[row][col] = false;
			}
		}
		return result;
	}
}

79. 单词搜索

解法:和上题一样:

class Solution {
    public static int[][] moves = {{1,0},{-1,0},{0,1},{0,-1}};
    public boolean exist(char[][] board, String word) {
        if(word == null) {
            return true;
        }else if(board.length == 0 || (board.length == 1 && board[0].length == 0)){
            return false;
        }else {
            boolean[][] visited = new boolean[board.length][board[0].length];
            boolean answer;
            //下面是不对的,关键是第二个for循环,多加一个条件,导致j无法正常++,for循环详解,因此要把word.charAt(0) == board[i][j]放在循环体里
            //这个判断的本意是在board的(i,j)处开始回溯,要是word.charAt(0) != board[i][j]就没有必要回溯,因为一定不匹配
            /*
            for(int i = 0;i < board.length;i ++) {
                for(int j = 0;j < board[0].length && word.charAt(0) == board[i][j];j ++) {
                    answer = backtrack(i,j,board,visited,word,0,board.length,board[0].length);
                    if(answer) {
                        return true;
                    }
                }
            }
            */
            for(int i = 0;i < board.length;i ++) {
                for(int j = 0;j < board[0].length;j ++) {
                    if(word.charAt(0) == board[i][j]) {
                        answer = backtrack(i,j,board,visited,word,0,board.length,board[0].length);
                        if(answer) {
                            return true;
                        }
                    }
                    
                }
            }
            return false;
        }
    }
    public boolean backtrack(int row,int col,char[][] board,boolean[][] visited,String word,int index,int rows,int cols) {
        if(index == word.length()) {
            return true;
        }else {
            boolean answer = false;
            if(row >= 0 && row < rows && col >= 0 && col < cols && (! visited[row][col]) && board[row][col] == word.charAt(index)) {
                visited[row][col] = true;
                index ++;
                
                for(int[] array : moves) {
                    //System.out.println(array[0]);
                    //System.out.println(array[1]);
                    answer = answer || backtrack(row + array[0],col + array[1],board,visited,word,index,rows,cols);
                }
                
                index --;
                visited[row][col] = false;
            }
            return answer;
        }
    }
    /*
    public boolean judge(int row,int col,char[][] board) {
        if(row >= 0 && row < rows && col >= 0 && col < cols) {
            return true;
        }else {
            return false;
        }
    }
    */
}

面试题13. 机器人的运动范围

解法:回溯法。需要注意的是回溯法的主体函数的一般写法,

class Solution {
    public int movingCount(int m, int n, int k) {
        if(m <= 0 || n <= 0 || k < 0){
        	return 0;
        }
        boolean[][] marked = new boolean[m][n];
        for(int i =0;i < m;i ++){
        	Arrays.fill(marked[i], false);
        }
        //由于规定了从(0,0)出发,因此不同于面试题12. 矩阵中的路径,从任意一点出发
        return getlength(m, n, 0, 0, k, marked);
    }
	private int getlength(int rows,int cols,int row,int col,int k,boolean[][] marked){
		int length = 0;
		if(row >= 0 && row < rows && col >= 0 && col < cols && sum(row, col) <= k && ! marked[row][col]){
			marked[row][col] = true;
			//System.out.println(row + "纵坐标" + col);
			//由于问的是机器人能够到达多少个格子,
			//而不是机器人能到达的最长路径有多少个格子,因此下面是相加的关系而不是取最大值的关系
                        //并且不需要标记数组,就是marked或者visited数组
			length = 1 + getlength(rows, cols, row - 1, col, k, marked)
					+ getlength(rows, cols, row + 1, col, k, marked)
					+ getlength(rows, cols, row, col - 1, k, marked)
					+ getlength(rows, cols, row, col + 1, k, marked);
		}
		return length;
	}
	public int sum(int row,int col){
		int ans = 0;
		//如果是大于等于零会死循环
		while(row > 0){
			ans += row % 10;
			row = row / 10;
		}
		while(col > 0){
			ans += col % 10;
			col = col / 10;
		}
		return ans;
	}
}

1219. 黄金矿工 

解法:回溯法。首先,不同的路径是有交叉的,因此marked数组必须复位,那么什么时候复位?主函数在二重循环中每一个点搜索完路径之后?这是不可以的,因为搜索路径的时候,可能会把一个点周围的四个点对应的marked都设置为true,这在一次搜索的过程中会引发错误,因此应该在搜索路径的每一步执行之后,立刻复位。也可以不用marked数组,将matrix的数值存到tem中,并将matrix相应位置置空,并且在搜索路径一步之后,在恢复matrix相应位置的值。

class Solution {
    public int getMaximumGold(int[][] matrix) {
		if(matrix.length == 0 || matrix == null || (matrix.length == 1 && matrix[0].length == 0)){
			return 0;
		}
		int maxlength = 0;
		int rows = matrix.length;
		int cols = matrix[0].length;
		int[][] lengths = new int[rows][cols];
		//boolean[][] marked = new boolean[rows][cols];
		for(int i = 0;i < rows;i ++){
			for(int j = 0;j < cols;j ++){
				if(matrix[i][j] != 0){
                    lengths[i][j] = getLongestPath(matrix, rows, cols, i, j);
                }
				//因为各个路径可能有交叉,所以得重置marked数组,但是这写的位置是不对的
				/*
                for(int x = 0;x < marked.length;x ++){
					Arrays.fill(marked[x], false);
				}
                */
			}
		}
		for(int i = 0;i < rows;i ++){
			for(int j = 0;j < cols;j ++){
				if(lengths[i][j] > maxlength){
					maxlength = lengths[i][j];
				}
				System.out.println(lengths[i][j]);
			}
		}
		return maxlength;
    }
	public int getLongestPath(int[][] matrix,int rows,int cols,int row,int col){
		int length = 0;
		
		//int[][] plus = {{0,1},{1,0},{0,-1},{-1,0}};
		if(row >= 0 && row < rows && col >= 0 && col < cols && matrix[row][col] != 0){
			//marked[row][col] = true;
			int tem = matrix[row][col];
			matrix[row][col] = 0;
			length += tem;
			/*
			for(int i = 0;i < 4;i ++){
				int newrow = row + plus[i][0];
				int newcol = col + plus[i][1];
				length += getLongestPath(matrix, rows, cols, newrow, newcol, marked);
			}
			*/
			int right = getLongestPath(matrix, rows, cols, row + 1, col);
			int left = getLongestPath(matrix, rows, cols, row - 1, col);
			int up = getLongestPath(matrix, rows, cols, row, col - 1);
			int down = getLongestPath(matrix, rows, cols, row, col + 1);
			length += Math.max(right, Math.max(left, Math.max(up, down)));
			/*
			length += Math.max(Math.max(getLongestPath(matrix, rows, cols, row + 1, col, marked)
					, getLongestPath(matrix, rows, cols, row - 1, col, marked))
					, Math.max(getLongestPath(matrix, rows, cols, row, col + 1, marked)
					, getLongestPath(matrix, rows, cols, row, col - 1, marked)));
			
			*/
			matrix[row][col] = tem;
		}
		return  length;
	}
}
class Solution {
    public int getMaximumGold(int[][] matrix) {
		if(matrix.length == 0 || matrix == null || (matrix.length == 1 && matrix[0].length == 0)){
			return 0;
		}
		int maxlength = 0;
		int rows = matrix.length;
		int cols = matrix[0].length;
		int[][] lengths = new int[rows][cols];
		boolean[][] marked = new boolean[rows][cols];
		for(int i = 0;i < rows;i ++){
			for(int j = 0;j < cols;j ++){
				//boolean[][] marked = new boolean[rows][cols];
				lengths[i][j] = getLongestPath(matrix, rows, cols, i, j,marked);
				
				if(lengths[i][j] > maxlength){
					maxlength = lengths[i][j];
				}
				/*
				for(int x = 0;x < rows;x ++){
					for(int y = 0;y < cols;y ++){
						marked[x][y] = false;
					}
				}
				*/
				/*
				//因为各个路径可能有交叉,所以得重置marked数组
				for(int x = 0;x < marked.length;x ++){
					Arrays.fill(marked[x], false);
				}
				*/
			}
		}
		/*
		for(int i = 0;i < rows;i ++){
			for(int j = 0;j < cols;j ++){
				if(lengths[i][j] > maxlength){
					maxlength = lengths[i][j];
				}
				System.out.println(lengths[i][j]);
			}
		}
		*/
		return maxlength;
    }
	public int getLongestPath(int[][] matrix,int rows,int cols,int row,int col,boolean[][] marked){
		int length = 0;
		
		//int[][] plus = {{0,1},{1,0},{0,-1},{-1,0}};
		if(row >= 0 && row < rows && col >= 0 && col < cols && !marked[row][col] && matrix[row][col] != 0){
			marked[row][col] = true;
			//int tem = matrix[row][col];
			//matrix[row][col] = 0;
			//length += tem;
			length += matrix[row][col];
			/*
			for(int i = 0;i < 4;i ++){
				int newrow = row + plus[i][0];
				int newcol = col + plus[i][1];
				length += getLongestPath(matrix, rows, cols, newrow, newcol, marked);
			}
			*/
			int right = getLongestPath(matrix, rows, cols, row + 1, col,marked);
			int left = getLongestPath(matrix, rows, cols, row - 1, col,marked);
			int up = getLongestPath(matrix, rows, cols, row, col - 1,marked);
			int down = getLongestPath(matrix, rows, cols, row, col + 1,marked);
			length += Math.max(right, Math.max(left, Math.max(up, down)));
			/*
			length += Math.max(Math.max(getLongestPath(matrix, rows, cols, row + 1, col, marked)
					, getLongestPath(matrix, rows, cols, row - 1, col, marked))
					, Math.max(getLongestPath(matrix, rows, cols, row, col + 1, marked)
					, getLongestPath(matrix, rows, cols, row, col - 1, marked)));
			
			*/
			//matrix[row][col] = tem;
			marked[row][col] = false;
		}
		return  length;
	}
}

下面这两个我建议还是我讲比较好

剑指 Offer 51. 数组中的逆序对

解法:分治法,相当于在归并排序的merge阶段判断[left,mid]和[mid+1,right]之间的逆序对的个数。

需要tem数组暂时存储nums数组[left,right]的数据,merge进入nums数组中。

public int reversePairs(int[] nums) {
        if(nums.length <= 1) {
        	return 0;
        }else {
        	int[] tem = new int[nums.length];
        	return reversepairs(nums, 0, nums.length - 1,tem);
        }
    }
	public int reversepairs(int[] nums,int left,int right,int[] tem) {
		if(left == right){
			return  0;
		}else{
			int mid = left + (right - left) / 2;
			int leftnum = reversepairs(nums, left, mid, tem);
			int rightnum = reversepairs(nums, mid + 1, right, tem);
			if(nums[mid] <= nums[mid + 1]){
				//数组已经有序,无需merge
				return leftnum + rightnum;
			}else{
				int i = 0;
				/*
				//超时,归并排序的是[left,right],因此数组拷贝的时候只需要往tem数组中拷贝[let,right]
				for(i = 0;i < nums.length;i ++){
					tem[i] = nums[i];
				}
				*/
				for(i = left;i <= right;i ++){
					tem[i] = nums[i];
				}
                                //i,j,index的值,都是与left,right,mid有关
				i = left;
				int j = mid + 1;
				int index = left;
				int num = 0;
				//在合并阶段当[left,mid]放入nums时寻找逆序对
				while(i <= mid && j <= right){
					//当tem[i] <= tem[j]时,寻找第一个大于等于tem[i]的下标j,那么[mid+1,j - 1]即为所求
					if(tem[i] <= tem[j]){
						//[left,mid]放入nums中
						nums[index ++] = tem[i ++];
						//举例可得
						num += j - mid - 1;
					}else{
						nums[index ++] = tem[j ++];
					}
				}
				while(i <= mid){
					nums[index ++] = tem[i ++];
					num += j - mid - 1;
				}
				while(j <= right){
					nums[index ++] = tem[j ++];
				}
				
				/*
				//在合并阶段当[mid+1,right]放入nums时寻找逆序对
				while(i <= mid && j <= right){
					//是小于不是小于等于,根据题意可知
					if(tem[j] < tem[i]){
						nums[index ++] = tem[j];
						j ++;
						num += mid - i + 1;
					}else{
						nums[index ++] = tem[i];
						i ++;
					}
				}
				while(i <= mid){
					nums[index ++] = tem[i];
					i ++;
				}
				while(j <= right){
					nums[index ++] = tem[j];
					j ++;
				}
				*/
				return num + leftnum + rightnum;
			}
		}
		
	}

315. 计算右侧小于当前元素的个数

注意和找到右侧第一个大于该元素的值得题目,用的是递减栈,739每日温度

解法:分治,步骤类似于归并排序,但是是对下标进行归并排序,归并排序之后nums数组不变。

如果和上题一样进行归并排序,不好在常数时间内找到merge阶段的数在原数组的下标,因此对下标进行归并排序。

class Solution {
    int[] answer;
    public List<Integer> countSmaller(int[] nums) {
        if(nums.length == 0){
            return new LinkedList<Integer>();
        }else{
            LinkedList<Integer> list = new LinkedList<>();
            if(nums.length == 1){
                list.add(0);
                return list;
            }else{
                int[] tem = new int[nums.length];
                answer = new int[nums.length];
                int[] indexes = new int[nums.length];
                for(int i = 0;i < nums.length;i ++){
                    //待排序的数组就是indexes数组,对nums的下标进行排序
                    indexes[i] = i;
                }
                divide(nums,0,nums.length - 1,tem,indexes);
                for(int number : answer){
                    list.add(number);
                }
                return list;
            }
        }
    }
    //分治法+归并排序的思想
    //tem数组在合并阶段(merge)阶段使用
public void divide(int[] nums,int left,int right,int[] tem,int[] indexes){
        if(right == left){
            return;
        }else{
            int mid = left + (right - left) / 2;
            
            divide(nums,left,mid,tem,indexes);
            divide(nums,mid + 1,right,tem,indexes);
            if(nums[indexes[mid]] <= nums[indexes[mid + 1]]){
                return;
            }else{
                //[left,mid]和[mid+1,right]的merge需要使用tem数组
                int i = 0;
                int j = 0;
                //int index = 0;
                int index = left;
                for(i = left;i <= right;i ++){
                    tem[i] = indexes[i];
                }
                i = left;
                j = mid + 1;
                while(i <= mid && j <= right){
                    if(nums[tem[i]] <= nums[tem[j]]){
                        indexes[index ++] = tem[i];
                        answer[tem[i]] += j - mid - 1;
                        i ++;
                    }else{
                        indexes[index ++] = tem[j ++];
                    }
                }
                while(i <= mid){
                    indexes[index ++] = tem[i];
                    answer[tem[i]] += j - mid - 1;
                    i ++;
                }
                while(j <= right){
                    indexes[index ++] = tem[j ++];
                }
            }
        }
        
    }
    /*
    int[] answer;
    int[] indexes;
    public List<Integer> countSmaller(int[] nums) {
        if(nums.length == 0){
            return new LinkedList<Integer>();
        }else{
            LinkedList<Integer> list = new LinkedList<>();
            if(nums.length == 1){
                list.add(0);
                return list;
            }else{
                int[] tem = new int[nums.length];
                answer = new int[nums.length];
                indexes = new int[nums.length];
                divide(nums,0,nums.length - 1,tem);
                for(int number : answer){
                    list.add(number);
                }
                return list;
            }
        }
    }
    //分治法+归并排序的思想
    //tem数组在合并阶段(merge)阶段使用
    public void divide(int[] nums,int left,int right,int[] tem){
        if(right == left){
            return;
        }else{
            int mid = left + (right - left) / 2;
            
            divide(nums,left,mid,tem);
            divide(nums,mid + 1,right,tem);
            if(nums[mid] <= nums[mid + 1]){
                return;
            }else{
                //[left,mid]和[mid+1,right]的merge需要使用tem数组
                int i = 0;
                int j = 0;
                //int index = 0;
                int index = left;
                for(i = left;i <= right;i ++){
                    tem[i] = nums[i];
                    indexes[i] = i;
                }
                i = left;
                j = mid + 1;
                while(i <= mid && j <= right){
                    if(tem[i] <= tem[j]){
                        nums[index ++] = tem[i];
                        //不对,不能在常数时间找到tem[i]在排序之前的数组对应的下标
                        answer[i] += j - mid - 1;
                        i ++;
                    }else{
                        nums[index ++] = tem[j ++];
                    }
                }
                while(i <= mid){
                    nums[index ++] = tem[i];
                    answer[i] += j - mid - 1;
                    i ++;
                }
                while(j <= right){
                    nums[index ++] = tem[j ++];
                }
            }
        }
        
    }
    */
}

932. 漂亮数组

解法:分治法,漂亮数组的性质:一个漂亮数组所有项同时乘以一个数或者加上一个数,这个数组还是漂亮数组

以下是先把奇数组成的漂亮数组放在前面,当然也可以把偶数组成的漂亮数组放在前面。那么为什么将N中的奇偶数分开讨论呢,因为下面参考链接的一句话,类似于{1,5,3}是漂亮数组,{2,4}是漂亮数组,那么他俩拼起来还是漂亮数组,无非就是拼起来之后,一个数的二倍是否等于奇数加上偶数,奇数加上偶数等于奇数,因此成立。

比如N = 5,漂亮数组应该包含1 2 3 4 5。奇数有1 3 5,因此奇数有(N + 1) / 2个,三个数的漂亮数组,比如是{1,3,2}那么将他们乘以2减去1就是{1,5,3};N等于5的时候,有两个偶数,两个数的漂亮数组是{1,2},那么N = 5的时候的偶数组成的漂亮数组是{2,4}。可以这么理解:3 = (5 + 1) / 2;那么N的3的漂亮数组的元素乘以2减去1等于N等于5的漂亮数组的元素;2 = 5 / 2,那么N=2的漂亮数组的元素乘以2就是N = 5的漂亮数组的一部分

具体解答:https://leetcode-cn.com/problems/beautiful-array/solution/piao-liang-shu-zu-by-leetcode/

import java.util.*;
import java.lang.*;
import java.math.*;
class Solution {
    HashMap<Integer,int[]> map;
    public int[] beautifulArray(int N) {
        if(N == 1){
            return new int[]{1};
        }else if(N <= 0){
            throw new IllegalArgumentException();
        }else{
            map = new HashMap<>();
            return create(N);
        }
    }
    //奇数在偶数之前(偶数在奇数之前也可以)
    public  int[] create(int N){
        if(map.containsKey(N)){
            return map.get(N);
        }
        int[] answer = new int[N];
        if(N == 1){
            answer[0] = 1;
        }else{
            int index = 0;
            //奇数有(N + 1) / 2个
            //下面是Divide和Merge阶段
            for(int number : create((N + 1) / 2)){
                //2 * N - 1不是加一
                //最大的number=(N + 1) / 2)
                answer[index ++] = number * 2 - 1;
            }
            for(int number : create(N / 2)){
                answer[index ++] = number * 2;
            }
        }
        map.put(N,answer);
        return answer;
    }
}

124. 二叉树中的最大路径和

解法:后序遍历

class Solution {
    //避免使用静态变量,应该使用实例变量,测试的时候就new一个实例,可能会串数据
    public int answer;
    public int maxPathSum(TreeNode root) {
        if(root == null){
            return 0;
        }else{
            answer = Integer.MIN_VALUE;
            oneside(root);
            return answer;
        }
    }
    //这个函数获得从root出发(包括root)的一条路径(要么往左子树要么往右子树)的最大长度
    public int oneside(TreeNode root) {
        if(root == null){
            //answer = Math.max(answer,0);
            return 0;
        }else{
            int left = Math.max(0,oneside(root.left));
            int right = Math.max(0,oneside(root.right));
            //answer记录node.val+oneside(node.left)+oneside(node.right)之和的最大值
            answer = Math.max(answer,left + right + root.val);
            return root.val + Math.max(left,right);
        }
    }

}

543. 二叉树的直径

解法:

class Solution {
    
    int length = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        if(root == null) {
            return 0;
        }else {
            oneside(root);
            //返回length不对,因为求的是边数
            //return length;
            return length - 1;
        }
    }
    public int oneside(TreeNode root) {
        if(root == null) {
            return 0;
        }else {
            int left = oneside(root.left);
            int right = oneside(root.right);
            //要是下面这样结果是0
            //length = Math.max(length,left + right);
            //return Math.max(left,right);
            length = Math.max(length,left + right + 1);
            return Math.max(left,right) + 1;
        }
    }
}

99. 恢复二叉搜索树

解法:中序遍历二叉树,在中序遍历序列中,后面的数如果小于前面的数,那么这两个数对应的节点,可能要交换;也可能存在两个这种情况(后面的数如果小于前面的数),那么第一个情况,前面的数和第二种情况后面的数交换

class Solution {
    //中序遍历BST,获得的序列应该升序
    //pre表示BST中序遍历的当前节点的前一个节点
    public TreeNode prenode;
    //first是第一个出错的节点
    public TreeNode first;
    //end是第二个出错的节点
    public TreeNode end;
    public void recoverTree(TreeNode root) {
        find(root);
        if(first != null && end != null){
            int tem = first.val;
            first.val = end.val;
            end.val = tem;
        }
    }
    public void find(TreeNode root) {
        //实例变量的初始化就是null,每次调用都赋值为null显然会出错
        //prenode = null;
        if(root != null){
            find(root.left);
            if(prenode != null && prenode.val > root.val){
                //情况一,可能只有一对前面的数大于后面的数
                if(first == null){
                    first = prenode;
                    end = root;
                }else{
                    //情况二,第二对前面的数大于后面的数
                    end = root;
                }
            }
            prenode = root;
            find(root.right);
        }
        //不能在find函数中交换,因为可能是情况一导致first和end都不为空
        /*
        if(first != null && end != null){
            int tem = first.val;
            first.val = end.val;
            end.val = tem;
        }
        */
    }
}

46. 全排列

解法:回溯法。回溯法的一般模板:https://labuladong.gitbook.io/algo/suan-fa-si-wei-xi-lie/hui-su-suan-fa-xiang-jie-xiu-ding-ban

一般track都用LinkedList或者ArrayDeque,因为回溯的时候还要删除加进去的元素,这两个类都可以把待添加的元素加到数据结构的尾部,调用removeLast()方法删除,但是ArrayList没有removeLast()方法,因此track不用ArrayList这个数据结构。

LinkedList<Integer> ll = new LinkedList<>();

List<Integer> list = new ArrayList/LinkedList(ll);

ArrayDeque<Integer> queue = new ArrayDeque<>();

List<Integer> list = new ArrayList/LinkedList(queue);

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> permute(int[] nums) {
        if(nums.length == 0){
            return null;
        }else{
            answer = new LinkedList<>();
            backtrack(nums,new LinkedList<Integer>());
            return answer;
        }
        
    }
    //回溯对应树搜索的DFS
    //backtrack(路径,选择列表)
    //本题中,path就是路径,选择列表在nums之中,需要判断
    public void backtrack(int[] nums,LinkedList<Integer> path) {
        if(nums.length == path.size()) {
            //必须为深拷贝
            //浅拷贝的话,path后面可能会改变
            answer.add(new LinkedList(path));
        }else {
            /*
            for(选项:选择列表){
                选择
                进入下一层决策树
                回溯
            }
            */
            for(int i = 0;i < nums.length;i ++){
                //path中不含有nums[i],那么nums[i]就在选择列表之中
                if( ! path.contains(nums[i])){
                    //选择
                    path.add(nums[i]);
                    //进入下一层决策树
                    backtrack(nums,path);
                    //回溯
                    path.removeLast();
                }
            }
            /*
            for(int i = 0;i < nums.length;i ++){
                if( ! path.contains(nums[i])){
                    path.add(nums[i]);
                }
                break;
            }
            backtrack(nums,path);
            path.removeLast();
            */
        }
    }
}

47. 全排列 II

有重复,先进行排序,然后再回溯的过程剪枝,建议不看我下面的使用HashMap去重复。

解法:回溯法 + HashMap去重复

class Solution {
    LinkedList<List<Integer>> answer;
    public List<List<Integer>> permuteUnique(int[] nums) {
        answer = new LinkedList<>();
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i ++){
            if(map.containsKey(nums[i])){
                map.put(nums[i],map.get(nums[i]) + 1);
            }else{
                map.put(nums[i],1);
            }
        }
        backtrack(nums,map,new LinkedList<Integer>());
        return answer;
    }
    public void backtrack(int[] nums,Map<Integer,Integer> map,LinkedList<Integer> path){
        if(path.size() == nums.length){
            answer.add(new LinkedList(path));
        }else{
            for(int key : map.keySet()){
                //if(! path.contains(key) && map.get(key) > 0)
                //这个条件不对,含有也可以往里面加
                if(map.get(key) > 0){
                    //选择
                    path.add(key);
                    map.put(key,map.get(key) - 1);
                    //下一层决策树
                    backtrack(nums,map,path);
                    //回溯
                    path.removeLast();
                    map.put(key,map.get(key) + 1);
                }
            }
        }
    }
}

解法: 排序剪枝+visited

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> permuteUnique(int[] nums) {
        if(nums.length == 0) {
            return null;
        }else {
            Arrays.sort(nums);
            answer = new ArrayList<>();
            boolean[] visited = new boolean[nums.length];
            backtrack(nums,new LinkedList<Integer>(),visited);
            return answer;
        }
    }
    public void backtrack(int[] numbers,LinkedList<Integer> list,boolean[] visited) {
        //2.停止递归条件
        if(list.size() == numbers.length) {
            answer.add(new LinkedList(list));
        }else {
            for(int i = 0;i < numbers.length;i ++) {
                //3.numbers[i]被使用了,剪枝
                if(visited[i]) {
                    continue;
                }
                //4.画出树搜索的图,两个字符相同,只有前一个字符被使用了,在下一层决策树中,后一个相同的才可以使用
                if(i > 0 && (! visited[i - 1]) && numbers[i] == numbers[i - 1]) {
                    continue;
                }
                //5.做出一个选择
                list.addLast(numbers[i]);
                visited[i] = true;
                //6.进入更深一层的决策树(搜索树)
                backtrack(numbers,list,visited);
                //7.回溯
                list.removeLast();
                visited[i] = false;
            }
        }
    }
}

剑指 Offer 38. 字符串的排列 

解法1:将字符串转换为对应的整数数组,排列剪枝

class Solution {
    List<String> answer;
    public String[] permutation(String s) {
        if(s.length() == 0) {
            return null;
        }else {
            int[] numbers = new int[s.length()];
            boolean[] visited = new boolean[s.length()];
            //1.把字符数组转化为整数数组,使用排序剪枝法
            for(int i = 0;i < numbers.length;i ++) {
                numbers[i] = (int) s.charAt(i);
            }
            Arrays.sort(numbers);
            answer = new ArrayList<>();
            backtrack(numbers,new LinkedList<Character>(),visited);
            return answer.toArray(new String[answer.size()]);
        }
    }
    public void backtrack(int[] numbers,LinkedList<Character> list,boolean[] visited) {
        //2.停止递归条件
        if(list.size() == numbers.length) {
            StringBuilder sb = new StringBuilder();
            for(int i = 0;i < list.size();i ++) {
                sb.append(list.get(i));
            }
            answer.add(sb.toString());
        }else {
            for(int i = 0;i < numbers.length;i ++) {
                //3.numbers[i]被使用了,剪枝
                if(visited[i]) {
                    continue;
                }
                //4.画出树搜索的图,两个字符相同,只有前一个字符被使用了,在下一层决策树中,后一个相同的才可以使用
                if(i > 0 && (! visited[i - 1]) && numbers[i] == numbers[i - 1]) {
                    continue;
                }
                //5.做出一个选择
                list.add((char) numbers[i]);
                visited[i] = true;
                //6.进入更深一层的决策树(搜索树)
                backtrack(numbers,list,visited);
                //7.回溯
                list.removeLast();
                visited[i] = false;
            }
        }
    }
}

93. 复原IP地址

解法:回溯法,注意剪枝操作,剪枝操作一般借助于break,continue,return来完成

这个题目中的剪枝操作:选出来的数字等于0,那么就要进行剪枝,因为没有先导0;选出来的数字大于255或者小于0要进行剪枝

backtrack函数各个参数的定义详见注释

class Solution {
    List<String> answer;
    public List<String> restoreIpAddresses(String s) {
        answer = new LinkedList<String>();
        if(s.length() < 4 || s.length() > 12){
            return answer;
        }else{
            backtrack(s,0,new LinkedList<String>(),0);
            return answer;
        }
    }
    //index表示遍历到s的哪个下标
    //counter表示,目前选出来几个数字
    public void backtrack(String s,int index,LinkedList<String> track,int counter){
        if(index >= s.length() && counter == 4) {
            StringBuilder sb = new StringBuilder();
            for(int i = 0;i < track.size();i ++){
                sb.append(track.get(i));
            }
            answer.add(sb.toString());
        } else {
            for(int i = 0;i <= 2;i ++){
                if(index + i < s.length()){
                    int number = Integer.parseInt(s.substring(index,index + i + 1));
                    //System.out.println(number);
                    if(number > 255){
                        //剪枝
                        continue;
                    }else if(number < 0){
                        continue;
                    }else{
                        //选择
                        counter ++;
                        index = index + i + 1;
                        StringBuilder sb = new StringBuilder();
                        sb.append(number);
                        if(counter < 4){
                            //String string = number + '.';
                            sb.append(".");
                        }
                        track.add(sb.toString());
                        //下一层决策树
                        backtrack(s,index,track,counter);
                        //回溯
                        index = index - i - 1;
                        counter --;
                        track.removeLast();
                        //剪枝操作
                        //index位置的0,只能是0,比如010010不能划分成{01,00,1,0},就是没有前导0
                        if(number == 0){
                            break;
                        }
                    }
                } 
            }
        } 
    }
}

78. 子集

解法:回溯法

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> subsets(int[] nums) {
        answer = new LinkedList<>();
        if(nums.length == 0){
            answer.add(new LinkedList<Integer>());
            return answer;
        }
        backtrack(nums,new LinkedList<Integer>(),0,0);
        return answer;
    }
    //track保存回溯的一个结果
    //height表示当前搜索树的高度,搜索树的最大高度是nums的长度
    //index表示当前遍历到nums数组的下标,nums[index]有两种选择,加入track或者不加
    public void backtrack(int[] nums,LinkedList<Integer> track,int index,int height){
        //这里条件一开始写错了,写成height == 3
        //决策树的最大高度就是nums的长度
        if(height == nums.length){
            answer.add(new LinkedList(track));
        }else if(index < nums.length){
            for(int i = 0;i < 2;i ++){
                //做出选择
                if(i == 0){
                    //
                    track.add(nums[index]);
                }
                index ++;
                height ++;
                //下一层决策树
                backtrack(nums,track,index,height);
                //回溯
                height --;
                index --;
                if(i == 0){
                    track.removeLast();
                }
            }
        }
    }
}

解法2:

class Solution {
    List<List<Integer>> res;
    public List<List<Integer>> subsets(int[] nums) {
        res = new ArrayList<>();
        backtrack(0, nums, new ArrayList<Integer>());
        return res;

    }
    //i表示nums的下标
    private void backtrack(int i, int[] nums, ArrayList<Integer> tmp) {
        res.add(new ArrayList<>(tmp));
        //j = i,开始遍历,极大避免了重复
        for (int j = i; j < nums.length; j++) {
            //做出选择
            tmp.add(nums[j]);
            j ++;
            //进入下一层决策树
            backtrack(j, nums, tmp);
            //回溯
            j --;
            tmp.remove(tmp.size() - 1);
        }
    }
}

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> subsets(int[] nums) {
        answer = new LinkedList<>();
        if(nums.length == 0){
            return answer;
        }
        backtrack(nums,new LinkedList<Integer>(),0);
        return answer;
    }
    //1.track保存回溯的一个结果,index表示从nums的[index,nums.length  - 1]往后开始寻找一个作为子集的一部分
    public void backtrack(int[] nums,LinkedList<Integer> track,int index){
        /*
        //2.回溯的终止条件,应该写成下面或者不写
        if(index == nums.length) {
            return;
        }else {
        */
        if(index == nums.length + 1) {
            return;
        }else {
            //3.要想在这加入到answer中,就不能设置回溯条件成index == nums.length(),以{1,2,3}为例,index = 2的时候,把3加入list,但是只有进入下一个backtrack(...)才可以保存这个情况
            answer.add(new LinkedList(track));
            for(int i = index;i < nums.length;i ++) {
                track.addLast(nums[i]);
                //4.下面是一个错误,深一层的决策树不是从index ++开始
                //backtrack(nums,track,index);
                backtrack(nums,track,i + 1);
                track.removeLast();
                
            }
        }
    }
}
class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> subsets(int[] nums) {
        answer = new LinkedList<>();
        if(nums.length == 0){
            return answer;
        }
        backtrack(nums,new LinkedList<Integer>(),0);
        answer.add(new LinkedList<Integer>());
        return answer;
    }
    //1.track保存回溯的一个结果,index表示从nums的[index,nums.length  - 1]往后开始寻找一个作为子集的一部分
    public void backtrack(int[] nums,LinkedList<Integer> track,int index){
        if(index == nums.length) {
            return;
        }else {
            //改进方案之一是在track加入数据之后,就添加到answer中,但是不能添加空集的情况,应该另外考虑
            //answer.add(new LinkedList(track));
            for(int i = index;i < nums.length;i ++) {
                track.addLast(nums[i]);
                answer.add(new LinkedList(track));
                //下面是一个错误,深一层的决策树不是从index ++开始
                //backtrack(nums,track,index);
                backtrack(nums,track,i + 1);
                track.removeLast();
                
            }
        }
    }
}

90. 子集 II

解法:排序之后,剪枝树搜索;但是不能用HashMap,去重用排序去重

class Solution {
    //建议先看高级算法搜索树的DFS,搜索树的剪枝
    List<List<Integer>> res;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        res = new ArrayList<>();
        if(nums.length == 0){
            return res;
        }else{
            Arrays.sort(nums);
            Map<Integer,Boolean> map = new HashMap<>();
            backtrack(0, nums, new ArrayList<Integer>());
            return res;
        }
    }
    //i表示nums的下标
    private void backtrack(int i, int[] nums, ArrayList<Integer> track) {
        res.add(new ArrayList(track));
        //j = i,开始遍历,极大避免了重复
        for (int j = i; j < nums.length; j++) {
            //做出选择
            //不是j>0而是j>i
            //因为i是每一层树搜索开始的下标,j>i表示,即将加入的nums[j]不是搜索树的最左边的节点。
            if(j > i && nums[j - 1] == nums[j]){
                continue;
            }else{
                track.add(nums[j]);
                j ++;
                //进入下一层决策树
                backtrack(j, nums, track);
                //回溯
                j --;
                track.remove(track.size() - 1);
            }
            
        }
    }
}
/*
    //[[],[1],[1,2],[1,2,2],[2],[2,1],[2,1,2],[2,2],[2,2,1]]
    //以上是[1,2,2]的输出,究其原因是,每次遍历都是for(int key : map.keySet()),应该有子集1中,for(int j = i;j < nums.length;j ++)
    public void backtrack(HashMap<Integer,Integer> map,LinkedList<Integer> track){
        //answer.add(track);
        answer.add(new LinkedList(track));
        for(int key : map.keySet()){
            if(map.get(key) > 0){
                track.add(key);
                map.put(key,map.get(key) - 1);

                backtrack(map,track);

                track.removeLast();
                map.put(key,map.get(key) + 1);
            }
        }
    }
    */

77. 组合

解法:回溯。

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> combine(int n, int k) {
        answer = new LinkedList<>();
        if(k < 0 || k > n){
            throw new IllegalArgumentException();
        }else if(k == 0){
            return answer;
        }else{
            int[] nums = new int[n];
            backtrack(new LinkedList<Integer>(),k,nums,0);
            return answer;
        }
    }
    //nums[i]表示i + 1是否被加入track
    //index表示算法从nums数组的i下标开始树搜索
    public void backtrack(LinkedList<Integer> track,int k,int[] nums,int index){
        if(track.size() == k){
            //深拷贝
            answer.add(new LinkedList(track));
        }else{
            for(int i = index;i < nums.length;i ++){
                if(nums[i] == 0){
                    //做出选择
                    int tem = index;
                    nums[i] = 1;
                    track.add(i + 1);
                    index = i;
                    //进入更深一层的决策树
                    backtrack(track,k,nums,index);
                    //回溯
                    index = tem;
                    nums[i] = 0;
                    track.removeLast();
                }
            }
        }
    }   
}

面试题 16.19. 水域大小

解法:和剑指offer第13题机器人的运动范围一样

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
class Solution {
    public static int[][] move = {{0,1},{0,-1},{1,0},{-1,0},{1,1},{-1,-1},{1,-1},{-1,1}};
	ArrayList<Integer> answer;
    public int[] pondSizes(int[][] land) {
        answer = new ArrayList<>();
        if(land.length == 0 || (land.length == 1 && land[0].length == 0)){
        	int[] res = new int[0];
        	return res;
        	
        }else{
            int[][] visited = new int[land.length][land[0].length];
            LinkedList<Integer> set = new LinkedList<>();
            for(int i = 0;i < land.length;i ++){
            	for(int j = 0;j < land[0].length;j ++){
                    int number = backtrack(land, i, j, visited);
                    if(number != 0){
                        set.add(number);
                    }
                    
            	}
            }
            int[] res = new int[set.size()];
            int counter = 0;
            for(int number : set){
            	res[counter ++] = number;
            }
            Arrays.sort(res);
            return res;
        } 
    }
    //计算从land数组的(row,col)出发的池塘的数量
    public int backtrack(int[][] land,int row,int col,int[][] visited){
        if(row < 0 || row >= land.length || col < 0 || col >= land[0].length || visited[row][col] == 1 || land[row][col] != 0){
    		return 0;
    	}else {
    		if(land[row][col] == 0){
    			int max = 1;
                visited[row][col] = 1;
        		for(int[] arr : move){
                    
                    /*
                    max = Math.max(max, backtrack(land, row + arr[0], col + arr[1] , visited));
                    */
                    max = max + backtrack(land, row + arr[0], col + arr[1] , visited);
        		}
        		//很重要不要遗忘,从(row,col)出发,如果land[row][col] == 0
        		//最后的长度当然要加上1,也就是land[row][col]
        		//visited[row][col] = 0;
        		return max;
    		}else{
    			visited[row][col] = 1;
    			return 0;
    		}
    	}
    }
}

473. 火柴拼正方形

解法:回溯法,nums数组(长度大于4)中,先加入四个数到track数组(长度为4),在把后面的nums的数,以树搜索的方式加到track[i]上,不行。因为nums数组中,得随机加入track数组。

因此,定义track数组(长度为4),将nums数组中的每一个数,以树搜索的方式加入到track[i]中,但是如果加入导致track[i]大于average,就剪枝。其中average是nums数组中所有数的和的平均数,另外和不能被4整除,直接返回false

class Solution {
    boolean answer;
    public boolean makesquare(int[] nums) {
        if(nums.length < 4) {
            return false;
        }else {
            int average = 0;
            for(int i = 0;i < nums.length;i ++){
                average += nums[i];
            }
            
            if(average % 4 != 0){
                return false;
            }else{
                //average = average / nums.length;
                average = average / 4;
                for(int i = 0;i < nums.length;i ++){
                    if(nums[i] > average){
                        return false;
                    }
                    
                }
                //answer = false;
                Arrays.sort(nums);
                backtrack(nums.length - 1,nums,new int[4],average);
                return answer;
            }
        }
    }
    //倒序遍历nums,index表示即将要判断nums[index]要加入到track[i](i属于[0,3])
    public void backtrack(int index,int[] nums,int[] track,int average) {
        //剪枝操作1,如果找到一个,就没有必要继续找了
        if(answer){
            return;
        }else if(index == -1){
            //index == -1,证明dfs结束
            answer = (average == track[0] 
            && average == track[1]
            && average == track[2]
            && average == track[3]);
            return;
        }else{
            for(int i = 0;i < track.length;i ++){
                //剪枝操作2:把nums[indedx]放到track[i]之后,如果大于average,剪枝
                if(track[i] + nums[index] > average){
                    continue;
                }else{
                    //做出选择
                    int tem = track[i];
                    track[i] = track[i] + nums[index];
                    index --;
                    //进入更深一层的决策树
                    backtrack(index,nums,track,average);
                    //回溯
                    index ++;
                    track[i] = tem;
                }
            }
        }
    }
}

103. 二叉树的锯齿形层次遍历

解法:ArrayDueue是一种双端队列,既可以模拟栈(addLast(),pollLast()或者addFirst()pollLast())又可以模拟队列(addLast(),pollFirst()或者addFirst(),pollLast()),因此按照层次遍历的大概思路,每次入双端队列,都让同一层的节点从从左到右存储在双端队列中,奇数层,从队头出双端队列(子节点加到队尾),偶数层从队尾出双端队列(子节点加到队头)。

另外,LinkedList也可以模拟双端队列。

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> answer = new LinkedList<>();
        if(root == null){
            return answer;
        }else{
            Deque<TreeNode> queue = new ArrayDeque<>();
            queue.addLast(root);
            int level = 1;
            while( ! queue.isEmpty()){
                //list记录一层的节点值
                ArrayList<Integer> list = new ArrayList<>();
                //不能用while,num记录当前队列的长度,一次性for循环出双端队列
                //然后循环中,节点出队,节点的子树入双端队列
                //while(! queue.isEmpty()){
                int num = queue.size();
                //不能直接用i < queue.size(),因为queue的长度在循环中变化
                for(int i = 0;i < num;i ++){
                    TreeNode tem = null;
                    //奇数层,队头出队,队尾入队(先左子树后右子树)
                    if(level % 2 == 1){
                        tem = queue.pollFirst();
                        list.add(tem.val);
                        if(tem.left != null){
                            queue.addLast(tem.left);
                        }
                        if(tem.right != null){
                            queue.addLast(tem.right);
                        }
                    }else{
                        //偶数层,双端队列的队尾出队,也就是模拟栈
                        //那么肯定要队头入队,为了保证层次遍历的从左到右遍历
                        //右子树先从队头入队,然后左子树
                        tem = queue.pollLast();
                        list.add(tem.val);
                        if(tem.right != null){
                            queue.addFirst(tem.right);
                        }
                        if(tem.left != null){
                            queue.addFirst(tem.left);
                        }
                    }
                }
                //for循环之后,进入下一层遍历,level++
                level ++;
                answer.add(list);
            }
            return answer;
        }
    }
}

39. 组合总和

解法:回溯,这个题和求子集不一样,因为选取的元素可以被选取多次。和40题不一样的地方就是backtrack(candidates,i,track,sum,target);40是backtrack(candidates,i + 1,track,sum,target);

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        answer = new LinkedList<>();
        if(candidates.length == 0){
            return answer;
        }else{
            backtrack(candidates,0,new LinkedList<Integer>(),0,target);
            return answer;
        }
    }
    /*
        index表示从candidates数组的index处向后DFS搜索,在数组中的搜索区间是[index,candidates.length - 1]
        track记录被选中的元素
        sum记录track中各个元素的和,判断的时候不用在遍历track得到和了
        target就是题目中的target

    */
    public void backtrack(int[] candidates,int index,LinkedList<Integer> track,int sum,int target){
        if(sum == target){
            answer.add(new LinkedList(track));
        }else if(sum > target || index >= candidates.length){
            //剪枝,sum > target加在这和加在for循环里都可以,加在for循环内部是立刻剪枝,比较快
            return;
        }else{
            for(int i = index;i < candidates.length;i ++){
                if(sum + candidates[i] > target){
                    //剪枝
                    continue;
                }else{
                    //做出选择
                    track.add(candidates[i]);
                    sum += candidates[i];
                    /*
                    index ++;

                    backtrack(candidates,index,track,sum,target);

                    index --;
                    */
                    //进入更深一层的决策树
                    //因为题目中每个元素可以被重复选取
                    //而函数的index参数表示从index开始往后判断,
                    //因此index = i
                    backtrack(candidates,i,track,sum,target);
                    //回溯
                    sum -= candidates[i];
                    track.removeLast();
                }
                
            }
        }
    }
}

40. 组合总和 II

candidates 中的每个数字在每个组合中只能使用一次。

解法:回溯,同层剪枝,排序后在搜索树中,剪去和同层上一个节点值一样的分支

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        answer = new LinkedList<>();
        if(candidates.length == 0){
            return answer;
        }else{
            Arrays.sort(candidates);
            backtrack(candidates,0,new LinkedList<Integer>(),0,target);
            return answer;
        }
    }
    /*
        index表示从candidates数组的index处向后DFS搜索,在数组中的搜索区间是[index,candidates.length - 1]
        track记录被选中的元素
        sum记录track中各个元素的和,判断的时候不用在遍历track得到和了
        target就是题目中的target

    */
    public void backtrack(int[] candidates,int index,LinkedList<Integer> track,int sum,int target){
        if(sum == target){
            answer.add(new LinkedList(track));
        }else if(sum > target || index >= candidates.length){
            //剪枝,sum > target加在这和加在for循环里都可以,加在for循环内部是立刻剪枝,比较快
            return;
        }else{
            for(int i = index;i < candidates.length;i ++){
                if(sum + candidates[i] > target){
                    //剪枝
                    continue;
                }else if(i > index && candidates[i] == candidates[i - 1]){
                    //画出来搜索树
                    //这是同层剪枝
                    continue;
                }else{
                    //做出选择
                    track.add(candidates[i]);
                    sum += candidates[i];
                    /*
                    index ++;

                    backtrack(candidates,index,track,sum,target);

                    index --;
                    */
                    //进入更深一层的决策树
                    //因为题目中每个元素不可以被重复选取
                    //而函数的index参数表示从index开始往后判断,
                    //因此index = i + 1
                    backtrack(candidates,i + 1,track,sum,target);
                    //回溯
                    sum -= candidates[i];
                    track.removeLast();
                }
                
            }
        }
    }
}

111. 二叉树的最小深度

解法:递归

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }else {
            int left = minDepth(root.left);
            int right = minDepth(root.right);
            if(root.left == null) {
                return right + 1;
            }else if(root.right == null) {
                return left + 1;
            }else {
                return Math.min(left,right) + 1;
            }
        }
    }
}

剑指 Offer 34. 二叉树中和为某一值的路径

注意:树的DFS/回溯法,模板中:判断是否满足条件的放到加入选择之后,例如以下两题,backtrack函数不是进来就判断而是选择之后在判断。

        如果在开头判断track是否可以加入answer
        //如果一个叶子节点X加入track之后,sumnow=sum,那么会继续调用backtrack(X.left,...)
        //backtrack(X.right,...)其中X.left=X.right=null
        //同一条路径会被添加两次

树的回溯问题,backtrack(TreeNode current,...)函数,进入更深一层决策树,其实应该是:

...code

TreeNode tem = current;

current = current.left;

backtrack(current,...);

current = tem;

...code

current = current.right;

backtrack(current,...);

current = tem;

可以简化为:

backtrack(current.left,...);

bakctrack(current.right,...);

解法:回溯

class Solution {
    List<List<Integer>> answer;
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        answer = new ArrayList<>();
        if(root == null) {
            return answer;
        }else {
            backtrack(root,sum,0,new LinkedList<Integer>());
            return answer;
        }
    }
    /*
    //backtrack判断是否要把root节点加入track
    public void backtrack(TreeNode root,int sum,int sumnow,LinkedList<Integer> track) {
        //不能在这判断,因为题目所求路径是从根节点到叶子节点
        //如果在开头判断track是否可以加入answer
        //如果一个叶子节点X加入track之后,sumnow=sum,那么会继续调用backtrack(X.left,...)
        //backtrack(X.right,...)其中X.left=X.right=null
        //同一条路径会被添加两次
        
        if(sumnow == sum && root == null) {
            answer.add(new LinkedList(track));
        }else if(root == null) {
            return;
        }else {
            //做出选择
            if(sumnow + root.val > sum) {
                return;
            }else {
                sumnow += root.val;
                track.add(root.val);
                backtrack(root.left,sum,sumnow,track);
                backtrack(root.right,sum,sumnow,track);
                
                sumnow -= root.val;
                track.removeLast();
            }
            
        }
    }
    */
    //node是当前遍历到的子树的根节点,sumnow表示遍历到node之前的路径和
    //track记录详细路径
    //backtrack函数就是判断把node节点的val加入track,并且sumnow += node.val
    //如果sumnow == sum并且node是叶子节点,找到一个路径
    public void backtrack(TreeNode node,int sum,int sumnow,LinkedList<Integer> track) {
        //node为空,不需要继续判断,剪枝
        if(node == null) {
            return;
        }else {
            /*
            //树节点的val有负值,因此这是无效的
            if(sumnow + node.val > sum) {
                return;
            }else {
                */
            //做出选择
            sumnow += node.val;
            track.add(node.val);
            //题目中是根节点到叶子节点的路径之和等于sum
            //因此track加入answer的条件还得判断root是不是叶子节点
            //这也是为什么answer.add(...)不在开头判断的原因,逻辑是在开头判断,node的val还没有加入track,无法判断track的最后一个节点对应的树节点是不是叶子节点
            if(sumnow == sum && node.left == null && node.right == null) {
                //深拷贝
                answer.add(new LinkedList(track));
                /*
                //下面有没有都可以
                //做出了选择就得回溯
                sumnow -= node.val;
                track.removeLast();
                //没有必要继续向下执行了
                return;
                */
            }
            //进入更深一层的决策树,哪怕node的左右子树为null
            backtrack(node.left,sum,sumnow,track);
            backtrack(node.right,sum,sumnow,track);
            //回溯
            sumnow -= node.val;
            track.removeLast();
        }
    }
}

257. 二叉树的所有路径 

解法:树的回溯

//回溯法
//注意树的回溯法,判断条件在将current加入路径之后
class Solution {
    List<String> answer;
    public List<String> binaryTreePaths(TreeNode root) {
        answer = new ArrayList<>();
        if(root == null) {
            return answer;
        }else {
            backtrack(new LinkedList<Integer>(),root);
            return answer;
        }
    }
    public void backtrack(LinkedList<Integer> list,TreeNode current) {
        if(current == null) {
            /*
            //不能在这判断,因为current为空,不能证明current的父节点是叶子节点
            StringBuilder sb = new StringBuilder();
            for(int i = 0;i < list.size();i ++) {
                sb.append(list.get(i));
            }
            answer.add(sb.toString());
            */
        }else {
            list.add(current.val);
            //树的回溯是加入之后,立刻判断
            //下面判断current是否是叶子节点
            if(current.left == null && current.right == null) {
                StringBuilder sb = new StringBuilder();
                for(int i = 0;i < list.size() - 1;i ++) {
                    sb.append(list.get(i));
                    sb.append("->");
                }
                sb.append(list.get(list.size() - 1));
                answer.add(sb.toString());
            }
            backtrack(list,current.left);
            backtrack(list,current.right);
            list.removeLast();
        }
    }
}

面试题 04.12. 求和路径

解法1:双DFS,时间复杂度高

class Solution {
    int counter;
    public int pathSum(TreeNode root, int sum) {
        if(root == null) {
            return 0;
        }else {
            preorder(root,sum);
            return counter;
        }
    }
    public void preorder(TreeNode root,int sum) {
        if(root == null) {
            return;
        }else {
            backtrack(root,0,sum);
            preorder(root.left,sum);
            preorder(root.right,sum);
        }
    }
    public void backtrack(TreeNode node,int sumnow,int sum) {
        //树的DFS,判断都加到做出选择之后
        //否则会有重复,模拟一下当node变成叶子节点的空子树的时候,就会发生重复
        /*
        if(sumnow == sum) {
            counter ++;
        }
        */
        
        if(node != null) {
            //做出选择
            sumnow += node.val;
            //判断
            if(sumnow == sum) {
                counter ++;
            }
            //更深一层的决策树
            backtrack(node.left,sumnow,sum);
            backtrack(node.right,sumnow,sum);
            //回溯
            sumnow -= node.val;
        }

    }
}

解法2:用HashMap存储前缀和

class Solution {
    //map记录的是前缀和的出现次数
    //节点的前缀和是指,从根节点到当前节点的路径的节点的val之和
    Map<Integer,Integer> map;
    public int pathSum(TreeNode root, int sum) {
        if(root == null) {
            return 0;
        }else {
            map = new HashMap<>();
            //put(0,1)是为了解决从根节点到某个节点的路径和恰好等于sum的时候
            //但是当sum等于0的时候,因为已经把0的value设置为1,因为每次都要answer --
            map.put(0,1);
            return preorder(root,sum,0);
        }
    }
    //currentvalue是遍历到node节点之前,遍历路径节点的val之和
    public int preorder(TreeNode node,int sum,int currentvalue) {
        if(node == null) {
            return 0;
        }else {
            int answer = 0;
            //做出选择
            currentvalue += node.val;
            if(map.containsKey(currentvalue)) {
                map.put(currentvalue,map.get(currentvalue) + 1);
            }else {
                map.put(currentvalue,1);
            }
            //树的回溯/DFS,判断条件不是在算法开始的时候,那也会重复
            //比如node是叶子节点,node的left和node的right的情况,
            //都会加入answer
            if(map.containsKey(currentvalue - sum)) {
                answer += map.get(currentvalue - sum);
            }
            if(sum == 0) {
                answer --;
            }
            //更深一层的决策树
            answer += preorder(node.left,sum,currentvalue);
            answer += preorder(node.right,sum,currentvalue);
            //回溯
            map.put(currentvalue,map.get(currentvalue) - 1);
            currentvalue -= node.val;
            return answer;
        }
    }
}

526. 优美的排列

class Solution {
    public int count;
    public int countArrangement(int n) {
        if(n < 0) {
            return 0;
        }else if(n == 1) {
            return 1;
        }
        int[] visited = new int[n + 1];
        backtrack(new LinkedList<Integer>(), n, visited);
        return count;
    }
    public void backtrack(LinkedList<Integer> list, int n, int[] visited) {
        if(list.size() == n) {
            count ++;
        }else {
            for(int i = 1;i <= n;i ++) {
                if(visited[i] == 0 && (list.size() == 0 || i % (list.size() + 1) == 0 || (list.size() + 1) % i == 0)) {
                    list.addLast(i);
                    visited[i] = 1;
                    backtrack(list, n, visited);
                    list.removeLast();
                    visited[i] = 0;
                }   
            }
        }
    }
}

22. 括号生成

class Solution {
    List<String> answer;
    public List<String> generateParenthesis(int n) {
        if(n == 0) {
            return null;
        }
        answer = new LinkedList<>();
        backtrack(new StringBuilder(), 0, 0, n);
        return answer;
    }
    // left是左括号用的数量,right是右括号用的数量
    public void backtrack(StringBuilder sb,int left, int right, int n) {
        if(left == n && right == n) {
            answer.add(sb.toString());
        }
        // 剪枝
        if(left< right) {
            return;
        }
        // 有效的括号以左括号开始,优先加入左括号
        if(left < n) {
            backtrack(sb.append("("), left + 1, right, n);
            // 退回,最佳答案使用String不用退回,因为对String加上括号之后是不同的对象
            sb.deleteCharAt(sb.length() - 1);
        }
        if(right < n) {
            backtrack(sb.append(")"), left, right + 1, n);
            sb.deleteCharAt(sb.length() - 1);
        }
    }
}

129. 求根节点到叶节点数字之和

class Solution {
    public int sum;
    public int sumNumbers(TreeNode root) {
        if (root == null) {
            return 0;
        }
        sum = 0;
        backtrack(root, "");
        return sum;
    }
    public void backtrack(TreeNode root, String sb) {
        if (root != null) {
            sb += root.val + "";
            if (root.left == null && root.right == null) {
                sum += Integer.parseInt(sb);
                return;
            }
            backtrack(root.left, sb);
            backtrack(root.right, sb);
        }
    }
}
class Solution {
    public int sumNumbers(TreeNode root) {
        return helper(root, 0);
    }
    public int helper(TreeNode root, int sum) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return sum * 10 + root.val;
        }
        return helper(root.left, sum * 10 + root.val) + helper(root.right, sum * 10 + root.val);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值