回溯算法是一种DFS算法,在递归调用之前做选择,在递归调用之后撤销选择,再具体一点就是解决一个决策树的遍历过程,该算法注重三个部分,路径,选择列表和结束条件
46--全排列问题
直接画出回溯树,对于当前你的红点来说的话,[1,3]为选择列表,2为路径,结束条件就是到达树的底层
所以回溯算法的核心框架为
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表
List<List<Integer>> res;
public List<List<Integer>> permute(int[] nums) {
res=new LinkedList<>();
//路径
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums,track);
return res;
}
private void backtrack(int[] nums, LinkedList<Integer> track) {
//结束条件
if(nums.length==track.size()){
res.add(new LinkedList<>(track));
return;
}
//
for (int i = 0; i < nums.length; i++) {
//排除掉重复的
if(track.contains(nums[i]))continue;
//前序遍历位置做出选择
track.add(nums[i]);//添加合法路径
backtrack(nums, track);//进入下一个决策树
//后续遍历位置撤销操作
track.removeLast();
}
}
51--n皇后问题
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
//棋盘n行n列
char[][] chessboard = new char[n][n];
for (char[] c : chessboard) {
Arrays.fill(c, '.');//全部初始化为.
}
backTrack(n, 0, chessboard);
return res;
}
public void backTrack(int n, int row, char[][] chessboard) {
//终止条件
if (row == n) {
res.add(Array2List(chessboard));
return;
}
for (int col = 0;col < n; ++col) {
if (isValid (row, col, n, chessboard)) {
//选择
chessboard[row][col] = 'Q';
//回溯位置
backTrack(n, row+1, chessboard);
//销毁选择
chessboard[row][col] = '.';
}
}
}
// 传入一个二维数组返回一个List
public List Array2List(char[][] chessboard) {
List<String> list = new ArrayList<>();
for (char[] c : chessboard) {
list.add(String.copyValueOf(c));//char数组转成string
}
return list;
}
//测试合法性
public boolean isValid(int row, int col, int n, char[][] chessboard) {
// 检查列
for (int i=0; i<row; ++i) { // 相当于剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查45度对角线
for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查135度对角线
for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
java操作字符串还是优点台繁琐了,附上c++代码
vector<vector<string>> res;
/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}
// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}
int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}
698--等和子集划分
public boolean canPartitionKSubsets(int[] nums, int k) {
int sum=0;
for (int num : nums) {
sum += num;
}
if(sum%k!=0)return false;
boolean[] used=new boolean[nums.length];
int target=sum/k;
//k号桶一开始什么都没有装,从nums[0]开始做选择
return backtrack(k,0,nums,0,used,target);
}
private boolean backtrack(int k, int bucket, int[] nums, int start, boolean[] used, int target) {
if(k==0)return true;//所有的桶都被装满了,而且nums一定全部用完了
if(bucket==target){
//当前桶装满了,换下一个桶
return backtrack(k-1,0,nums,0,used,target);
}
//从start开始向后探查有效的nums[i]装入当前桶
for (int i = start; i <nums.length ; i++) {
if(used[i])continue;//nums[i]已经被装入到其他的桶中了
if(nums[i]+bucket>target)continue;//当前的桶装不下
//做出选择
used[i]=true;
bucket+=nums[i];
//回溯
if(backtrack(k,bucket,nums,i+1,used,target)){
return true;
}
//撤销
used[i]=false;
bucket-=nums[i];
}
//穷举完了都无法装满
return false;
}
78--子集
List<List<Integer>> res;
public List<List<Integer>> subsets(int[] nums) {
res=new LinkedList<>();
//记录走过的路径
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums,0,track);
return res;
}
private void backtrack(int[] nums, int start, LinkedList<Integer> track) {
//java集合的巨坑,要注意不能直接把track传进去
res.add(new LinkedList<>(track));
for (int i = start; i <nums.length ; i++) {
//做选择
track.add(nums[i]);
//回溯
backtrack(nums,i+1,track);
//撤销选择
track.removeLast();
}
}
77--组合
List<List<Integer>> res;
public List<List<Integer>> combine(int n, int k) {
//准备集合接受结果
res=new LinkedList<>();
//准备数组
int[] nums = new int[n];
for (int i = 0; i <n ; i++) {
nums[i]=i+1;
}
//回溯路径准备
LinkedList<Integer> track=new LinkedList<>();
trackBack(track,k,nums,0);
return res;
}
//集合大小限制,start为其实索引,用于排除[1,4]和[4,1]这样的重复序列
private void trackBack(LinkedList<Integer> track, int k, int[] nums, int start) {
if(k==0)res.add(new LinkedList<>(track));
for (int i = start; i <nums.length ; i++) {
//规避非法数据
if(track.contains(nums[i]))continue;
//选择
track.add(nums[i]);
//回溯
trackBack(track,k-1,nums, i+1);
//消除
track.removeLast();
}
}
37--解数独
public void solveSudoku(char[][] board) {
backtrack(board,0,0);
}
boolean backtrack(char[][] board, int i, int j) {
int m = 9, n = 9;
//穷举到最后一列了,换到下一行重新开始
if (j == n) return backtrack(board, i + 1, 0);
if (i == m) return true;//找到一个可行解,触发base case
if (board[i][j] != '.') {
//有数字不需要自己写
return backtrack(board, i, j + 1);
}
//1到9穷举
for (char ch = '1'; ch <= '9'; ch++) {
//遇到不合法的数字就跳过
if (!isValid(board, i, j ,ch)) continue;
board[i][j] = ch;//选择操作
//找到了一个可行解,结束
if (backtrack(board, i, j + 1)) return true;
board[i][j] = '.';//回撤操作
}
return false;//穷举完还是没有
}
private boolean isValid(char[][] board, int r, int c, char n) {
for (int i = 0; i < 9; i++) {
// 判断行是否存在重复
if (board[r][i] == n) return false;
// 判断列是否存在重复
if (board[i][c] == n) return false;
// 判断 3 x 3 方框是否存在重复 /3*3操作回到了九格的第一格
if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n)
return false;
}
return true;
}
22--生成括号
public List<String> generateParenthesis(int n) {
if(n==0)return null;
//记录所有组合
ArrayList<String> res = new ArrayList<>();
StringBuilder track=new StringBuilder();
backtrack(n,n,track,res);
return res;
}
//left和right表示左右两个括号的数量
private void backtrack(int left, int right, StringBuilder track, ArrayList<String> res) {
if(right<left)return;//左括号剩下的多,不合法
//又先后判断顺序,right必须在前
if(right<0||left<0)return;//小于0不合法
if(left==0&&right==0){
res.add(track.toString());//添加结果
return;
}
//尝试放一个左括号
track.append('(');//选择
backtrack(left-1,right,track,res);//回溯
track.deleteCharAt(track.length()-1);//撤销
//尝试放一个右括号
track.append(')');
backtrack(left,right-1,track,res);
track.deleteCharAt(track.length()-1);
}
BFS核心框架
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构
Set<Node> visited; // 避免走回头路
q.offer(start); // 将起点加入队列
visited.add(start);
int step = 0; // 记录扩散的步数
while (q not empty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj())
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
/* 划重点:更新步数在这里 */
step++;
}
}
111--二叉树的最小深度
public int minDepth(TreeNode root) {
if(root==null)return 0;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);//添加初始点
int dep=1;//初始为1
while (!queue.isEmpty()){
int n=queue.size();
for (int i = 0; i < n; i++) {
TreeNode cur=queue.poll();//弹出一个
//查看是否是target
if(cur.left==null&&cur.right==null)return dep;
//不是target添加相邻元素
if(cur.left!=null)queue.offer(cur.left);//添加一个入队
if(cur.right!=null)queue.offer(cur.right);//添加入队
}
//更新步数
dep++;
}
return dep;
}
752--打开转盘锁
public int openLock(String[] deadends, String target) {
Queue<String> q=new LinkedList<>();
q.offer("0000");
Set<String> dead=new HashSet<>();//记录所有死亡密码
Set<String> memo=new HashSet<>();//备忘录,避免走回头路
for (String deadend : deadends) {
dead.add(deadend);
}
int step=0;
q.offer("0000");
memo.add("0000");
while (!q.isEmpty()){
int sz=q.size();
for (int i = 0; i < sz; i++) {
String cur=q.poll();
//如果遇到死亡名单就跳过
if(dead.contains(cur))continue;
//判断终点是否到达
if(cur.equals(target))return step;
for (int j = 0; j < 4; j++) {
String up=plusOne(cur,j);
String down=MinusOne(cur,j);
if(!memo.contains(up)){
q.offer(up);
memo.add(up);
}
if(!memo.contains(down)){
q.offer(down);
memo.add(down);
}
}
}
step++;
}
return -1;
}
//向上拨动一次
String plusOne(String s, int j){
char[] ch=s.toCharArray();
if(ch[j]=='9')ch[j]='0';
else ch[j]+=1;
return new String(ch);
}
//向下拨动一次
String MinusOne(String s, int j){
char[] ch=s.toCharArray();
if(ch[j]=='0')ch[j]='9';
else ch[j]-=1;
return new String(ch);
}
773--滑动谜题
//这里记录了所有相邻的位置 比如0的相邻位置是1和3
int[][] neighbors = {{1, 3}, {0, 2, 4}, {1, 5}, {0, 4}, {1, 3, 5}, {2, 4}};
public int slidingPuzzle(int[][] board) {
//当前棋盘
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
sb.append(board[i][j]);
}
}
String initial = sb.toString();
//如果一开始就排好了
if ("123450".equals(initial)) {
return 0;
}
int step = 1;
Queue<String> queue = new LinkedList<String>();
queue.offer(initial);//添加初始情况
Set<String> seen = new HashSet<String>();//备忘录
seen.add(initial);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; ++i) {
String status = queue.poll();
//对于交换过后的所有装填
for (String nextStatus : get(status)) {
if (!seen.contains(nextStatus)) {
if ("123450".equals(nextStatus)) {
return step;
}
queue.offer(nextStatus);
seen.add(nextStatus);
}
}
}
++step;//增加操作数
}
return -1;
}
// 枚举 status 通过一次交换操作得到的状态
public List<String> get(String status) {
List<String> ret = new ArrayList<String>();
char[] array = status.toCharArray();
int x = status.indexOf('0');
for (int y : neighbors[x]) {
swap(array, x, y);
ret.add(new String(array));
//复原棋盘
swap(array, x, y);
}
return ret;
}
// 交换函数
public void swap(char[] array, int x, int y) {
char temp = array[x];
array[x] = array[y];
array[y] = temp;
}