剑指Offer-算法篇

人生不像做饭,不能等万事俱备了才下锅

面试题03:数组中重复的数字

题目一:找出数组中的重复的数字。
在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。
例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路:既然所有的数字都在0 ~ n-1之间,那么如果没有重复的数,所有的数都应该在其对应位置上。可以扫描这个数组,如果某个元素与其下标不相等,就将该元素与其对应下标的元素互换,也就是swap(arr[i], arr[arr[i]]),直到发现一个重复的数。

题目二:不修改数组找出重复的数字
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。
例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或3。
思路:创建一个新的数组,将每个数依次复制到其对应的位置,需要消耗(n+1)的空间。或者以时间换空间,既然不能修改数组,那么不能采用交换的方式,考虑使用二分查找。首先将n分为两个部分,分别统计两个区间内元素的个数,如果个数大于区间范围,那么一定存在重复的数,不断缩小范围找到重复的数,时间复杂度为O(nlogn)

面试题32:从上到下打印二叉树

题目一:从上往下打印二叉树的每个节点,同一层的节点按照从左到右的顺序打印。例如输入下图的二叉树,则一次打印出8,6,10,5,7,9,11。
     8
   6  10
5 7 9 11

 // struct TreeNode {
 //	int val;
 //	struct TreeNode *left;
 //	struct TreeNode *right;
 // };
class Solution {
public:
    vector<vector<int> > levelOrder(TreeNode* root) {
        if (root == nullptr)
            return vector<vector<int> >();
        int curLevel = 1;//当前层的结点数量
        int nextLevel = 0;//下一层的结点数量
        queue<TreeNode*> Nodes;
        vector<vector<int> > result;
        Nodes.push(root);
        while (!Nodes.empty())
        {
            vector<int> levelNode;
            while (curLevel--)
            {
                TreeNode* node = Nodes.front();
                if (node->left != nullptr)
                {
                    Nodes.push(node->left);
                    ++nextLevel;
                }
                if (node->right != nullptr)
                {
                    Nodes.push(node->right);
                    ++nextLevel;
                }
                levelNode.push_back(node->val);
                Nodes.pop();
            }
            curLevel = nextLevel;
            nextLevel = 0;
            result.push_back(levelNode);
        }
        return result;
    }
};

题目三:请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如输入下图的二叉树,则一次打印出8,10,6,5,7,9,11。
     8
   6  10
5 7 9 11
思路:两个栈实现,一个装偶数层结点,一个装奇数层结点。

#define VII vector<vector<int> >
#define VI vector<int>
class Solution {
public:
    vector<vector<int> > zigzagLevelOrder(TreeNode* root) {
        if (root == nullptr)
            return VII();
        stack<TreeNode*> stack1, stack2;
        VII result;
        int next = 1;
        stack1.push(root);
        VI level;
        while (!stack1.empty() || !stack2.empty())
        {
            if (next == 1)
            {
                while (!stack1.empty())
                {
                    TreeNode* node = stack1.top();
                    if (node->left != nullptr)
                        stack2.push(node->left);
                    if (node->right != nullptr)
                        stack2.push(node->right);
                    level.push_back(node->val);
                    stack1.pop();
                }
                result.push_back(level);
                level.clear();
                next = 0;
            }
            else
            {
                while (!stack2.empty())
                {
                    TreeNode* node = stack2.top();
                    if (node->right != nullptr)
                        stack1.push(node->right);
                    if(node->left != nullptr)
                        stack1.push(node->left);
                    level.push_back(node->val);
                    stack2.pop();
                }
                result.push_back(level);
                level.clear();
                next = 1;
            }
        }
        return result;
    }
};

面试题33:二叉搜索树的后序遍历序列

二叉搜索树(又名二叉查找树,二叉排序树):或者为一棵空树;若左子树不为空,则左子树所有结点的值均小于根结点的值,同时左子树也为二叉搜索树。若右子树不为空,则右子树所有结点的值均大于根结点的值,同时右子树也为二叉搜索树。
题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。例如,输入数组{5,7,6,9,11,10,8},则返回true,因为这个整数序列是下图所示二叉树的后序遍历结果。如果输入的数组是{7,4,6,5},则没有哪棵二叉搜索树的后序遍历结果是这个序列,因此返回false。
思路:该题目即是判断能否根据一个整数数组建立一棵二叉搜索树。由于该数组是二叉树的后序遍历序列,那么最后一个结点即是根结点,前面的序列前一半是左子树(如果存在),后一半是右子树(如果存在);同时由于该二叉树是二叉搜索树,因此左子树必定小于根结点,右子树必定大于根结点。即序列前一半小于序列最后一个值(根结点),后一半大于序列最后一个值(根结点)。之后迭代判断左子树和右子树是否都为二叉搜索树。

