队列

二叉树的层次遍历

例题:

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

使用一个队列,来保存每一层的结点。每次我们从队列中依次取出这一层的所有结点,然后保存它们的值为一个列表,并将它们的左右结点入队。
当队列为空的时候,二叉树也就被遍历完了。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        Queue<TreeNode> queue = new LinkedList<TreeNode>(); //用一个队列来存储每一层的节点
        if(root!=null) queue.offer(root);
        while(queue.size()>0){
            List<Integer> list = new ArrayList<Integer>();
            int length = queue.size();
            for(int i=0;i<length;i++){
                TreeNode t = queue.poll();
                list.add(t.val);
                if(t.left!=null) queue.offer(t.left);
                if(t.right!=null) queue.offer(t.right);
            }
            res.add(list);
        }
        return res;
    }
}

其实,这个题也可以用DFS的方法来做,只是不再使用队列,但这里也写一下。

  • 使用递归的方式,在每个递归方法中都会传入一个值level(从0开始)。
  • level的作用是用来标注现在是第几层,然后在自己对应层的List中添加当前结点的值。
  • 如果res.size()==level,说明当前层第一次被遍历,则会在res中创建一个新list。
class Solution {
    LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root!=null) findNextNode(root,0);
        return res;
    }
    public void findNextNode(TreeNode root,int level){
        List<Integer> list = null;
        if(res.size()==level){
            list = new LinkedList<Integer>();
            res.add(list);
        }else{
            list = res.get(level);
        }
        list.add(root.val);
        if(root.left!=null) findNextNode(root.left,level+1);
        if(root.right!=null) findNextNode(root.right,level+1);
    }
}

1. 二叉树的层次遍历 II

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

最简单的方法,就是先按照例题的方式得到从上到下的然后再反转List即可。反转的方式有很多种,这里采用最简单的头插法插入List。

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
        Queue<TreeNode> queue = new LinkedList<TreeNode>(); //用一个队列来存储每一层的节点
        if(root!=null) queue.offer(root);
        while(queue.size()>0){
            List<Integer> list = new ArrayList<Integer>();
            int length = queue.size();
            for(int i=0;i<length;i++){
                TreeNode t = queue.poll();
                list.add(t.val);
                if(t.left!=null) queue.offer(t.left);
                if(t.right!=null) queue.offer(t.right);
            }
            res.addFirst(list);
        }
        return res;
    }
}

2.二叉树的锯齿形层次遍历

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

初步思路(BFS):

  • 设置一个标志位,表示当前遍历的层是奇数层还是偶数层;
  • 根据上一题的经验可以得知,不需要对队列做什么改变,只需要在插入当前层的List时如果是奇数层就尾插,偶数层就头插即可。
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        boolean level = false;  //false表示奇数层,true表示偶数层
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
        if(root!=null) queue.offer(root);
        while(!queue.isEmpty()){
            int length = queue.size();
            LinkedList<Integer> list = new LinkedList<Integer>();
            for(int i=0;i<length;i++){
                TreeNode t = queue.poll();
                if(t.left!=null) queue.offer(t.left);
                if(t.right!=null) queue.offer(t.right);
                if(level){
                    list.addFirst(t.val);
                }else{
                    list.add(t.val);
                }
            }
            res.add(list);
            level = !level;
        }
        return res;
    }
}

3.二叉树的右视图

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

跟当初字节的面试题有点像,不过更简单。只需要打印出最右边的结点就好了,所以使用一个队列,每次都打印出当前层最后一个即可:

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> list = new ArrayList<Integer>();
        Queue<TreeNode> queue = new LinkedList<TreeNode>(); //用一个队列来存储每一层的节点
        if(root!=null) queue.offer(root);
        while(queue.size()>0){
            int length = queue.size();
            for(int i=0;i<length;i++){
                TreeNode t = queue.poll();
                if(i==length-1) list.add(t.val);
                if(t.left!=null) queue.offer(t.left);
                if(t.right!=null) queue.offer(t.right);
            }
        }
        return list;
    }
}

BFS和图的最短路径

1. 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

乍一看,这个题和图毫无关系,但是,实际上却是一个无权图的最短路径问题:

  • 从n到0,每一个数字表示一个节点
  • 如果两个数字x到y之间相差一个完全平方数,则连接一条边
  • 我们得到了一个无权图
  • 原问题转化成,求这个无权图中从n到0的最短路径
