文章目录
-
- 1.组合问题(同一个数字不可重复被选取)
-
-
- [1.组合总和 III](#1.组合总和 III)
- 2.电话号码的字母组合
-
- 2.组合问题(同一个数字可以无限制被选取)
- 3.组合问题(组合去重)
- 4.组合问题(扩展)
- 5.子集问题
- 6.全排列问题
- 7.其他
1.组合问题(同一个数字不可重复被选取)
1.组合总和 III
1.答案
java
package com.sunxiansheng.arithmetic.day5;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 216. 组合总和 III
*
* @Author sun
* @Create 2025/1/7 10:01
* @Version 1.0
*/
public class t216 {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> list = new LinkedList<>();
backtrace(1, list, res, 0, n, k);
return res;
}
public static void backtrace(int startIndex, LinkedList<Integer> path, List<List<Integer>> res, int sum, int n, int k) {
// 退出条件
if (sum > n || path.size() > k) {
return;
}
// 如果符合条件,就记录结果
if (sum == n && path.size() == k) {
res.add(new LinkedList<>(path));
return;
} else {
for (int i = startIndex; i <= 9; i ++ ) {
// 加入到路径中
path.add(i);
// 递归
backtrace(i + 1, path, res, sum + i, n, k);
// 回溯
path.removeLast();
}
}
}
}
2.思路
核心就是参数i从startIndex开始传递状态,如果同一个元素不可重复被选取,就传递i + 1
2.电话号码的字母组合
1.答案
java
package com.sunxiansheng.arithmetic.day5;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Description: 17. 电话号码的字母组合
*
* @Author sun
* @Create 2025/1/7 10:19
* @Version 1.0
*/
public class t17 {
// 静态初始化映射表
private static final Map<Character, String> PHONE_MAP = new HashMap<>();
static {
PHONE_MAP.put('2', "abc");
PHONE_MAP.put('3', "def");
PHONE_MAP.put('4', "ghi");
PHONE_MAP.put('5', "jkl");
PHONE_MAP.put('6', "mno");
PHONE_MAP.put('7', "pqrs");
PHONE_MAP.put('8', "tuv");
PHONE_MAP.put('9', "wxyz");
}
public List<String> letterCombinations(String digits) {
List<String> digitsList = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return digitsList;
}
// 将String转换为list
for (int i = 0; i < digits.length(); i++) {
digitsList.add(PHONE_MAP.get(digits.charAt(i)));
}
List<String> result = new ArrayList<>();
backtrace(0, result, new StringBuilder(), digitsList);
return result;
}
public static void backtrace(int startIndex, List<String> res, StringBuilder path, List<String> digitsList) {
if (path.length() == digitsList.size()) {
// 记录结果
res.add(path.toString());
return;
} else {
char[] charArray = digitsList.get(startIndex).toCharArray();
for (int i = 0; i < charArray.length; i++) {
// 加入路径
path.append(charArray[i]);
// 递归
backtrace(startIndex + 1, res, path, digitsList);
// 回溯
path.deleteCharAt(path.length() - 1);
}
}
}
}
2.思路
这里的核心就是startIndex指的是字符串数组的下标
2.组合问题(同一个数字可以无限制被选取)
1.组合总和
1.答案
java
package com.sunxiansheng.arithmetic.day5;
import java.util.ArrayList;
import java.util.List;
/**
* Description: 39. 组合总和
*
* @Author sun
* @Create 2025/1/7 10:35
* @Version 1.0
*/
public class t39 {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
backtrace(0, res, new ArrayList<>(), 0, candidates, target);
return res;
}
public static void backtrace(int startIndex,List<List<Integer>> res, List<Integer> path, int sum, int[] candidates, int target) {
if (sum > target) {
return;
}
if (sum == target) {
// 记录结果
res.add(new ArrayList<>(path));
return;
} else {
for (int i = startIndex; i < candidates.length; i++) {
// 加入路径
path.add(candidates[i]);
// 递归
backtrace(i, res, path, sum + candidates[i], candidates, target);
// 回溯
path.remove(path.size() - 1);
}
}
}
}
2.思路
还是从startIndex去循环的传递状态,只不过传递的startIndex变成了i,而不是i+1,这样就表示可以选择重复元素
3.组合问题(组合去重)
1. 组合总和 II
1.答案
java
package com.sunxiansheng.arithmetic.day5;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 40. 组合总和 II
*
* @Author sun
* @Create 2025/1/7 10:51
* @Version 1.0
*/
public class t40 {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
// 先排序
Arrays.sort(candidates);
List<List<Integer>> res = new LinkedList<>();
backtrace(0, res, new LinkedList<>(), 0, candidates, target);
return res;
}
public static void backtrace(int startIndex, List<List<Integer>> res, LinkedList<Integer> path, int sum, int[] candidates, int target) {
if (sum == target) {
// 记录结果
res.add(new LinkedList<>(path));
return;
} else {
for (int i = startIndex; i < candidates.length; i++) {
// 去除同一列的重复
if (i > startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
// 加入路径
path.add(candidates[i]);
// 递归
backtrace(i + 1, res, path, sum + candidates[i], candidates, target);
// 回溯
path.removeLast();
}
}
}
}
2.思路
核心就在于去重的逻辑,这样就不会有重复数组了
if (i > startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
4.组合问题(扩展)
1.分割回文串
1.答案
java
package com.sunxiansheng.arithmetic.day5;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 131. 分割回文串
*
* @Author sun
* @Create 2025/1/7 11:31
* @Version 1.0
*/
public class t131 {
public List<List<String>> partition(String s) {
List<List<String>> res = new LinkedList<>();
backtrace(0, res, new LinkedList<>(), s);
return res;
}
public static void backtrace(int startIndex, List<List<String>> res, LinkedList<String> path, String s) {
if (startIndex == s.length()) {
res.add(new LinkedList<>(path));
return;
} else {
for (int i = startIndex; i < s.length(); i++) {
// 只要不是回文就跳过
String substring = s.substring(startIndex, i + 1);
if (!palindrome(substring)) {
continue;
}
// 放到路径中
path.add(s.substring(startIndex, i + 1));
// 递归
backtrace(i + 1, res, path, s);
// 回溯
path.removeLast();
}
}
}
/**
* 判断回文
*
* @param s
* @return
*/
private static boolean palindrome(String s) {
int left = 0;
int right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
}
2.思路
就是通过startIndex先获取到子串,然后判断如果是回文再加入路径,最后进行递归
2.复原 IP 地址
1.答案
java
package com.sunxiansheng.arithmetic.day6;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 93. 复原 IP 地址
*
* @Author sun
* @Create 2025/1/8 09:29
* @Version 1.0
*/
public class t93 {
public List<String> restoreIpAddresses(String s) {
List<String> res = new LinkedList<>();
backtrace(0, res, new LinkedList<>(), s);
return res;
}
public static void backtrace(int startIndex, List<String> res, LinkedList<String> path, String s) {
if (path.size() == 4 && startIndex == s.length()) {
// 构造结果
StringBuilder sb = new StringBuilder();
for (String str : path) {
sb.append(str);
sb.append(".");
}
sb.deleteCharAt(sb.length() - 1);
res.add(sb.toString());
} else {
for (int i = startIndex; i < s.length(); i++) {
// 如果path的长度已经为4了,那么也不合法
if (path.size() >= 4) {
return;
}
// 获取到这个数
String num = s.substring(startIndex, i + 1);
// 如果不合法,直接返回
if (!judgment(num)) {
return;
}
// 如果合法,则加入路径
path.addLast(num);
// 递归
backtrace(i + 1, res, path, s);
// 回溯
path.removeLast();
}
}
}
/**
* 判断是否合法
* @return
*/
private static boolean judgment(String num) {
// 1.数字大于255
if (Integer.parseInt(num) > 255) {
return false;
}
// 2.有前导零
if (num.length() > 1 && num.charAt(0) == '0') {
return false;
}
// 3.有特殊符号
for (char c : num.toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
}
2.思路
参数,循环,如果判断合法就传递状态!
5.子集问题
1.子集
1.答案
java
package com.sunxiansheng.arithmetic.day6;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 78. 子集
*
* @Author sun
* @Create 2025/1/8 10:12
* @Version 1.0
*/
public class t78 {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new LinkedList<>();
// 空集
res.add(Collections.emptyList());
backtrace(0, res, new LinkedList<>(), nums);
return res;
}
public static void backtrace(int startIndex, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
if (startIndex == nums.length) {
return;
} else {
for (int i = startIndex; i < nums.length; i++) {
// 加入路径
path.add(nums[i]);
// 加入结果
res.add(new LinkedList<>(path));
// 递归传递状态
backtrace(i + 1, res, path, nums);
// 回溯
path.removeLast();
}
}
}
}
2.思路
子集就是每一次加入路径就直接记录结果
2.子集 II
1.答案
java
package com.sunxiansheng.arithmetic.day6;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 90. 子集 II
*
* @Author sun
* @Create 2025/1/8 10:20
* @Version 1.0
*/
public class t90 {
public List<List<Integer>> subsetsWithDup(int[] nums) {
// 排序
Arrays.sort(nums);
List<List<Integer>> res = new LinkedList<>();
// 空集
res.add(Collections.emptyList());
backtrace(0, res, new LinkedList<>(), nums);
return res;
}
public static void backtrace(int startIndex, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
if (startIndex == nums.length) {
return;
} else {
for (int i = startIndex; i < nums.length; i++) {
// 去重
if (i > startIndex && nums[i] == nums[i - 1]) {
continue;
}
path.add(nums[i]);
res.add(new LinkedList<>(path));
backtrace(i + 1, res, path, nums);
path.removeLast();
}
}
}
}
2.思路
去重问题,首先一定要排序,然后在判断合法性的位置上去重即可
6.全排列问题
1.全排列
1.答案
java
package com.sunxiansheng.arithmetic.day6;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 46. 全排列
*
* @Author sun
* @Create 2025/1/8 10:31
* @Version 1.0
*/
public class t46 {
public static List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new LinkedList<>();
backtrace(new boolean[nums.length], res, new LinkedList<>(), nums);
return res;
}
public static void backtrace(boolean[] visited, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
if (path.size() == nums.length) {
// 记录结果
res.add(new LinkedList<>(path));
return;
} else {
for (int i = 0; i < nums.length; i++) {
// 如果访问过就跳过
if (visited[i]) {
continue;
}
// 没有访问过就加入路径并传递状态
path.add(nums[i]);
visited[i] = true;
backtrace(visited, res, path, nums);
// 回溯
path.removeLast();
visited[i] = false;
}
}
}
}
2.思路
使用visited数组来记录访问状态,在判断合法性的位置进行判断即可
2.全排列 II
1.答案
java
package com.sunxiansheng.arithmetic.day6;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* Description: 47. 全排列 II
*
* @Author sun
* @Create 2025/1/8 10:44
* @Version 1.0
*/
public class t47 {
public static List<List<Integer>> permuteUnique(int[] nums) {
// 排序
Arrays.sort(nums);
List<List<Integer>> res = new LinkedList<>();
backtrace(new boolean[nums.length], res, new LinkedList<>(), nums);
return res;
}
public static void backtrace(boolean[] visited, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
if (path.size() == nums.length) {
res.add(new LinkedList<>(path));
return;
} else {
for (int i = 0; i < nums.length; i++) {
// 合法性判断
// 1.访问过了,直接退出
if (visited[i]) {
continue;
}
// 2.如果当前元素跟前一个元素一样,并且没有被访问过,也要去重
if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
continue;
}
// 递归传递状态
path.add(nums[i]);
visited[i] = true;
backtrace(visited, res, path, nums);
// 回溯
path.removeLast();
visited[i] = false;
}
}
}
}
2.思路
跟全排列一,不同的是在合法性判断里面多加了一个去重条件:如果当前元素跟前一个元素一样,并且没有被访问过,也要去重
7.其他
1.括号生成
1.答案
java
package com.sunxiansheng.arithmetic.day6;
import java.util.ArrayList;
import java.util.List;
/**
* Description: 22. 括号生成
*
* @Author sun
* @Create 2025/1/8 11:06
* @Version 1.0
*/
public class t22 {
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
backtrace(new StringBuilder(), res, 0, 0, n);
return res;
}
public static void backtrace(StringBuilder path, List<String> res, int left, int right, int n) {
if (path.length() == n * 2) {
// 记录结果
res.add(path.toString());
return;
} else {
// 加左括号
if (left < n) {
// 递归传递状态
path.append("(");
backtrace(path, res, left + 1, right, n);
// 回溯
path.deleteCharAt(path.length() - 1);
}
// 加右括号
if (right < left) {
// 递归传递状态
path.append(")");
backtrace(path, res, left, right + 1, n);
// 回溯
path.deleteCharAt(path.length() - 1);
}
}
}
}
2.思路
循环,合法性判断:加左括号的条件是left < n,加右括号的条件就是right < left 一旦合法了,就进行递归传递状态然后回溯即可