#include <iostream>
using namespace std;
bool verifySequence(int* seq, int length)
{
	if (seq == nullptr || length == 0)
		return false;
	if (length == 1)//只有根结点
		return true;
	int* last = seq + length - 1;
	int leftLength = 0;
	while (seq != last)
	{
		if (*seq > *last)
			break;
		seq++;
	}
	leftLength = seq - (last - length + 1);//左子树的长度
	while (seq != last)
	{
		if (*seq < *last)
			return false;
		seq++;
	}

	bool left = true;
	if (leftLength)
		left = verifySequence(seq, leftLength);
	bool right = true;
	if (leftLength < length)
		right = verifySequence(seq + leftLength, length - leftLength);
	return left&&right;
}

面试题34:二叉树中和为某一值的路径

题目:输入一颗二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。路径定义为从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
例如,输入如下的二叉树,整数为22,则打印两条路径,第一条为{10,12},第二条为{10,5,7} 二叉树如下:
    10
  5    12
4   7
思路:由于路径定义为从树的根节点开始往下一直到叶节点所经过的节点可以联想到前序遍历(根节点->左子树->右子树)。可以考虑从根结点开始,如从10开始,同时将10保存。然后访问10的左子树(5),此时10+5=15,然后保存5,之后继续访问5的左子树(4),由于10+5+4=19,因此保存4,但是4无左右子树,所以进行回溯。可是该二叉树的定义中无父结点,无法进行回溯。那么考虑首先对二叉树整个进行前序遍历,并保存序列。首先根结点是10,那么可以分别考虑左右子树和为22-10=12的路径,也就是可以进行迭代。以左子树为例,此时需要找出左子树中和为12的路径,也就是左子树的左右结点中和为12-5=7的路径。然后将每次的路径存入队列,如果根结点的值等于所给的整数,则输出整个队列。

#include <iostream>
#include <queue>
using namespace std;
struct BinaryTree//二叉树结构
{
	int value;
	BinaryTree* LeftNode;
	BinaryTree* RightNode;
};
void printOrder(queue<int>& order);//输出序列
void findCore(BinaryTree* Root, int Sum, queue<int> order);
void findPath(BinaryTree* Root, int Sum)
{
	if (Root == nullptr || Sum <= 0)
		return;
	queue<int> order;
	findCore(Root, Sum, order);
}
void findCore(BinaryTree* Root, int Sum, queue<int> order)
{
	if (Root == nullptr || Sum == 0)
		return;
	order.push(Root->value);//将根结点压入队列
	if (Root->value == Sum)//如果此时的根结点与整数相等,则输出整个队列
		printOrder(order);
	else if (Root->value < Sum)//如果小于所给出的整数,则继续遍历左右子树
	{
		if (Root->LeftNode != nullptr)
			findCore(Root->LeftNode, Sum - Root->value, order);
		if (Root->RightNode != nullptr)
			findCore(Root->RightNode, Sum - Root->value, order);
	}
}
void printOrder(queue<int>& order) 
{
	while (!order.empty())
	{
		printf("%d\t", order.front());
		order.pop();
	}
	cout << endl;
}
BinaryTree* CreateBinaryTreeNode(int value)
{
	BinaryTree* Node = new BinaryTree();
	Node->value = value;
	Node->LeftNode = nullptr;
	Node->RightNode = nullptr;
	return Node;
}
void ConnectTreeNodes(BinaryTree* Parent, BinaryTree* Left, BinaryTree* Right)
{
	if (Parent != nullptr)
	{
		Parent->LeftNode = Left;
		Parent->RightNode = Right;
	}
}

面试题35:复杂链表的复制

题目:请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在复杂链表中,每个结点除了有一个pNext指针指向下一个结点之外,还有一个pSibling指向链表中的任意结点或者NULL。

面试题36:二叉搜索树与双向链表

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:由于二叉搜索树是排序的,左子树<根结点<右子树,因此要转换一个排序的双向链表,可以考虑中序遍历。如果根结点的左子树已排序连接,那么将左子树的最大结点与根结点进行连接,然后再与右子树进行连接,则整个链表完成,可以使用迭代的方式。

