面试题31:栈的压入 弹出序列
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
//题目:栈的压入 弹出序列
/*
* 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
* 假设压入栈的所有数字均不相等。
* 例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,
* 但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
* 示例:
* 输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
* 输出:true
* 解释:我们可以按以下顺序执行:
* push(1), push(2), push(3), push(4), pop() -> 4,
* push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
*/
/*思路:
* 如何判断栈的压入序列和弹出序列?
* 设置两个指针,初始时分别指向压入序列和弹出序列的第一个元素
* 从压栈序列开始遍历,首先将压栈序列的第一个元素压入栈中,压栈序列指针前进
* 随后开始比较,如果栈顶元素和弹出序列指针所指元素不同,就继续向将栈中插入压入序列,压栈序列指针前进
* 除非压栈序列所有元素均已被压入,或栈顶元素和弹出序列指针所指元素相同 进行下一步
* 若此时元素仍不同,则说明不匹配,返回false,若元素相同,则更新弹出序列指针
* 将匹配元素从栈中弹出,并继续循环,直到压入序列遍历完毕
* 最后检查栈是否为空,若为空 说明匹配成功,若非空,则匹配失败
*/
//leetcode:https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/
//牛客:https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
if (pushed.size() == 0 || popped.size() == 0)
return true;
int n = popped.size();
int indexOfPush = 0;
int indexOfPop = 0;
stack<int> data;
while (indexOfPop<popped.size()) {
while (data.empty() || popped[indexOfPop] != data.top()) {
if (indexOfPush == pushed.size())
break;
data.push(pushed[indexOfPush++]);
}
if (data.top() != popped[indexOfPop])
return false;
data.pop();
indexOfPop++;
}
return data.empty();
}
面试题32-1:从上到下打印二叉树
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
//题目:从上到下打印二叉树
//从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
/*思路:
* 若想从上到下打印二叉树,类似于广度优先遍历,需要借助队列来完成
* 从根节点开始,先把根节点压入队列中,并进入循环
* 只要队列非空,就执行以下操作:
* 首先从队列中取出一个节点,打印该节点,如果该节点左子节点非空,则将左子节点入队列;如果右子节点非空,则将右子节点入队列
* 退出循环时,即已完成所有节点元素的顺序打印
*/
//leetcode:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/
//牛客:https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
vector<int> levelOrder(TreeNode* root) {
if (root == nullptr)
return {};
vector<int> res;
queue<TreeNode*> q;
TreeNode* pNode = root;
q.push(root);
while (!q.empty()) {
TreeNode* pLast = q.front();
q.pop();
res.push_back(pLast->val);
if (pLast->left)
q.push(pLast->left);
if (pLast->right)
q.push(pLast->right);
}
return res;
}
面试题32-2:按层打印二叉树
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
//题目:按层打印二叉树
//从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
/*思路:
* 和前一小题类似,唯一的区别在于,这次不是只打印在一个vector中,而是要按层分开,每次循环中要进行计数
* 从根节点开始,先把根节点压入队列中,并进入循环
* 只要队列非空,就执行以下操作:
* 首先记录当前栈中的元素个数n,创建vector,并开始进入子循环,将以下步骤进行n次:
* 首先从队列中取出一个节点,打印该节点到vector中,如果该节点左子节点非空,则将左子节点入队列;如果右子节点非空,则将右子节点入队列
* 子循环完毕,将该vector保存下来,继续循环
* 当队列为空时说明打印完毕,退出
*/
//leetcode:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/
//牛客:https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
vector<vector<int>> levelOrder(TreeNode* root) {
if (root == nullptr)
return {};
vector<vector<int>> res;
TreeNode *pNode = root;
queue<TreeNode*> q;
q.push(pNode);
while (!q.empty()) {
int n = q.size();
vector<int> tmp;
for (int i = 0; i<n; i++) {
TreeNode *pLast = q.front();
tmp.push_back(pLast->val);
q.pop();
if (pLast->left)
q.push(pLast->left);
if (pLast->right)
q.push(pLast->right);
}
res.push_back(tmp);
}
return res;
}
面试题32-3:z字形打印二叉树
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
//题目:z字形打印二叉树
//从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
/*思路:
* 与前面不同的是,本小题要求z字形打印,即并不是每层都是从左到右打印
* 奇数层为从左到右,偶数层则为从右到左,所以重点就在于如何能够保证奇偶层不同的打印效果
* 从右到左 和 从右到左 需要“两个翻转”,其一为当其父节点将两个兄弟节点加入时对奇偶层的不同处理
* 其二是借助栈来实现反序
* 举个例子 根节点值为1,左右子节点值分别为2和3,左子节点2的两个子节点值为4和5,右子节点3的两个子节点值为6和7
* 第一层只有一个节点1,先将节点1放入栈1中,开始循环
* 打印1的同时,将2和3(先左后右)送入了栈2中,第一次子循环完毕,vector中只有1一个元素 [1]
* 第二次子循环,从栈2中取出3和2(和入栈顺序相反),并打印到vector中,vector中有两个元素 [3,2]
* 同时分别将节点3和节点2的左右子节点放到栈1中,这里要注意,对于每个节点,是先右后左放入节点的子节点
* 因为只有这样才能保证顺序 栈2从栈底到栈顶分别为[7,6,5,4]
* 第三次子循环,从栈1中依次弹出[4,5,6,7],这些节点的子节点都为空,所以不会再有元素进入栈中
* 第三次子循环完毕,vector中为[4,5,6,7]
* 此后栈1和栈2皆为空,打印完毕
* 相比于前面两小题,这一题的难点就在于如何实现从左到右和从右到左的变化 要通过栈的后进先出
* 以及对奇偶层不同的策略来实现,对于奇数层,先右子树后左子树,对于偶数层,先左子树后右子树
*/
//leetcode:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/
//牛客:https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
vector<vector<int>> levelOrder(TreeNode* root) {
if (root == nullptr)
return {};
TreeNode *pNode = root;
vector<vector<int>> res;
stack<TreeNode*> q1;
stack<TreeNode*> q2;
q1.push(pNode);
while (!q1.empty() || !q2.empty()) {
vector<int> tmp;
if (q1.empty()) {
int n = q2.size();
for (int i = 0; i<n; i++) {
TreeNode *pLast = q2.top();
q2.pop();
tmp.push_back(pLast->val);
if (pLast->right)
q1.push(pLast->right);
if (pLast->left)
q1.push(pLast->left);
}
}
else {
int n = q1.size();
for (int i = 0; i<n; i++) {
TreeNode *pLast = q1.top();
q1.pop();
tmp.push_back(pLast->val);
if (pLast->left)
q2.push(pLast->left);
if (pLast->right)
q2.push(pLast->right);
}
}
res.push_back(tmp);
}
return res;
}
面试题33:二叉搜索树的后序遍历
#include<iostream>
#include<vector>
using namespace std;
//题目:二叉搜索树的后序遍历
//输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。
//如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
/*思路:
* 二叉搜索树的特点就是 当前节点的值大于所有左子节点,且小于所有右子节点
* 先取由右边界处的元素,即当前数组的最后一个元素值为根节点的值
* 随后从左边界开始,找到第一个比根节点值大的数,并记录此处的下标cutPos,如果遍历结果正确,该数开始就应该是根节点的右子节点
* 即从此处到右边界-1的位置,应该都大于根节点值,以此为衡量准则,进行判断,如果最终没有到达右边界
* 则说明不满足,否则,就继续一分为二进行判断,原问题转换为两个规模更小的问题
* 最终结果即为判断这两个小问题得到的结果相与
*/
//leetcode:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
//牛客:https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
bool verifyCore(vector<int>& postorder, int l, int r) {
int rootVal = postorder[r];
int idx = l;
while (idx<r&&postorder[idx]<rootVal)
idx++;
int cutPos = idx;
while (idx<r&&postorder[idx]>rootVal)
idx++;
if (idx != r)
return false;
bool res = true;
if (cutPos>l)
res &= verifyCore(postorder, l, cutPos - 1);
if (cutPos<r)
res &= verifyCore(postorder, cutPos, r - 1);
return res;
}
bool verifyPostorder(vector<int>& postorder) {
if (postorder.size() == 0)
return true;
return verifyCore(postorder, 0, postorder.size() - 1);
}
面试题34:二叉树中和为某一值的路径
#include<iostream>
#include<vector>
using namespace std;
//题目:二叉树中和为某一值的路径
//输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。
//从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
/*思路:
* 该问题的实质是深度优先遍历,也就是说我只要把所有的路径都走一遍,就能得到最终的结果
* 但是一个一个试肯定是不现实的,所以可以考虑采用递归的方式来实现:
* 首先设定好用于存储数值的结构,然后开始遍历
* 从根节点开始,每到一个点,先用预设值rest减去该点的值,随后问题变成表示从该点出发到叶子节点路径总和能否为 rest-当前节点值 的问题
* 首先需要将节点的值加入到临时数组tmp中,注意,这个临时数组的“出出进进”,替我们完成了路径上各节点值的拾取
* 随后判断:如果此时节点正好为叶子节点(左右子节点为空)且此时rest正好为零,则此时的tmp中存在一条符合条件的路径
* 将其保存进最终结果
* 否则,若其左右子节点非空,进入递归
* 最后,需要把加入tmp的该节点从tmp中弹出,这表示“这条路不合适,不从这走了”
* 那么需要把减掉数值的rest再加回来吗?答案是不需要,因为rest是值传递,其值的变化只体现在当前的函数中
*/
//leetcode:https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/
//牛客:https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
vector<vector<int>> res;
vector<int> tmp;
vector<vector<int>> pathSum(TreeNode* root, int sum) {
if (root == nullptr)
return {};
getPath(root, sum);
return res;
}
void getPath(TreeNode* pNode, int rest) {
rest -= pNode->val;
tmp.push_back(pNode->val);
if (rest == 0 && pNode->left == nullptr&&pNode->right == nullptr)
res.push_back(tmp);
if (pNode->left)
getPath(pNode->left, rest);
if (pNode->right)
getPath(pNode->right, rest);
tmp.pop_back();
}
面试题35:复杂链表的复制
#include<iostream>
using namespace std;
//题目:复杂链表的复制
//请实现 copyRandomList 函数,复制一个复杂链表。
//在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
/*思路:
* 该题代码写出来很多,但其实很简单,难点就在于对random指针的复制,这个问题无法直接来解决
* 所以,将整个复制分成三部分:
* 第一部分,先忽略random指针的指向,将原链表复制一份,比如原来链表为1->2->3,复制后变为1->1->2->2->3->3
* 第二部分,处理random指针的指向,第一步完成后,原链表扩展,但复制节点的random并没有指向,只有原节点有
按照以下方法来复制原节点的random指针:
原节点的next指针(指向复制而来、与该节点一样的新节点)的random指针指向 原节点random指针的next节点
* 第三部分则是将这个大链表一分为二,就能得到一个新的复制而来的节点
* 该题的整个复制步骤还是比较繁琐的,注意千万不要出错
*/
//leetcode:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/
//牛客:https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&&tqId=11178&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val):val(_val), next(nullptr), random(nullptr) {}
};
//第一步,复制节点
void copyNode(Node* head) {
Node *pNode = head;
while (pNode != nullptr) {
Node *pNext = pNode->next;
pNode->next = new Node(pNode->val);
pNode->next->next = pNext;
pNode = pNext;
}
}
//第二步,复制random指针
void copyRandomPtr(Node *head) {
Node *pNode = head;
while (pNode != nullptr) {
Node *pNext = pNode->next;
if (pNode->random != nullptr)
pNext->random = pNode->random->next;
pNode = pNext->next;
}
}
//第三步,拆分链表
Node* divideList(Node *head) {
Node *newHead = nullptr;
Node *pNewNode = nullptr;
Node *pNode = head;
if (pNode != nullptr) {
newHead = pNode->next;
pNewNode = newHead;
pNode->next = pNewNode->next;
pNode = pNode->next;
}
while (pNewNode != nullptr&&pNode != nullptr) {
pNewNode->next = pNode->next;
pNewNode = pNewNode->next;
pNode->next = pNewNode->next;
pNode = pNode->next;
}
return newHead;
}
Node* copyRandomList(Node *head) {
copyNode(head);
copyRandomPtr(head);
return divideList(head);
}