16.面试算法-树的层次遍历与相关面试题

news2024/11/18 22:31:44

1. 树的层次遍历与相关面试题

1.1 层次遍历简介

广度优先在面试里出现的频率非常高,但是相对简单,题目也比较少,常见的题目也就七八道。

广度优先又叫层次遍历,基本过程如下:
在这里插入图片描述
层次遍历就是从根节点开始,先访问根节点下面一层全部元素,再访问之后的层次,类似金字塔一样一层层访问。上面的图示按照层次访问的结果就是:[1,2,3,4,5,6,7]

我们可以看到这里就是从左到右一层一层的去遍历二叉树,先访问1,之后访问1的左右子孩子2和3,之后分别访问2和3的左右子孩子[4,5]和[6,7]。

由此我们发现如果使用队列来存储的话,访问某一层的时候就将该层的元素全部入队,某个元素出队的时候,就将该元素的左右子节点分别入队,就能保证完美访问所有元素。例如上面的图中:

  • 首先1入队。
  • 然后1出队,之后将1的左右子孩子2和3入队。
  • 之后2出队,将2的左右子孩子4和5入队。
  • 之后3出队,将3的左右子孩子6和7入队。
  • 之后4,5,6,7分别出队,因为都没有子孩子了,所以都只出队就行了。

该过程不复杂,我们再来看一下,如果能将层次分开了,那我们能整出什么花样?

首先,我能否将每层的顺序反转一下呢?那这就是一棵树的镜像问题了。那能否奇数行不变,偶数行反转呢?那就是锯齿状输出元素了。我能否将输出层次从低到root逐层输出呢?那这就是层的反转问题了。

再来,既然能拿到每一层的元素了,我能否找到当前层最大的元素?最小的元素?最右的元素(右视图)?最左的元素(左视图)?整个层的平均值?

很明显都可以,但是这么折腾有啥用呢?没啥用,但是如果告诉你这几种情况就是层次遍历的常见算法题,你还觉得没用吗?如果告诉你上述几个折腾就是下面的LeetCode题,你还觉得没用吗?如果告诉你研究清楚如何将层次分开,这些问题就不用做了,你还觉得没用吗?

LeetCode 102.二叉树的层序遍历
LeetCode 107.二叉树的层次遍历II
LeetCode 199.二叉树的右视图
LeetCode 637.二叉树的层平均值
LeetCode 429.N叉树的前序遍历
LeetCode 515.在每个树行中找最大值
LeetCode 116.填充每个节点的下一个右侧节点指针
LeetCode 117.填充每个节点的下一个右侧节点指针II
LeetCode 103.锯齿层序遍历

1.2 基本的层序遍历与变换

我们先看最简单的情况,仅仅遍历并输出一遍,不考虑分层的问题。基本的层次遍历方法如下,这个代码很容易理解,就是先访问根节点,然后将其左右子孩子放到队列里,接着继续出队,出来的元素都将其左右自孩子放到队列里知道队列为空了就退出。

/**
*树结构如下
* 3
/ \
9  20
/  \
15   7
* 应输出结果   [3, 9, 20, 15, 7]
*/
public static List<Integer> simpleLevelOrder(TreeNode root) {
	if (root == null) {
		return new ArrayList<Integer>();
	}

	List<Integer> res = new ArrayList<Integer>();
	LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
	//将根节点放入队列中,然后不断遍历队列
	queue.add(root);
	//有多少元素执行多少次
	while (queue.size() > 0) {
		//获取当前队列的长度,这个长度相当于当前这一层的节点个数 
		TreeNode t = queue.remove();
		res.add(t.val);
		if (t.left != null) {
			queue.add(t.left);
		}
		if (t.right != null) {
			queue.add(t.right);
		}
	}
	return res;
}

根据树的结构可以看到,一个结点在一层访问之后,其子孩子都是在下层按照FIFO的顺序才处理的,因此队列就是一个缓存的作用。

我们继续加码?我们该如何将每层的元素分开呢?请看一下题。

1.2.1 LeetCode102 二叉树的层序遍历

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

