[LeetCode]-BFS

前言

DFS/BFS这块的参考
BFS,Breadth First Search,宽度优先搜索。其与DFS一大区别在于,BFS搜索到的第一个符合条件的结果就是最优的

102.二叉树的层序遍历

宽度优先搜索跟二叉树的层次遍历可以说几乎一摸一样,即借助队列存放当前遍历节点的所有相邻节点,遍历时按照队列先进先出的顺序遍历节点,可以看我另一篇有关二叉树的文章,这里给出和那篇文章略有不同的另一种代码

public List<List<Integer>> levelOrder(TreeNode root) {
    if(root == null) return new LinkedList<List<Integer>>();
    //记录最终答案
    List<List<Integer>> res = new ArrayList<>();
    //存放节点的队列
    LinkedList<TreeNode> list = new LinkedList<>();
    list.offer(root);
    while(!list.isEmpty()){
        int s = list.size();
        //存放遍历当前层次的结果
        LinkedList<Integer> level = new LinkedList<>();
        //根据队列元素个数来遍历
        for(int i = 0;i < s;i++){
            TreeNode t = list.poll();
            level.offer(t.val);
            if(t.left != null) list.offer(t.left);
            if(t.right != null) list.offer(t.right);
        }
        res.add(level);
    }
    return res;
}

103.二叉树的锯齿形层序遍历

与102题类似,只需要多加一个当前层次是向左遍历还是向右遍历的判断同时根据遍历方向不同修改节点值添加的位置

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    if(root == null) return new LinkedList<List<Integer>>();
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<TreeNode> list = new LinkedList<>();
    list.offer(root);
    //增加一个布尔变量
    boolean flag = true;
    while(!list.isEmpty()){
        int s = list.size();
        LinkedList<Integer> level = new LinkedList<>();
        for(int i = 0;i < s;i++){
            TreeNode t = list.poll();
            //增加向左遍历还是向右遍历的判断
            if(flag){
            	//向右遍历节点值就要加在level的最后面
                level.offer(t.val);
            }else{
                //向左遍历节点值就要加在level的最前面
                level.addFirst(t.val);
            }
            if(t.left != null) list.offer(t.left);
            if(t.right != null) list.offer(t.right);
        }
        res.add(level);
        //每层遍历完翻转顺序
        flag = !flag;
    }
    return res;
}

107.二叉树的层序遍历Ⅱ

把102中记录最终答案的res改为栈就可以实现每一层的遍历结果逆序了

public List<List<Integer>> levelOrderBottom(TreeNode root) {
    if(root ==null) return new LinkedList<List<Integer>>();
    //记得类型应为LinkedList才能调用addFirst()方法实现栈的效果
    LinkedList<List<Integer>> res = new LinkedList<>();
    LinkedList<TreeNode> list = new LinkedList<>();
    list.offer(root);
    while(!list.isEmpty()){
        int s = list.size();
        LinkedList<Integer> level = new LinkedList<>();
        for(int i = 0;i < s;i++){
            TreeNode t = list.poll();
            level.offer(t.val);
            if(t.left != null) list.offer(t.left);
            if(t.right != null) list.offer(t.right);
        }
        res.addFirst(level);
    }
    return res;
}

111.二叉树的最小深度

在我另一篇有关二叉树的文章中也做到了这道题,不过那里面使用的做法是递归分治,这里基于102题的层序遍历代码进行一些修改,使用BFS做法来解题
这道题的BFS跟上面那几道的区别在于,前面更像是宽度优先遍历,要做的就是遍历整棵树,而不需要在遍历过程加一些决策;而这道题需要在搜索的过程中加入决策,即判断遍历到的节点是不是最小深度对应的节点
使用BFS来完成这种涉及决策的题时,可以总结出大致如下的框架:借助队列存放节点,每次循环遍历当前队列中所含的元素(注意是“当前”,不包括遍历过程中可能会加入的新的节点),判断遍历到的节点是否符合条件,如果符合直接return,不符合就把与这个节点相邻的所有未被遍历到的节点加到队列中,然后进行下一轮循环,直到队列为空