class Solution {
    public int numSquares(int n) {
        Queue<Integer> queue = new LinkedList<Integer>();
        
        boolean[] visit = new boolean[n+1];   //标志是否被访问
        for(boolean v:visit) v = false;
        visit[n] = true;

        queue.offer(n);
        int res = 0;
        while(!queue.isEmpty()){
            res++;
            int length = queue.size();
            for(int i=0;i<length;i++){
                int num = queue.poll();
                for(int j=1;num-j*j>=0;j++){
                    if(num-j*j==0)  return res;
                    if(!visit[num-j*j]){
                        queue.offer(num-j*j);
                        visit[num-j*j] = true;
                    }
                    
                }
            }
        }
        return res;
    }
}

2.单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord
的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。 转换过程中的中间单词必须是字典中的单词。
说明:
-如果不存在这样的转换序列,返回 0。
-所有单词具有相同的长度。
-所有单词只由小写字母组成。
-字典中不存在重复的单词。
-你可以假设beginWord 和 endWord 是非空的,且二者不相同。

和上一题相似的原理,这个题也可以转化为无权图求最短路径。

  • 从beginWord到endWord,每一个单词代表一个节点
  • 如果两个单词只相差一个字母,则两个节点之间有一条边
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        if(!wordList.contains(endWord)) return 0;   //当字典中没有endword直接返回0
        boolean[] visit = new boolean[wordList.size()]; //标志是否被访问
        for(int i=0;i<visit.length;i++) visit[i]=false;
        Queue<String> queue = new LinkedList<String>();
        int level = 1;  //标志最短路径长度
        queue.offer(beginWord);
        while(!queue.isEmpty()){
            level++;
            int length = queue.size();
            for(int i=0;i<length;i++){
                String s = queue.poll();
                if(canTransfer(s,endWord)) return level;    //当可W以转化至endWord时,返回长度
                for(int j=0;j<wordList.size();j++){
                    if(!visit[j] && canTransfer(s,wordList.get(j))){
                        visit[j] = true;
                        queue.offer(wordList.get(j));
                    }
                }
            }
        }
        return 0;
    }
    //用来判断是否可以转化
    public boolean canTransfer(String a,String b){
        int count = 0;
        for(int i=0;i<a.length();i++){
            if(a.charAt(i)!=b.charAt(i)){
                count++;
                if(count>1) return false;
            }
        }
        return true;
    }
}

此外,这个解法目前复杂度还比较高,可以使用双向遍历来优化,也就是从beginWord到endWord,再从endWord到beginWord。当然,单纯的这样耗时会更长,应该是每次哪个方向的当前层包含节点少遍历哪一个方向

优先队列

例题:前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

最简单的思路是遍历一遍数组,然后统计每个元素的个数,排序后找到前k个元素。

另一个思路则是使用:优先队列
维护一个含有k个元素的优先队列。当队列满的时候,如果遍历到的元素比队列中的最小频率元素的频率高,则取出队列中最小频率的元素,将新元素入队。最终,队列中剩下的,就是前k个出现频率最高的元素。

class Solution {
  public List<Integer> topKFrequent(int[] nums, int k) {
        HashMap<Integer,Integer> map = new HashMap<>(); //用来统计每个数字的出现次数
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>((n1, n2) -> map.get(n1) - map.get(n2));
        for (int num : nums) {
            map.put(num,map.getOrDefault(num,0)+1);
        }
        for (Integer n : map.keySet()) {
            queue.offer(n);
            if(queue.size()>k) queue.poll();
        }
        List<Integer> list = new ArrayList<>();
        while(!queue.isEmpty()) list.add(queue.poll());
        return list;
  }
}

1.合并K个排序链表

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

这个怎么看也不像是可以用优先队列解决的,看了看题解发现,原来如此:

  • 维护一个长度为k的优先队列,存储k个链表的头结点
  • 每次出队最小的结点,然后挂在新链表上,同时将它的下一个结点入队。
  • 最后直到队列为空,新的链表也就建好了。
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>((n1,n2) -> n1.val-n2.val);
        ListNode newList = new ListNode(0);
        ListNode cur = newList;
        for (ListNode node : lists) {
            if(node!=null) queue.offer(node);
        }
        while(!queue.isEmpty()){
            ListNode node = queue.poll();
            cur.next = node;
            cur = cur.next;
            if(node.next!=null) queue.offer(node.next);
        }
        return newList.next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值