剑指offer 6~10

面试题6:从尾到头打印链表

#include<iostream>
#include<vector>
#include<stack>
using namespace std;

//题目:
//从尾到头打印链表元素

//思路:
/*
* 看到逆序,就要想到栈,想到递归
* 如果用栈,就是使用了额外的空间,先从头到尾把元素进栈,再将元素倒出,即可完成逆序
* 递归的原理其实也是栈,递归就是自己调用自己,一层层的深入,直到满足递归终止条件,再边执行操作边返回
*/

//leetcode https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/submissions/
//牛客网:https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&&tqId=11156&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :val(x), next(nullptr) {}
};

//用栈实现倒序打印:
void PrintListReversingly_Iteratively(ListNode* pHead)
{
	if (pHead == nullptr)
		return;
	stack<ListNode*> stk;
	ListNode *pNode = pHead;
	while (pNode != nullptr) {
		stk.push(pNode);
		pNode = pNode->next;
	}
	while (!stk.empty()) {
		cout << stk.top()->val << " ";
		stk.pop();
	}
}

//递归,先调用再输出
void PrintListReversingly_Recursively(ListNode* pHead)
{
	//每次递归,pHead都是变化的,当pHead不为空而pHead->next为空,即pHead指向最后一个元素时,执行打印操作
	//并返回上一层,上一层的pHead为倒数第二个节点,接着执行cout,以此类推,递归从深层一直到浅层,同时完成打印操作
	//最后,pHead为原链表头,打印其值,函数退出
	if (pHead != nullptr)
	{
		if (pHead->next != nullptr)
			PrintListReversingly_Recursively(pHead->next);
	}
	cout<<pHead->val<<" ";
}

面试题7:重建二叉树

#include<iostream>
#include<vector>
using namespace std;

//题目:输入某二叉树的前序遍历和中序遍历的结果,重建该二叉树。
//类似的还可以用后序遍历和中序遍历结果重建二叉树

//思路:
/*
* 前序/后序+中序遍历结果可以唯一确定一棵二叉树
* 以前序+中序为例,在每次操作中,需要先根据当前子树的前序遍历序列确定子树根节点,即其第一个元素
* 随后在中序序列中找到该元素,以该元素将序列一分为二,并统计出以该元素为子树根节点时 左右子树元素的数量
* 再回到前序序列中,根据左右子树元素数量将前序序列也一分为二
* 此时一个大问题变成了两个小问题,如此不断一分为二
* 当序列长度为1的时候,说明到达叶子节点,直接返回
* 还是递归的思路,层层深入,每一层先进行划分,把大问题变成两个小问题,然后分别进入递归下一层
* 直到遇到叶子节点,递归退出,返回到上一层,最后一直回到最浅层,返回整个二叉树的根节点
* 以前序{1,2,4,7,3,5,6,8} 中序{4,7,2,1,5,3,8,6}为例
* 首先创建根节点值为1,在中序中找到1,1之前的{4,7,2}为中序子序列1,1之后的{5,3,8,6}为中序子序列2
* 对应的前序子序列分别是{2,4,7}和{3,5,6,8}
* 接下来分别再对前序{2,4,7}中序{4,7,2}  前序{3,5,6,8}中序{5,3,8,6}进行处理
* 以此类推,直到最后遇到叶子节点,再一层层返回
*/

//leetcode:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
//牛客网:https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&rp=1&ru=%2Factivity%2Foj&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=1
struct TreeNode {
	int val;
	TreeNode *left;
	TreeNode *right;
	TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
	if (preorder.size() == 0 || inorder.size() == 0)
		return nullptr;
	//用四个index值来明确当前问题的处理对象,即子序列是从哪到哪
	int PreStart = 0;
	int PreEnd = preorder.size() - 1;
	int InStart = 0;
	int InEnd = inorder.size() - 1;
	return build(preorder, inorder, PreStart, PreEnd, InStart, InEnd);
}
TreeNode* build(vector<int>& preorder, vector<int>& inorder, int PreStart, int PreEnd, int InStart, int InEnd) {
	//首先取preorder数组的PreStart下标处的值,创建子树根节点
	int rootVal = preorder[PreStart];
	TreeNode *root = new TreeNode(rootVal);
	//如果当前子序列只包含一个数,表明该子树根节点叶子节点,直接返回所创建的节点
	if (PreStart == PreEnd && InStart == InEnd) {
		return root;
	}
	//否则在中序子序列中查找子树根节点值,并记录左子树的元素个数
	int rootIndex = 0;
	while (inorder[rootIndex] != rootVal)
		rootIndex++;
	int leftLength = rootIndex - InStart;
	//如果左子树元素个数不为0,则对左子树再进行子树重建
	//其前序子序列的下标从PreStart+1~PreStart+leftLength
	//而中序子序列下标则从InStart~rootIndex-1
	if (leftLength>0)
		root->left = build(preorder, inorder, PreStart + 1, PreStart + leftLength, InStart, rootIndex - 1);
	if (leftLength<PreEnd - PreStart)
		//如果右子树元素个数不为0,对右子树进行子树重建
		//其前序子序列的下标从PreStart+leftLength1~PreEnd
		//而中序子序列下标则从rootIndex+1~InEnd
		root->right = build(preorder, inorder, PreStart + leftLength + 1, PreEnd, rootIndex + 1, InEnd);
	return root;
}
int main()
{
	vector<int> a = { 1,2,4,7,3,5,6,8 };
	vector<int> b = { 4,7,2,1,5,3,8,6 };
	TreeNode* res = buildTree(a, b);
	return 0;
}