public int minDepth(TreeNode root) {
    if(root == null) return 0;
    LinkedList<TreeNode> list = new LinkedList<>();
    list.offer(root);
    //记录接下来要遍历到的节点层的层数
    int res = 1;
    while(!list.isEmpty()){
        int s = list.size();
        for(int i = 0;i < s;i++){
            TreeNode t = list.poll();
            //找到了一个节点没有左右儿子,那它就是最小深度所对应的节点,返回它的层数就是答案
            if(t.left == null && t.right == null) return res;
            if(t.left != null) list.offer(t.left);
            if(t.right != null) list.offer(t.right);
        }
        //当前层没有找到符合最小深度对应的节点,继续往下一层找
        res++;
    }
    return res;
}

事实证明这种做法的时间花费比递归分治的要少得多

752. 打开转盘锁

四个转盘锁,经过一次旋转后可以得到8个结果,即分别每一个转盘锁分别向上及向下旋转一次。映射到图的结构中,8种情况就相当于当前转盘锁的相邻顶点。再套入上面说的框架,先将"0000"入队列,每次循环遍历当前队列中所含的四位串,如果该串不是deadend而且就和target相同,那就返回当前的旋转次数,否则就将该串经过一次旋转得到的8个串加入队列,同时标记为已被遍历,然后进行下一次循环

class Solution {
    public int openLock(String[] deadends, String target) {
    	//deads记录所有deadend,用来判断搜索到的解是不是deadend
    	//visited记录遍历过的解
    	//这里如果把两个Set改为List的话会有样例超时
    	//所以如果集合只是为了用来判断元素的存在与否,应用Set而不用List
        Set<String> deads = new HashSet<>();
        Set<String> visited = new HashSet<>();
        for(int i = 0;i < deadends.length;i++){
            deads.add(deadends[i]);
        }
        //所需旋转次数
        int num = 0;
        LinkedList<String> queue = new LinkedList<>();
        queue.offer("0000"); 
        visited.add("0000");
        while(!queue.isEmpty()){
            int s = queue.size();
            for(int i = 0;i < s;i++){
                String str = queue.poll();
                //要判断deadends中有没有该串,借助deads
                if(deads.contains(str)) continue;
                if(target.equals(str)) return num;
                for(int j = 0;j < 4;j++){
                    String up = up(str,j);
                    //只将未遍历的串放入队列,所以还需要记录已遍历过的串
                    if(!visited.contains(up)){
                        queue.offer(up);
                        //对每一个解尝试一次即可,所以不用等到从队列中取出来
                        //时才将其标记为已被遍历
                        visited.add(up);
                    }
                    String down = down(str,j);
                    if(!visited.contains(down)){
                        queue.offer(down);
                        visited.add(down);
                    }
                }
            }
            num++;
        }
        //找不到可行解,返回-1
        return -1;
    }
    //将索引pos的位置的数字向上旋转
    public String up(String s,int pos){
        char[] c = s.toCharArray();
        if(c[pos] == '9') c[pos] = '0';
        else c[pos] += 1;
        return new String(c);
    }
    //将索引pos的位置的数字向上旋转
    public String down(String s,int pos){
        char[] c = s.toCharArray();
        if(c[pos] == '0') c[pos] = '9';
        else c[pos] -= 1;
        return new String(c);
    }
}

773.滑动谜题

跟752题一样的分析思路。将二维数组转化为一个六位串进行操作。对于某一个串,其相邻的情况就是把0经过上下左右交换所能得到的情况

class Solution {
    public int slidingPuzzle(int[][] board) {
        int row = 2;
        int column = 3;
        String begin = "";
        for(int i = 0;i < 2;i++){
            for(int j = 0;j < 3;j++){
                begin += board[i][j];
            }
        }
        //move[i][]记录的是当0在六位串中的索引i位置时,其能进行交换的位置
        //如move[0][],表示0在六位串的0号位置,即二维数组中的(0,0)位置,
        //它能跟(0,1)即1号位置交换,也能和(1,0)即3号位置交换,所以move[0][]={1,3}
        int[][] move = new int[][]{
            {1,3},
            {0,2,4},
            {1,5},
            {0,4},
            {1,3,5},
            {2,4}
        };
        String end = "123450";
        LinkedList<String> queue = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        int num = 0;
        queue.offer(begin);
        visited.add(begin);
        while(!queue.isEmpty()){
            int s = queue.size();
            for(int i = 0;i < s;i++){
                String str = queue.poll();
                if(end.equals(str)) return num;
                int zeroPos = str.indexOf("0"); //找出0的位置
                for(int j = 0;j < move[zeroPos].length;j++){
                    String newStr = swap(str,move[zeroPos][j],zeroPos);
                    if(!visited.contains(newStr)){
                        queue.offer(newStr);
                        visited.add(newStr);
                    }
                }
            }
            num++;
        }
        return -1;
    }
    //用于交换s中索引i跟j位置的字母
    public String swap(String s,int i,int j){
        char[] c = s.toCharArray();
        char tmp = c[i];
        c[i] = c[j];
        c[j] = tmp;
        return new String(c);
    }
}

