offer68题 Day2

面试题 07. 重建二叉树

前中序构建

要根据二叉树的前序遍历中序遍历结果来构建二叉树,我们可以利用以下性质:

  1. 前序遍历的第一个元素总是当前树的根节点。
  2. 中序遍历中,根节点将二叉树分为左子树和右子树。

思路

  • 根据前序遍历的第一个元素确定根节点。
  • 在中序遍历中找到根节点位置,这样可以确定左子树和右子树的元素。
  • 递归地对左子树和右子树执行同样的操作,构建二叉树。

递归构建的步骤:

  1. 在前序遍历中,根节点是第一个元素。
  2. 找到这个根节点在中序遍历中的位置,左侧的所有元素属于左子树,右侧的所有元素属于右子树。
  3. 根据这个分割,递归构建左子树和右子树。

C++ 实现

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

// 定义二叉树节点
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class Solution {
public:
    // 哈希表用于快速查找中序遍历中根节点的位置
    unordered_map<int, int> inorder_map;

    // 构建二叉树函数
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        // 将中序遍历的值和它们对应的下标存入哈希表,便于快速查找根节点位置
        for (int i = 0; i < inorder.size(); ++i) {
            inorder_map[inorder[i]] = i;
        }
        return build(preorder, 0, preorder.size() - 1, 0, inorder.size() - 1);
    }

private:
    TreeNode* build(const vector<int>& preorder, int preStart, int preEnd, int inStart, int inEnd) {
        // 如果已经没有节点需要构建,返回空
        if (preStart > preEnd || inStart > inEnd) {
            return nullptr;
        }

        // 前序遍历的第一个节点是当前树的根节点
        int rootVal = preorder[preStart];
        TreeNode* root = new TreeNode(rootVal);

        // 在中序遍历中找到根节点的位置
        int inRoot = inorder_map[rootVal];

        // 左子树的节点数目
        int leftTreeSize = inRoot - inStart;

        // 递归构建左子树和右子树
        root->left = build(preorder, preStart + 1, preStart + leftTreeSize, inStart, inRoot - 1);
        root->right = build(preorder, preStart + leftTreeSize + 1, preEnd, inRoot + 1, inEnd);

        return root;
    }
};

// 测试函数
void printTree(TreeNode* root) {
    if (root == nullptr) {
        return;
    }
    cout << root->val << " "; // 打印根节点
    printTree(root->left);    // 递归打印左子树
    printTree(root->right);   // 递归打印右子树
}

int main() {
    vector<int> preorder = {3, 9, 20, 15, 7}; // 前序遍历
    vector<int> inorder = {9, 3, 15, 20, 7};  // 中序遍历

    Solution sol;
    TreeNode* root = sol.buildTree(preorder, inorder);

    // 打印构建后的二叉树,前序遍历方式
    printTree(root); // 输出: 3 9 20 15 7

    return 0;
}

代码解析

  1. 哈希表优化:我们使用 unordered_map 将中序遍历的每个值与它在数组中的位置进行映射,以便快速查找根节点在中序遍历中的位置,时间复杂度从 O(n) 降到 O(1)。

  2. 递归函数 build

    • preStartpreEnd 分别表示前序遍历中当前子树的起始和结束位置。
    • inStartinEnd 表示中序遍历中当前子树的起始和结束位置。
    • 每次从前序遍历中获取根节点,然后在中序遍历中找到这个根节点的位置,从而将左右子树分割开。
    • 递归地构建左右子树。
  3. 时间复杂度:O(n),因为每个节点都需要访问一次。

  4. 空间复杂度:O(n),用于存储哈希表和递归调用栈。

测试输出

对于输入的前序遍历 [3, 9, 20, 15, 7] 和中序遍历 [9, 3, 15, 20, 7],构建出的二叉树结构如下:

      3
     / \
    9  20
       / \
      15  7

输出的前序遍历为:3 9 20 15 7,与原始前序遍历一致,说明二叉树构建正确。


中后序构建

使用中序遍历后序遍历结果构建二叉树的过程与前序和中序构建类似,不过需要注意:

  • 在后序遍历中,最后一个元素是当前树的根节点
  • 在中序遍历中,根节点将二叉树分为左子树和右子树。
  • 递归地对左子树和右子树执行同样的操作,构建二叉树。

构建步骤

  1. 后序遍历的最后一个元素即为当前树的根节点。
  2. 在中序遍历中找到根节点的位置,从而将左子树和右子树分开。
  3. 递归地构建左右子树。

C++ 实现

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

// 二叉树节点的定义
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class Solution {
public:
    unordered_map<int, int> inorder_map; // 哈希表存储中序遍历的索引,方便查找

    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        // 存储中序遍历值对应的索引
        for (int i = 0; i < inorder.size(); ++i) {
            inorder_map[inorder[i]] = i;
        }
        return build(inorder, postorder, 0, inorder.size() - 1, 0, postorder.size() - 1);
    }

