剑指offer 打卡计划 | 每日进步一点点 | 第二天

104 阅读4分钟

image-20220403225516303.png

一起养成写作习惯!这是我参与「掘金日新计划 · 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)

二叉树前序遍历的顺序为:根左右

二叉树中序遍历的顺序为:左根右

递归建立整棵二叉树:先创建根节点,然后递归创建左右子树,并让指针指向两棵子树。

我们画个图来说明,二叉树的前序和中序遍历。

二叉树:

图片.png 前序遍历:

图片.png 中序遍历:

图片.png 具体步骤如下:

  • 1、先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
  • 2、在中序遍历中找到根节点的位置 pos,则 pos 左边是左子树的中序遍历,右边是右子树的中序遍历;
  • 3、假设左子树的中序遍历的长度是 k,则在前序遍历中,根节点后面的 k 个数,是左子树的前序遍历,剩下的数是右子树的前序遍历;
  • 4、有了左右子树的前序遍历和中序遍历,我们可以先递归创建出根节点,然后再递归创建左右子树,再将这两颗子树接到根节点的左右位置;

细节1: 如何在中序遍历中对根节点快速定位?

一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。

细节2: 如何确定左右子树的前序遍历和中序遍历范围?

  • 1、根据哈希表找到中序遍历的根节点位置,我们记作pos

  • 2、用pos-il (il为中序遍历左端点) 得到中序遍历的长度k ,由于一棵树的前序遍历和中序遍历的长度相等,因此前序遍历的长度也为k。有了前序和中序遍历的长度,根据如上具体步骤2,3,我们就能很快确定左右子树的前序遍历和中序遍历范围。如下图所示:

图片.png 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;
     }
 };

\