面试手撕常见的网红必刷题目-搜索部分

106 阅读11分钟

1 二叉树的层序遍历

image.png

具体流程整理: 特例处理: 当根节点为空,则返回空列表 [] 。

初始化: 打印结果列表 res = [] ,包含根节点的队列 queue = [root] 。

BFS 循环: 当队列 queue 为空时跳出。

新建一个临时列表 tmp ,用于存储当前层打印结果。

当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度)。

出队: 队首元素出队,记为 node。

打印: 将 node. Val 添加至 tmp 尾部。

添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue 。 将当前层结果 tmp 添加入 res 。 返回值: 返回打印结果列表 res 即可。

/**

 * Definition for a binary tree node.

 * public class TreeNode {

 *     int val;

 *     TreeNode left;

 *     TreeNode right;

 *     TreeNode() {}

 *     TreeNode(int val) { this.val = val; }

 *     TreeNode(int val, TreeNode left, TreeNode right) {

 *         this.val = val;

 *         this.left = left;

 *         this.right = right;

 *     }

 * }

 */

class Solution {

    public List<List<Integer>> levelOrder(TreeNode root) {

        //使用队列来进行遍历

       // if(root==null) return ;

        List<List<Integer>> res=new ArrayList<>();

        Queue<TreeNode>queue=new LinkedList<>();

        if (root != null)

        queue.add(root);

        while(!queue.isEmpty()){

             List<Integer> tmp = new ArrayList<>();//存储当前的打印结果

            for(int i = queue.size(); i > 0; i--){

                TreeNode node=queue.poll();

                tmp.add(node.val);

                if(node.left!=null){

                    queue.add(node.left);

                }

                 if(node.right!=null){

                    queue.add(node.right);

                }

            }

            res.add(tmp);

  

        }

        return res;

  
  

    }

}

上面是一种二叉树的层序遍历的一种形式,下面是另外一种可以不定义的 for 循环,因为 queue 的 size 的值会不断变动所以我们可以使用 while 循环进行操作

List<List<Integer>> res=new ArrayList<>();

        Queue<TreeNode>queue=new LinkedList<>();

        if (root != null)

        queue.add(root);
         while(!queue.isEmpty()){

             List<Integer> tmp = new ArrayList<>();//存储当前的打印结果
             int len=queue.size();

            while(len>0){
	            len--;

                TreeNode node=queue.poll();

                tmp.add(node.val);

                if(node.left!=null){

                    queue.add(node.left);

                }

                 if(node.right!=null){

                    queue.add(node.right);

                }

            }

            res.add(tmp);

  

        }

        return res;

2 交替打印层序遍历

image.png

BFS 循环: 循环打印奇 / 偶数层,当 deque 为空时跳出。 打印奇数层: 从左向右打印,先左后右加入下层节点。 若 deque 为空,说明向下无偶数层,则跳出。 打印偶数层: 从右向左打印,先右后左加入下层节点。 需要注意,打印奇数层的时候,

双端队列的两端皆可添加元素的特性,设打印列表(双端队列) tmp ,并规定:

  • 奇数层 则添加至 tmp 尾部 ,
  • 偶数层 则添加至 tmp 头部 。
  TreeNode node = deque.removeFirst();

/**

 * Definition for a binary tree node.

 * public class TreeNode {

 *     int val;

 *     TreeNode left;

 *     TreeNode right;

 *     TreeNode() {}

 *     TreeNode(int val) { this.val = val; }

 *     TreeNode(int val, TreeNode left, TreeNode right) {

 *         this.val = val;

 *         this.left = left;

 *         this.right = right;

 *     }

 * }

 */

class Solution {

    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {

        //二叉树的层序遍历的变形的思路

        List<List<Integer>> res=new ArrayList<>();

      Deque<TreeNode> queue = new LinkedList<>();

  

        if(root != null)queue.add(root);

  

        while(!queue.isEmpty()){

            //打印奇数层,从左向右

            List<Integer>tmp=new ArrayList<>();

            int len=queue.size();

            while(len>0){

                TreeNode node=queue.removeLast();

                tmp.add(node.val);

                if(node.left!=null)

                queue.addLast(node.left);

                 if(node.right!=null)

                queue.addLast(node.right);

                len--;

            }

            res.add(tmp);

             if (queue.isEmpty()) break; // 若为空则提前跳出

          tmp=new ArrayList<>();

            //偶数层先右后左,数据存储的地方和奇数分开存储

               len=queue.size();

            while(len>0){

  

                 TreeNode node=queue.removeFirst();

                tmp.add(node.val);

                if(node.right!=null)

                queue.addFirst(node.right);

                if(node.left!=null)

                queue.addFirst(node.left);

                 len--;

  

            }

            res.add(tmp);

  
  
  

        }

         return res;

  

    }

}

