1、排列问题
问题描述
一般是给定一个集合(可能含有重复元素),计算符合条件的所有排列。
注:排列内部可能会出现相同元素,但是不能出现相同排列。
例题
幂集
class Solution {
List<List<Integer>> res;
private void backtrack(List<Integer> path,int start,int[] nums){
//1、停止条件\
res.add(new ArrayList<>(path));
//2、for循环遍历同一层的节点、递归遍历子节点
for(int i=start;i<nums.length;i++){
path.add(nums[start]);
backtrack(path,i+1,nums);
path.remove(path.size()-1);
}
}
public List<List<Integer>> subsets(int[] nums) {
res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
backtrack(path,0,nums);
return res;
}
}
class Solution46 {
List<List<Integer>> res;
int[] visited;
private void backtrack(List<Integer> path,int[]nums){
if(path.size()==nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++){
if(visited[i]==0){
visited[i]=1;
path.add(nums[i]);
backtrack(path,nums);
path.remove(path.size()-1);
visited[i]=0;
}
}
}
public List<List<Integer>> permute(int[] nums) {
res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
backtrack(path,nums);
return res;
}
}
class Solution {
List<String> res;
int[] visited;
private void backtrack(String S,StringBuilder path){
if(path.length()==S.length()){
res.add(path.toString());
return;
}
for(int i=0;i<S.length();i++){
if(visited[i]==0){
visited[i]=1;
path.append(S.charAt(i));
backtrack(S,path);
path.deleteCharAt(path.length()-1);
visited[i]=0;
}
}
}
public String[] permutation(String S) {
res=new ArrayList<>();
visited=new int[S.length()];
StringBuilder path=new StringBuilder();
backtrack(S,path);
String[] result=new String[res.size()];
for(int i=0;i<res.size();i++){
result[i]=res.get(i);
}
return result;
}
}
class Solution {
int res;
int[] visited;
private void backtrack(List<Integer> path,int n){
if(path.size()==n){
res++;
return;
}
for(int i=1;i<=n;i++){
if (visited[i] == 0&&((path.size()+1)%i==0||i%(path.size()+1)==0)) {
visited[i]=1;
path.add(i);
backtrack(path,n);
path.remove(path.size()-1);
visited[i]=0;
}
}
}
public int countArrangement(int n) {
res=0;
visited=new int[n+1];
List<Integer> path=new ArrayList<>();
backtrack(path,n);
return res;
}
}
class Solution {
List<List<Integer>> res;
int[] visited;
private void backtrack(List<Integer> path,int[] nums){
if(path.size()==nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++){
if(i>0&&nums[i]==nums[i-1]&&visited[i-1]==0)
continue;
if(visited[i]==0){
visited[i]=1;
path.add(nums[i]);
backtrack(path,nums);
path.remove(path.size()-1);
visited[i]=0;
}
}
}
public List<List<Integer>> permuteUnique(int[] nums) {
res=new ArrayList<>();
Arrays.sort(nums);
List<Integer> path=new ArrayList<>();
visited=new int[nums.length];
backtrack(path,nums);
return res;
}
}
解题方法
- 回溯函数内部for循环每次从0开始遍历,使用visited数组去除已经访问的元素。
- 若是每一个排列允许出现重复元素,则递归调用时从i开始,否则从i+1开始;
- 若是集合内有重复元素,为了避免出现重复排列,应该使用if(i>0&&nums[i]==nums[i-1]&&visited[i-1]==0)continue;跳过相同元素。
将回溯问题看作二维的递归,横方向是同一层,竖方向是同一个路径。这里我们一般都是允许同一条路径中出现重复元素,但是同一层不允许出现相同排列,因此这里使用visited[i-1]==0表示同一层中出现了相同元素,因此直接跳过即可。而visited[i-1]==1表示nums[i]和nums[i-1]位于同一条路径中。
2、组合问题
对于一组数据,改变其内部元素顺序就变成了不同排列,但是还是同一个组合。组合只考虑内部元素,而不在乎其顺序。
一般是给定集合,求出满足条件的所有组合。
例题
组合
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
class Solution77 {
List<List<Integer>> res;
private void backtrack(int n,int k,List<Integer> path,int start){
if(path.size()==k){
res.add(new ArrayList<>(path));
return;
}
//组合内部不考虑顺序
for(int i=start;i<=n;i++){
path.add(i);
backtrack(n,k,path,i+1);
path.remove(path.size()-1);
}
}
public List<List<Integer>> combine(int n, int k) {
res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
backtrack(n,k,path,1);
return res;
}
}
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
class Solution {
List<List<Integer>> res;
private void backtrack(int start,List<Integer> path,int[] candidates,int target,int sum){
if(sum==target){
res.add(new ArrayList<>(path));
return;
}
if(sum>target)return;
for(int i=start;i<candidates.length;i++){
path.add(candidates[i]);
backtrack(i,path,candidates,target,sum+candidates[i]);
path.remove(path.size()-1);
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
backtrack(0,path,candidates,target,0);
return res;
}
}
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
class Solution {
List<List<Integer>> res;
int[] visited;
private void backtrack(int[] candidates,int start,List<Integer> path,int sum,int target){
if(sum==target){
res.add(new ArrayList<>(path));
return;
}
if(sum>target)
return;
for(int i=start;i<candidates.length;i++){
if(i>0&&candidates[i]==candidates[i-1]&&visited[i-1]==0)
continue;
path.add(candidates[i]);
visited[i]=1;
sum+=candidates[i];
backtrack(candidates,i+1,path,sum,target);
sum-=candidates[i];
visited[i]=0;
path.remove(path.size()-1);
}
}
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
res=new ArrayList<>();
visited=new int[candidates.length];
Arrays.sort(candidates);
List<Integer> path=new ArrayList<>();
backtrack(candidates,0,path,0,target);
return res;
}
}
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
class Solution {
List<List<Integer>> res;
private boolean backtrack(int start,List<Integer> path,int k,int n,int sum){
if(path.size()==k&&sum==n){
res.add(new ArrayList<>(path));
return true;
}
if(path.size()<k&&sum<n){
for(int i=start;i<=9;i++){
path.add(i);
sum+=i;
backtrack(i+1,path,k,n,sum);
sum-=i;
path.remove(path.size()-1);
}
}
return false;
}
public List<List<Integer>> combinationSum3(int k, int n) {
res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
backtrack(1,path,k,n,0);
return res;
}
}
class Solution {
List<String> res;
int[] visited;
private void backtrack(String S,StringBuilder path){
if(path.length()==S.length()){
res.add(path.toString());
return;
}
for(int i=0;i<S.length();i++){
if(visited[i]==0){
visited[i]=1;
path.append(S.charAt(i));
backtrack(S,path);
path.deleteCharAt(path.length()-1);
visited[i]=0;
}
}
}
public String[] permutation(String S) {
res=new ArrayList<>();
visited=new int[S.length()];
StringBuilder path=new StringBuilder();
backtrack(S,path);
String[] result=new String[res.size()];
for(int i=0;i<res.size();i++){
result[i]=res.get(i);
}
return result;
}
}
class Solution {
public String[] permutation(String S) {
List<String> list = new ArrayList<>();
char[] arr = S.toCharArray();
Arrays.sort(arr);
boolean[] book = new boolean[arr.length];
dfs(list, new StringBuilder(), book, arr);
String[] res = new String[list.size()];
for (int i = 0; i < res.length; i++)
res[i] = list.get(i);
return res;
}
public void dfs(List<String> res, StringBuilder sb, boolean[] book, char[] arr) {
if (sb.length() == arr.length) {
res.add(sb.toString());
return;
}
for (int i = 0; i < arr.length; i++) {
if (!book[i]) {
if (i > 0 && arr[i] == arr[i - 1] && !book[i - 1])
continue;
else {
sb.append(arr[i]);
book[i] = true;
dfs(res, sb, book, arr);
book[i] = false;
sb.deleteCharAt(sb.length() - 1);
}
}
}
}
}
解题方法总结
回溯函数使用start
若是允许组合内部出现重复元素,则递归时从i开始,否则从i+1开始;
使用if(i>0&&candidates[i]==candidates[i-1]&&visited[i-1]==0) continue;去除重复组合。
3、子集问题
例题
子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
class Solution {
private List<List<Integer>> res = new ArrayList<List<Integer>>();
public List<List<Integer>> subsets(int[] nums) {
dfs(0,nums,new ArrayList<>());
return res;
}
public void dfs(int start, int[] nums, ArrayList<Integer> list){
if(!res.contains(new ArrayList<>(list))){
res.add(new ArrayList<>(list));
}
for(int i=start;i<nums.length;i++){
list.add(nums[i]);
dfs(i+1, nums, list);
list.remove(list.size()-1);
}
}
}
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
class Solution {
List<List<Integer>> res;
private void backtrack(List<Integer> path,int start,int[] nums){
res.add(new ArrayList<>(path));
for(int i=start;i<nums.length;i++){
if(i>start&&nums[i]==nums[i-1])continue;
path.add(nums[i]);
backtrack(path,i+1,nums);
path.remove(path.size()-1);
}
}
public List<List<Integer>> subsetsWithDup(int[] nums) {
res=new ArrayList<>();
Arrays.sort(nums);
List<Integer> path=new ArrayList<>();
backtrack(path,0,nums);
return res;
}
}
4、路径问题
例题
你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0。
为了使收益最大化,矿工需要按以下规则来开采黄金:
每当矿工进入一个单元,就会收集该单元格中的所有黄金。
矿工每次可以从当前位置向上下左右四个方向走。
每个单元格只能被开采(进入)一次。
不得开采(进入)黄金数目为 0 的单元格。
矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。
class Solution {
int res;
int[][] direction={{-1,0},{1,0},{0,1},{0,-1}};
int[][] visited;
private void backtrack(int sum,int[][] grid,int r,int c,int m,int n){
if(r>=m||r<0||c<0||c>=n||grid[r][c]==0||visited[r][c]==1)
{
res=Math.max(res,sum);
return;
}
sum+=grid[r][c];
visited[r][c]=1;
for(int i=0;i<4;i++){
backtrack(sum,grid,r+direction[i][0],c+direction[i][1],m,n);
}
visited[r][c]=0;
sum-=grid[r][c];
}
public int getMaximumGold(int[][] grid) {
res=0;
int m=grid.length;
int n=grid[0].length;
visited=new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]>0){
backtrack(0,grid,i,j,m,n);
}
}
}
return res;
}
}
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
class Solution {
int[][] visited;
int[][] direction={{-1,0},{1,0},{0,-1},{0,1}};
private boolean backtrack(char[][] board, String word, int index,int r,int c,int m,int n){
if(index==word.length()){
return true;
}
if(r<0||r>=m||c<0||c>=n||visited[r][c]==1||board[r][c]!=word.charAt(index))
return false;
visited[r][c]=1;
for(int i=0;i<4;i++){
if(backtrack(board,word,index+1,r+direction[i][0],c+direction[i][1],m,n))
return true;
}
visited[r][c]=0;
return false;
}
public boolean exist(char[][] board, String word) {
int m=board.length;
int n=board[0].length;
visited=new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(backtrack(board,word,0,i,j,m,n))
return true;
}
}
return false;
}
}
输入:4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
List<List<String>> res;
char[][] grid;
private boolean isValid(int r,int c,int n){
for(int i=r-1;i>=0;i--){
if(grid[i][c]=='Q')return false;
}
for(int i=r-1,j=c-1;i>=0&&j>=0;i--,j--){
if(grid[i][j]=='Q')return false;
}
for(int i=r-1,j=c+1;i>=0&&j<n;i--,j++){
if(grid[i][j]=='Q')return false;
}
return true;
}
private void backtrack(int n,int r){
if(r==n){
List<String> tmp=new ArrayList<>();
for(int i=0;i<n;i++){
tmp.add(new String(grid[i]));
}
res.add(tmp);
return;
}
for(int c=0;c<n;c++){
if(isValid(r,c,n)){
grid[r][c]='Q';
backtrack(n,r+1);
grid[r][c]='.';
}
}
}
public List<List<String>> solveNQueens(int n) {
grid=new char[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
grid[i][j] = '.';
}
}
res=new ArrayList<>(n);
backtrack(n,0);
return res;
}
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。
class Solution {
int res;
int[][] visited;
private void backtrack(int n,int r){
if(r==n){
res++;
return;
}
for(int c=0;c<n;++c){
if(isValid(r,c,n)){
visited[r][c]=1;
backtrack(n,r+1);
visited[r][c]=0;
}
}
}
private boolean isValid(int r,int c,int n){
for(int i=r-1;i>=0;i--){
if(visited[i][c]==1)return false;
}
for(int i=r-1,j=c+1;i>=0&&j<n;--i,++j){
if(visited[i][j]==1)return false;
}
for(int i=r-1,j=c-1;i>=0&&j>=0;--i,--j){
if(visited[i][j]==1)return false;
}
return true;
}
public int totalNQueens(int n) {
visited=new int[n][n];
res=0;
backtrack(n,0);
return res;
}
}
5、分割问题
例题
class Solution {
List<List<String>> res;
private void backtrack(List<String> path,String s,int l,int r){
if(path.size()==4&&l==r) {
res.add(new ArrayList<>(path));
return;
}
for(int i=l+1;i<=Math.min(l+3,r);i++){
if(s.charAt(l)=='0'&&i>l+1)
break;
int t=Integer.valueOf(s.substring(l,i));
if(t>=0&&t<=255){
path.add(s.substring(l,i));
backtrack(path,s,i,r);
path.remove(path.size()-1);
}
}
}
public List<String> restoreIpAddresses(String s) {
res=new ArrayList<>();
List<String> rr=new ArrayList<>();
if(s.length()>12)
return rr;
List<String> path=new ArrayList<>();
backtrack(path,s,0,s.length());
StringBuilder tmp=new StringBuilder();
for(int i=0;i<res.size();i++){
for(int j=0;j<res.get(i).size();j++){
tmp.append(res.get(i).get(j));
tmp.append('.');
}
rr.add(tmp.deleteCharAt(tmp.length()-1).toString());
tmp.delete(0,tmp.length());
}
//System.out.println(rr);
return rr;
}
}
class Solution {
int res;
Set<String> set;
private void backtrack(String s,int start){
if(start==s.length()){
res=Math.max(res,set.size());
return;
}
for(int i=start+1;i<=s.length();i++){
String sub=s.substring(start,i);
if(set.contains(sub)){
continue;
}
set.add(sub);
backtrack(s,i);
set.remove(sub);
}
}
public int maxUniqueSplit(String s) {
res=0;
set=new HashSet<>();
backtrack(s,0);
return res;
}
}