【剑指Offer】6-10题(栈的运用+重建二叉树+中序遍历的下一个结点+斐波那契变形)

📝从尾到头打印链表

剑指 Offer 06. 从尾到头打印链表 - 力扣(LeetCode)

从尾到头,可以更改指向,但是比较麻烦,而且也改变了链表的结构。

观察打印的结构,是先打印尾再打印头的,和栈的结构很像,所以采用栈来写。

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int> st;
        vector<int>ret;
        ListNode* cur=head;
        while(cur!=NULL)
        {
            st.push(cur->val);
            cur=cur->next;
        }
        int sz=st.size();
        ret.resize(sz);
        for(int i=0;i<sz;i++)
        {
            ret[i]=st.top();
            st.pop();
        }
        return ret;
    }
};

📝重建二叉树

剑指 Offer 07. 重建二叉树 - 力扣(LeetCode)

构建树的思想:前序序列的第一个节点肯定是根节点,中序序列中根节点左边所有的节点都是左子树的,右边的都是右子树的,具体做法不再赘述。下面从代码实现的角度进行分析

这题的难点在于确定基准和确定递归函数的参数,区间其实都好算。

确定基准的意思是我用的元素的下标位置是以中序序列为基准还是以前序序列为基准。

下面我采用的是根节点的位置以前序序列为基准,所以递归函数参数中的pre_root就代表当前这棵树的根节点在前序序列中的位置,in_root和in_right表示的是这棵树在中序序列里的左右边界。

class Solution {
public:
    //TreeNode* _bulidTree(TreeNode* root,int left,int right)//root如果不是int就不能确定根节点在前序遍历序列中的位置
    TreeNode* _bulidTree(int pre_root,int in_left,int in_right)
    {
        if(in_left>in_right)//说明该结点对应的左右子树已经不合理
        {
            return NULL;
        }
        TreeNode* root=new TreeNode(preorder[pre_root]);//创建根节点
        int x=find(inorder.begin(),inorder.end(),preorder[pre_root])-inorder.begin();//找到根节点在中序序列中的位置
        int l_length=x-in_left;//左边区间的长度
        int r_length=in_right-x;//右边区间的长度
        root->left=_bulidTree(pre_root+1,in_left,in_left+l_length-1);//构建左子树
        root->right=_bulidTree(pre_root+l_length+1,in_right-r_length+1,in_right);//构建右子树
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        this->preorder=preorder;//让子函数_buildTree里可以拿到preorder
        this->inorder=inorder;//同理
        TreeNode* root=_bulidTree(0,0,preorder.size()-1);//我的区间两边都是闭的
        return root;
    }
private:
    vector<int> preorder;
     vector<int> inorder;
};

这个代码还有一个可优化的点,仔细观察可以发现每次递归我都要计算根节点在中序序列中的位置,而find的底层是一个一个找过去的,效率不高,所以这里我们可以用哈希的思想把根节点在中序序列中的位置映射出来。

class Solution {
public:
    //TreeNode* _bulidTree(TreeNode* root,int left,int right)//root如果不是整形就不能确定根节点在前序遍历序列中的位置
    TreeNode* _bulidTree(int pre_root,int in_left,int in_right)
    {
        if(in_left>in_right)
        {
            return NULL;
        }
        TreeNode* root=new TreeNode(preorder[pre_root]);
       // int x=find(inorder.begin(),inorder.end(),preorder[pre_root])-inorder.begin();
        int l_length=dic[preorder[pre_root]]-in_left;//左边区间的长度
        int r_length=in_right-dic[preorder[pre_root]];//右边区间的长度
        root->left=_bulidTree(pre_root+1,in_left,in_left+l_length-1);
        root->right=_bulidTree(pre_root+l_length+1,in_right-r_length+1,in_right);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        this->preorder=preorder;
        for(int i=0;i<inorder.size();i++)
        {
            dic[inorder[i]]=i;//映射中序序列元素的下标
        }
        TreeNode* root=_bulidTree(0,0,preorder.size()-1);
        return root;
    }
private:
    vector<int> preorder;
    vector<int> inorder;
    unordered_map<int,int>dic;//字典,表示映射关系
};

