【好书推荐】《剑指Offer》之硬技能(编程题7~11)
本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword
持续更新,敬请关注公众号:coderbuff,回复关键字“sword”获取相关电子书。
7.重建二叉树
定义二叉树节点
/**
* 二叉树节点
* @author OKevin
* @date 2019/5/30
**/
public class Node<T> {
/**
* 左孩子
*/
private Node left; /**
* 右孩子
*/
private Node right; /**
* 值域
*/
private T data; public Node() {
} public Node(T data) {
this.data = data;
} //省略getter/setter方法
}
解法一:递归
/**
* 根据前序遍历序列和中序遍历序列重建二叉树
* @author OKevin
* @date 2019/5/30
**/
public class Solution {
public Node<Integer> buildBinaryTree(Integer[] preorder, Integer[] inorder) {
if (preorder.length == 0 || inorder.length == 0) {
return null;
}
Node<Integer> root = new Node<>(preorder[0]);
int index = search(0, inorder, root.getData());
root.setLeft(buildBinaryTree(Arrays.copyOfRange(preorder, 1, index + 1), Arrays.copyOfRange(inorder, 0, index)));
root.setRight(buildBinaryTree(Arrays.copyOfRange(preorder, index + 1, preorder.length), Arrays.copyOfRange(inorder, index + 1, inorder.length)));
return root;
} /**
* 在中序遍历的序列中查询根节点所在的位置
* @param start 开始查找的下标
* @param inorder 中序遍历序列
* @param rootData 根节点值
* @return 节点值在中序遍历序列中的下标位置
*/
private int search(int start, Integer[] inorder, Integer rootData) {
for (; start < inorder.length; start++) {
if (rootData.equals(inorder[start])) {
return start;
}
}
return -1;
}
}
二叉树的遍历一共分为:前序遍历、中序遍历和后序遍历
前序遍历遍历顺序为:根节点->左节点->右节点
中序遍历遍历顺序为:左节点->根节点->右节点
后序遍历遍历顺序为:左节点->右节点->根节点
例如二叉树:
1
/ \
2 3
/ / \
4 5 6
\ /
7 8
前序遍历结果为:1、2、4、7、3、5、6、8
中序遍历结果为:4、7、2、1、5、3、8、6
后序遍历结果为:7、4、2、5、8、6、3、1
此题给出前序和中序的遍历结果,要求重建二叉树。从前序遍历结果得知,第一个节点一定是根节点。从中序遍历结果可知,根节点左侧一定是其左子树右侧一定是其右子树。
那么可以得到:
第一次:
根据前序遍历结果得知,1为根节点,根据中序遍历结果得知,4、7、2为左子树,5、3、8、6为右子树。
第二次:
根据前序遍历结果得知,2为节点,根据中序遍历,4、7位节点2的左子树,节点2没有右子树。
第三次:
根据前序遍历结果得知,4为节点,根据中序遍历,7为节点4的右子树,节点4没有左子树。
以此类推,根据递归即可构建一颗二叉树。
测试程序
/**
* 1
* / \
* 2 3
* / / \
* 4 5 6
* \ /
* 7 8
* @author OKevin
* @date 2019/5/30
**/
public class Main {
public static void main(String[] args) {
Integer[] preorder = new Integer[]{1, 2, 4, 7, 3, 5, 6, 8};
Integer[] inorder = new Integer[]{4, 7, 2, 1, 5, 3, 8, 6};
Solution solution = new Solution();
Node<Integer> node = solution.buildBinaryTree(preorder, inorder);
}
}
8.二叉树的下一个节点
题目:给定一颗二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?节点中除了两个分别指向左、右子节点的指针,还有一个指向父节点的指针。
分析:熟悉二叉树中序遍历的特点。查找节点的下一个节点,一共有两种情况:一、节点有右子树,节点的下一个节点即为右子树的最左子节点;二、节点没有右子树,此时又要分为两种情况:1、如果节点位于父节点的左节点,节点的下一个节点即为父节点;2、如果节点位于父节点的右节点,此时向上遍历,找到它是父节点的左节点。
节点定义
/**
* 二叉树节点定义
* @author OKevin
* @date 2019/6/3
**/
public class Node<T> {
/**
* 值域
*/
private T data; /**
* 左节点
*/
private Node<T> left; /**
* 右节点
*/
private Node<T> right; /**
* 父节点
*/
private Node<T> parent; public Node() {
} public Node(T data) {
this.data = data;
}
//省略getter/setter方法
}
中序遍历情况下,查找二叉树节点的下一个节点
/**
* 中序遍历情况下,查找节点的下一个节点
* @author OKevin
* @date 2019/6/3
**/
public class Solution {
public Node getNextNode(Node<Integer> head) {
if (head == null) {
return null;
}
Node<Integer> p = head;
//第一种情况,节点有右子树。节点右子树的最左子节点即为节点中序遍历的下一个节点
if (p.getRight() != null) {
p = p.getRight();
while (p.getLeft() != null) {
p = p.getLeft();
}
return p;
}
//第二种情况,节点没有右子树。仍然有两种情况:一、节点位于父节点的左节点,此时父节点即为节点中序遍历的下一个节点;二、节点位于父节点的右节点,此时一直向上查找,直到是它父节点的左节点
while (p.getParent() != null) {
if (p == p.getParent().getLeft()) {
return p.getParent();
}
p = p.getParent();
}
return null;
}
}
测试程序
/**
* 1
* / \
* 2 3
* / / \
* 4 5 6
* \ /
* 7 8
* 中序遍历序列:4,7,2,1,5,3,8,6
* @author OKevin
* @date 2019/6/3
**/
public class Main {
public static void main(String[] args) {
Node<Integer> node1 = new Node<>(1);
Node<Integer> node2 = new Node<>(2);
Node<Integer> node3 = new Node<>(3);
Node<Integer> node4 = new Node<>(4);
Node<Integer> node5 = new Node<>(5);
Node<Integer> node6 = new Node<>(6);
Node<Integer> node7 = new Node<>(7);
Node<Integer> node8 = new Node<>(8);
node1.setLeft(node2);
node1.setRight(node3);
node2.setLeft(node4);
node2.setParent(node1);
node3.setLeft(node5);
node3.setRight(node6);
node3.setParent(node1);
node4.setRight(node7);
node4.setParent(node2);
node5.setParent(node3);
node6.setLeft(node8);
node6.setParent(node3);
node7.setParent(node4);
node8.setParent(node6);
Solution solution = new Solution();
System.out.println(solution.getNextNode(node6).getData());
}
}
9.用两个栈实现队列
题目:用两个栈实现一个队列。
分析:栈的结构是FILO(先进后出),队列的结构是FIFO(先进先出)。栈s1用于存储元素,栈s2当执行删除队列尾元素时,从s1弹出数据进入s2,再弹出s2,即实现一个队列。
/**
* 两个栈实现一个队列
* @author OKevin
* @date 2019/6/3
**/
public class MyQueue<T> {
private Stack<T> s1 = new Stack<>();
private Stack<T> s2 = new Stack<>(); /**
* 从队尾添加元素
* @param t 元素
* @return 添加的数据
*/
public T appendTail(T t) {
s1.push(t);
return t;
} /**
* 对队头删除元素
* @return 删除的元素
*/
public T deleteTail() {
if (s1.empty() && s2.empty()) {
return null;
}
if (s2.empty()) {
while (!s1.empty()) {
s2.push(s1.pop());
}
}
T t = s2.pop();
return t;
}
}
10.斐波那契数列
题目:求斐波那契数列的第n项。
解法一:递归
/**
* 求斐波那契数列的第n项
* @author OKevin
* @date 2019/6/3
**/
public class Solution1 { public Integer fibonacci(Integer n) {
if (n.equals(0)) {
return 0;
}
if (n.equals(1)) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
优点:简单易懂
缺点:如果递归调用太深,容易导致栈溢出。并且节点有重复计算,导致效率不高。
解法二:循环
/**
* 求斐波那契数列的第n项
* @author OKevin
* @date 2019/6/3
**/
public class Solution2 { public Integer fibonacci(Integer n) {
if (n.equals(0)) {
return 0;
}
if (n.equals(1)) {
return 1;
}
Integer first = 0;
Integer second = 1;
Integer result = first + second;
for (int i = 2; i <= n; i++) {
result = first + second;
first = second;
second = result;
}
return result;
}
}
通过循环计算斐波那契数列能避免重复计算,且不存在调用栈过深的问题。
11. 旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增的数组的一个旋转,输出旋转数组的最小元素。例如,数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
*题中本意是希望能找到数组中的最小数字,直接暴力解法遍历即可。
引子:通过“二分查找”算法查找有序数组中的数字。
二分查找有序数组是否存在数字
/**
* 二分查找有序数组中的数字
* @author OKevin
* @date 2019/6/3
**/
public class BinarySearch { public boolean find(Integer[] array, Integer target) {
Integer start = 0;
Integer end = array.length - 1;
return partition(array, start, end, target);
} private boolean partition(Integer[] array, Integer start, Integer end, Integer target) {
if (target < array[start] || target > array[end] || start > end) {
return false;
} int middle = (end + start) / 2; if (target > array[middle]) {
return partition(array, middle + 1, end, target);
} else if (target < array[middle]) {
return partition(array, start, middle - 1, target);
} else {
return true;
}
}
}
利用二分法思想查找旋转数组中的最小数字,注意当出现原始数组为:{0,1,1,1,1}时,{1,1,1,0,1}和{1,0,1,1,1}均是旋转数组,这两种情况left=middle=right都是1,不能区别,此时只能按照顺序查找的方式。
/**
* 找到旋转数组中的最小值
* @author OKevin
* @date 2019/6/3
**/
public class Solution { public Integer find(Integer[] array) {
if (array.length == 0) {
return -1;
}
int left = 0;
int right = array.length - 1;
int middle = 0;
while (array[left] >= array[right]) {
if (right - left == 1) {
middle = right;
break;
}
middle = (left + right) / 2;
if (array[left].equals(array[right]) && array[left].equals(array[middle])) {
return min(array, left, right);
}
if (array[middle] >= array[left]) {
left = middle;
} else {
right = middle;
}
}
return array[middle];
} /**
* 当出现原始数组为:{0,1,1,1,1}时,{1,1,1,0,1}和{1,0,1,1,1}均是旋转数组,这两种情况left=middle=right都是1,不能区别
* @param array 数组
* @param left 起始
* @param right 结束
* @return
*/
private Integer min(Integer[] array, int left, int right) {
int min = array[left];
for (int i = left + 1; i < right; i++) {
if (array[i] < min) {
min = array[i];
}
}
return min;
}
}
本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword
持续更新,敬请关注公众号:coderbuff,回复关键字“sword”获取相关电子书。
这是一个能给程序员加buff的公众号 (CoderBuff)
【好书推荐】《剑指Offer》之硬技能(编程题7~11)的更多相关文章
- 剑指offer(41-45)编程题
41.入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的. class Solution { public: vector&l ...
- 剑指offer(36-40)编程题
两个链表的第一个公共结点 数字在排序数组中出现的次数 二叉树的深度 平衡二叉树 数组中只出现一次的数字 36.输入两个链表,找出它们的第一个公共结点. class Solution1 { public ...
- (4)剑指Offer之链表相关编程题
一 链表中倒数第k个节点 题目描述: 输入一个链表,输出该链表中倒数第k个结点 问题分析: 一句话概括: 两个指针一个指针p1先开始跑,指针p1跑到k-1个节点后,另一个节点p2开始跑,当p1跑到最后 ...
- 剑指offer——python【第54题】字符流中第一个不重复的字符
题目描述 请实现一个函数用来找出字符流中第一个只出现一次的字符.例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g".当从该字符流中读出 ...
- 剑指offer——python【第21题】栈的压入、弹出序列
题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压 ...
- 《剑指offer》第十九题(正则表达式匹配)
// 面试题19:正则表达式匹配 // 题目:请实现一个函数用来匹配包含'.'和'*'的正则表达式.模式中的字符'.' // 表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次).在本题 ...
- 《剑指offer》数学题及其它 (牛客11.05)
比较多的思维题,涉及位运算.快速幂.二进制.约瑟夫问题.队列.贪心.dp等等. 难度 题目 知识点 ☆ 12.数值的整数次方 细节,快速幂 ☆☆ 47.求1+2+3+···+n 思维发散 ☆☆ 48. ...
- 剑指offer——python【第59题】按之子形顺序打印二叉树
题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 解题思路 这道题其实是分层打印二叉树的进阶版 ...
- 剑指offer——python【第60题】把二叉树打印成多行
题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行.#类似于二维列表[[1,2],[4,5]] 解题思路 其实这倒题和其他类似的题有所区别,这里是分层打印,把每层的节点值放在同一 ...
- 剑指offer——python【第39题】平衡二叉树
题目描述 输入一棵二叉树,判断该二叉树是否是平衡二叉树. 解题思路 平衡二叉树首先是二叉搜索树,且它每个节点的左子树和右子树高度差至多等于1:只要从根节点,依次递归判断每个节点是否满足如上条件即可 ...
随机推荐
- HihoCoder1449 后缀自动机三·重复旋律6
描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数.但是K不是固定的,小Hi想知道对 ...
- Node笔记 - process.cwd() 和 __dirname 的区别
process.cwd() 返回工作目录 __dirname 返回脚本所在的目录位置 单看概念觉得都差不多,有种似懂非懂的感觉,那么接下用一个简单易懂的例子来理解下这两者的区别,在此之前先看一个方法 ...
- 使用iCamera 测试MT9F002 1400w高分辨率摄像头小结 之!!看清细节!!!
使用iCamera 测试MT9F002 1400w高分辨率摄像头小结 之!!看清细节!!! 本方案测试两种种分辨率输出(其他更多分辨率设置,可以参考手册配置) 4608*3288=1515万像素 11 ...
- 新更新的OV7670 OV7725模块效果展示 OV7670 FPC版 30万像素 CMOS模块 兼容官哥方便 FPGA stm32f407 68013等使用
原创OV7670,30W像素摄像头模块, 3) 光学尺寸1/6 ,像素面积3.6 μm x 3.6 μm,灵敏度1.3V/Lux-sec 4) 工作电压:3.3V 5) 接口定义为10*2的2.54间 ...
- Vue中兄弟组件间传值-(Bus/总线/发布订阅模式/观察者)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【Java Web开发学习】Spring MVC异常统一处理
[Java Web开发学习]Spring MVC异常统一处理 文采有限,若有错误,欢迎留言指正. 转载:https://www.cnblogs.com/yangchongxing/p/9271900. ...
- oracle管理角色和权限
介绍 这一部分主要看看oracle中如何管理权限和角色,权限和角色的区别在哪里. 当刚刚建立用户时,用户没有任何权限,也不能执行任何操作.如果要执行某种特定的数据库操作,则必需为其授予系统的权限:如果 ...
- 使用vsCode配合IAR搭建arm开发环境
众所周知IAR的编辑功能就是个垃圾,但是不得不承认IAR的编译器相当的牛X,经常以稳定可靠而著称,为此我们把VSCODE强大的编辑功能和IAR结合一下来加快我们的开发周期. 一.下载VSCODE并安装 ...
- Python使用QQ邮箱发送邮件报错smtplib.SMTPAuthenticationError
最新在学习Python的基础入门系列课程,今天学习到使用python 的内置库smtplib发送邮件内容. 使用Python发送邮件步骤简单: 创建SMTP连接 使用邮箱和密码登录SMTP服务器 创建 ...
- 高性能mysql——高性能索引策略
<高性能MySQL>读书笔记 一. 索引的优点 1. 索引可以让服务器快速定位到表的指定位置,大大减少了服务器需要扫描的数量: 2. 最常见的B-Tree索引按照顺序存储数据,可以用来做o ...