文章目录
Backtracking
- 解题思路:
- 此题关键点在于理解题意
- 分钟为必须为2位数,可以包含开头为0,例如"1:2"是不合法的,应为"1:02"
- 小时位不允许开头为0,例如"01:00"是不合法的,应为"1:00"
- 代表分钟的一列有6个灯,分别是[1, 2, 4, 8, 16, 32], 表示0~59区间(分钟最大显示是59,到60为0)
- 代表小时的一列有4个灯,分别是[1, 2, 4, 8],表示0~11区间(因为小时最大是11,到12即为0点)
- 要找出指定灯数下所有可能出现的情况,可以使用回溯算法解决,比暴力解法效率要高,因为可能出现的情况较多
-
定义一个数组,初始化将小时范围和分钟范围的值全部放入
int[] arr = {1,2,4,8,1,2,4,8,16,32};
-
在backtracking的过程中我们需要知道在某个位置,当前值是小时还是分钟,因此我们定义一个方法判断是小时还是分钟
private boolean isHour(int pos) { return pos >= 0 && pos <= 3; }
-
定义backtracking方法,使用标准backtracking模板
/** arr: 需要遍历的数组 pos: 数组的索引 hour: 当前小时的值 minute: 当前分钟的值 led: 当前led数 ret: 当前的所有可能的时间列表 **/ private void backtracking(int[] arr, int pos, int hour, int minute, int led, List<String> ret) { // 递归终止条件: 当led为0时,将当前小时和分钟写入列表 if (led == 0) { // 根据上面的说明,小时最大显示11,分钟最大显示59 if (hour <= 11 && minute <= 59) { StringBuilder sb = new StringBuilder(); // 分钟的处理 sb.append(hour).append(":").append(minute <= 9 : "0" + minute : minute); ret.add(sb.toString()); } return; } // 开始遍历 for (int i = pos; i < arr.length; i ++) { // 若是小时则小时叠加当前值 if (isHour(i)) { hour += arr[i]; } else { minute += arr[i]; } // 递归pos + 1,led则-1 backtracking(arr, pos + 1, hour, minute, led - 1, ret); // 移除叠加 if (isHour(i)) { hour -= arr[i]; } else { minute -= arr[i]; } } }
-
在主方法中初始化调用backtracking
// 初始化索引pos为0,小时为0,分钟为0 backtracking(arr, 0, 0, 0, led, ret);
-
- 此题关键点在于理解题意
Leetcode 77: Combinationsmedium
- 解题思路:
- 根据题意获取关键信息
- 子数组长度为k
- 子数组中最大值不超过n
- 无需考虑排序,找到所有可能的子集
- 使用backtracking来穷举
- 定义一个数组缓存所有可能的子集,这里可以在全局定义,便于回溯时更新子集元素,无需每次都创建新的子集数组
private int[] tmp;
- 定义backtracking方法,考虑所需要的参数
- 最后的返回集合(List<List> ret)
- 子数组长度(int k)
- 数组最大值(int n)
- 索引值(int index)
private void backtracking(List<List<String>> ret, int n, int index, int k) { // 递归终止条件 // 当index到k + 1位置时结束递归,开始将当前tmp数组转化为list并插入返回的集合 if (index == k + 1) { List<Integer> subList = new ArrayList<>(); for (int i: tmp) { subList.add(i); } ret.add(subList); return; } // 定义当前索引值 int curr = (index == 1) ? 1: tmp[index - 2] + 1; // 开始遍历 for (int i = curr; i <= n; i ++) { tmp[index - 1] = i; backtracking(ret, n, index + 1, k); } }
- 最后在主方法中调用,然后返回ret集合即可
tmp = new int[k]; List<List<Integer>> ret = new ArrayList<>(); backtracking(ret, n, 1, k); return ret;
- 定义一个数组缓存所有可能的子集,这里可以在全局定义,便于回溯时更新子集元素,无需每次都创建新的子集数组
- 根据题意获取关键信息
Leetcode 46: Permutationsmedium
- 解题思路
- 全排列是比较经典的回溯算法题,穷举出集合中任意的可能排列项
-
// 主函数中我们初始化backtracking方法中的参数 // 选择链表结构,便于在递归过程中移除节点,更新操作 LinkedList<Integer> tmp = new LinkedList<>(); backtracking(nums, tmp);
- 开始进行backtracking递归
private void backtracking(int[] nums, LinkedList<Integer> tmp) { // 递归结束条件 // 当tmp的大小等于nums数组长度,说明元素都已经在tmp中填充完毕,此时将tmp添加到返回列表中 if (tmp.size == nums.length) { ret.add(new LinkedList(tmp)); return; } for (int i = 0; i < nums.length; i ++) { // 若tmp中包含nums[i],则跳过往下执行 if (tmp.contains(nums[i])) continue; // 将元素添加到tmp中 tmp.add(nums[i]); backtracking(nums, tmp); tmp.removeLast(); } }
-
- 全排列是比较经典的回溯算法题,穷举出集合中任意的可能排列项
- TODO: explanation