示例1:
二叉树:[3,9,20,null,null,15,7]
   3
  / \
 9  20
   /  \
  15   7
输出:[[3],[9,20],[15,7]]

示例 2:
输入:root = [1]
输出:[[1]]

示例 3:
输入:root = []
输出:[]

广度优先需要用队列作为辅助结构,我们先将根节点放到队列中,然后不断遍历队列。这里的问题是如何判断某一层访问完了呢?很简单,用一个变量size就完了,size表示某一层的元素个数,只要出队,就将size减1,见到0就说明该层元素访问完了。

很容易想到,size变成0之后,就该为其设置下一层的元素个数。那问题又来了,size该怎么知道每层的元素是多少呢?为了更清晰,我们增加几个结点,然后看一下执行的过程图:
在这里插入图片描述

从图中可以看出,首先拿出根节点,如果左子树/右子树不为空,就将他们放入队列中。处理完后,根节点已经从队列中拿走了,而根节点的两个孩子已放入队列中了,现在队列中就有两个节点9和20。恰好就是第二层的所有结点。

继续,我们将9从队列中拿走,并将其子孩子8和13入队。之后再将20出队,并将其子孩子15和7入队,这是我们发现当第二层的结点访问完的时候,队列的里的元素恰好都是第三层的元素。

综上,我们可以得到结论:当某一层元素访问完毕(size–一直到0就表示该层访问完),当size变成0时,只要让size重新等于队列元素的个数(size = queue.size())就行了。

最后,我们把每层遍历到的节点都放入到一个结果集中,最后返回这个结果集就可以了。代码如下:

class Solution {
	public List<List<Integer>> level102Order(TreeNode root) {
		if(root==null) {
			return new ArrayList<List<Integer>>();
		}

		List<List<Integer>> res = new ArrayList<List<Integer>>(); 
		LinkedList<TreeNode> queue = new LinkedList<TreeNode>();  
		//将根节点放入队列中,然后不断遍历队列
		queue.add(root);
		while(queue.size()>0) {
			//获取当前队列的长度,这个长度相当于当前这一层的节点个数 
			int size = queue.size();
			ArrayList<Integer> tmp = new ArrayList<Integer>();
			//将队列中的元素都拿出来(也就是获取这一层的节点),放到临时list中 
			//如果节点的左/右子树不为空,也放入队列中
			for(int i=0;i<size;++i) {
				TreeNode t = queue.remove();
				tmp.add(t.val);
				if(t.left!=null) {
					queue.add(t.left);
				}
				if(t.right!=null) {
					queue.add(t.right);
				}
			}
			//将临时list加入最终返回结果中 
			res.add(tmp);
		}
		return res;
	}
}

上面的代码是本章最重要的算法之一,也是整个算法体系的核心算法之一,与链表反转、二分查找属于同一个级别,务必认真学习!理解透彻,然后记住!

上面的算法理解了,那接下来一些列的问题就轻松搞定了。

1.2.2 LeetCode 107 层序遍历-自底向上

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

例如给定的二叉树为:
   3
  / \
 9  20
   /  \
  15   7
返回结果为:
[[15,7],[9,20],[3]]

如果要求从上到下输出每一层的节点值,做法是很直观的,在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的尾部。这道题要求从下到上输出每一层的节点值,只要对上述操作稍作修改即可,在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的头部。

为了降低在结果列表的头部添加一层节点值的列表的时间复杂度,结果列表可以使用链表的结构,在链表头部添加一层节点值的列表的时间复杂度是O(1)。在Java中,由于我们需要返回的List是一个接口,这里可以使用链表实现。

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> levelOrder = new LinkedList<List<Integer>>();
        if (root == null) {
            return levelOrder;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            List<Integer> level = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                TreeNode left = node.left, right = node.right;
                if (left != null) {
                    queue.offer(left);
                }
                if (right != null) {
                    queue.offer(right);
                }
            }
            levelOrder.add(0, level);
        }
        return levelOrder;
    }
}

1.2.3 LeetCode103 二叉树的锯齿形层序遍历

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

