- 笨人是一个高考数学高分的女生,但是刷题一直效果欠佳,现在琢磨着用学高中数学的方法来学算法题,重大策略:同类题型找共同点,总结出模板以后每次套模板就好!
- 以下题目都是用同一块板子来写的(更易于套模板),思路都是回溯法
全排列
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
int len = nums.length;
if (len == 0) {
return result;
}
List<Integer> x = new ArrayList<Integer>();
boolean[] used = new boolean[len];
dfs(nums, x, len, result, used);
return result;
}
public void dfs(int[] nums, List<Integer> x, int len, List<List<Integer>> result, boolean[] used) {
if (x.size() == len) {
result.add(new ArrayList<>(x));
// 注意这个地方一定要新开宇哥ArrayList把x附进去,如果是x的话
//其实传进去的是x的一个引用,之后x再递归过程中不断变化,会让result变化
return;
}
for (int j = 0; j < len; j++) {
//直接使用一个used数组来表示是否使用(因为对应位置可以在used对应)
if (!used[j]) {
x.add(nums[j]);
used[j] = true;
dfs(nums, x, len, result, used);
x.remove(x.size() - 1);
//List如果要remove就可以直接用下标
//如果想用内容的话会返回一个Boolean值,要用多态
used[j] = false;
}
}
}}
使用used数组,在排列问题中非常有用,因为它帮助跟踪哪些元素已被使用,确保每个元素在每个排列中只使用一次。这个方法很好地解决了排列问题,其中元素顺序不同也视为不同的排列。
ip地址
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Stack;
public class Solution {
public List<String> restoreIpAddresses(String s) {
int len = s.length();
List<String> res = new ArrayList<>();
// 如果长度不够,不搜索
if (len < 4 || len > 12) {
return res;
}
//双向链表
Deque<String> path = new ArrayDeque<>(4);
//成功分割次数
int splitTimes = 0;
//len是剩余字符串的长度
dfs(s, len, splitTimes, 0, path, res);
return res;
}
/**
* 判断 s 的子区间 [left, right] 是否能够成为一个 ip 段
* 判断的同时顺便把类型转了
*
* @param s
* @param left
* @param right
* @return
*/
private int judgeIfIpSegment(String s, int left, int right) {
//-1代表失败
int len = right - left + 1;
// 大于 1 位的时候,不能以 0 开头
if (len > 1 && s.charAt(left) == '0') {
return -1;
}
// 转成 int 类型
int res = 0;
for (int i = left; i <= right; i++) {
res = res * 10 + s.charAt(i) - '0';
}
if (res > 255) {
return -1;
}
return res;
}
private void dfs(String s, int len, int split, int begin, Deque<String> path, List<String> res) {
if (begin == len) {//如果待处理的begin==len说明已经遍历完了
if (split == 4) {//遍历完乐,如果已经分割了4段了,那就OK
res.add(String.join(".", path));//把path用.来组合
}
return;
}
// 看到剩下的不够了,就退出(剪枝),len - begin 表示剩余的还未分割的字符串的位数
//剩下的位数要么一位,要么3位
if (len - begin < (4 - split) || len - begin > 3 * (4 - split)) {
return;
}
//试图分出新的一段
for (int i = 0; i < 3; i++) {
if (begin + i >= len) {
break;
}
int ipSegment = judgeIfIpSegment(s, begin, begin + i);
if (ipSegment != -1) {
// 在判断是 ip 段的情况下,才去做截取
path.addLast(ipSegment + "");
dfs(s, len, split + 1, begin + i + 1, path, res);
path.removeLast();//回溯
}
}
}
}
- 使用start来作为递归的变化索引
求子集
- 这道题方法非常简洁,使用的变量也很少,因为完全按照人脑自然求子集的方法去做,思路可以看注释
import java.util.ArrayList;
import java.util.List;
class Solution {
public static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
//直接建一个新的数组传给dfs作为(current)
dfs(nums, 0, new ArrayList<>(), result);
return result;
}
private static void dfs(int[] nums, int start, List<Integer> current, List<List<Integer>> result) {
//不需要剪枝条件,直接把current放进result,最开始是空,之后分别1,(1,2),(1,2,3)——跳出for循环后结束一次dfs,然后remove了3,2,然后(1,3)……一直往后
result.add(new ArrayList<>(current));
for (int i = start; i < nums.length; i++) {
current.add(nums[i]);
dfs(nums, i + 1, current, result);
current.remove(current.size() - 1);
}
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
List<List<Integer>> subsets = subsets(nums);
System.out.println(subsets);
}
}
- 这题很简单,几乎 不用剪枝
- 这里和上面一样都是使用地址维数变化(start+i)的形式,因为求子集和上一题一样,不关注元素的顺序
组合总和
import java.util.ArrayList;
import java.util.List;
class Solution {
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
// 返回的结果不变
List<List<Integer>> result = new ArrayList<>();
// 直接建一个新的数组传给dfs作为(current)
dfs(candidates, target, 0, new ArrayList<>(), result, 0);
return result;
}
private static void dfs(int[] candidates, int target, int start, List<Integer> current, List<List<Integer>> result,
int sum) {
// 先判断是不是已经可以加入
if (sum == target) {
result.add(new ArrayList<>(current));
return;
}
if (sum > target)
return;
// if(sum+candidates[start]>target) return;
// 开始遍历了
for (int i = start; i < candidates.length; i++) {
// 这里是试探可否加入新的
if (sum + candidates[i] > target) {
if (i == candidates.length - 1)
return;
i++;
}
current.add(candidates[i]);
sum += candidates[i];
dfs(candidates, target, i, current, result, sum);
sum -= current.get(current.size() - 1);
current.remove(current.size() - 1);
}
}
}
- 本题主要是借用sum来在递归中传下去
电话号码的字母组合
import java.util.*;
//本题最大的问题就是我以为有两层循环不知道如何使用回溯法
//其实主要需要使用回溯法(需要向下延伸的是选了List中的一个字符以后,需要看下一个数字的时候)
//其实可以使用indextOf(String,index)
class Solution {
public List<String> letterCombinations(String digits) {
Map<Character, List<Character>> hashtable = new HashMap<Character, List<Character>>();
hashtable.put('2', new ArrayList<Character>((Arrays.asList('a', 'b', 'c'))));
hashtable.put('3', new ArrayList<Character>((Arrays.asList('d', 'e', 'f'))));
hashtable.put('4', new ArrayList<Character>((Arrays.asList('g', 'h', 'i'))));
hashtable.put('5', new ArrayList<Character>((Arrays.asList('j', 'k', 'l'))));
hashtable.put('6', new ArrayList<Character>((Arrays.asList('m', 'n', 'o'))));
hashtable.put('7', new ArrayList<Character>((Arrays.asList('p', 'q', 'r', 's'))));
hashtable.put('8', new ArrayList<Character>((Arrays.asList('t', 'u', 'v'))));
hashtable.put('9', new ArrayList<Character>((Arrays.asList('w', 'x', 'y', 'z'))));
char[] s = digits.toCharArray();
int len = s.length;
List<String> result = new ArrayList<String>();
//这一段单独返回比较重要,否则过不了
if (len == 0) {
return result;
}
dfs(s, len, result, hashtable, 0, new StringBuffer());
return result;
}
public void dfs(char[] s, int len, List<String> result, Map<Character, List<Character>> hashtable, int start,
StringBuffer uu) {
// 比如说2,3,所有2代表的和所有3代表的进行组合,感觉没有剪枝条件
if (start == len) {
String q = new String(uu);
result.add(q);
return;
}
char current = s[start];// a、b、c,对a来说要进入一次回溯;
List<Character> x = hashtable.get(current);
for (int i = 0; i < x.size(); i++) {
uu.append(x.get(i));
dfs(s, len, result, hashtable, start + 1, uu);
uu.deleteCharAt(uu.length() - 1);
}
}
}
括号生成
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
class Solution {
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
if (n == 0) return result;
dfs(result, "", 0, 0, n);
return result;
}
private void dfs(List<String> result, String current, int open, int close, int max) {
if (current.length() == max * 2) {
result.add(current);
return;
}
//如果左括号的数目还小于总数,则
if (open < max) {
dfs(result, current + "(", open + 1, close, max);
}
if (close < open) {
dfs(result, current + ")", open, close + 1, max);
}
}
}