赞
踩
一)字母大小写全排列
1)从每一个字符开始进行枚举,如果枚举的是一个数字字符,直接忽视
如果是字母的话,进行选择是变还是不变
2)当进行遍历到叶子结点的时候,直接将结果存放到ret里面即可
3)相对于是对于这个决策树做一个深度优先遍历
dfs函数的设计:
1)无论是数字字符还是字母,都是需要考虑变或者不变的情况
2)当当前字符是一个非数字字符,此时就需要进行改变,然后进入下一层
class Solution { List<String> ret=new ArrayList<>(); StringBuilder path=new StringBuilder(); public List<String> letterCasePermutation(String s) { char[] array=s.toCharArray(); dfs(array,0); return ret; } public void dfs(char[] array,int index){ if(path.length()==array.length){ ret.add(path.toString()); return; } if(index>=array.length) return; //选 if(array[index]>='0'&&array[index]<='9'){ path.append(array[index]); dfs(array,index+1); path.deleteCharAt(path.length()-1); }else{ path.append(array[index]); dfs(array,index+1); path.deleteCharAt(path.length()-1); if(array[index]>='a'&&array[index]<='z') path.append((char)(array[index]-32)); else path.append((char)(array[index]+32)); dfs(array,index+1); path.deleteCharAt(path.length()-1); } } }
- class Solution {
- StringBuilder path=new StringBuilder();
- List<String> ret=new ArrayList<>();
- public void dfs(char[] array,int index){
- if(path.length()==array.length){
- ret.add(path.toString());
- return;
- }
- if(array[index]>='a'&&array[index]<='z'){
- path.append((char)(array[index]-32));
- dfs(array,index+1);
- path.deleteCharAt(path.length()-1);
- path.append((char)(array[index]));
- dfs(array,index+1);
- path.deleteCharAt(path.length()-1);
- }else if(array[index]>='A'&&array[index]<='Z'){
- path.append((char)(array[index]+32));
- dfs(array,index+1);
- path.deleteCharAt(path.length()-1);
- path.append((char)(array[index]));
- dfs(array,index+1);
- path.deleteCharAt(path.length()-1);
- }else{
- path.append(array[index]);
- dfs(array,index+1);
- path.deleteCharAt(path.length()-1);
- }
- }
- public List<String> letterCasePermutation(String s) {
- char[] array=s.toCharArray();
- dfs(array,0);
- return ret;
-
- }
- }
二)优美的排列
1)每一层就是相当于是数组的最终结果的每一个位置,开始就是从第一个数开始将数组中的所有的数进行枚举一遍,只要这个数没有使用过,就可以把这个数放在这里面试一试
2)还是需要判断一下这个数能否整除下标或者是能够被下标整除,只要上面的条件有一种情况不满足,那么就进行剪枝操作;
3)函数的递归出口:当遇到叶子节点的时候,直接返回结果
1)假设现在n=3,现在我们有1 2 3这三个数字,我们需要找到这三个数字的全排列,这三个数填到三个格子里面,然后判断一下哪一种填发使这个格子是一个优美的排列;
在第一个位置枚举1 2 3三个数,在第二个位置枚举1 2 3三个数,在第三个位置枚举1 2 3三个数,重复子问题就是在特定的下标开始枚举数组中所有的数
2)本质上在第一层枚举的过程中其实已经就进行了剪枝的操作了
3)此时我们先排第一个位置,然后再排第二个位置,最后排第三个位置
dfs函数的设计:每一层在做的事情:将数组中的数从头到尾进行枚举一遍,只要这个数没有被使用过或者是这个数可以整除下标或者能被下标整除,这个数就可以存放在这里
- class Solution {
- List<List<Integer>> ret=new ArrayList<>();
- List<Integer> path=new ArrayList<>();
- boolean[] check;
- public int countArrangement(int n) {
- check=new boolean[n+1];//因为下标是从1开始所以要开辟n+1大小的数组
- dfs(n,1);//从下标是1的位置开始进行枚举
- return ret.size();
- }
- public void dfs(int n,int pos){
- if(path.size()==n){
- ret.add(new ArrayList<>(path));
- return;
- }
- for(int i=1;i<=n;i++){
- if((i%pos==0||pos%i==0)&&check[i]==false){
- path.add(i);
- check[i]=true;
- dfs(n,pos+1);//枚举下一个位置的数
- path.remove(path.size()-1);
- check[i]=false;
- }
- }
- }
- }
三)N皇后:
难点:剪枝+代码能力
第一种画决策树的方法:
先考虑第一个格子能不能进行存放,当进行考虑下一个格子的时候,是进行挨着考虑的,一个格子一个格子看看能不能进行存放
第二种画决策树的方法:是通过一行一行的来进行考虑的
此时我不是一个格子一个格子的进行考虑,这一次我一次考虑一行,我只考虑第0行的皇后应该摆放在那里,应该只考虑第一行的皇后摆放在哪里,然后第二行的皇后应该摆放在那里,第三行的皇后应该摆放在那里,下面我们进行考虑一下当N=3的时候:
1)首先我们第一步应该考虑一下,第0行的皇后可以放在哪里,这个时候又出现三种情况可以放在(0,0)可以放在(0,1),还可以放在(0,2)
2)然后再次进行考虑,当我们第0行的三个皇后已经放好的情况下,那么此时再来进行考虑第一行,正常情况下如果不出现剪枝的情况,第一行也是可以存放三个位置的分别是(1,0),(1,1)和(1,2),但是此时考虑剪枝,这个时候就出现了,某一个行上面有两个皇后,对角线上面有两个皇后
1)我们再进行画决策树的时候,先将皇后画在格子里面,再来进行判断是否要进行剪枝
2)每一层在做的事情:你给我一个行数,我在这一行内每一个格子尝试存放一个皇后,如果我能够存放,我就把这个皇后放在这里,然后进行考虑下一行,具体下一行如何放我并不关心,我只是关心的事dfs函数一定可以帮助我们搞定
3)递归出口VS收集结果:当我们枚举行数发生越界的时候,此时说明就已经得到了合法的情况,此时就可以把这个合法的情况加入到我们最终的结果中即可
如何剪枝:剪枝的目的就是考虑当前这个位置是否可以能够放上皇后
1)无脑循环:当我们进行判断这个位置是否可以放皇后的时候,先假设当前这个这个位置可以存放皇后,首先来判断这一行有没有放皇后,这一列有没有放皇后,这个皇后的左对角线有没有放皇后,这个皇后的右对角线有没有放皇后,时间复杂度是4n*2^n,某一个决策上来四层循环,在这种决策树的画法里面,我们是每一行每一行的来进行列举的,所以当我们进行列举这一行上面的三个位置的时候,是一定不会出现在这一行上面的,这一行是一定不会出现相互攻击的情况的
class Solution { List<List<String>> ret=new ArrayList<>(); public void GetString(char[][] array){ List<String> path=new ArrayList<>(); for(int i=0;i<array.length;i++){ StringBuilder result=new StringBuilder(); for(int j=0;j<array[0].length;j++){ result.append(array[i][j]); } path.add(result.toString()); } ret.add(new ArrayList<>(path)); } public void dfs(char[][] array,int row,int n){ if(row==n){ GetString(array); return; } for(int col=0;col<n;col++){ if(isVaild(array,row,col,n)==true){ array[row][col]='Q'; dfs(array,row+1,n); array[row][col]='.'; } } } public boolean isVaild(char[][] array,int row,int col,int n){ //1.先检查列 for(int i=0;i<n;i++){ if(array[i][col]=='Q') return false; } // //2.在来进行检查行 // for(int i=0;i<n;i++){ // if(array[][col]=='Q') return false; // } //3.检查45度对角线 for(int i=row,j=col;i>=0&&j>=0;i--,j--){ if(array[i][j]=='Q') return false; } //4.检查135度对角线 for(int i=row,j=col;i>=0&&j<=n-1;i--,j++){ if(array[i][j]=='Q') return false; } return true; } public List<List<String>> solveNQueens(int n) { char[][] array=new char[n][n]; int row=array.length; int col=array[0].length; for(int i=0;i<row;i++){ for(int j=0;j<col;j++){ array[i][j]='.'; } } dfs(array,0,n); return ret; } }
2)类似于哈希表的策略:使用三个数组快速解决判断行列横对角线和竖行对角线
在这里面只是需要搞一个boolean数组就可以搞定这个题了
boolean checkcol[]=new boolean[n],如果在第一列上放了一个皇后,那么只是需要让这一列变成true即可,那么就是checkcol[1]=true,说明这一列已经有皇后了,所以这个布尔数组使用布尔数组可以快速判断某一列是否有皇后了
3)判断对角线以及副对角线的策略:使用布尔数组+数学来进行解决
1)当我们的某一条线的y-x=b的时候,我们是有理由相信他这一条主对角线上面是有皇后的,因为这一条线上面的所有的纵坐标减去横坐标的值都是等于一个定值,如果对角线的boolean值是true,说明对角线上有皇后,如果这条线的boolean值是一个false,说明对角线上面没有皇后,也就是名这个点可能是可以放皇后的,但是使用y-x是不可以的
2)但是此时出现了一个致命的问题,原点下面的截距是一个负数,但是数组的下标是没有负数的,所以此时可以修改一下表达式y-x=b=>y-x+N=b+N,这样就做到了将所有的截距统一向上平移了N个单位,此时数组的下标一定是一个整数
class Solution { List<List<String>> ret=new ArrayList<>(); boolean[] checkCol; boolean[] checkZline; boolean[] checkFline; //这个函数是将字符串数组中的所有字符转化成一个String存放到哈希表中 public void GetString(char[][] array){ List<String> path=new ArrayList<>(); for(int i=0;i<array.length;i++){ StringBuilder result=new StringBuilder(); for(int j=0;j<array[0].length;j++){ result.append(array[i][j]); } path.add(result.toString()); } ret.add(new ArrayList<>(path)); } public void dfs(char[][] array,int row,int n){ if(row==n){ GetString(array); return; } for(int col=0;col<n;col++){ if(checkCol[col]==false&&checkFline[row+col]==false &&checkZline[col-row+n]==false){ checkCol[col]=true; checkFline[row+col]=true; checkZline[col-row+n]=true; array[row][col]='Q'; dfs(array,row+1,n); array[row][col]='.'; checkCol[col]=false; checkFline[row+col]=false; checkZline[col-row+n]=false; } } } public List<List<String>> solveNQueens(int n) { //1.首先定义一个二维的棋盘,并且初始化里面的字符 this.checkCol=new boolean[2*n]; this.checkZline=new boolean[2*n]; this.checkFline=new boolean[2*n]; char[][] array=new char[n][n]; int row=array.length; int col=array[0].length; for(int i=0;i<row;i++){ for(int j=0;j<col;j++){ array[i][j]='.'; } } dfs(array,0,n); return ret; } }
四)有效的数独:
1)1-9的数字只能出现一次,每一行不能出现重复的元素,每一列也是不能出现重复的元素的,况且在3X3矩阵中也是不能出现重复的元素的
2)接下来使用哈希表的思想来解决一下这个问题,创建一个row[9][10]表示的是一共有9行,里面存放10个大小范围,row[2][4]表示第二行里面是否出现了4这个数,如果这里面的值是true就表示出现过,如果是false就表示没有出现过
3)col[9][10]表示创建一个大小9列,里面存放数的大小范围就是10,col[7][9]表示第7竖列是否存在9这个数,如果是true表明出现过,如果是false表明没有出现过
4)创建一个三维数组:grad[3][3][10]
总结:所以我们只是需要创建3个布尔类型的数组就可以判断某一行是否出现过某一个数字重复,某一列是否出现过某一个数字重复,某一个小方格里面是否某一个数字出现重复,典型的是使用空间换时间的思想
class Solution { boolean[][] checkLine;//checkLine[i][data]判断第i行data这个数是否出现过 boolean[][] checkRow;//checkRow[j][data]判断第j列data这个数是否出现过 boolean[][][] checkBroad; //checkBroad[x/3][y/3][data]判断在指定的矩形范围内,data这个数是否出现过 public boolean isValidSudoku(char[][] array) { this.checkLine=new boolean[9][10]; this.checkRow=new boolean[9][10]; this.checkBroad=new boolean[3][3][10]; for(int i=0;i<9;i++){ for(int j=0;j<9;j++){ if(array[i][j]!='.'){ int data=array[i][j]-'0'; if(checkRow[i][data]==true||checkLine[j][data]==true||checkBroad[i/3][j/3][data]==true) return false; checkRow[i][data]=true; checkLine[j][data]=true; checkBroad[i/3][j/3][data]=true; } } } return true; } }
五)解数独
算法原理:
主要还是画决策树,如何不重不漏地将所有的情况枚举到,直到找到最终的正确答案
1)首先我们直接遍历这个棋盘,当遍历到某一个空位置的时候,就直接向上面进行填数,直到将这个棋盘上面的所有格子全部填满的时候,就最终找到了最终结果,想想原来在一维数组那里,无论是在子集那道题还是全排列那道题,直接考虑某一个位置,考虑当前这个位置填1填2还是填3,当前这个位置填好之后再来进行考虑下一个位置填1填2还是填3,无非就是说将原来一维数组的形式搬到二维数组里面了
2)本质上来说就是给你二维数组的一个位置,判断这个位置是否能够填写1-9的数字,如果能填写就判断下一个位置,某一个位置是可以有9个分支的,因为可以填写9个不同类型的数字,但是最终的结果一定是有一些分支要被剪掉的,我们还是像上道题那样,快速地判断出当前这个位置如果填写某一个数是否合适了;
3)如果我们在画决策树的时候发现,有一个位置1-9的数字都无法填写成了,直接向上返回个false;
class Solution { boolean[][] checkLine; boolean[][] checkRow; boolean[][][] checkBroad; public boolean dfs(char[][] board){ for(int i=0;i<9;i++){ for(int j=0;j<9;j++){ if(board[i][j]!='.') continue; else{ //直接进行填数 for(int k=1;k<=9;k++){ if(!checkLine[j][k]&&!checkRow[i][k]&&!checkBroad[i/3][j/3][k]){ board[i][j]=(char)('0'+k); checkLine[j][k]=checkRow[i][k]=checkBroad[i/3][j/3][k]=true; if(dfs(board)) return true; //dfs函数是继续去填下一个位置,如果下一个位置填写失败,dfs(board)返回false,说明下一个位置填写123456789都是不行的 //开始去尝试下一个数 checkLine[j][k]=checkRow[i][k]=checkBroad[i/3][j/3][k]=false; board[i][j]='.'; } } //说明1-9数字都不行 return false; } } } //经过这两层for循环之后,程序既没有返回true也没有返回false,说明这个表格恰好填写完成,此时返回一个true即可 return true; } public void solveSudoku(char[][] board) { //1.先进行初始化操作 this.checkLine=new boolean[9][10]; this.checkRow=new boolean[9][10]; this.checkBroad=new boolean[3][3][10]; //2.提前给这些数组进行赋值 for(int i=0;i<9;i++) for(int j=0;j<9;j++) if(board[i][j]!='.'){ int data=board[i][j]-'0'; checkLine[j][data]=true; checkRow[i][data]=true; checkBroad[i/3][j/3][data]=true; } //3.进行设计dfs函数 dfs(board); } }
1)本体求的是解数独问题,此时只是需要返回一个结果即可,之前的回溯算法的题目都是组合都是有多个结果,我们需要用一个结果集收集我们的所有结果然后返回即可,那些题目都是有多个结果,多个结果意味着这些结果都是散落在树形结构里面,所以我们需要搜索所有的树形结构,然后最终才能把我们想要的结果全部放入到结果集里面,最后返回即可,只要有一个解决方案,那么立即就返回
2)我们只是需要搜索到一个最终结果,其他树枝不搜了,所以本题的返回类型是一个布尔类型,只是做一个标记,这样才能在树枝上找到结果之后这样才能告诉上一层找到结果立即返回,想上一层一层进行返回,其他数层都不用搜索了,所以说搜索整个树形结构使用void,搜索单个树枝就使用布尔;
3)就比如说路经总和那道题,我们只是需要找到某一条路径和等于目标值即可,找到单个结果就立即返回,因为没有必要遍历所有的节点
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。