《剑指Offer》题三十一~题四十
三十一、栈的压入、弹出序列
题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的数字均不相等。例如,序列{1, 2, 3, 4 ,5}是某栈的压栈序列,序列{4, 5, 3, 2, 1}是该压栈序列对应的一个弹出序列,但{4, 3, 5, 1, 2}就不可能是该压栈序列的弹出序列。
分析:本题中的压栈序列并非是一次全部压入堆栈!如果没有思路,可以举一两个例子,一步步分析压栈、弹出的过程,从中找出规律。
三十二、从上到下打印二叉树
题目一:不分行从上到下打印二叉树。从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
分析:二叉树的层次遍历。
队列解法:
void print_top_to_bottom(BinaryTreeNode *pRoot)
{
if(pRoot == nullptr) return;
queue<BinaryTreeNode*> que; // 用于层次遍历
que.push(pRoot);
while(!que.empty()) {
BinaryTreeNode *pNode = que.front();
que.pop();
printf("%d\t", pNode->value);
if(pNode->lChild != nullptr) que.push(pNode->lChild);
if(pNode->rChild != nullptr) que.push(pNode->rChild);
}
}
小结:层次遍历这一过程可概括为,首先把起始节点(如根节点)放入队列,接下来每次从队列的头部取出一个节点,遍历这个节点之后把它能到达的节点(如子节点)都依次放入队列,重复这个遍历过程,直到队列中的节点全部被遍历为止。
题目二:分行从上到下打印二叉树。从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
分析:为了把二叉树的每一行单独打印到一行里,我们需要两个变量,toBePrinted变量表示在当前层中还没有打印的节点数,nextLevel变量表示下一层的节点数。
解法:
void print_by_row(BinaryTreeNode *pRoot)
{
if(pRoot == nullptr) return;
queue<BinaryTreeNode*> que;
que.push(pRoot);
int toBePrinted = 1;
int nextLevel = 0;
while(!que.empty()) {
BinaryTreeNode *pNode = que.front();
que.pop();
toBePrinted--;
printf("%d\t", pNode->value);
if(toBePrinted == 0) {
printf("\n");
toBePrinted = nextLevel;
nextLevel = 0;
}
if(pNode->lChild != nullptr) {
que.push(pNode->lChild);
nextLevel++;
}
if(pNode->rChild != nullptr){
que.push(pNode->rChild);
nextLevel++;
}
}
}
题目三:之字形打印二叉树。请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
分析:如果找不到解决办法,可以试着用具体的例子一步步分析。可知,按之字形打印需要两个栈,如果当前打印的是奇数层(第一层、第三层等),则先保存左子节点再保存右子节点到第一个栈里;如果当前打印的是偶数层,则先保存右子节点再保存左子节点到第二个栈里。之所以需要两个栈,是因为栈是后入先出的容器,故用一个栈不能将同一层的节点依次打印出来。
解法:
void print(BinaryTreeNode *pRoot)
{
if(pRoot == nullptr) return;
stack<BinaryTreeNode*> levels[2];
int current = 0;
int next = 1;
levels[0].push(pRoot);
while(!levels[0].empty() || !levels[1].empty()) {
BinaryTreeNode *pNode = levels[current].top();
levels[current].pop();
printf("%d\t", pNode->value);
if(current == 0) {
if(pNode->lChild != nullptr)
levels[next].push(pNode->lChild);
if(pNode->rChild != nullptr)
levels[next].push(pNode->rChild);
}
else {
if(pNode->rChild != nullptr)
levels[next].push(pNode->rChild);
if(pNode->lChild != nullptr)
levels[next].push(pNode->lChild);
}
if(levels[current].empty()) {
printf("\n");
current = 1 - current;
next = 1 - next;
}
}
}
小结:本题表面看上去像层次遍历,但不能用队列来实现,故应该根据具体情况选用合适的数据结构和算法。
三十三、二叉搜索树的后序遍历序列
题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。假设输入的数组的任意两个数字互不相同。
分析:要完成这道题,需要先掌握后序遍历这一定义。比如,对于后序遍历序列{5, 7, 6, 9, 11, 10, 8},最后一个数字{8}是树的根节点的值,而前面6个数字可以分为两部分,第一部分{5, 7, 6}是左子树节点的值,它们都比根节点的值小,第二部分{9, 11, 10}是右子树节点的值,它们都比根节点的值大。接下来,我们可以用同样的方法来处理这两个子序列{5, 7, 6}和{9, 11, 10}。这其实就是一个递归的过程。
解法:
bool seq_of_BST(int *seq, int length)
{
if(seq == nullptr || length <= 0) return false;
int root = seq[length - 1];
// 在BST中,左子树节点的值小于根节点的值
int i = 0;
for(; i < length - 1; ++i) {
if(seq[i] > root)
break;
}
// 在BST中,右子树节点的值大于根节点的值
for(int j = i; j < length - 1; ++j) {
if(seq[j] < root)
return false;
}
// 递归,判断左子树是不是二叉搜索树
bool left = true;
if(i > 0) left = seq_of_BST(seq, i);
// 递归,判断右子树是不是二叉搜索树
bool right = true;
if(i < length - 1) right = seq_of_BST(seq + i, length - i - 1);
return (left && right);
}
三十四、二叉树中和为某一值的路径
题目:输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。
提示:一般的数据结构教材中都没有介绍树的路径,因此对大多数应聘者而言,这是一个新概念,此时我们可以试着从一两个具体的例子入手,找到规律。
分析:路径是从根节点出发到叶节点,即路径总是以根节点为起始点,因此我们首先需要遍历根节点,故必须选择前序遍历这一方式。
三十五、复杂链表的复制
题目:请实现函数ComplexListNode* Clone(ComplexListNode *pHead),复制一个复杂链表。在复杂链表中,每个节点除了有一个m_pNext指针指向下一个节点,还有一个m_pSibling指针指向链表中的任意节点或者nullptr。
分析:本题中的复杂链表是一种不太常见的数据结构,并且复制这种链表的过程也较为复杂。我们可以把复杂链表的复制过程分解成3个步骤,这样能帮我们理清思路。
三步解法:
ComplexListNode* Clone(ComplexListNode* pHead)
{
CloneNodes(pHead);
ConnectSiblingNodes(pHead);
return ReconnectNodes(pHead);
} void CloneNodes(ComplexListNode* pHead)
{
ComplexListNode* pNode = pHead;
while(pNode != nullptr)
{
ComplexListNode* pCloned = new ComplexListNode();
pCloned->m_nValue = pNode->m_nValue;
pCloned->m_pNext = pNode->m_pNext;
pCloned->m_pSibling = nullptr; pNode->m_pNext = pCloned; pNode = pCloned->m_pNext;
}
} void ConnectSiblingNodes(ComplexListNode* pHead)
{
ComplexListNode* pNode = pHead;
while(pNode != nullptr)
{
ComplexListNode* pCloned = pNode->m_pNext;
if(pNode->m_pSibling != nullptr)
{
pCloned->m_pSibling = pNode->m_pSibling->m_pNext;
} pNode = pCloned->m_pNext;
}
} ComplexListNode* ReconnectNodes(ComplexListNode* pHead)
{
ComplexListNode* pNode = pHead;
ComplexListNode* pClonedHead = nullptr;
ComplexListNode* pClonedNode = nullptr; if(pNode != nullptr)
{
pClonedHead = pClonedNode = pNode->m_pNext;
pNode->m_pNext = pClonedNode->m_pNext;
pNode = pNode->m_pNext;
} while(pNode != nullptr)
{
pClonedNode->m_pNext = pNode->m_pNext;
pClonedNode = pClonedNode->m_pNext; pNode->m_pNext = pClonedNode->m_pNext;
pNode = pNode->m_pNext;
} return pClonedHead;
}
三十六、二叉搜索树与双向链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
三十七、序列化二叉树
题目:请实现两个函数,分别用来序列化和反序列化二叉树。
三十八、字符串的排列
题目:输入一个字符串,打印出该字符串中字符的所有排列。例如,输入字符串"abc",则打印出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
三十九、数组中出现次数超过一半的数字
题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
分析:最直观的解决方法是将该数组排序,然后找出该数字,但这种方法的时间复杂度为O(nlogn)。我们要思考时间复杂度为O(n)的方法。
四十、最小的K个数
题目:输入n个整数,找出其中最小的K个数。
提示:和上题一样,我们可以将这n个整数排序,排序之后位于最前面的K个数就是最小的K个数,但这种思路的时间复杂度为O(nlogn)。我们需要更快的方法。
分析:我们可以先创建一个大小为K的数据容器来存储最小的K个数字,接下来每次从输入的n个整数中读入一个数。如果容器中已有的数字少于K个,则直接把这次读入的整数放入容器之中;如果容器中已有K个数字了,我们要做3件事情(①在K个整数中找到最大数;②有可能在这个容器中删除最大数;③有可能要插入一个新的数字)。如果用一棵二叉树来实现这个数据容器,比如最大堆,它每次可以在O(1)时间内得到已有的K个数字中的最大值,在O(logk)时间内完成删除及插入操作。但我们自己从头实现一个最大堆需要一定的代码,在面试中很难完成,故我们还可以采用红黑树来实现这个容器,而在红黑树中的查找、删除和插入操作都只需要O(logk)时间。因为set和multiset都是基于红黑树实现的,故本题可使用multiset来作为容器。
解法:
typedef multiset<int, greater<int>> intSet;
typedef multiset<int, greater<int>>::iterator setIterator; void get_least_numbers(const vector<int> &data, intSet &leastNumbers, int k)
{
if(k < 1 || data.size() < k) return;
leastNumbers.clear();
vector<int>::const_iterator iter = data.begin();
for(; iter != data.end(); ++iter) {
if(leastNumbers.size() < k)
leastNumbers.insert(*iter);
// 容器已满时
else {
setIterator iterGreatest = leastNumbers.begin();
// 在该multiset<int, greater<int>>类型中,第一个元素最大
if(*iter < *(leastNumbers.begin())) {
// 删除最大数,插入一个新数
leastNumbers.erase(iterGreatest);
leastNumbers.insert(*iter);
}
}
}
}
小结:在上段代码中,每次输入一个新元素时,需要O(logk)时间,而总共有n个输入数字,故总的时间效率就是O(nlogk)。
《剑指Offer》题三十一~题四十的更多相关文章
- 剑指Offer(三十一):整数中1出现的次数(从1到n整数中1出现的次数)
剑指Offer(三十一):整数中1出现的次数(从1到n整数中1出现的次数) 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https:// ...
- 《剑指offer》第十一题(旋转数组的最小数字)
// 面试题:旋转数组的最小数字 // 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. // 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如数组 // {3, ...
- 《剑指offer》第二十一题(调整数组顺序使奇数位于偶数前面)
// 面试题21:调整数组顺序使奇数位于偶数前面 // 题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有 // 奇数位于数组的前半部分,所有偶数位于数组的后半部分. #inclu ...
- 剑指Offer(三十四):第一个只出现一次的字符
剑指Offer(三十四):第一个只出现一次的字符 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net ...
- 剑指Offer(二十一):栈的压入、弹出序列
剑指Offer(二十一):栈的压入.弹出序列 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/b ...
- 剑指Offer(三十二):把数组排成最小的数
剑指Offer(三十二):把数组排成最小的数 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/b ...
- 剑指Offer(三十七):数字在排序数组中出现的次数
剑指Offer(三十七):数字在排序数组中出现的次数 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.n ...
- 剑指Offer(三十六):两个链表的第一个公共结点
剑指Offer(三十六):两个链表的第一个公共结点 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.ne ...
- 剑指Offer(三十五):数组中的逆序对
剑指Offer(三十五):数组中的逆序对 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/bai ...
- 剑指Offer(三十三):丑数
剑指Offer(三十三):丑数 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/baidu_31 ...
随机推荐
- 团体队列 UVA540 Team Queue
题目描述 有t个团队的人正在排一个长队.每次新来一个人时,如果他有队友在排队,那么新人会插队到最后一个队友的身后.如果没有任何一个队友排队,则他会被排到长队的队尾. 输入每个团队中所有队员的编号,要求 ...
- 【2018 ICPC亚洲区域赛南京站 A】Adrien and Austin(博弈)
题意: 有一排n个石子(注意n可以为0),每次可以取1~K个连续的石子,Adrien先手,Austin后手,若谁不能取则谁输. 思路: (1) n为0时的情况进行特判,后手必胜. (2) 当k=1时, ...
- Shell中的${}、##和%%使用范例
假设定义了一个变量为,代码如下: file=/dir1/dir2/dir3/my.file.txt 可以用${ }分别替换得到不同的值: ${file#*/}: 删掉第一个 / 及其左边的字符串:di ...
- 利用tornado使请求实现异步非阻塞
基本IO模型 网上搜了很多关于同步异步,阻塞非阻塞的说法,理解还是不能很透彻,有必要买书看下. 参考:使用异步 I/O 大大提高应用程序的性能 怎样理解阻塞非阻塞与同步异步的区别? 同步和异步:主要关 ...
- Git命令行和Xcode结合使用(我来告诉你这行代码谁写的)
现在一直使用Git来管理代码,对于有强迫症的我来说,依旧选择了命令行,下面这段话可以更好的解释我为什么喜欢使用终端敲命令. There are a lot of different ways to u ...
- 万恶的a标签
相信很多人碰见过这些问题吧 给某个a标签套的元素中添加点击事件 在外面就能获取到但是点击事件不生效把 或者在页面中点击一个a标签元素发现页面返回了最顶端 然后就开始郁闷了 哈哈 其实这些看似神奇的 ...
- hadoop的自定义分组实现 (Partition机制)
hadoop开发中我们会遇到类似这样的问题,比如 如何将不同省份的手机号分别输出到不同的文件中,本片文章将对hadoop内置的Partition类进行重写以解决这个问题. MapReduce的使用者通 ...
- 北京Uber优步司机奖励政策(3月30日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- Hive窗口函数之LAG、LEAD、FIRST_VALUE、LAST_VALUE的用法
一.创建表: create table windows_ss ( polno string, eff_date string, userno string ) ROW FORMAT DELIMITED ...
- 11gR2RAC更换CRS磁盘组文档
磁盘(pv)准备 在生产环境中,提前从存储上划分一些磁盘挂载到RAC系统的两个节点上(node1,node2). 新增加磁盘组为(hdisk14--hdisk24) 1.1磁盘使用规划 ...