#include <cstdio>
#include "..\Utilities\BinaryTree.h"
void ConvertNode(BinaryTreeNode* pNode, BinaryTreeNode** pLastNodeInList);
BinaryTreeNode* Convert(BinaryTreeNode* pRootOfTree)
{
    //创建指向链表尾端的指针
    BinaryTreeNode *pLastNodeInList = nullptr;
    //之所以采用引用传参的方式,是因为该指针一直会移动
    ConvertNode(pRootOfTree, &pLastNodeInList);
    // pLastNodeInList指向双向链表的尾结点
    //因此一直往左得到头结点(m_pLeft指针为空时)
    BinaryTreeNode *pHeadOfList = pLastNodeInList;
    while(pHeadOfList != nullptr && pHeadOfList->m_pLeft != nullptr)
        pHeadOfList = pHeadOfList->m_pLeft;
    return pHeadOfList;
}
void ConvertNode(BinaryTreeNode* pNode, BinaryTreeNode** pLastNodeInList)
{
    if(pNode == nullptr)
        return;
    BinaryTreeNode *pCurrent = pNode;
    //对左子树进行排序
    if (pCurrent->m_pLeft != nullptr)
        ConvertNode(pCurrent->m_pLeft, pLastNodeInList);
    //此时左子树已经排序完成,pLastNodeInList指针指向左子树最后一个结点,也就是最大的结点。
    //将左子树最后的结点与根结点进行连接
    //将根结点的m_pLeft指针指向链表最后一个节点
    pCurrent->m_pLeft = *pLastNodeInList; 
    //将链表最后一个节点的m_pRight指针指向根结点
    if(*pLastNodeInList != nullptr)
        (*pLastNodeInList)->m_pRight = pCurrent;
    //将pLastNodeInList指针指向当前结点,也就是根结点,此时根结点是链表最后一个结点
    *pLastNodeInList = pCurrent;
    //当右子树存在时,对右子树进行排序。
    //那么为什么右子树不需要进行连接呢?
    //其实右子树已经进行连接,可以考虑一种情况,右子树只有一个结点,此时根结点的左子树和根结点已排序完毕并连接,
    //当我们重复这个函数,就会将已排序的链表与这个右结点进行连接。或者可以这样理解,我们将这棵树进行旋转,
    //将这最后一个右结点视为根结点,那么其他所有节点即是左子树,我们只需将左子树所得的链表与根结点(即是该右结点)进行连接。
    if (pCurrent->m_pRight != nullptr)
        ConvertNode(pCurrent->m_pRight, pLastNodeInList);
}

面试题38:字符串的排列

题目:输入一个字符串,打印出该字符串中字符的所有排列。例如,输入字符串abc,则打印出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
思路:全排列的问题,考虑使用递归,就是第一个位置依次为三个不同的字母,假设之后的两个位置已经全部排列。递归的问题可以假设之后的问题都已解决,只需要解决第一个问题。因为之后的问题可以依次将其视为第一个问题,再次调用函数。需要注意考虑边界条件,也就是递归结束的条件。同时要注意异常输入。

void Permutation(char* strs, char* begin);
void Permutation(char* strs) 
{
	if (strs == nullptr)//首先注意异常输入
		return;
	char* begin = strs;
	Permutation(strs, begin);//输入两个参数是因为,我们需要begin来指示当前需要进行全排列的字符串。
	//也就是说当begin指向第一个元素,我们需要全排列的就是后面三个(包括自己),
	//当begin指向第二个元素,我们需要全排列的就是后面两个(包括自己)。
}
void Permutation(char* strs, char* begin)
{
	if (*begin == '\0')//考虑边界条件,就是什么时候进行输出,
	//显然当我们begin指向‘\0’,也就是所有的字符都已经进行全排列的时候。
		printf("%s\n", strs);
	else
	{
		char* str = begin;
		while (*str != '\0')
		{
			char tempStr = *str;
			*str = *begin;
			*begin = tempStr;
			Permutation(strs, begin + 1);
			//需要注意,这里进行交换之后,我们再次将两个数交换回来。
			//如果不进行交换,可以假设一下,原字符串是abc,交换一次之后是bac,
			//如果不交换回来,那么下次第1 3元素互换得到的就是字符串cab,
			//而实际上我们需要的是将a和c互换,也就是需要得到cab。
			tempStr = *str;
			*str = *begin;
			*begin = tempStr;
			str += 1;//指针后移,依次将第一个元素与之后的每一个元素进行交换。
		}
	}
}

面试题44:数字序列中某一位的数字

题目:数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数求任意位对应的数字。

class Solution 
{
public:
	int NthNumber(int n)
	{
		if (n < 0)
			return -1;
		if (n <= 9)//1-9
			return n;
		int k = 0, num = 0;
		while (n > 0)
		{
			num = n;
			n -= 9 * int(pow(10, k)) * (k + 1);
			k++;
		}
		int a = (num - 1) / k + int(pow(10, k - 1));
		int b = (num - 1) % k;
		string s = to_string(a);
		return int(s[b]-'0');
	}
	//类似题目,假设有一个报文段,是数学序列,如123456789101112……
	///其中前三个数为000,表示确认,之后四个数字为剩余报文的长度,然后是256长度的报文
	//例如,长度为9999的报文为000 9999 123……
    //那么该报文中第n个字符是什么
	int message(int n)
	{
		if (n < 1)
			return -1;
		if (n <= 3)
			return 0;
		int amount = (n - 3) / 260;//有多少个260,256报文+长度为4的头部
		int index = (n - 4) % 260;
		if (index <= 3)//如果正好是头部序列
		{
			string s;
			s = to_string(9999 - 256 * amount);
			return int(s[index] - '0');
		}
		else//如果不是头部,则减去头部正常计算
		{
			int num = n - 3 - 4 * (amount + 1);//至少存在一个头部
			return NthNumber(num);
		}
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值