例如给定的二叉树为:
   3
  / \
 9  20
   /  \
  15   7
返回结果为:
[[3],[20,9],[15,7]]

这个题也是102的变种,只是最后输出的要求有所变化,要求我们按层数的奇偶来决定每一层的输出顺序。规定二叉树的根节点为第 0层,如果当前层数是偶数,从左至右输出当前层的节点值,否则,从右至左输出当前层的节点 值。

我们依然可以沿用第 102 题的思想,修改广度优先搜索,对树进行逐层遍历,用队列维护当前层的所有元素,当队列不为空的时候,求得当前队列的长度 size,每次从队列中取出 size 个元素进行拓展,然后进行下一次迭代。

为了满足题目要求的返回值为「先从左往右,再从右往左」交替输出的锯齿形,我们可以利用「双端队列」的数据结构来维护当前层节点值输出的顺序。

双端队列是一个可以在队列任意一端插入元素的队列。在广度优先搜索遍历当前层节点拓展下一层节点的时候我们仍然从左往右按顺序拓展,但是对当前层节点的存储我们维护一个变量 isOrderLeft 记录是从左至右还是从右至左的:

如果从左至右,我们每次将被遍历到的元素插入至双端队列的末尾。 如果从右至左,我们每次将被遍历到的元素插入至双端队列的头部。

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> ans = new LinkedList<List<Integer>>();
        if (root == null) {
            return ans;
        }

        Queue<TreeNode> nodeQueue = new ArrayDeque<TreeNode>();
        nodeQueue.offer(root);
        boolean isOrderLeft = true;

        while (!nodeQueue.isEmpty()) {
            Deque<Integer> levelList = new LinkedList<Integer>();
            int size = nodeQueue.size();
            for (int i = 0; i < size; ++i) {
                TreeNode curNode = nodeQueue.poll();
                if (isOrderLeft) {
                    levelList.offerLast(curNode.val);
                } else {
                    levelList.offerFirst(curNode.val);
                }
                if (curNode.left != null) {
                    nodeQueue.offer(curNode.left);
                }
                if (curNode.right != null) {
                    nodeQueue.offer(curNode.right);
                }
            }
            ans.add(new LinkedList<Integer>(levelList));
            isOrderLeft = !isOrderLeft;
        }

        return ans;
    }
}

1.2.4 LeetCode429 N 叉树的层序遍历

给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。

树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

示例 1:
在这里插入图片描述
输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]

示例 2:
在这里插入图片描述
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]

N叉树的定义为:

class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
}

这个也是102的扩展,很简单的广度优先,与二叉树的层序遍历基本一样,借助队列即可实现。

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        if (root == null) {
            return new ArrayList<List<Integer>>();
        }

        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        Queue<Node> queue = new ArrayDeque<Node>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            int cnt = queue.size();
            List<Integer> level = new ArrayList<Integer>();
            for (int i = 0; i < cnt; ++i) {
                Node cur = queue.poll();
                level.add(cur.val);
                for (Node child : cur.children) {
                    queue.offer(child);
                }
            }
            ans.add(level);
        }

        return ans;
    }
}

1.3 几个处理每层元素的题目

如果我们拿到了每一层的元素,那是不是可以利用一下造几个题呢?例如每层找最大值、平均值、最右侧的值呢? 当然可以。 LeetCode里就有三道非常明显的题目。

LeetCode 515.在每个树行中找最大值(最小)

LeetCode 637.二叉树的层平均值

LeetCode 199.二叉树的右视图

既然能这么干,为啥我们自己不能造几个题:求每层最小值可以不?求每层最左侧的可以不?我们是不是可以给LeetCode贡献几道题了?

1.3.1 LeetCode 515 在每个树行中找最大值

题目要求:给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

示例1:
     1
    / \
   3   2
  / \   \
 5   3   9
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]

示例2:
输入: root = [1,2,3]
输出: [1,3]