这是思路 1 代码有些多,其实可以消耗时间来进行判断,通过记录当前节点是奇数层还是偶数层的来计算

下面是更加的思路方法

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if (root != null) queue.add(root);
        while (!queue.isEmpty()) {
            LinkedList<Integer> tmp = new LinkedList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                if (res.size() % 2 == 0) tmp.addLast(node.val);
                else tmp.addFirst(node.val);
                if (node.left != null) queue.add(node.left);
                if (node.right != null) queue.add(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}


这里面每层都会放到 res 里面,因此通过 res 的 size 来判断奇数层和偶数层

3 二叉树的最近公共祖先

image.png

思路深度优先搜索 思路都在注释里面了,思路分类讨论1. p q 都能找到返回最近公共祖先 2. p q 找到一个,返回 p q 3. 都没找到返回 null

/**

 * Definition for a binary tree node.

 * public class TreeNode {

 *     int val;

 *     TreeNode left;

 *     TreeNode right;

 *     TreeNode(int x) { val = x; }

 * }

 */

class Solution {

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        if(root==null||root==p||root==q)return root;

        //只要是根节点为空或者是p和q当中的一个直接返回

        //根节点不是p和q中的任意一个,那么就继续分别往左子树和右子树找p和q

  

        TreeNode left= lowestCommonAncestor(root.left,p,q);

          TreeNode right= lowestCommonAncestor(root.right,p,q);

  

           //p和q都没找到,那就没有

        if(left == null && right == null) {

            return null;

        }

         //左子树没有p也没有q,就返回右子树的结果

         if(left == null) return right;

          //右子树没有p也没有q就返回左子树的结果

        if(right == null) return left;

         //左右子树都找到p和q了,那就说明p和q分别在左右两个子树上,所以此时的最近公共祖先就是root

        return root;

  
  
  

    }

}

4 二叉搜索树的第 k 个节点

image.png

思路:二叉搜索树满足左子树< 根节点< 右节点,实际上是中序遍历的第 k 个节点

  1. 递归遍历时计数,统计当前节点的序号。
  2. 递归到第 kkk 个节点时,应记录结果 resresres 。
  3. 记录结果后,后续的遍历即失去意义,应提前返回。
/**

 * Definition for a binary tree node.

 * public class TreeNode {

 *     int val;

 *     TreeNode left;

 *     TreeNode right;

 *     TreeNode() {}

 *     TreeNode(int val) { this.val = val; }

 *     TreeNode(int val, TreeNode left, TreeNode right) {

 *         this.val = val;

 *         this.left = left;

 *         this.right = right;

 *     }

 * }

 */

class Solution {

    int res,k;

  

    void dfs(TreeNode root) {

        if(root==null)return;

        dfs(root.left);

        if(k==0)return;//判断m是不是==0,等于0的话直接返回

        if(--k==0)//不是第k个数的话,进行--然后赋值

        res = root.val;

        dfs(root.right);

  

    }

    public int kthSmallest(TreeNode root, int k) {

        //中序遍历的k个节点

        this.k = k;

        dfs(root);

        return res;

  

    }

}

5 二叉搜索树的最近公共祖先

image.png

思路:实际上是一个中序遍历的过程中,寻找这个链表的中点的过程 祖先的定义: 若节点 ppp 在节点 rootrootroot 的左(右)子树中,或 p=rootp = rootp=root,则称 rootrootroot 是 ppp 的祖先。

最近公共祖先的定义: 设节点 rootrootroot 为节点 p, qp, qp, q 的某公共祖先,若其左子节点 root. Leftroot. Leftroot. Left 和右子节点 root. Rightroot. Rightroot. Right 都不是 p, qp, qp, q 的公共祖先,则称 rootrootroot 是 “最近的公共祖先” 。