private:
    TreeNode* build(const vector<int>& inorder, const vector<int>& postorder, int inStart, int inEnd, int postStart, int postEnd) {
        // 递归终止条件
        if (inStart > inEnd || postStart > postEnd) {
            return nullptr;
        }

        // 后序遍历的最后一个元素是根节点
        int rootVal = postorder[postEnd];
        TreeNode* root = new TreeNode(rootVal);

        // 在中序遍历中找到根节点的位置
        int inRoot = inorder_map[rootVal];
        int leftTreeSize = inRoot - inStart; // 左子树的节点数目

        // 递归构建左子树和右子树
        root->left = build(inorder, postorder, inStart, inRoot - 1, postStart, postStart + leftTreeSize - 1);
        root->right = build(inorder, postorder, inRoot + 1, inEnd, postStart + leftTreeSize, postEnd - 1);

        return root;
    }
};

// 打印树的前序遍历
void printPreOrder(TreeNode* root) {
    if (root == nullptr) return;
    cout << root->val << " ";
    printPreOrder(root->left);
    printPreOrder(root->right);
}

int main() {
    vector<int> inorder = {9, 3, 15, 20, 7};    // 中序遍历
    vector<int> postorder = {9, 15, 7, 20, 3};  // 后序遍历

    Solution sol;
    TreeNode* root = sol.buildTree(inorder, postorder);

    // 输出前序遍历的结果,以检查树是否构建正确
    printPreOrder(root); // 输出: 3 9 20 15 7
    return 0;
}

代码解析

  1. 构建哈希表

    • 使用 unordered_map 存储中序遍历中每个值的索引,方便快速找到根节点在中序遍历中的位置。
  2. 递归构建函数 build

    • inStartinEnd 表示当前子树在中序遍历中的范围。
    • postStartpostEnd 表示当前子树在后序遍历中的范围。
    • 从后序遍历的最后一个元素获取根节点,并在中序遍历中找到该节点的位置,进而划分左右子树。
    • 递归调用 build 分别构建左右子树。
  3. 测试

    • 构建的树结构:
            3
          /   \
         9    20
              /  \
            15    7
      
    • 使用前序遍历打印输出:3 9 20 15 7,验证构建的树结构正确。

复杂度分析

  • 时间复杂度:O(n),遍历所有节点,并在哈希表中查找根节点索引的操作是 O(1)。
  • 空间复杂度:O(n),用于存储哈希表和递归调用栈。

层序编列

  1
 / \
2   3
 \ / \
  4 5 6

面试题 09. 用两个栈实现队列

这个问题可以用两个栈来实现,用一个栈保存归还的书籍(push 操作),而借出书籍时(pop 操作)可以通过另一个栈来保证借出的是最早归还的书。具体思路如下:

  1. 使用两个栈 stack1stack2stack1 用于存放归还的书籍,stack2 用于按顺序借出书籍。
  2. push 操作:直接将书籍编号压入 stack1
  3. pop 操作:如果 stack2 为空,则将 stack1 中的所有元素依次弹出并压入 stack2,这样最早归还的书会在 stack2 的顶部。然后从 stack2 弹出顶部的元素,返回该书籍编号。

在实现中,这种方式保证了最早归还的书优先借出。

以下是 C++ 代码:

#include <stack>

class BookQueue {
private:
    std::stack<int> stack1; // 用于归还书籍
    std::stack<int> stack2; // 用于借出书籍

public:
    BookQueue() {}

    void push(int bookID) {
        stack1.push(bookID);
    }

    int pop() {
        if (stack2.empty()) {
            while (!stack1.empty()) {
                stack2.push(stack1.top());
                stack1.pop();
            }
        }
        if (stack2.empty()) {
            return -1; // 没有书可以借出
        }
        int bookID = stack2.top();
        stack2.pop();
        return bookID;
    }
};

示例

BookQueue bookQueue;
bookQueue.push(1); // 书队列为 [1]
bookQueue.push(2); // 书队列为 [1, 2]
std::cout << bookQueue.pop(); // 返回 1,书队列变为 [2]

解释

  • push 操作只涉及 stack1,时间复杂度为 (O(1))。
  • pop 操作在 stack2 不为空时为 (O(1));如果 stack2 为空时,则需要将 stack1 的元素移动到 stack2 中,平均时间复杂度仍为 (O(1))。

这样,我们就可以实现按照顺序的借书和还书功能了。

面试题 10- I. 斐波那契数列

class Solution {
public:
    int fib(int n) {
        const int MOD=1000000007;
        if(n<2){ return n;}

        int a=0,b=1;
        int result;
        for(int i=2;i<=n;i++){
            result = (a+b)%MOD; // 前两项相加,F(n-1)+F(n-2)
            a=b;                // 更新F(n-2)
            b=result;           // 更新F(n-1)
        }

        return result;
    }
};

面试题 10- II. 青蛙跳台阶问题(斐波那契额数列问题)


class Solution {
public:
    int trainWays(int num) {
        const int MOD=1000000007;

        if(num<2){ return 1;} //num=0时,原地不动,也算一种跳法?;num=1时,从0跳到1,一种跳法

        int a=0,b=1;
        int result;
        for(int i=1;i<=num;i++){
            result=(a+b)%MOD;
            a=b;
            b=result;
        }

        return result;
    }
};

面试题 11. 旋转数组的最小数字


// 就说对不对嘛,旋不旋转有什么关系呢,到底是谁在出这些无聊的题目
// 时间复杂O(n),n是stock数组长度;空间O(1)

class Solution {  
public:
    int inventoryManagement(vector<int>& stock) {
        return *min_element(stock.begin(),stock.end());
    }
};

posted @   itsinsane  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示