例题一:组合总数
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
分析:
问题:所有组合和为target的组合排列
所有组合排列,不能重复,同一个数字可重复选取
思路一:dfs
- 对整数数组排升序
- 从低位选取
- nums[i] <target :选用 dfs(target-nums[i])
- 否则不选用,并剪枝(排升序)
- 组合不可重复,数字可重复选取
- 从选取的低位开始遍历,防止组合重复,满足重复选取
class Solution {
List<List<Integer>> ret = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
int len = candidates.length;
int index = 0;
List<Integer> list = new ArrayList<>();
dfs(candidates,0,target,list,index);
return ret;
}
private void dfs(int[] candidates,int start,int target,List<Integer> list,int index){
if(target==0){
ret.add(new ArrayList<Integer>(list));
return;
}
if(target<0)
return;
int len = candidates.length;
for(int i = start;i<len;i++){
if(candidates[i]<=target){
list.add(candidates[i]);
dfs(candidates,i,target-candidates[i],list,index+1);
list.remove(index);
}else{
break;
}
}
}
}
例题二:活字印刷
你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。
注意:本题中,每个活字字模只能使用一次。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-tile-possibilities
输入:"AAB" 输出:8 解释:可能的序列为 "A", "B", "AA", "AB", "BA", "AAB", "ABA", "BAA"。
分析:
所有非空字母序列数目; dfs
字模使用一次;使用过一次即标记;
可能存在重复字模;去重 set
思路一:dfs
class Solution {
HashSet<String> ret = new HashSet<>();
public int numTilePossibilities(String tiles) {
StringBuffer str = new StringBuffer();
boolean[] flag = new boolean[tiles.length()];
dfs(tiles,str,0,flag);
return ret.size();
}
private void dfs(String tiles,StringBuffer str,int len,boolean[] flag){
if(len!=0)
ret.add(str.toString());
for(int i =0;i<tiles.length();i++){
//这个字符用过了,剪枝
if(flag[i])
continue;
flag[i] = true;
str.append(tiles.charAt(i));
dfs(tiles,str,len+1,flag);
//回溯
flag[i] = false;
str.deleteCharAt(str.length()-1);
}
}
}
思路二:计数,dfs
class Solution {
public int numTilePossibilities(String tiles) {
// 统计每一种字母出现的次数
int[] map = new int[26];
for(int idx = 0; idx < tiles.length(); ++idx){
++map[tiles.charAt(idx) - 'A'];
}
return traverse(map);
}
// 计算由当前map中所有字母可以组合的字母序列数目
private int traverse(int[] map){
int sum = 0;
// 遍历每一种字母
for(int idx = 0; idx < map.length; ++idx){
if(map[idx] > 0){
++sum; // 表示以当前字母结尾的情况
--map[idx];
sum += traverse(map); // 表示使用idx位置字母的情况下,剩余字母有多少种组合
++map[idx];
}
}
return sum;
}
}
思路三:剪枝思路不同
public class Solution {
/*
* 使用DFS回溯的思路,每新增一个结果就将答案加一,每一层中不能使用相同的元素,这一点可以通过回溯判断
* */
int ans;
char[] s;
public int numTilePossibilities(String tiles) {
s = tiles.toCharArray();
Arrays.sort(s); //排序将所有使得所有相同字符在数组中连续出现
boolean[] used = new boolean[s.length]; //used用来记录某个字符是否被使用过
DFS(used);
return ans;
}
public void DFS(boolean[] used) {
char last = '*'; //在选择某一层的第一个字符时,将last设置为*,保证任意未使用过的字符均可被选择
for (int i = 0; i < s.length; i++) {
if (!used[i] && s[i] != last) {
//只有被未被选择过并且不同于上一轮回溯使用的字符才会在同一层中被遍历
ans++;
used[i] = true;
DFS(used); //继续DFS遍历
used[i] = false; //回溯
last = s[i]; //回溯后更新last
}
}
}
}