目录
1、 电话号码的字母组合
题目描述:
思路:首先我们看到题目,要我们得到电话号码字母组合,很容易我们就会想到用回溯算法,但关键点在于,这道题与常规的回溯算法不同(常规的回溯算法都会给定需要进行排列组合的集合)
所以我们就要进行一个转化,把题目中的2~9的按键转化成我们需要的集合,这里我们可以采用两种转化方式,一种是转化成map集合,一种是用转化为一个String[]类型的数组,两种方法没什么区别,在这里我就给大家分别来讲讲。
1、转化map集合
public List<String> letterCombinations(String digits) {
List<String> list = new ArrayList<>();
if (digits.length() == 0) return list;
Map<Character,String> map = new HashMap<>();
map.put('2', "abc");
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
backtrack(map,digits,list,0,new StringBuilder());
return list;
}
public void backtrack(Map<Character,String> map,String digits,List<String> list,int index,StringBuilder builder){
if (index == digits.length()) list.add(builder.toString());
else {
//利用递归回溯
for (int i = 0; i < map.get(digits.charAt(index)).length(); i++) {
builder.append(map.get(digits.charAt(index)).charAt(i));
backtrack(map,digits,list,index+1,builder);
builder.deleteCharAt(index); //回溯算法的精髓
}
}
}
2、转化string[]类型的数组
public List<String> letterCombinations(String digits) {
List<String> list = new ArrayList<>();
if (digits.length() == 0) return list;
String[] letters = new String[digits.length()];
for (int i = 0; i < digits.length(); i++) {
letters[i] = get(digits.charAt(i));
}
backtrack(letters,list,0,new StringBuilder());
return list;
}
public void backtrack(String[] letters,List<String> list,int index,StringBuilder builder){
if (index == letters.length) list.add(builder.toString());
else {
//利用回溯
for (int i = 0; i < letters[index].length(); i++) {
builder.append(letters[index].charAt(i));
backtrack(letters,list,index+1,builder);
builder.deleteCharAt(index); //回溯算法的精髓
}
}
}
public String get(char digit){
String result = "";
switch (digit){
case '2':
result = "abc";
break;
case '3':
result = "def";
break;
case '4':
result = "ghi";
break;
case '5':
result = "jkl";
break;
case '6':
result = "mno";
break;
case '7':
result = "pqrs";
break;
case '8':
result = "tuv";
break;
case '9':
result = "wxyz";
}
return result;
}
2、组合总和
题目描述:
思路:当一看到这种组合题的时候,我们就该第一时间想到,回溯算法。这道题就是一道变种的组合题,以往我们组合一般都是不允许出现重复元素的,所以我们要做写一些变通,引入一个循环初始值int begin放在参数列表中;
另外题目是要求在数组中组合出相加等于目标值的集合,我们就得引入一个剪支的概念了,当组合出来的值大于8时,我们立刻返回,进行剪支;
在这里,我为了减少递归的次数,进行了一个判断:如果上一层递归,结果大于target,那我直接break退出循环,不再对数组后面的数进行遍历判断(前提数组有序)
看看代码:
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> lists = new ArrayList<>();
LinkedList<Integer> list = new LinkedList<>();
Arrays.sort(candidates); //排序
backTrack(candidates,0,target,list,lists);
return lists;
}
private boolean backTrack(int[] candidates, int begin, int target, LinkedList<Integer> list, List<List<Integer>> lists) {
if (target == 0){
lists.add(new ArrayList<>(list));
return true;
}
if (target < 0) return false;
for (int i = begin; i < candidates.length; i++) {
list.add(candidates[i]);
if (backTrack(candidates,i,target-candidates[i],list,lists))
list.removeLast();
else {
list.removeLast();
//作判断,减少递归次数
break;
}
}
return true;
}
时间复杂度与 candidate 数组的值有关:
如果 candidate 数组的值都很大,target 的值很小,那么树上的结点就比较少;
如果 candidate 数组的值都很小,target 的值很大,那么树上的结点就比较多。
3、全排列
题目描述:
思路:老规矩,看到排列组合就想到回溯算法,这道题要求的是全排列,意味着我们不能像往常一样把循环起始值当作参数,我们遍历到后面的数,下次递归依旧要能够遍历前面的数,本来我们直接每次递归都从0开始循环就行,但是题目要求不能重复,这个时候大家应该会想我们怎么做能够实现呢?
这个时候我们就可以引进一个boolen类型的数组isVisited了,当数组内的第i个值加入集合了,就设置isVisited[i] = true;下次递归,入集合前,先做一个判断,前提条件是 isVisited[i] = flase;
思路清晰了,大家一起看看代码:
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
LinkedList<Integer> list = new LinkedList<>();
boolean[] isVisited = new boolean[nums.length];
backTrack(isVisited,0,nums,lists,list);
return lists;
}
public void backTrack(boolean[] isVisited, int index, int[] nums, List<List<Integer>> lists, LinkedList<Integer> list) {
if (index == nums.length) {
lists.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!isVisited[i]) {
list.add(nums[i]);
isVisited[i] = true;
backTrack(isVisited, index + 1, nums, lists, list);
isVisited[i] = false;
list.removeLast();
}
}
}
时间复杂度:就是和nums数组有关,数组越大,时间复杂度越大
4、子集
题目描述:
思路:依旧是一道组合题,题目给我们一个数组,要求我们返回该数组所以可能的子集;
可能有小伙伴不了解子集的概念,我们可以看看:如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集(就比如A[1,2],B[1,2,3],我们就称A为B的子集)
了解了子集的概念就好办了,既然要求我们找出所以可能的子集,就意味着我们进行lists.add()时,不用加任何的前提条件,每一个可能的结果都加入集合中;
咱们进入代码:
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
LinkedList<Integer> list = new LinkedList<>();
backTrack(0,nums,list,lists);
return lists;
}
public void backTrack(int begin, int[] nums, LinkedList<Integer> list, List<List<Integer>> lists){
lists.add(new ArrayList<>(list));
for (int i = begin; i < nums.length; i++) {
list.add(nums[i]);
backTrack(i+1,nums,list,lists);
list.removeLast();
}
}
时间复杂度依旧是和数组相关,数组越大, 时间复杂度越大~
持续更新中~~