📝二叉树的下一个结点

这题力扣没找到链接

找出中序遍历的下一个结点

以前写红黑树的++时写过类似的,这里把当时的代码粘过来,详细的如下图(这个图是我红黑树那篇博客里的内容

image-20220702111021781

self& operator++()//前置++
{
    Node* cur = _node;
    if (cur->_right)
    {
        //右子树不为空,不能直接把右子树的根给_node
        //因为右子树最左边的叶子才是下一个应该访问的结点
        Node* right = cur->_right;
        while (right&&right->_left)
        {
            right = right->_left;
        }
        _node = right;
        return *this;
    }
    else//右子树为空
        //看父亲
    {
        Node* parent = cur->_parent;
        while (parent&&parent->_right == cur)
        {
            cur = parent;
            parent = cur->_parent;
        }
        if (parent == nullptr)
        {
            _node = nullptr;
        }
        else
        {
            _node = parent;
        }

        return *this;
    }
}

📝用两个栈实现队列

剑指 Offer 09. 用两个栈实现队列 - 力扣(LeetCode)

用两个栈模拟队列的实现。

特别注意空栈情况,不处理空栈力扣编译都无法通过(VS跑的过)

class CQueue {
public:
    CQueue() {}
  
    void appendTail(int value) {
        st1.push(value);
    }
    
    int deleteHead() {
        if(st1.empty()&&st2.empty())//考虑两个都是空栈的情况,因为_ move并没有处理空栈
        //如果两个传入的都是空栈,会导致stack为空时访问栈顶数据报错
        {
            return -1;
        }
        _move(st1,st2);
        int x=st2.top();
        st2.pop();
        _move(st2,st1);
        return x;
    }
    void _move(stack<int>&s1,stack<int>&s2)//把s1的数据移到s2里面
    {
        while(!s1.empty())
        {
            s2.push(s1.top());
            s1.pop();
        }
    }
private:
    stack<int>st1;
    stack<int>st2;
};

📝斐波那契数列

剑指 Offer 10- I. 斐波那契数列 - 力扣(LeetCode)

根据题目给的公式递推即可。

class Solution {
public:
    int fib(int n) {       
        if(n==0)
        {
            return 0;
        } 
        else if(n==1) 
        {
            return 1;
        }
        else
        {
            int pprev=0;//当前数字前一个的前一个
            int prev=1;//当前数字的前一个
            for(int i=2;i<=n;i++)
            {
                int tmp=(prev+pprev)%1000000007;
                pprev=prev%1000000007;
                prev=tmp;
            }
            return prev;//最后一次计算的结果由tmp给了prev
        }
    }
};

书上还给了一种logn的解法,但是写起来比较复杂,不够实用。

📄【扩展】青蛙跳台阶

剑指 Offer 10- II. 青蛙跳台阶问题 - 力扣(LeetCode)

斐波那契的变形,也可以用dp去做。

记青蛙跳上第n级阶梯的方案数为dp[n];则dp[n]=dp[n-1]+dp[n-2];比如青蛙在第N级阶梯,那它只能是从第N-1或者第N-2级上来的

class Solution {
public:
    int numWays(int n) {
        int a[]={1,1};
        if(n<2)
        {
            return a[n];
        }
        int ans=0;
        int pprev=1;
        int prev=1;
        for(int i=2;i<=n;i++)
        {
            ans=(pprev+prev)%1000000007;
            pprev=prev%1000000007;
            prev=ans;
        }
        return ans;
    }
};

📄【扩展】矩形覆盖

这题没找到链接…但也是斐波那契的变形。

image-20220702145411112

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜欢乙醇的四氯化碳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值