一、组合问题
1.1 组合问题(剪枝)
注意剪枝!i <= n-(k-list.size())+1
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return res;
}
private void backtracking(int n,int k,int startIndex){
if(list.size()==k){
res.add(new ArrayList(list));
return;
}
for(int i = startIndex;i<=n-(k-list.size())+1;i++ ){
list.add(i);
backtracking(n,k,i+1);
list.removeLast();
}
}
}
1.2 组合总和(一)
剪枝:①和已经大于目标值,剪
②i<=9-(k-list.size())+1
class Solution {
List<List<Integer>> res;
List<Integer> list;
int sum;
public List<List<Integer>> combinationSum3(int k, int n) {
sum = 0;
res = new ArrayList<>();
list = new ArrayList<>();
backTracking(k,n,1);
return res;
}
private void backTracking(int k,int n,int startIndex){
if(sum>n){
return;
}
if(sum==n){
if(list.size()==k){
res.add(new ArrayList(list));
}
}
for(int i = startIndex;i<=9-(k-list.size())+1;i++){
list.add(i);
sum += i;
backTracking(k,n,i+1);
sum -= i;
list.removeLast();
}
}
}
1.3 组合总和(二)
区别:没有数字个数的要求,且数字可以无限重复
什么时候需要startIndex:组合问题中,一个集合求组合问题需要,多个集合互不影响则不需要
class Solution {
List<List<Integer>> res;
List<Integer> list;
int sum;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
res = new ArrayList<>();
list = new ArrayList<>();
sum = 0;
backTracking(candidates,target,0);
return res;
}
private void backTracking(int[] candidates,int target,int startIndex){
if(sum>target){
return;
}
if(sum==target){
res.add(new ArrayList(list));
}
for(int i = startIndex;i<candidates.length;i++ ){
if(i>startIndex){
if(candidates[i]==candidates[i-1]){
continue;
}
}
list.add(candidates[i]);
sum += candidates[i];
backTracking(candidates,target,i);
sum -= candidates[i];
list.removeLast();
}
}
}
1.4 组合总和(三)
区别:元素会有重复,但要求组合不能重复
used[i - 1] == false,说明同一树层candidates[i - 1]使用过
class Solution {
List<List<Integer>> res;
List<Integer> list;
int sum;
boolean[] used;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
res = new ArrayList<>();
list = new ArrayList<>();
used = new boolean[candidates.length];
sum = 0;
backTracking(candidates,target,0);
return res;
}
private void backTracking(int[] candidates,int target,int startIndex){
if(sum>target){
return;
}
if(sum==target){
res.add(new ArrayList(list));
}
for(int i = startIndex;i<candidates.length;i++){
//相同的数字,并且前一个数字已使用过
//跳过,避免重复
if(i>0 && candidates[i]==candidates[i-1] && !used[i-1]){
continue;
}
used[i] = true;
sum += candidates[i];
list.add(candidates[i]);
backTracking(candidates,target,i+1);
sum -= candidates[i];
used[i] = false;
list.removeLast();
}
}
}
1.5 多个集合求组合
重点:每一个数字代表的是不同集合,也就是求不同集合之间的组合
class Solution {
List<String> res;
String[] letter;
StringBuilder sb;
public List<String> letterCombinations(String digits) {
res = new ArrayList<>();
sb = new StringBuilder();
letter = new String[]{"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
if(digits==null || digits.length()==0){
return res;
}
backTracking(digits,0);
return res;
}
private void backTracking(String digits,int num){
if(sb.length()==digits.length()){
res.add(sb.toString());
return;
}
String str = letter[digits.charAt(num)-'0'];
for(char c:str.toCharArray()){
sb.append(c);
backTracking(digits,num+1);
sb.deleteCharAt(sb.length()-1);
}
}
}
二、切割问题
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> list = new ArrayList<>();
public List<List<String>> partition(String s) {
backTracking(s,0);
return res;
}
private void backTracking(String s,int startIndex){
if(startIndex == s.length()){
res.add(new ArrayList(list));
}
for(int i = startIndex;i<s.length();i++){
if(isValid(s,startIndex,i)){
list.add(s.substring(startIndex,i+1));
backTracking(s,i+1);
list.removeLast();
}
}
}
private boolean isValid(String s,int start,int end){
for(int i = start,j = end;i<j;i++,j--){
if(s.charAt(i)!=s.charAt(j)){
return false;
}
}
return true;
}
}
三、子集问题
3.1 子集问题(一)
无重复元素
本题其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了,本来我们就要遍历整棵树
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backTracking(nums,0);
return res;
}
private void backTracking(int[] nums,int startIndex){
res.add(new ArrayList(list));
// if(startIndex == nums.length){
// return;
// }
for(int i = startIndex;i<nums.length;i++){
list.add(nums[i]);
backTracking(nums,i+1);
list.removeLast();
}
}
}
3.2 子集问题(二)
有重复元素
方法一:使用used数组
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
boolean[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
used = new boolean[nums.length];
backtracking(nums,0);
return res;
}
private void backtracking(int[] nums,int startIndex){
res.add(new ArrayList(list));
for(int i = startIndex;i<nums.length;i++){
//used[i-1]为false说明使用过了!
if(i>0 && nums[i]==nums[i-1] && !used[i-1]){
continue;
}
used[i] = true;
list.add(nums[i]);
backtracking(nums,i+1);
list.removeLast();
used[i] = false;
}
}
}
方法二:不使用used数组
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
backtracking(nums,0);
return res;
}
private void backtracking(int[] nums,int startIndex){
res.add(new ArrayList(list));
if(startIndex==nums.length){
return;
}
for(int i = startIndex;i<nums.length;i++){
if(i>startIndex){
if(nums[i]==nums[i-1]){
continue;
}
}
list.add(nums[i]);
backtracking(nums,i+1);
list.removeLast();
}
}
}
3.3 递增子序列
方法一:使用hashset
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums,0);
return res;
}
private void backTracking(int[] nums,int startIndex){
if(list.size()>=2){
res.add(new ArrayList(list));
}
Set<Integer> hashset = new HashSet<>();
for(int i = startIndex;i<nums.length;i++){
if(!list.isEmpty() && nums[i]<list.getLast() || hashset.contains(nums[i])){
continue;
}
hashset.add(nums[i]);
list.add(nums[i]);
backTracking(nums,i+1);
list.removeLast();
}
}
}
方法二:(优化)使用数组
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums,0);
return res;
}
private void backTracking(int[] nums,int startIndex){
if(list.size()>=2){
res.add(new ArrayList(list));
}
// Set<Integer> hashset = new HashSet<>();
int[] used = new int[201];
Arrays.fill(used,0);
for(int i = startIndex;i<nums.length;i++){
// if(!list.isEmpty() && nums[i]<list.getLast() || hashset.contains(nums[i])){
// continue;
// }
if(!list.isEmpty() && nums[i]<list.get(list.size()-1) || used[100+nums[i]]==1){
continue;
}
//hashset.add(nums[i]);
used[100+nums[i]] = 1;
list.add(nums[i]);
backTracking(nums,i+1);
list.removeLast();
}
}
}
四、排列问题
4.1 排列问题(一)
排列是有序的
特别点:①每层都是从0开始搜索,而不是从startIndex开始
②需要used数组记录list中放了什么元素
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
backTracking(nums,0);
return res;
}
private void backTracking(int[] nums,int startIndex){
if(list.size()==nums.length){
res.add(new ArrayList(list));
}
for(int i=0;i<nums.length;i++){
if(used[i]){
continue;
}
used[i]=true;
list.add(nums[i]);
backTracking(nums,i+1);
used[i]=false;
list.removeLast();
}
}
}
4.2 排列问题(二)
区别:包含重复元素【去重】
class Solution {
List<List<Integer>> res;
List<Integer> list;
boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);//注意:因为后面需要判断nums[i]==nums[i-1],故必须先给数组排序
res = new ArrayList<>();
list = new ArrayList<>();
used = new boolean[nums.length];
Arrays.fill(used,false);
backTracking(nums,0);
return res;
}
private void backTracking(int[] nums,int startIndex){
if(list.size()==nums.length){
res.add(new ArrayList(list));
}
for(int i = 0;i<nums.length;i++){
if(i>0 && nums[i]==nums[i-1] && !used[i-1]){
continue;
}
if(!used[i]){
used[i]=true;
list.add(nums[i]);
backTracking(nums,i+1);
list.remove(list.size()-1);
used[i]=false;
}
}
}
}
五、重新安排行程
class Solution {
private List<String> res = new ArrayList<>();
private Map<String,Map<String,Integer>> map;
public List<String> findItinerary(List<List<String>> tickets) {
res = new ArrayList<>();
map = new HashMap<>();
//t:把每对出发和到达机场取出来
//tmp:map后半部分的那个map
//先定义tmp,再在后面进行初始化
Map<String,Integer> tmp;
for(List<String> t:tickets){
if(map.containsKey(t.get(0))){
tmp = map.get(t.get(0));
tmp.put(t.get(1),tmp.getOrDefault(t.get(1),0)+1);
}else{
tmp = new TreeMap<>();
tmp.put(t.get(1),1);
}
map.put(t.get(0),tmp);
}
res.add("JFK");
backTracking(tickets.size());
return res;
}
private boolean backTracking(int ticketNum){
if(res.size()==ticketNum+1){
return true;
}
String last = res.getLast();
//注意大前提!出发机场应该是res的最后一个机场
if(map.containsKey(last)){
for(Map.Entry<String,Integer> target : map.get(last).entrySet()){
int count = target.getValue();
if(count>0){
//可以用
res.add(target.getKey());
target.setValue(count-1);
if(backTracking(ticketNum)){
return true;
}
target.setValue(count);
res.removeLast();
}
}
}
return false;
}
}
六、棋盘问题
6.1 N皇后问题
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chessboard = new char[n][n];
for(char[] c:chessboard){
Arrays.fill(c,'.');
}
backTracking(n,0,chessboard);
return res;
}
private List Array2List(char[][] chessboard){
List<String> list = new ArrayList<>();
for(char[] c:chessboard){
//将字符数组转换为字符串
list.add(String.copyValueOf(c));
}
return list;
}
private void backTracking(int n,int row,char[][] chessboard){
if(row==n){
res.add(Array2List(chessboard));
//return;
}
//列在变,行都是回溯的时候才加1,表示进入下一行
for(int col = 0;col<n;col++){
if(isValid(row,col,n,chessboard)){
chessboard[row][col] = 'Q';
backTracking(n,row+1,chessboard);
chessboard[row][col] = '.';
}
}
}
private boolean isValid(int row,int col,int n,char[][] chessboard){
//检查列(行不用检查,因为for循环,一次只会选一行的一个元素)
for(int i=0 ;i<row;i++){
if(chessboard[i][col]=='Q'){
return false;
}
}
//注意:检查斜对角时,应该是检查右上方和左上方,因为放置皇后是从上至下放置的,下方的位置还没有放置皇后
//45度
for(int i=row-1,j=col-1;i>=0 && j>=0;i--,j--){
if(chessboard[i][j]=='Q'){
return false;
}
}
//135度
for(int i=row-1,j=col+1;i>=0 && j<n;i--,j++){
if(chessboard[i][j]=='Q'){
return false;
}
}
return true;
}
}
6.2 解数独问题
class Solution {
public void solveSudoku(char[][] board) {
backTracking(board);
}
private boolean backTracking(char[][] board){
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]!='.'){
continue;//已有数字的,跳过
}else{
for(char k='1';k<='9';k++){
//还是要传k的,因为要判断是否已出现过k
//没出现过再把k赋值过去
if(isValid(board,i,j,k)){
board[i][j] = k;
if(backTracking(board)){
return true;
}
//回溯
board[i][j] = '.';
}
}
//9个试了都不行,返回false
return false;
}
}
}
//遍历完没有返回false,说明找到合适位置了
return true;
}
private boolean isValid(char[][] board,int row,int col,int k){
//检查行:行不变,列变
for(int i = 0;i<9;i++){
if(board[row][i]==k){
return false;
}
}
//检查列:列不变,行变
for(int i=0;i<9;i++){
if(board[i][col]==k){
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]==k){
return false;
}
}
}
return true;
}
}