77 组合
改了非常非常非常久!!不知道为什么用set去重就是没成功。在遍历的时候剪枝感觉没那么容易想到
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
ArrayList<Integer> num = new ArrayList<Integer>();
public List<List<Integer>> combine(int n, int k) {
//21
dfs(n,1,k);
return res;
}
public void dfs(int n,int index,int k){
if(num.size()==k){//长度够了
res.add(new ArrayList<>(num));
return;
}
for(int i=index;i<=n;i++){
if(num.contains(i)) continue;
num.add(i);
dfs(n,i+1,k);//注意不是index+1 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
//这样后面的元素一定比之前的元素来得大!!!!
num.remove(num.size()-1);
}
}
}
216 组合总和3
一开始看成是三数之和,后来发现可以是任意k个数量,所以还是用回溯
没想到还是一次过了
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> num = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
//44-52 三数之和 但是是k个数!!52-00
dfs(1,k,n,0);
return res;
}
public void dfs(int count,int k,int n,int sum){
if(sum>n) return;//减枝
if(num.size()==k&&sum==n){
res.add(new ArrayList<>(num));
return;
}
for(int i=count;i<=9;i++){
num.add(i);
dfs(i+1,k,n,sum+i);
num.remove(num.size()-1);
}
}
}
时间复杂度好高
17 电话号码的字母组合
一开始对映射无从下手,还在找字母和数字之间的规律,看了题解发现可以用String[]数组存,用对应数字做下标就可以方便找到。然后就是回溯的常规写法了,就是对应数字的遍历要仔细一些
class Solution {
List<String> res = new ArrayList<>();
StringBuilder tmp = new StringBuilder();
public List<String> letterCombinations(String digits) {
//30-54
if(digits.length()==0) return res;
String[] map = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
dfs(map,digits,0);
return res;
}
public void dfs(String[] map, String s, int index){
if(tmp.length()==s.length()){
res.add(new String(tmp.toString()));
return;
}
for(int i=0;i<map[s.charAt(index)-'0'].length();i++){
tmp.append(map[s.charAt(index)-'0'].charAt(i));
dfs(map,s,index+1);
tmp.deleteCharAt(tmp.length()-1);
}
}
}
39 组合总和
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> tmp = new ArrayList<Integer>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//26-37
Arrays.sort(candidates);
dfs(candidates,target,0);
return res;
}
public void dfs(int[] candidates, int target,int index){
if(target==0){
res.add(new ArrayList<Integer>(tmp));
return;
}
for(int i=index;i<candidates.length;i++){
if(candidates[i]>target) break;
tmp.add(candidates[i]);
dfs(candidates,target-candidates[i],i);//注意是i 这样可以重复使用,并且后面的都不会比之前小,不需要排序再去重
tmp.remove(tmp.size()-1);
}
}
}
40 组合总和2
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> tmp = new ArrayList<Integer>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
//10
Arrays.sort(candidates);
// List<Integer>
dfs(candidates,target,0);
return res;
}
public void dfs(int[] candidates, int target, int index){
if(target==0){
res.add(new ArrayList<Integer>(tmp));
return;
}
for(int i=index;i<candidates.length;i++){
if(candidates[i]>target) break;
//要对同一树层使用过的元素进行跳过
if(i>index&&candidates[i-1]==candidates[i]) continue;
//一开始写的是
//if(i>0&&candidates[i-1]==candidates[i]&&!tmp.contains(candidates[i-1])) continue;
tmp.add(candidates[i]);
dfs(candidates,target-candidates[i],i+1);
tmp.remove(tmp.size()-1);
}
}
}
debug了很久!!我原来的判断方式也就是注释掉的那行思路是没问题,问题在于contains函数的判断
[2,2,2] target=4这样的例子就会出来两个[2,2].因为contains判断的是值的内容!!也就是第几个2在里面都会判断出包含,而我希望不同索引的2是有区别的,所以要这样判断就要用索引去判断!!还是用答案中的方法更简洁!!!
//要对同一树层使用过的元素进行跳过
if(i>index&&candidates[i-1]==candidates[i]) continue;
131 分割回文串
感觉这题没有太简单,代码独立完成之后测试出现了问题,比如会出现b这样的答案,手动debug才改了过来(注释掉的for循环),要是有这个循环,i就有可能从中间开始,答案就不连续,改完bug总共花了半个小时,还是需要再练习
class Solution {
ArrayList<List<String>> res = new ArrayList<List<String>>();
List<String> tmp = new ArrayList<String>();
public List<List<String>> partition(String s) {
//37-08
dfs(s,0);
return res;
}
public void dfs(String s, int index){
if(index==s.length()){
res.add(new ArrayList<String>(tmp));
return;
}
// for(int i = index;i<s.length();i++){
//可以遍历的长度
int i = index;
for(int j=1;j<=s.length()-i;j++){
if(isH(s.substring(i,i+j))){
tmp.add(s.substring(i,i+j));
dfs(s,i+j);
tmp.remove(tmp.size()-1);
}
}
// }
}
//判断是否是回文串
public boolean isH(String s){
char[] c = s.toCharArray();
for(int i=0,j=c.length-1;i<=j;i++,j--){
if(c[i]!=c[j]) return false;
}
return true;
}
}
改一下用通用的写法
class Solution {
ArrayList<List<String>> res = new ArrayList<List<String>>();
List<String> tmp = new ArrayList<String>();
public List<List<String>> partition(String s) {
//37-08
dfs(s,0);
return res;
}
public void dfs(String s, int index){
if(index==s.length()){
res.add(new ArrayList<String>(tmp));
return;
}
for(int i = index;i<s.length();i++){
if(isH(s.substring(index,i+1))){
tmp.add(s.substring(index,i+1));
dfs(s,i+1);
tmp.remove(tmp.size()-1);
}
}
}
public boolean isH(String s){
char[] c = s.toCharArray();
for(int i=0,j=c.length-1;i<=j;i++,j--){
if(c[i]!=c[j]) return false;
}
return true;
}
}
93 复原ip地址
这题写了很久没写出来!!要重点关注
class Solution {
List<String> res = new ArrayList<String>();
StringBuilder tmp = new StringBuilder();
public List<String> restoreIpAddresses(String s) {
//21
dfs(s,0,0);
return res;
}
public void dfs(String s, int index, int num){//总共4段
if(index==s.length()&&num==4){
res.add(new String(tmp));
return;
}
if(index==s.length()||num==4) return; //点过多或过少都不行
for(int i=index;i<Math.min(3+index,s.length());i++){ //从index开始,最多3位
int data = Integer.parseInt(s.substring(index,i+1));
if(data>255||data<0) break;//数据范围不对直接返回
if(i-index>0&&s.charAt(index)=='0') continue;//第一位为0并且这段长度>1
tmp.append(s.substring(index,i+1));
if(num<3) tmp.append(".");//最后一段不用加点
dfs(s,i+1,num+1);
tmp.delete(index + num, tmp.length());//i + number + 2
//tmp.delete(tmp.length()-i-2+index,tmp.length());
}
}
}
78 子集
这题写得比较快,也不需要减枝
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> tmp = new ArrayList<Integer>();
public List<List<Integer>> subsets(int[] nums) {
//03-13
for(int i=0;i<=nums.length;i++){//有几位
dfs(nums,0,i);
}
return res;
}
public void dfs(int[] nums, int index, int num){
if(tmp.size()==num){
res.add(new ArrayList<Integer>(tmp));
return;
}
for(int i=index;i<nums.length;i++){
tmp.add(nums[i]);
dfs(nums,i+1,num);
tmp.remove(tmp.size()-1);
}
}
}
90 子集2
就是结合了79子集和20组合总和2,加个树层去重即可
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> tmp = new ArrayList<Integer>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
//40-46
Arrays.sort(nums);
for(int i=0;i<=nums.length;i++){
dfs(nums,0,i);
}
return res;
}
public void dfs(int[] nums, int index, int num){
if(tmp.size()==num){
res.add(new ArrayList<Integer>(tmp));
return;
}
for(int i=index;i<nums.length;i++){
if(i>index&&nums[i-1]==nums[i]) continue;
tmp.add(nums[i]);
dfs(nums,i+1,num);
tmp.remove(tmp.size()-1);
}
}
}
491 递增子序列
剪枝没想出来!!这题不能排序!!可以利用标记数组
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> tmp = new ArrayList<Integer>();
public List<List<Integer>> findSubsequences(int[] nums) {
//49
// Arrays.sort(nums);
dfs(nums,0);
return res;
}
public void dfs(int[] nums, int index){
if(tmp.size()>1)
res.add(new ArrayList<Integer>(tmp));
// 注意这里不要加return,要取树上的节点
int[] used = new int[201];//nums[i]+100 定义在dfs内同一层就会公用一个used数组,下层used数组又清零,所以实现了数层减枝,而树叶不减
Arrays.fill(used,0);
for(int i=index;i<nums.length;i++){
//if(i>index&&nums[i-1]==nums[i]) continue;
if(used[nums[i] + 100] == 1) continue;
if(tmp.size()==0||(tmp.size()>0&&nums[i]>=tmp.get(tmp.size()-1))){
used[nums[i] + 100] = 1;
tmp.add(nums[i]);
dfs(nums,i+1);
tmp.remove(tmp.size()-1);
}
}
}
}
47 有重复数字的全排列
不用set去重,直接剪枝,数组标记每个位置的数字是否被使用过而不是统计每个数字出现的次数,这样在剪枝判断的时候就可以利用上一个相同的数字是否出现过来判断。
class Solution {
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> tmp = new ArrayList<Integer>();
int[] count = new int[10];
public List<List<Integer>> permuteUnique(int[] nums) {
//59-16
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
count[i]=1;
}
dfs(nums,0);
return res;
}
public void dfs(int[] nums, int index){
if(index==nums.length){
res.add(new ArrayList<Integer>(tmp));
return;
}
for(int i=0;i<nums.length;i++){
if(i>0&&nums[i-1]==nums[i]&&count[i-1]==1) continue;
if(count[i]<=0) continue;
tmp.add(nums[i]);
count[i]=0;
dfs(nums,index+1);
tmp.remove(tmp.size()-1);
count[i]=1;
}
}
}
332 重新安排行程
没看题解真的想不到和回溯有什么关系,还是看了别人的代码再自己手打的,还是从这个代码中学到了不少,compareTo函数可以用来比较String的大小,写法和其他回溯没有什么区别,就是多用来判断如果找到了结果就返回,不用继续往下,因为先对tickets排序过了,先找到的一定是符合要求的字典序最小的,所以找到了就不用再找了
class Solution {
List<String> res = new ArrayList<String>();
List<String> path = new ArrayList<String>();
boolean used[] = new boolean[301];
public List<String> findItinerary(List<List<String>> tickets) {
//17
Collections.sort(tickets,(a,b)->a.get(1).compareTo(b.get(1)));//compareTo函数是对string的比较?
//第一个本来就是要匹配的,所以只要比较第二个
Arrays.fill(used,false);
path.add("JFK");
dfs(tickets);
return res;
}
public boolean dfs(List<List<String>> tickets){
if(path.size()==tickets.size()+1){
res = new ArrayList<String>(path);
return true;
}
for(int i=0;i<tickets.size();i++){
if(!used[i]&&path.get(path.size()-1).equals(tickets.get(i).get(0))){
path.add(tickets.get(i).get(1));
used[i] = true;
if(dfs(tickets)) return true;//找到了就不用再找
path.remove(path.size()-1);
used[i] = false;
}
}
return false;
}
}
51 N皇后
这个写法是最简洁的,特别是判断是否合法的函数,第二次写还是写错了
class Solution {
ArrayList<List<String>> res = new ArrayList<List<String>>();
List<String> tmp = new ArrayList<String>();
int[] pos = new int[9];//表示i行y列存放了
public List<List<String>> solveNQueens(int n) {
//55-13
dfs(n, 0);
return res;
}
public void dfs(int n, int index){
if(index==n){
res.add(new ArrayList<String>(tmp));
return;
}
for(int j=0;j<n;j++){//index表示的就是行,不用再一个行的for循环
if(isValid(index,j)){
pos[index] = j;
String s = "";
for(int k = 0;k<n;k++){
if(k!=j) s+=".";
else s+="Q";
}
tmp.add(new String(s));
dfs(n,index+1);
tmp.remove(tmp.size()-1);
pos[index] = 0;
}
}
}
public boolean isValid(int row, int col){
for(int i=0;i<row;i++){
//第二次写还是写错
//只需要遍历到row之前的
//对角线的判断是Math.abs(i-row)==Math.abs(col-pos[i])而不一定都等于1
if(i==row||col==pos[i]||Math.abs(i-row)==Math.abs(col-pos[i]))
return false;
}
return true;
}
}
37 解数独
没写出来,一直想在dfs里传index,发现不太行,还是需要再看一下这个写法
class Solution {
public void solveSudoku(char[][] board) {
//20
dfs(board);
}
public boolean dfs(char[][] board){
for(int row=0;row<9;row++){//遍历行
for(int col=0;col<9;col++){//遍历列
if(board[row][col]=='.'){
for(char num = '1';num<='9';num++){//遍历数字
if(isValid(board,row,col,num)){
board[row][col] = num;
if(dfs(board)) return true;
board[row][col] = '.';
}
}
return false;
}
}
}
return true;
}
public boolean isValid(char[][] board, int row, int col, char num){
//比较同行
for(int i=0;i<9;i++){
if(board[row][i]==num) return false;
}
//比较同列
for(int i=0;i<9;i++){
if(board[i][col]==num) return false;
}
//九宫格内
int startRow = (row/3)*3;
int startCol = (col/3)*3;
for(int i=startRow;i<startRow+3;i++){
for(int j =startCol;j<startCol+3;j++){
if(board[i][j]==num) return false;
}
}
return true;
}
}