注意分治和快排:分治是函数名(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);
}
}