这里其实就是在得到一层之后使用一个变量来记录当前得到的最大值:

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        if (root == null) {
            return new ArrayList<Integer>();
        }
        List<Integer> res = new ArrayList<Integer>();
        dfs(res, root, 0);
        return res;
    }

    public void dfs(List<Integer> res, TreeNode root, int curHeight) {
        if (curHeight == res.size()) {
            res.add(root.val);
        } else {
            res.set(curHeight, Math.max(res.get(curHeight), root.val));
        }
        if (root.left != null) {
            dfs(res, root.left, curHeight + 1);
        }
        if (root.right != null) {
            dfs(res, root.right, curHeight + 1);
        }
    }
}

1.3.2 LeetCode 637 在每个树行中找平均值

给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。

示例 1:
   3
  / \
 9  20
   /  \
  15   7
输入:root = [3,9,20,null,null,15,7]
输出:[3,14.5,11]
解释:第 0 层的平均值为 3,第 1 层的平均值为 14.5,第 2 层的平均值为 11 。
因此返回 [3, 14.5, 11] 。

这个题和前面的几个一样,只不过是每层都先将元素保存下来,最后求平均就行了:

class Solution {
	public List<Double> averageOfLevels(TreeNode root) {
		List<Double> res = new ArrayList<>();
		if (root == null) return res;
		Queue<TreeNode> list = new LinkedList<>();
		list.add(root);
		while (list.size() != 0){
			int len = list.size();
			double sum = 0;
			for (int i = 0; i < len; i++){
				TreeNode node = list.poll();
				sum += node.val;
				if (node.left != null) 
					list.add(node.left);
				if (node.right != null) 
					list.add(node.right);
			}
			res.add(sum/len);
		}
		return res;
	}
}

1.3.3 LeetCode 199 二叉树的右视图

题目要求是:给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
在这里插入图片描述
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]

class Solution {
	public List<Integer> rightSideView(TreeNode root) {
		List<Integer> res = new ArrayList<>();
		if (root == null) {
			return res;
		}
		Queue<TreeNode> queue = new LinkedList<>();
		queue.offer(root);
		while (!queue.isEmpty()) {
			int size = queue.size();
			for (int i = 0; i < size; i++) {
				TreeNode node = queue.poll();
				if (node.left != null) {
					queue.offer(node.left);
				}
				if (node.right != null) {
					queue.offer(node.right);
				}
				//将当前层的最后一个节点放入结果列表
				if (i == size - 1) {  
					res.add(node.val);
				}
			}
		}
		return res;
	}
}

是不是很简单,轻轻松松搞定3道题,而这三个题本质都是层次遍历的调整。

1.3.4 最底层最左边的值

给定一个二叉树的根节点root,请找出该二叉树的最底层最左边节点的值。
假设二叉树中至少有一个节点。

示例1:
     2
    / \
   1   3
输入 : root = [2,1,3] 
输出 : 1

示例2:
     1
    / \
   2   3
  /   / \
 4   5   6
    /
   7
输入 : [1,2,3,4,null,5,6,null,null,7] 
输出 : 7

很明显,找最后一行第一个,使用层次遍历最方便了。层次遍历,我们前面分析很多了,这里直接上代码:

class Solution {
	public int findBottomLeftValue(TreeNode root) {
		if (root.left == null && root.right == null) {
			return root.val;
		}

		Queue<TreeNode> queue = new LinkedList<>();
		queue.offer(root);
		TreeNode temp = new TreeNode(-100);

		while (!queue.isEmpty()) {
			temp = queue.poll();
			if (temp.right != null) {
				// 先把右节点加入  queue
				queue.offer(temp.right);
			}
			if (temp.left != null) { 
				// 再把左节点加入  queue
				queue.offer(temp.left);
			}
		}
		return temp.val;
	}
}

如果使用深度优先可以吗?也可以,难点在于如何判断是最后一行呢,其实就是深度最大的叶子节点一定是最后一行,所以要找深度最大的叶子节点。

那么如果找最左边的呢?可以使用前序遍历,这样才先优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。不过这个想想就感觉复杂。

