8.BFS(广度优先)+DFS(深度优先)算法

回溯算法是一种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;
    }

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值