双向BFS

双向 BFS 的题自然都能用 传统的 BFS 来做;而单向 BFS 要用双向 BFS 来做的前提是知道 BFS 的终点

127. 单词接龙

知道 BFS 的起点是 beginWord,终点是 endWord,所以可以双向 BFS。
双向 BFS 中,每个方向上的 BFS 都维护自己的 BFS 队列,即接下来要访问的 “点”,而双向 BFS 的终点就是某个方向正在 BFS 扩散的时候,访问到的一个点刚好在另外一个方向的 BFS 队列中,即两个方向的 BFS 出现了交点,这就说明从起始点到终点有路径了

public class Solution {
    Set<String> wordSet; //将 wordList 放到HashSet里,方便判断某个单词是否在 wordList 里
    Set<String> visited; //维护全局已被访问的单词
    //下面用Set代替Queue作为BFS队列,是因为两个方向的BFS终点是当前方向接下来要访问的单词是否就在对方的BFS队列中,是的话说明找到了序列
    Set<String> beginVisited; //从beginWord到endWord的BFS队列
    Set<String> endVisited;   从beginWord到endWord的BFS队列
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        wordSet = new HashSet<>(wordList);
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
            return 0;
        }
        visited = new HashSet<>();
        beginVisited = new HashSet<>();
        beginVisited.add(beginWord);
        endVisited = new HashSet<>();
        endVisited.add(endWord);
        int step = 1;
        while (!beginVisited.isEmpty() && !endVisited.isEmpty()) {
            //优先选择小的哈希表进行扩散,考虑到的情况更少。让beginVisited指向较小的一个
            if (beginVisited.size() > endVisited.size()) {
                Set<String> temp = beginVisited;
                beginVisited = endVisited;
                endVisited = temp;
            }
            //nextLevelVisited 在扩散完成以后,会成为新的 beginVisited
            Set<String> nextLevelVisited = new HashSet<>();
            for (String word : beginVisited) {
                if (changeWordEveryOneLetter(word,nextLevelVisited)) {
                    return step + 1;
                }
            }
            //原来的 beginVisited 废弃,从 nextLevelVisited 开始新的双向 BFS
            beginVisited = nextLevelVisited;
            step++;
        }
        return 0;
    }
    //枚举word的每一位,查找是否能找到某一位改为其它字符后的单词出现在另外一个方向的BFS队列中
    private boolean changeWordEveryOneLetter(String word,Set<String> nextLevelVisited) {
        char[] charArray = word.toCharArray();
        for (int i = 0; i < word.length(); i++) {
            char originChar = charArray[i];
            for (char c = 'a'; c <= 'z'; c++) {
                if (c == originChar) {
                    continue;
                }
                charArray[i] = c;
                String nextWord = String.valueOf(charArray);
                if (wordSet.contains(nextWord)) {
                    //如果当前访问(扩散)到的单词刚好有出现在另一个方向上的BFS中,说明找到序列了
                    if (endVisited.contains(nextWord)) { 
                        return true;
                    }
                    //全局还没访问过的单词就可以在下一层BFS扩散时访问。当所有单词都被访问过了,即visited已经包含了所有单词
                    //那么就不能继续BFS了,即找不到序列,最终要返回false
                    if (!visited.contains(nextWord)) { 
                        nextLevelVisited.add(nextWord);
                        visited.add(nextWord);
                    }
                }
            }
            //恢复
            charArray[i] = originChar;
        }
        return false;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值