由此可见,很多题目可以使用多种方式进行,但是难易程度会有很大差异,所以我们将每一道题都想想有几种方式做,哪种简单哪种复杂,心里有数再下笔写。

1.5 我们也来造题

前面我们看到LeetCode里有右视图,那能否再来个俯视图呢?从上向下看,不就是每一层最左和最右吗?这里其实有问题的,直接取每层最左和最右是不行的。如果我们这里加个条件,完全二叉树行吗?貌似也不行,如果是满 二叉树呢?貌似用不上层次也能解决,所以这个题不适合考试,但是练习了我们的思维。

如果将要求换成左视图呢?请读者自行思考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2166554.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32】 TCP/IP通信协议(1)

一、前言 TCP/IP是干啥的&#xff1f;它跟SPI、IIC、CAN有什么区别&#xff1f;它如何实现stm32的通讯&#xff1f;如何去配置&#xff1f;为了搞懂这些问题&#xff0c;查询资料可解决如下疑问&#xff1a; 1.为什么要用以太网通信? 以太网(Ethernet) 是指遵守 IEEE 802.3 …

【mbti课堂】计算机系统的六个层次与指令系统

课件&#xff1a;可以从 6 个层次分析和看待计算机系统的基本组成。 指令系统层处在硬件系统和软 件系统之间&#xff0c; 是硬、 软件之间的接口部分&#xff0c; 对两部分都有重要影响。 硬件系统用于实现每条指令的功能&#xff0c; 解决指令之间的衔接关系&#xff1b; 软件…

Golang | Leetcode Golang题解之第437题路径总和III

题目&#xff1a; 题解&#xff1a; func pathSum(root *TreeNode, targetSum int) (ans int) {preSum : map[int64]int{0: 1}var dfs func(*TreeNode, int64)dfs func(node *TreeNode, curr int64) {if node nil {return}curr int64(node.Val)ans preSum[curr-int64(targ…

复杂网络分析_NetworkX

一&#xff1a;NetworkX简介 NetworkX库是一个用于创建、操作复杂网络的结构、动态和功能的Python库。在经济网络中&#xff0c;它可以帮助分析各种经济实体&#xff08;如公司、个人、国家&#xff09;之间的相互关系和互动模式。以下是一些NetworkX在经济网络分析中的实际应…

ArrayList源码实现(一)

ArrayList源码实现&#xff08;一&#xff09; 1. ArrayList的大小是如何自动增加的&#xff1f; 初始化 在构造函数中&#xff0c;可以设定列表的初始值大小&#xff0c;如果没有的话默认使用&#xff0c;提供的静态数据 public ArrayList(int initialCapacity) {if (initi…

Milvus - 架构设计详解

Milvus 是一个专为在大规模密集向量数据集上进行相似性搜索而设计的开源向量数据库系统。其架构建立在流行的向量搜索库之上&#xff0c;如 Faiss、HNSW、DiskANN 和 SCANN&#xff0c;能够处理数百万、数十亿甚至数万亿的向量数据。为了全面了解 Milvus 架构&#xff0c;我们首…

计网作业3

1.交换机是依据 MAC地址 来转发数据包的 2.数据链路层 负责将数据封装成帧&#xff0c;在相邻节点间进行传输 数据链路层负责以下任务&#xff1a; 封装数据 物理地址寻址&#xff1a;使用MAC地址进行寻址&#xff0c;确保数据能够在局域网中正确传输到目标节点 介质访问控…

正确理解C++的友元friend

C的友元&#xff08;friend&#xff09;是个很重要的概念&#xff0c;该如何正确理解呢&#xff1f;本文将以友元函数为例讲解一下&#xff0c;仔细看。 友元的特性&#xff1a; 1、使用friend修饰的成员函数可以访问其他成员的私有成员(private)和保护成员(protected)。 2、…

快速上手基于Vue的动画引擎vueuse/motion

在现代前端开发中&#xff0c;动画可以极大地提升用户体验和界面美感。VueUse Motion 是一个强大的动画库&#xff0c;旨在为 Vue 应用提供简单易用的动画功能。那我们就来看看它在Vue项目中是如何应用的&#xff0c;快速手上为主&#xff0c;官网地址&#x1f447; vueuse/mo…