面试题8:二叉树中序遍历的下一个节点

#include<iostream>
using namespace std;
//题目:给定二叉树的一个节点,求中序遍历顺序的下一个结点

//思路:
/*
* 看到题要立刻想到 该二叉树节点必然有父节点,如果没有父节点,无法找到任意一点中序遍历的下一个结点
* 除非给定根节点,先从根节点开始做中序遍历,再找给定二叉树节点的下一个节点
* 所有的树的遍历,实现++ --,都依赖于父节点,stl源码中rbtree的节点也包括了父节点
* 回到问题,中序遍历下一个节点有三种可能:
* 1.节点的右子树不为空时,下一个节点就是右子树的最左子节点
* 2.节点右子树为空且节点非根节点,则利用父节点进行上溯
* 沿父节点上溯,直到找到是父节点左节点的节点,下一个节点就是该节点的父节点
* 此时以该父节点为根节点的子树其左子树遍历完毕,给定节点正是其最右子节点,下一个节点就是该父节点
* 3.节点右子树为空,且节点是根节点,下一个节点为nullptr
*/

//leetcode:没找到?待补充
//牛客网:https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&rp=1&ru=%2Factivity%2Foj&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=3
struct TreeLinkNode {
	int val;
	struct TreeLinkNode *left;
	struct TreeLinkNode *right;
	struct TreeLinkNode *parent;
	TreeLinkNode(int x) :val(x), left(nullptr), right(nullptr), parent(nullptr) {}
};
TreeLinkNode* GertNext(TreeLinkNode* pNode)
{
	if (pNode == nullptr)
		return nullptr;

	TreeLinkNode* pNext = nullptr;
	//当节点的右子树不为空时,下一个节点就是右子树的最左子节点
	if (pNode->right = nullptr)//右子树不为空
	{
		TreeLinkNode* pRight = pNode->right;
		while (pRight->left != nullptr)//找到右子树的最左子节点
			pRight = pRight->left;
		pNext = pRight;
	}
	//当节点右子树为空 节点非根节点时
	else if (pNode->parent != nullptr)
	{
		TreeLinkNode* pCurrent = pNode;
		TreeLinkNode* pParent = pNode->parent;
		while (pParent != nullptr&&pCurrent == pParent->right)//如果节点不为根节点且节点是其父节点的右子节点,沿指针向上直到一个是他父节点左节点的节点
		{
			pCurrent = pParent;
			pParent = pParent->parent;
		}
		//该节点是父节点左节点时,下一个节点就是父节点
		pNext = pParent;
	}

	return pNext;
}

面试题9-1:栈实现队列

#include<iostream>
#include<vector>
#include<stack>
using namespace std;

//题目:用两个栈实现队列

//思路:
/*
* 栈为后进先出,队列为先进先出,要用栈实现队列,相当于要"颠倒"两次
* 假设两个栈分别为栈1和栈2,要实现两次颠倒,“队列”的入队和出队(即入栈和出栈)不能在同一个栈上进行
* 规定元素入队(入栈)进入栈1,而出队(出栈)从栈2出
* 入队操作比较简单,直接将元素push进栈1即可
* 出队操作涉及到两个栈之间元素的转移,有以下几种情况:
* 1.两个栈均为空,返回错误
* 2.栈2非空,返回栈2栈顶元素
* 3.栈2为空,先将栈1元素倒进栈2,只留下一个,留下的就是要弹出的
* 事实上 栈2先从顶到底,栈1再从底到顶,构成了“队列”的真实次序
*/

//leetcode:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/submissions/
//牛客网:https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&&tqId=11158&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking

class CQueue {
private:
	stack<int> s1;
	stack<int> s2;
public:
	CQueue() {

	}

	void appendTail(int value) {
		s1.push(value);
	}

	int deleteHead() {
		if (s1.empty() && s2.empty())
			return -1;

		//首要的关注点是栈2,栈2只要有数字就从栈2出
		if (!s2.empty()) {
			int res = s2.top();
			s2.pop();
			return res;
		}
		//栈2为空时再将栈1中数字“倒”进栈2,只留一个,留下的就是要弹出的
		//这是一种简化,其实应该是所有数据都倒入栈2,再从栈2的栈顶弹出
		while (s1.size() != 1) {
			int tmp = s1.top();
			s2.push(tmp);
			s1.pop();
		}
		int res = s1.top();
		s1.pop();
		return res;
	}
};