image.png

/**

 * Definition for a binary tree node.

 * public class TreeNode {

 *     int val;

 *     TreeNode left;

 *     TreeNode right;

 *     TreeNode(int x) { val = x; }

 * }

 */

  

class Solution {

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        while(root!=null){

            if(root.val<p.val&&root.val<q.val){

                root=root.right;

            }

            else if(root.val>p.val&&root.val>q.val){

            root=root.left;

        }

        else{

            break;

        }

        }

  

        return root;

  

    }

}

6 数组中第 k 大的元素

image.png

经典题目,面试手撕的时候的容易考的题目,容易错的题目 可以采用堆排序和快速排序的思想来做,这里借助快速排序来做 这里借助快排的思想,快排的过程会形成一个有序序列,在这有序序列当中第 n-k 个元素就是 k 大的元素。

class Solution {

    public int findKthLargest(int[] nums, int k) {

            //随机选择基准数

            List<Integer> numList=new ArrayList<>();

            for(int num:nums){

                numList.add(num);

            }

          return quickselect(numList,k);

    }

    private int quickselect(List<Integer>nums,int k){

          Random random=new Random();

            int pivot=nums.get(random.nextInt(nums.size()));

            //将大于小于等于pivot的元素划分至big small,equal当中

            List<Integer> big=new ArrayList<>();

            List<Integer>equal=new ArrayList<>();

            List<Integer>small=new ArrayList<>();

            for(int num:nums){

                if(num>pivot){

                    big.add(num);

                }else if(num<pivot){

                    small.add(num);

                }else{

                    equal.add(num);

                }

            }

  

            //如果第k大元素在big中,递归划分

            if(k<=big.size()){//前k个大的中存在的话,对big的元素进行划分

                return quickselect(big,k);

            }

            if(big.size()+equal.size()<k){

                //nums.size()=small.size()+big.size()+equal.size()

                //本质是equal.size+bigsize()<k

                // int tmp=k-nums.size()+small.size();

            return quickselect(small,k - (big.size() + equal.size()));

  

            }//这个的意思是整个数组减去最小部分剩下的不到k,所以第k

            //一定在small当中

            return pivot;//正好被选中

    }

}

下面是思路 2: 初始化一个小顶堆,其堆顶元素最小。 先将数组的前 k 个元素依次入堆。 从第 k+1 个元素开始,若当前元素大于堆顶元素,则将堆顶元素出堆,并将当前元素入堆。 遍历完成后,堆顶保存的就是最大的第 k 个元素。

class Solution {

    public int findKthLargest(int[] nums, int k) {

            //初始化小顶堆

        Queue<Integer> heap=new PriorityQueue<Integer>();

        // 将数组的前k个元素入堆

        for(int i=0;i<k;i++){

            heap.offer(nums[i]);

        }

        //从k+1个元素开发,保持堆的长度为k

        for(int i=k;i<nums.length;i++){

            // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆

            if(nums[i]>heap.peek()){//堆顶的元素是最小的

                heap.poll();

                heap.offer(nums[i]);

            }

  

        }

        return heap.peek();

    }

}

7 课程表网红题目

课程表最近的面试网红题目,堪称接雨水之后的容易出的题目。这里的思路是判断课程是否构成一个有向无环图,如果可以就可以,不可以的话也无法学习。

具体思路: 1.统计课程安排图中每个节点的入度,生成入度表 indegrees。 2.借助一个队列 queue,将所有入度为 000 的节点入队。 3.当 queue 非空时,依次将队首节点出队,在课程安排图中删除此节点 pre: 并不是真正从邻接表中删除此节点 pre,而是将此节点对应所有邻接节点 cur 的入度 −1,即 indegrees[cur] -= 1。 当入度 −1-后邻接节点 cur 的入度为 0,说明 cur 所有的前驱节点已经被 “删除”,此时将 cur 入队。

在每次 pre 出队时,执行 numCourses--; 若整个课程安排图是有向无环图(即可以安排),则所有节点一定都入队并出队过,即完成拓扑排序。换个角度说,若课程安排图中存在环,一定有节点的入度始终不为 0 因此,拓扑排序出队次数等于课程个数,返回 numCourses == 0 判断课程是否可以成功安排。

image.png

class Solution {

