《剑指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)。  

  

  

原文地址:https://www.cnblogs.com/xzxl/p/9551525.html

时间: 2024-10-11 05:36:46

《剑指Offer》题三十一~题四十的相关文章

剑指Offer(Java版)第四十题:在数组中的两个数字,如果前面一个数字大于后面的数字, 则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。 并将P对1000000007取模的结果输出。 即输出P%1000000007

/*在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对1000000007取模的结果输出. 即输出P%1000000007 */ import java.util.*; public class Class40 { public int InversePairs(int[] array){ int length = array.length; int P = 0; for(int i = 0; i < lengt

剑指Offer(Java版)第四十五题:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

/*一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. */ import java.util.*; public class Class45 { public void FindNumsAppearOnce(int[] array, int num1[], int num2[]){ ArrayList<Integer> list = new ArrayList<Integer>(); Arrays.sort(array); for(int

《剑指offer》第二十一题:调整数组顺序使奇数位于偶数前面

// 面试题21:调整数组顺序使奇数位于偶数前面 // 题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有 // 奇数位于数组的前半部分,所有偶数位于数组的后半部分. #include <cstdio> void Reorder(int* pData, unsigned int length, bool (*func)(int)); bool isEven(int n); // ====================方法一==================== void

剑指offer第三题 从尾到头打印链表

输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 解题思路:先入栈相当于链表逆序再出栈实现链表从尾到头的顺序输出. 1 /** 2 * public class ListNode { 3 * int val; 4 * ListNode next = null; 5 * 6 * ListNode(int val) { 7 * this.val = val; 8 * } 9 * } 10 * 11 */ 12 import java.util.*; 13 public class So

《剑指offer》第五题(重要!从尾到头打印链表)

文件main.cpp // 从尾到头打印链表 // 题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值. #include <iostream> #include <stack> #include "List.h" using namespace std; void PrintListReversingly_Iteratively(ListNode* pHead)//解法一:使用栈 { stack<ListNode*> nodes;//定义

《剑指offer》第十七题:打印1到最大的n位数

// 面试题17:打印1到最大的n位数 // 题目:输入数字n,按顺序打印出从1最大的n位十进制数.比如输入3,则 // 打印出1.2.3一直到最大的3位数即999. #include <cstdio> #include <memory> void PrintNumber(char* number); bool Increment(char* number); void Print1ToMaxOfNDigitsRecursively(char* number, int length

《剑指offer》之7-9题

7.斐波那契数列 问题描述 都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项. 实现思想 了解斐波那契数列的规律就Ok了.1,1,2,3,5,8,... 代码 function Fibonacci(n) { // write code here if(n==0||n==1){ return n; } var N1=1,N2=0; for(let i=2;i<=n;i++){ N1=N1+N2; N2=N1-N2; } return N1; } 8.跳台阶 问题描述 一只青

《剑指offer》第六题(重要!重建二叉树)

文件一:main.cpp // 面试题:重建二叉树 // 题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输 // 入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1, // 2, 4, 7, 3, 5, 6, 8}和中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则重建出 // 图2.6所示的二叉树并输出它的头结点. #include <iostream> #include "BinaryTree.h" using

【校招面试 之 剑指offer】第9-1题 用两个栈实现一个队列

#include<iostream> #include<stack> using namespace std; template <typename T> void pushQueue(stack<T> &stack1, T t){ stack1.push(t); } template<typename T> T popQueue(stack<T> &stack1, stack<T> &stack2