1 二叉树的层序遍历
具体流程整理: 特例处理: 当根节点为空,则返回空列表 [] 。
初始化: 打印结果列表 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 交替打印层序遍历
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 二叉树的最近公共祖先
思路深度优先搜索 思路都在注释里面了,思路分类讨论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 个节点
思路:二叉搜索树满足左子树< 根节点< 右节点,实际上是中序遍历的第 k 个节点
- 递归遍历时计数,统计当前节点的序号。
- 递归到第 kkk 个节点时,应记录结果 resresres 。
- 记录结果后,后续的遍历即失去意义,应提前返回。
/**
* 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 二叉搜索树的最近公共祖先
思路:实际上是一个中序遍历的过程中,寻找这个链表的中点的过程 祖先的定义: 若节点 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 是 “最近的公共祖先” 。
/**
* 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 大的元素
经典题目,面试手撕的时候的容易考的题目,容易错的题目 可以采用堆排序和快速排序的思想来做,这里借助快速排序来做 这里借助快排的思想,快排的过程会形成一个有序序列,在这有序序列当中第 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 判断课程是否可以成功安排。
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;
}
}