面试题9-2:队列实现栈

#include<iostream>
#include<vector>
#include<queue>
using namespace std;

//题目:用两个队列实现栈

//思路:
/*
* 栈为后进先出,队列为先进先出,要用队列实现栈,其实就是用先进先出实现后进先出,相当于要"颠倒"一次
* 不同于栈实现队列的 用两次“后进先出”来实现“先进先出”,也就是说每个数据只会在其中一个队列中待过
* 假设两个队列分别为q1和q2,因为只需要颠倒一次,可以时刻保持其中一个队列为空
* “入栈”(入队)的时候进入非空队列,“出栈”(出队)的时候,将当前非空队列的所有元素移到另一个队列中
* 只留下一个元素,再将该元素弹出,即可实现“颠倒”数据
* 因为是用队列实现的,所以无论来回怎么倒,还是保持原序的
*/

//leetcode:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/submissions/
//牛客网:暂未找到
class MyStack {
private:
	//保证任意时刻有一个队列为空
	queue<int> q1;
	queue<int> q2;
public:
	MyStack() {}
	//向非空的队列中加入元素,若两队列都为空,则优先向q1队列加入
	void push(int x) {
		if (q2.empty())
			q1.push(x);
		else if (q1.empty())
			q2.push(x);
	}
	//非空队列元素移到空队列中,只留一个元素,将该元素弹出,即实现栈的弹出
	int pop() {
		int res;
		if (q2.empty())
		{
			while (q1.size() != 1)
			{
				q2.push(q1.front());
				q1.pop();
			}
			res = q1.front();
			q1.pop();
		}
		else
		{
			while (q2.size() != 1)
			{
				q1.push(q2.front());
				q2.pop();
			}
			res = q2.front();
			q2.pop();
		}
		return res;
	}
	int top() {
		int res;
		if (q2.empty())
		{
			while (q1.size() != 1)
			{
				q2.push(q1.front());
				q1.pop();
			}
			res = q1.front();
			q2.push(q1.front());
			q1.pop();
		}
		else
		{
			while (q2.size() != 1)
			{
				q1.push(q2.front());
				q2.pop();
			}
			res = q2.front();
			q1.push(q2.front());
			q2.pop();
		}
		return res;
	}
	bool empty() {
		return q1.empty() && q2.empty();
	}
};

面试题10:斐波拉契数列 跳台阶

#include<iostream>
using namespace std;
//面试题10 题目一:求斐波拉契数列的第n项
//写一个函数,输入n,求斐波拉契数列的第n项
//思路:
//递归是最简单的方法,但是递归效率很低,会做很多重复运算
//比如求f(5) 先求f(4)和f(3),再分别求f(3)和f(2),f(2)和f(1),可以看到此时f(3)和f(2)已经重复求了
//为了提高效率,需要采用额外的空间来保存中间值,加快运算效率

//面试题二:青蛙跳台阶问题
//一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级台阶总共有多少种跳法

//思路:
/*
* 首先考虑简单情况:
* 如果只有一级台阶,显然只有一种跳法;
* 如果有2级台阶,就有两种跳法,一种是分两次跳每次跳一级;另一种是一次跳两级

* 再来考虑一般情况:
* 把n级台阶的跳法看成n的函数f(n)
* 当n>2时,第一次跳的时候就有两种不同的选择:
* (1)第一次跳1级,此时跳法数目等于后面剩下n-1级跳法数目,即f(n-1);
* (2)第一次跳2级,此时跳法数目等于后面剩下n-2级跳法数目,即f(n-2);
* 因此n级台阶不同跳法总数为f(n)=f(n+1)+f(n+2);
*/

//leetcode:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/
//          https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/
//牛客网:https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&rp=1&ru=%2Factivity%2Foj&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=1
//        https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&rp=1&ru=%2Factivity%2Foj&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=1

//递归方法
long long Fibonacci1(unsigned int n)
{
	if (n <= 0)
		return 0;
	if (n == 1)
		return 1;
	return Fibonacci1(n - 1) + Fibonacci1(n - 2);
}

//非递归方法1
long long Fibonacci2(unsigned int n)
{
	int results[2] = { 0,1 };
	if (n < 2)
		return results[n];

	long long fibNMinusOne = 0;
	long long fibNMinusTwo = 1;
	long long fibN = 0;
	for (int i = 2; i <= n; i++)
	{
		fibN = fibNMinusOne + fibNMinusTwo;
		fibNMinusOne = fibNMinusTwo;
		fibNMinusTwo = fibN;
	}
	return fibN;
}

//非递归方法2 最简单的版本
long long Fibonacci3(unsigned int n) {
	if (n<2)
		return n;
	long long fibA = 0;
	long long fibB = 1;
	n -= 1;
	while (n--)
	{
		fibB = fibA + fibB;
		fibA = fibB - fibA;
	}
	return fibB;
}

int main()
{
	cout << Fibonacci3(4);
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值