DevExpress WinForms中文教程:Data Grid - 如何添加或删除行?

本教程介绍DevExpress WinForm的Data Grid控件UI元素和API&#xff0c;它们使您和最终用户能够添加或删除数据行。您将首选学习如何启用内置的数据导航器&#xff0c;然后学习如何使用Microsoft Outlook启发的New Item行添加新记录。最后教程将向您展示基本的API&#xff0c;它…

全景可视化特点+可视化功能实现

全景可视化介绍 全景可视化是一种利用现代计算机技术、图像处理技术和虚拟现实技术&#xff0c;将现实世界中的场景以360度全景的方式呈现在用户面前的技术。它不仅能够提供水平方向360度的全景视野&#xff0c;还能通过垂直方向的视角变化&#xff0c;实现上下视角的调节&…

MVC core 、MVC framework addTagHelper、htmlhelper 、Environment

mvc core 标签助手 TagHelper 只有core 支持 htmlhelper mvc、mvc core 都支持 Environment <environment include"Development">*开发环境,使用不压缩的文件&#xff0c;排除压缩的文件*<link rel"stylesheet" asp-href-include"css/*"…

Linux相关概念和重要知识点(8)(操作系统、进程的概念)

1.操作系统&#xff08;OS&#xff09; &#xff08;1&#xff09;基本结构的认识 任何计算机系统都包含一个基本的程序集合&#xff0c;用于实现计算机最基本最底层的操作&#xff0c;这个软件称为操作系统。操作系统大部分使用C语言编写&#xff0c;少量使用汇编语言。 从…

[极客大挑战 2019]RCE ME1

<?php error_reporting(0); if(isset($_GET[code])){$code$_GET[code];if(strlen($code)>40){die("This is too Long.");}if(preg_match("/[A-Za-z0-9]/",$code)){die("NO.");}eval($code); } else{highlight_file(__FILE__); }// ?>…

【React】组件通信

1. 组件通信 组件间的数据传递 1.1 父传子 步骤&#xff1a; 父组件传递数据——在子组件标签上绑定属性子组件接收数据——子组件通过props参数接收数据 function Son(props) {return <div>{props.value}</div> }function App() {const value 父组件传给子…

实验二十:ds1302时钟实验

数码管硬件如以前实验所示‘ 完整代码如下 ds1302驱动 C文件 #include "ds1302.h" #include "intrins.h"u8 gWRITE_RTC_ADDR[7]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};//秒,分,时,日,月,周,年 u8 gREAD_RTC_ADDR[7]={0x81,0x83,0x85,0x87,0x89,…

Vue3 + TS 实现同一项目同一链接,pc端打开是web应用,手机打开是H5应用

前言&#xff1a; 我自己搭建的项目基本都是用 postcss-px-to-viewport 插件进行适配的&#xff1b; 最近在做一个项目&#xff0c;需求是同样的功能&#xff0c;用户可以在电脑上打开操作使用&#xff0c;也可以在手机上登录进去操作使用&#xff0c;但是跳转链接是同一个&am…

前端工程化之vite

vite常用的插件有哪些? vitejs/plugin-vue&#xff1a;用于支持 Vue.js 单文件组件&#xff08;.vue 文件&#xff09; vitejs/plugin-react&#xff1a;用于支持 React 和 JSX 语法 rollup-plugin-visualizer: 用于打包分析 vite-plugin-restart: 文件修改时自动重启vite …

Python 课程18-SQLAlchemy

前言 SQLAlchemy 是一个功能强大的 Python SQL 工具包和对象关系映射&#xff08;ORM&#xff09;库&#xff0c;它使得开发者能够通过 Python 代码与数据库进行交互&#xff0c;而不必编写 SQL 查询。SQLAlchemy 提供了对多种数据库的支持&#xff0c;包括 MySQL、PostgreSQL…

电路板上电子元件检测系统源码分享

电路板上电子元件检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comp…