Hello,大家好,我是猿码,新的一天从 CSDN 撸博客开始。
元旦假期接近尾声,不知道大家有没有玩的尽兴,或者与家人团聚?明天又要开始工作了,想想都有些开森,因为又可以帮同事解决问题,又可以在工作上大显身手了。不过,在工作之前,大家最好牟足劲,热热身,不如就从这篇奇偶树开始吧~~
🔍 题目介绍:奇偶树(Even Odd Tree)
📕 题目介绍(奇偶树概念):
如果一棵二叉树满足下述几个条件,则可以称为 奇偶树 :
- 二叉树根节点所在层下标为 0 ,根的子节点所在层下标为 1 ,根的孙节点所在层下标为 2 ,依此类推。
- 偶数下标 层上的所有节点的值都是 奇 整数,从左到右按顺序 严格递增
- 奇数下标 层上的所有节点的值都是 偶 整数,从左到右按顺序 严格递减
给你二叉树的根节点,如果二叉树为 奇偶树 ,则返回 true ,否则返回 false 。
tip:
- 树中节点数在范围
[1, ]
内1 <= Node.val <=
来源:力扣(LeetCode)
难度:中等(Medium)
💡 题目分析:
老生常谈。在做这道题之前,我们需要准备哪些基础知识?需要注意哪些细节?如何进一步优化等等。
树的概念在任何编程语言中都屡见不鲜,因为它是数据结构中的一个重要概念,且已经有很多实现可以为编程工作者应用,比如 ConcurrentHashMap、PriorityQueue 等等。
下面简单介绍一下树的相关概念:
树:
- 由 个有限节点组成
- 结构具有层次性,最顶层为根(root)节点,自顶向下(不含根节点)都属于叶子节点(leaf)
- 每个节点都有 个叶子节点
- 没有父节点的节点叫做根节点,每个叶子节点有且只有 1 个父节点
- 无序
二叉树:
- 由 个有限节点组成,特别地,当 n 为 0 时称为空树
- 有序
- 根节点概念同上
- 每个节点都有 个子节点
- 叶子节有左子树和右子树之分
草图: 二叉树(左),树(右)
了解了树的基本概念,接下来我们就可以结合题意,做题啦!
首先,大家都知道二叉树遍历,有前序(Preorder)、中序(Inorder)、后续(Postordder)以及层序(Level)4 种常见遍历方式。奇偶树的概念涉及到了层次概念,偶数(Even)层所有节点的元素值都为奇数(Odd),反之,奇数层的元素都为偶数。因此,我们可以采用相对好理解的层序遍历解题,会容易很多。所谓层序遍历,就是对二叉树的每一层进行从左向右遍历。
对于二叉树的层数,我么可以定义变量 。
一般层次遍历常用的都是递归,但这里我们不使用递归,而是用一个 while 循环,结合 while 循环将二叉树扁平化。
扁平化后的二叉树节点,我们使用可变数组 ArrayList 来存储,变量为 。每一层遍历后,可以统计出子节点的数量,我们定义变量 。同时 count 变量可以作为下一层遍历的区间,因此根据 变量可以求出,遍历的区间的 和 索引:
= , 。
🖊 代码实现
完成以上这些,就可以实现对二叉树的层序遍历了。剩下的就是对奇偶层的元素是否和法以及元素是否严格递增或递减进行校验了。这个留在后面介绍。下面是我写的一个模拟 Demo
public boolean isEvenOddTree(TreeNode root) {
// 存储扁平化后的二叉树元素的容器
ArrayList<TreeNode> ns = new ArrayList<>();
// 先放入根节点
ns.add(root);
// 根节点的层数是偶数,元素应为奇数,反之不合法
if((root.val & 1) != 1) return false;
int depth = 1, count = 1, last;
while (true) {
// 获取区间的开始和结束下标。tip:结束下标是动态的
int end = ns.size(), start = end - count;
count = 0;
TreeNode tn;
// 严格降序或升序的上下限
last = (depth & 1) == 1 ? 1000001 : 0;
for (int i = start; i < end; i++) {
tn = ns.get(i);
if(tn.left != null) {
ns.add(tn.left);
// 检查是否合法
if(!check(last, depth, tn.left.val)){
return false;
}
last = tn.left.val;
// 统计该层节点的数量
count++;
}
// 以下定义同上
if(tn.right != null) {
ns.add(tn.right);
if(!check(last, depth, tn.right.val)){
return false;
}
last = tn.right.val;
count++;
}
}
// 如果上下限初始值未改变,说明该层所有的节点都无子节点,结束循环
if(last == 0 || last == 1000001) break;
// 统计层数
depth++;
}
return true;
}
boolean check(int last, int depth, int cur){
if((depth & 1) == 0) { // 偶数层
if((cur & 1) == 0) return false;
// 判断是否严格升序
return cur > last;
} else { // 奇数层
if((cur & 1) == 1) return false;
// 判断是否严格递减
return cur < last;
}
}
时间复杂度: ,其中 为二叉树的节点数量
空间复杂度:
💡 代码分析
对于层数是否合法,就很简答了,只要对层数取模 2 就可以,这里我使用的是按位与运算,& 1 等同于 % 2,只不过前者的 & 运算符优先级低于 == 号,所以要加上括号,否则编译不通过。
元素是否严格递增或递减,这里我一开始闯入了一个排序误区,认为需要先将元素存储起来,然后再进行排序校验,其实这么做是很傻的,因为严格递增或递减已经将“问题”的维度较到了最低,进而复杂度就是线性的。我们写一个校验函数,来对上一次校验的元素和当前元素进行比对,这里我定义变量 和 ,
严格递增如下:
根据公式:则可以推理出:
若 ,; ;则 且 !=
严格递减的思路同上。
数组是线性数据结构,每个元素的下标是唯一的,若满足上面的条件,则不存在 和 的情况,因此此题的严格递增或递减校验的时间复杂度为 。所以,可以证明将每层元素存储起来再校验是愚钝的。同时,对于我自己来说这个点也是本题的优化点之一,
📕 总结
这道题对于我个人来说是了解严格递增或递减以及层序遍历的典范。从做到题之后我本人从中汲取了很多。当然这个解法并不是最优的,但时间和空间复杂度显而易见。我尤记得一位大佬告诫我的话:学会举一反三。编程的思路,大同小异,只是它们披着不同的外衣罢了。
学习点:
- 如何层序遍历二叉树
- 严格递增或递减的严格定义是什么
- 什么是奇偶树
🚩 结语
元旦假期,很快就和我们说再见了,不过还有春节哦,尽管因为疫情,有些许的遗憾,对于某些城市而言可能回家是一个奢望,但在回家之前,我们应该做好的自己,去迎接任何一种可能。想想妈妈做的香喷喷的饭菜以及和爸爸一起爬山钓鱼就觉得兴奋不已。最重要的是,带着一个健康的身体和积极得心态去面对...