    public boolean canFinish(int numCourses, int[][] prerequisites) {

        //通过拓扑排序判断是否可以构成一个有向无环图

        int [] indegress=new int[numCourses];//创建一个数组,用于存放每门课程的入度(即先修课程的数量)

        List<List<Integer>>adjacency=new ArrayList<>();//创建一个邻接表,用于存放每门课程的后续课程

        Queue<Integer> queue=new LinkedList<>();//创建一个队列,用于存放入度为0的课程(即没有先修课程,可以直接学习的课程

        // 初始化邻接表

        for(int i=0;i<numCourses;i++){

            adjacency.add(new  ArrayList<>());//为每个课程添加一个空的邻接表

  

        }

         // 获取每门课程的入度和邻接表

        for(int [] cp :prerequisites){

            indegress[cp[0]]++;//// cp[0] 的入度加一 想要学习cp[0] 就得先完成cp[1] cp[1] -> cp[0]

            adjacency.get(cp[1]).add(cp[0]);//// cp[0] 是 cp[1] 的后续课程,所以将cp[0]加入cp[1]

  
  

        }

  

        for(int i=0;i<numCourses;i++){

            if(indegress[i]==0) // 如果一门课程的入度为0,那么将其加入队列

            queue.add(i);

        }

        //BFS 进行拓扑排序

        while(!queue.isEmpty()){//当队列不为空 执行循环

            int pre=queue.poll();//依次将队首节点出队,在课程安排图中删除此节点 pre:

            numCourses--;// 学完一个课程,待学习的课程数减1

            for(int cur:adjacency.get(pre)){//将所有他的前置课程入度-1

                 indegress[cur]--;

                if(indegress[cur]==0)//如果入度为0也可以进行学习了,加入学习队列

                queue.add(cur);//// 如果一门后续课程的入度减为0,那么将其加入队列

            }

  

        }

        // 到这一步,queue为空(即没有可学课程了),此时判断待学习课程数numCourses是否为0,若为0,全部课程学习结束

        return numCourses==0;

  

    }

}

上面的深度拓扑排序下面是第二种思路 算法流程: 1.借助一个标志列表f1ags,用于判断每个节点i(课程) 的状态: a.未被DFS访问:i=g; b.已被其他节点启动的DFS访问:i=-1: c.已被当前节点启动的DFS访问:i=1. 2.对numCourses个节点依次执行DFS,判断每个节点起步DFS是否存在环,若存在环直接返回 False。。DFS流程; a.终止条件: ·当 f1g[i]=-1,说明当前访问节点已被其他节点启动的DFS访问,无需再重复搜 索,直接返回True. 当f1ag[i]=1,说明在本轮DFS搜索中节点i被第2次访问,即课程安排图有 环,直接返回False. b.将当前访问节点i对应f1ag[i]置1,即标记其被本轮DFS访问过; c.递归访问当前节点i的所有邻接节点j,当发现环直接返回False; d.当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点f1g置为-1并返回 True. 3.若整个图 DFS 结束并未发现环,返回 True。

class Solution {

    public boolean canFinish(int numCourses, int[][] prerequisites) {

  

        List<List<Integer>>adjacency=new ArrayList<>();

        for(int i=0;i<numCourses;i++){

            adjacency.add(new ArrayList<>());

        }

        int[] flags=new int[numCourses];

        for(int []cp:prerequisites){

            adjacency.get(cp[1]).add(cp[0]);

        }

        for(int i=0;i<numCourses;i++){

            if(!dfs(adjacency,flags,i))

            {

                return false;

            }

        }

        return true;

  
  
  

        //通过拓扑排序判断是否可以构成一个有向无环图

    }

    private boolean dfs(List<List<Integer>> adjacency,int[] flags,int i){

        if(flags[i]==1) return false;//已经被别的节点访问过了不需要访问了,!false的意思是进入for循环直接退出了,

        if(flags[i]==-1) return true;// 本轮 DFS 搜索中节点 i 被第 2 次访问课程安排图有环 ,这里是退出这一次

        flags[i]=1;

        //递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 FalseFalseFalse;

        for(Integer j:adjacency.get(i)){

            if(!dfs(adjacency,flags,j))

            return false;

        }

        //当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点为-1,代表已经过期了,

        flags[i] = -1;

        return true;

  

    }

}