题目链接:https://leetcode.cn/problems/non-decreasing-subsequences/description/
文档讲解:https://programmercarl.com/0491.%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.html
状态:已完成
思路:本题考察的是在无序数组中可能有重复元素的情况下,如何避免结果中出现重复的递增子序列。已知,避免重复的关键是跳过同一层递归中的重复元素。通常,通过对数组排序来简单跳过重复元素,然而,本题求解的递增子序列必须基于原数组获得,因此不能使用数组排序实现跳过重复元素的逻辑,只能使用Set记录已遍历的元素。
时间复杂度:递增子序列数量上限接近于 2 n 2^n 2n,如果将子序列的构建成本简化为 O ( N ) O(N) O(N),那么最坏情况下的时间复杂度为 O ( N ∗ 2 N ) O(N*2^N) O(N∗2N)
空间复杂度: O ( N ) O(N) O(N)
// 关键考点:数组中可能有重复元素,如何避免结果中出现重复的递增子序列?
// 同一层递归跳过重复元素
class Solution {
List<List<Integer>> result;
public List<List<Integer>> findSubsequences(int[] nums) {
result = new ArrayList();
Set<Integer> set = new HashSet();
for(int i=0;i<nums.length-1;i++){
if(set.contains(nums[i]))
continue;
else
set.add(nums[i]);
List<Integer> tmpList = new ArrayList();
tmpList.add(nums[i]);
backtrack(i+1, nums, tmpList);
}
return result;
}
public void backtrack(int idx, int[] nums, List<Integer> tmpList){
if(tmpList.size()>=2)
result.add(new ArrayList(tmpList));
Set<Integer> set = new HashSet();
for(int i=idx;i<nums.length;i++){
if(set.contains(nums[i]))
continue;
else
set.add(nums[i]);
if(nums[i] >= tmpList.get(tmpList.size()-1)){
tmpList.add(nums[i]);
backtrack(i+1, nums, tmpList);
tmpList.remove(tmpList.size()-1);
}
}
}
}
题目链接:https://leetcode.cn/problems/permutations/
文档讲解:https://programmercarl.com/0046.%E5%85%A8%E6%8E%92%E5%88%97.html
状态:已完成
思路:使用visited数组标记当前元素的选择情况,在每轮递归中遍历所有未被选择的元素,因此无需传递startIdx
时间复杂度: O ( N ∗ N ! ) O(N*N!) O(N∗N!)
空间复杂度: O ( N ) O(N) O(N)
class Solution {
List<List<Integer>> result;
public List<List<Integer>> permute(int[] nums) {
result = new ArrayList();
boolean[] visited = new boolean[nums.length];
backtrack(nums, visited, new ArrayList());
return result;
}
public void backtrack(int[] nums, boolean[] visited, List<Integer> tmpList){
if(tmpList.size() == nums.length){
result.add(new ArrayList(tmpList));
return;
}
for(int i=0;i<nums.length;i++){
if(visited[i])
continue;
tmpList.add(nums[i]);
visited[i] = true;
backtrack(nums, visited, tmpList);
visited[i] = false;
tmpList.remove(tmpList.size()-1);
}
}
}
题目链接:https://leetcode.cn/problems/permutations-ii/description/
文档讲解:https://programmercarl.com/0047.%E5%85%A8%E6%8E%92%E5%88%97II.html
状态:已完成
思路:
// 数组排序+同层跳重复元素
class Solution {
List<List<Integer>> result;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums); // O(nlogn)
result = new ArrayList();
boolean[] visited = new boolean[nums.length];
backtrack(nums, visited, new ArrayList());
return result;
}
public void backtrack(int[] nums, boolean[] visited, List<Integer> tmpList){
if(tmpList.size() == nums.length){
result.add(new ArrayList(tmpList));
return;
}
int last = -100;
for(int i=0;i<nums.length;i++){
if(visited[i] || nums[i] == last)
continue;
visited[i] = true;
tmpList.add(nums[i]);
backtrack(nums, visited, tmpList);
tmpList.remove(tmpList.size()-1);
visited[i] = false;
last = nums[i];
}
}
}
题目链接:https://leetcode.cn/problems/reconstruct-itinerary/description/
文档讲解:https://programmercarl.com/0332.%E9%87%8D%E6%96%B0%E5%AE%89%E6%8E%92%E8%A1%8C%E7%A8%8B.html
状态:需二刷,如何通过设计剪枝避免超时
思路:
// 1.数组排序,先ele1后ele2,按照字典序来升序排序
// 2.针对有序数组,构建HashMap,Key是左节点,Value是右节点列表(含Boolean)
// 3.从JFK开始回溯
class Solution {
public List<String> findItinerary(List<List<String>> tickets) {
// 1.数组排序,先ele1后ele2,按照字典序来升序排序
Collections.sort(tickets, new Comparator<List<String>>(){
public int compare(List<String> l1, List<String> l2){
if(l1.get(0).equals(l2.get(0)))
return l1.get(1).compareTo(l2.get(1));
return l1.get(0).compareTo(l2.get(0));
}
});
// 2.针对有序数组,构建HashMap,Key是左节点,Value是右节点列表(含Boolean)
Map<String, List<Middle>> map = new HashMap();
for(int i=0;i<tickets.size();i++){
String start = tickets.get(i).get(0);
String end = tickets.get(i).get(1);
if(!map.containsKey(start))
map.put(start, new ArrayList());
map.get(start).add(new Middle(end));
}
List<String> path = new ArrayList();
path.add("JFK");
backtrack(path, map, tickets.size()+1);
return path;
}
public boolean backtrack(List<String> path, Map<String, List<Middle>> map, int len){
if(path.size() == len)
return true;
if(!map.containsKey(path.get(path.size()-1)))
return false;
List<Middle> tmpList = map.get(path.get(path.size()-1));
for(int i=0;i<tmpList.size();i++){
Middle middle = tmpList.get(i);
if(middle.used || (i>0 && middle.point.equals(tmpList.get(i-1).point) && !tmpList.get(i-1).used))
continue;
path.add(middle.point);
middle.used = true;
if(backtrack(path, map, len))
return true;
else{
middle.used = false;
path.remove(path.size()-1);
}
}
return false;
}
}
class Middle{
String point;
boolean used;
public Middle(){}
public Middle(String point){
this.point = point;
}
public Middle(String point, boolean used){
this.point = point;
this.used = used;
}
}
题目链接:https://leetcode.cn/problems/n-queens/
文档讲解:https://programmercarl.com/0051.N%E7%9A%87%E5%90%8E.html
状态:已完成
思路:放置规则为每个皇后不同行不同列不同斜线(正&反)
时间复杂度:如果规则为不同行不同列,那么所有可能的结果为 O ( N ! ) O(N!) O(N!),构建单个结果的时间复杂度为 O ( N ) O(N) O(N),因此总的时间复杂度为 O ( N ∗ N ! ) O(N*N!) O(N∗N!),实际的时间复杂度小于这个值
空间复杂度: O ( N ) O(N) O(N)
// 每层递归逻辑:决定第i行的皇后放置在哪一列
// 关键:记录每列放置情况,正斜线放置情况,反斜线放置情况
// 正斜线坐标映射:[i, j] ---> i+j
// 反斜线坐标映射:[i, j] ---> i+(n-1)-j
// 注:正反斜线的数组长度为2n-1
// 使用int数组记录中间结果,arr[i]表示第i行皇后所在列,初始值为-1
class Solution {
List<List<String>> result;
public List<List<String>> solveNQueens(int n) {
result = new ArrayList();
boolean[] col = new boolean[n];
boolean[] arrow = new boolean[2*n-1];
boolean[] dearrow = new boolean[2*n-1];
int[] tmpCol = new int[n];
for(int i=0;i<n;i++)
tmpCol[i] = -1;
backtrack(0, col, arrow, dearrow, tmpCol, n);
return result;
}
public void backtrack(int row, boolean[] col, boolean[] arrow, boolean[] dearrow, int[] tmpCol, int n){
if(row == n){
List<String> tmpResult = new ArrayList();
char[] res = new char[n];
for(int i=0;i<n;i++)
res[i] = '.';
for(int i=0;i<n;i++){
res[tmpCol[i]] = 'Q';
tmpResult.add(new String(res));
res[tmpCol[i]] = '.';
}
result.add(tmpResult);
return;
}
for(int i=0;i<n;i++){
if(col[i] || arrow[row+i] || dearrow[row+n-1-i])
continue;
col[i] = true;
arrow[row+i] = true;
dearrow[row+n-1-i] = true;
tmpCol[row] = i;
backtrack(row+1, col, arrow, dearrow, tmpCol, n);
tmpCol[row] = -1;
col[i] = false;
arrow[row+i] = false;
dearrow[row+n-1-i] = false;
}
}
}
题目链接:https://leetcode.cn/problems/sudoku-solver/description/
文档讲解:https://programmercarl.com/0037.%E8%A7%A3%E6%95%B0%E7%8B%AC.html
状态:需二刷,因为忘记处理某个分支导致长时间debug
思路:使用List记录每行的数字,每列的数字,每格的数字集合,通过回溯寻找唯一解
// List> 记录每行的数字,每列的数字,每格的数字
// 格的坐标映射:(i,j) ---> (i/3)*3+(j/3)
// 每层递归逻辑:确定(i,j)的数字
// 一旦抵达坐标(9,0),说明找到唯一解,需要一路返回,因此回溯算法的返回值类型为boolean
// 注:对于分支 board[x][y] != '.',直接返回回溯算法即可,不要忘记处理这个分支
class Solution {
public void solveSudoku(char[][] board) {
List<List<Integer>> row = new ArrayList();
List<List<Integer>> col = new ArrayList();
List<List<Integer>> grid = new ArrayList();
for(int i=0;i<9;i++){
row.add(new ArrayList());
col.add(new ArrayList());
grid.add(new ArrayList());
}
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
row.get(i).add(board[i][j] - '0');
col.get(j).add(board[i][j] - '0');
grid.get((i/3)*3+(j/3)).add(board[i][j] - '0');
}
}
backtrack(0, 0, board, row, col, grid);
}
public boolean backtrack(int x, int y, char[][] board, List<List<Integer>> row, List<List<Integer>> col, List<List<Integer>> grid){
if(y == 9){
x++;
y = 0;
}
if(x == 9)
return true;
if(board[x][y] != '.')
return backtrack(x, y+1, board, row, col, grid);
else{
for(int i=1;i<=9;i++){
if(row.get(x).contains(i) || col.get(y).contains(i) || grid.get((x/3)*3+(y/3)).contains(i))
continue;
board[x][y] = (char)('0'+i);
row.get(x).add(i);
col.get(y).add(i);
grid.get((x/3)*3+(y/3)).add(i);
if(backtrack(x, y+1, board, row, col, grid))
return true;
grid.get((x/3)*3+(y/3)).remove(grid.get((x/3)*3+(y/3)).size()-1);
col.get(y).remove(col.get(y).size()-1);
row.get(x).remove(row.get(x).size()-1);
board[x][y] = '.';
}
}
return false;
}
}