一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
剑指 Offer 06. 从尾到头打印链表
思路
(遍历链表) O(n)
单链表只能从前往后遍历,不能从后往前遍历,因此:
- 1、我们先从前往后遍历一遍输入的链表,将结果记录在答案数组中。
- 2、最后再将得到的数组逆序即可。
时间复杂度分析: 链表和答案数组仅被遍历了常数次,所以总时间复杂度是 O(n)。
c++代码
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int> res;
ListNode* cur = head;
while(cur){
res.push_back(cur->val);
cur = cur->next;
}
reverse(res.begin(), res.end());
return res;
}
};
剑指 Offer 07. 重建二叉树
思路
(递归) O(n)
二叉树前序遍历的顺序为:根左右
二叉树中序遍历的顺序为:左根右
递归建立整棵二叉树:先创建根节点,然后递归创建左右子树,并让指针指向两棵子树。
我们画个图来说明,二叉树的前序和中序遍历。
二叉树:
前序遍历:
中序遍历:
具体步骤如下:
- 1、先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
- 2、在中序遍历中找到根节点的位置
pos,则pos左边是左子树的中序遍历,右边是右子树的中序遍历; - 3、假设左子树的中序遍历的长度是
k,则在前序遍历中,根节点后面的k个数,是左子树的前序遍历,剩下的数是右子树的前序遍历; - 4、有了左右子树的前序遍历和中序遍历,我们可以先递归创建出根节点,然后再递归创建左右子树,再将这两颗子树接到根节点的左右位置;
细节1: 如何在中序遍历中对根节点快速定位?
一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。
细节2: 如何确定左右子树的前序遍历和中序遍历范围?
-
1、根据哈希表找到中序遍历的根节点位置,我们记作
pos -
2、用
pos-il(il为中序遍历左端点) 得到中序遍历的长度k,由于一棵树的前序遍历和中序遍历的长度相等,因此前序遍历的长度也为k。有了前序和中序遍历的长度,根据如上具体步骤2,3,我们就能很快确定左右子树的前序遍历和中序遍历范围。如下图所示:
pl,pr对应一棵子树的前序遍历区间的左右端点, il,ir对应一棵子树的中序遍历区间的左右端点。
具体实现看代码。
时间复杂度分析: O(n),其中 n 是树中的节点个数。
c++代码
class Solution {
public:
unordered_map<int,int> pos;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
for(int i = 0; i < n; i++)
pos[inorder[i]] = i; //记录中序遍历的根节点位置
return dfs(preorder,inorder,0,n-1,0,n-1);
}
//pl,pr对应一棵子树的前序遍历区间的左右端点
//il,ir对应一棵子树的中序遍历区间的左右端点
TreeNode* dfs(vector<int>&pre,vector<int>&in,int pl,int pr,int il,int ir)
{
if(pl > pr) return NULL; //左子树为空1
int k = pos[pre[pl]] - il; // pos[pre[pl]]是中序遍历中根节点位置,k是子树前序和中序遍历的长度
TreeNode* root = new TreeNode(pre[pl]);
root->left = dfs(pre,in,pl+1,pl+k,il,il+k-1); //左子树前序遍历,左子树中序遍历
root->right = dfs(pre,in,pl+k+1,pr,il+k+1,ir); //右子树前序遍历,右子树中序遍历
return root;
}
};
剑指 Offer 09. 用两个栈实现队列
思路
(栈) O(n)
栈: 先进后出
队列: 先进先出
push(x) : 直接将x插入主栈stack1中。
pop(): 此时我们需要弹出最先进入栈的元素,也就是栈底元素。
- 1、在执行删除操作的时候我们首先看下第二个栈是否为空。如果为空,我们将第一个栈里的元素一个个弹出插入到第二个栈里,这样第二个栈里元素的顺序就是待删除的元素的顺序。
- 2、执行删除操作的时候我们直接弹出第二个栈的元素返回即可。
c++代码
class CQueue {
public:
/**
两个栈实现队列,栈: 先进后出
队列: 先进先出
push(x) : 直接将x插入主栈stack1中
pop() :
此时我们需要弹出最先进入栈的元素,也就是栈底元素。
在执行删除操作的时候我们首先看下第二个栈是否为空。如果为空,
我们将第一个栈里的元素一个个弹出插入到第二个栈里,这样第二个
栈里元素的顺序就是待删除的元素的顺序,要执行删除操作的时候我
们直接弹出第二个栈的元素返回即可。
**/
stack<int> st1, st2;
CQueue() {
}
void appendTail(int value) {
st1.push(value);
}
int deleteHead() {
if(st2.empty()){
while(!st1.empty()){
st2.push(st1.top());
st1.pop();
}
}
if(st2.empty()) return -1;
int res = st2.top();
st2.pop();
return res;
}
};
\