剑指offer刷题2

 

目录

 

四 重建二叉树 2/29

十四 链表倒数第k个节点

十五 翻转链表

十六 合并两个排序链表3/1

十七 树的子结构3/1

十八 二叉树的镜像3/1

二十二 从上到下打印二叉树3/1

二十三 二叉搜索树的后序遍历序列3/1


四 重建二叉树 2/29

参考https://blog.csdn.net/JMasker/article/details/86761566

浙江大学数据结构二叉树https://www.bilibili.com/video/av55114968?p=35

思路:

先序遍历的第一个元素为根节点A,找到中序遍历数组中A的位置,以此位置划分A的左右子树。

直到到达叶节点,也就是左子树只有一个元素leftPre = rightPre,再往下遍历叶节点的左右子树,因为叶节点没有左右子树,所以返回nullptr,跳出递归。

【先序遍历】【中序遍历】

class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
/*

1.先求出根节点(前序序列第一个元素)。
2.将根节点带入到中序遍历序列中求出左右子树的中序遍历序列。
3.通过左右子树的中序序列元素集合带入前序遍历序列可以求出左右子树的前序序列。
4.左右子树的前序序列第一个元素分别是根节点的左右儿子
5.求出了左右子树的4种序列可以递归上述步骤*/
        return helper(pre, vin, 0, pre.size()-1, 0, vin.size()-1);
    }
    
    TreeNode* helper(vector<int>& pre, vector<int>& vin,int leftPre, int rightPre, int leftIn, int rightIn){
        if(leftPre > rightPre)
            return nullptr;
        
        TreeNode* root = new TreeNode(pre[leftPre]);//先序遍历的第一个元素为根节点,即leftPre是根元素
        if(leftPre == rightPre)
            return root;//叶节点
        else{
            int i = leftIn;
            for(; i <= rightIn; i++){
                if(vin[i] == pre[leftPre])
                    break;
            }
            root->left = helper(pre, vin, leftPre+1, leftPre+i-leftIn, leftIn, i-1);
            root->right = helper(pre, vin, leftPre+i+1-leftIn, rightPre, i+1, rightIn);
            return root;
        }
    }
};

十四 链表倒数第k个节点

【快慢指针】【返回slow->next】

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
    //快慢指针
        if(pListHead == NULL || k == 0) return NULL;//注意这里unsigned,如果写<0会报错
        ListNode* dummy = new ListNode(-1);
        dummy -> next = pListHead;//插入假节点
        //这题可以不插入假节点
        ListNode* fast = dummy, *slow = dummy;//快慢指针指向假节点
        for(int i = 0; i < k; i++){//注意这里终止条件,循环了k次【易错】第一次写了<=,就是k+1次
            if(fast -> next) fast = fast -> next;//快指针先走k步
            else return NULL;//k超出了链表长度
        }
        while(fast -> next){//快慢指针一起走
            fast = fast -> next;
            slow = slow -> next;
        }
        return slow->next;//
    }
};

十五 翻转链表

浙江大学陈越老师讲过

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        //快速方法有堆栈,vector,为了避免有空节点,使用常规方法
        if(pHead == NULL || pHead -> next == NULL ) return pHead;//链表元素为0或者1,返回链表
        
        ListNode* dummy = new ListNode(-1);
        dummy -> next = pHead;//插入假节点,这题必需品
        
        ListNode* pre = pHead, *cur = pHead;
        while(cur){//链表翻转,知道cur = NULL,此时pre指向最后一个节点(逆序第一个元素)
            ListNode* temp = cur -> next;
            cur -> next = pre;
            pre = cur;
            cur = temp; 
        }
        dummy -> next -> next = NULL;//使第一个元素(逆序的最后一个元素)指向NULL
        dummy -> next = pre;//最后一个节点(逆序第一个节点)
        return dummy -> next;

    }
};

十六 合并两个排序链表3/1

【递归】

参考:https://blog.csdn.net/qq_41562704/article/details/89429773

采用递归的方法,假设待合并的链表1,链表2,目前链表1头结点值小于链表2,则链表1的头结点为合并后链表的链表的头结点,则合并后的链表头节点指向的next的值为链表1的剩余节点与链表2的合并后的链表

思路:建立一个新链表头,

          比较p1 p2val大小,将较小值存入新链表(循环此过程)


class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == NULL) return pHead2;
        if(pHead2 == NULL) return pHead1;
        //if(pHead1 == NULL && pHead2 ==NULL) return NULL;
        
        ListNode* dummy = new ListNode(-1);//新建头结点
        
        if(pHead1 -> val < pHead2 -> val){//递归,将p1 p2中较小val值连接到新头结点后面
            dummy->next = pHead1;//p1较小,连接到新头结点
            dummy -> next -> next = Merge(pHead1->next,pHead2);//新链表下一个节点为p1->next和p2中的较小值
        }else{
            dummy ->next = pHead2;
            dummy -> next -> next = Merge(pHead1, pHead2 -> next);
        }
        return dummy->next;
    }
};

常规做法:https://www.cnblogs.com/silentteller/p/11886551.html

可以用一个新的节点,来去比较两个单调递增的链表当前节点的值,如果p1当前的值小于p2,则新的节点的next=p1,p1移到下一个节点,新的节点p也要移动到下一个节点。

【易错】这里要新建一个指针指向新建链表,我一开始当作和递归一样,只建了一个新节点。

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == nullptr)
            return pHead2;
        if(pHead2 == nullptr)
            return pHead1;
        //ListNode resHead(0);
        ListNode* dummy = new ListNode(-1);//创建新的头结点
        ListNode* p = dummy;//p指向dummy,p指针向下移动,一直更新【易错】 
        while(pHead1 && pHead2){
            if(pHead1->val < pHead2->val){
                p->next = pHead1;//当前较小值为p1,连接到p指针后面
                pHead1 = pHead1->next;//p1指针后移一位
            }
            else{
                p->next = pHead2;
                pHead2 = pHead2->next;
            }
            p = p->next;//p指针后移
        }
        if(pHead1)//如果最后剩下的是p1链表,则把p1连接到p上
            p->next = pHead1;
        if(pHead2)//如果最后剩下的是p2链表,则把p2连接到p上
            p->next = pHead2;
        return dummy -> next;
    }
};

十七 树的子结构3/1

思路:(参考:https://blog.csdn.net/BlackLion_zhou/article/details/90706803

1、第一次判断时,如果B为空,则返回空
2、先判断B根节点的值,如果B根节点的与A的根节点值相同,则判断A的左子树和B的左子树及A的右子树和B的右子树是为子结构关系。(递归)
3、直到判断到B的节点值与A的节点值相等,而B没有子树,则此时为其子结构
4、如果判断过程中,B树的前面部分都为A树从节点root1Now开始的一部分,但是最后出现了值不相等,或者B还有子树而A没有子树的情况,则A应该返回至root1Now的左右节点,B应该返回值最初的B
代码参考:https://www.cnblogs.com/chenruibin0614/p/11627559.html

【最值得思考的并列if】【isSubTree】写的太好了

class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {

        if(pRoot1 == nullptr || pRoot2 == nullptr) return false;//若p1或p2空或二者都空,则返回false
        bool flag = false;//设置旗帜为false
        
        //以下三个if语句不是并列
        if(pRoot1 -> val == pRoot2 -> val) flag = isSubTree(pRoot1,pRoot2);//二者根节点val相等,进入判断
        if(!flag) flag = isSubTree(pRoot1 -> left, pRoot2);//根节点不同,判断2是否是1的左子树的子结构
        if(!flag) flag = isSubTree(pRoot1 -> right, pRoot2);//根节点不同,2树不是1左子树的子树,判断2是不是1右子树的子树
        return flag;
    }
    
    bool isSubTree(TreeNode* pRoot1, TreeNode* pRoot2){
        if(pRoot2 == nullptr) return true;//2树遍历完成(关键语句,结束的标志,出口A)
//【这里有点迷糊】返回true之后,还执行下一句吗,如果两个树同时遍历到最后,那下一句也为真
        if(pRoot1 == nullptr) return false;//1树异常(关键语句,1树被遍历完,出口B)
        
        bool flag = true;//
        if(pRoot1 -> val != pRoot2 -> val) return false;//若比较的值相同,则比较左右子树
        if(flag) flag = isSubTree(pRoot1 -> left, pRoot2 -> left);//上一节点相同,比较左二子是否相同
        if(flag) flag = isSubTree(pRoot1 -> right, pRoot2 -> right);//上一节点相同,左二子相同,比较右儿子
        return flag;
    }
};

十八 二叉树的镜像3/1

操作给定的二叉树,将其变换为源二叉树的镜像。

【左右翻转】 【从上到下】

class Solution {
public:
    void Mirror(TreeNode *pRoot) {

        if(pRoot == nullptr) return;//【易错】这里不能写return nullptr 要求 void
        
        TreeNode* temp = pRoot -> left;
        pRoot -> left = pRoot -> right;
        pRoot -> right = temp;//将左右儿子交换
        
        Mirror(pRoot -> left);//对左子树镜像
        Mirror(pRoot -> right);//对右子树镜像
    }
};

参考:https://www.cnblogs.com/silentteller/p/11910991.html

十九 顺时针打印矩阵

二十二 从上到下打印二叉树3/1

【层序遍历】【二叉树】【队列】

参考:浙江大学数据结构https://www.bilibili.com/video/av55114968?p=37

参考:https://blog.csdn.net/lyl194458/article/details/89790239

思路:

1 建一个数组res保存打印结果,建一个队列que辅助层序遍历(队列数据类型为TreeNode*,指针类型的啊)

2 将头结点入队

3当队列不空时候循环(队列为空,遍历结束,退出循环)

   a 新建一个TreeNode*指针指向队列头结点

   b 将头结点的val 存入数组res

   c 检查头结点是否有左右儿子,有的话入队列,无的话进入下一次循环

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {

        vector<int> res;//新建数组用于存放打印结果

        if(root == nullptr) return res;//如果二叉树为空,返回空的数组
        
        queue<TreeNode*> que;//建立队列
        que.push(root);//root入队列
        
        while(!que.empty()){//当队列不为空时循环,队列为空,遍历了全部节点结束
            TreeNode* temp = que.front();//指针指向队头节点
            res.push_back(temp -> val);//将头结点的值压入数组
            
            if(temp -> left != nullptr)//如果队头节点有左儿子,压入队列
                que.push(temp -> left);
            if(temp -> right != nullptr)//如果有右儿子,压入队列
                que.push(temp -> right);
            
            que.pop();//弹出队头节点
        }
        return res;
        
    }
};

 

二十三 二叉搜索树的后序遍历序列3/1

参考:https://blog.csdn.net/qq_41901915/article/details/90270150

参考:https://www.cnblogs.com/silentteller/p/11924310.html

【二叉搜索树】【后序遍历】

思路:

1 序列最后一个一定是根节点

2 二叉搜索树左子树一定小于右子树,扫描序列,

   a 直到遇到大于根节点的元素,记下左子树长度;

   b 扫描剩下元素,如果遇到小于根节点的元素,返回false;

    至此,分割出根节点左右子树,再将左右子树继续输入

    直到只剩下两个元素,即左右两个叶子节点,返回true 

 

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size() == 0) return false;//数组为空和数组长度为0有区别
        return helper(sequence, 0, sequence.size());
    }
    bool helper(vector<int> &v, int index, int length){//index是开始的下标,length是子树长度
        if(length <= 1)//递归到只剩两个元素时候,只剩左右两个子节点,序列正确
            return true;//出口A
        /*找到第一个大于根节点的元素偏移量,此元素右边都是右子树,包括停止时候的v【index+i】,所以左子树长度是index + i -i = i*/
        
        int i = 0;
        for(; i < length-1; ++i){
            if(v[index + i] > v[index + length -1])//注意是【index+i】
                break;
        }
        //检查右子树元素是否都大于根节点
        for(int j = i; j < length-1; ++j){
            if(v[index + j] < v[index + length -1])//如果右子树存在小于根节点的情况,则序列不是后序遍历
                return false;//,出口B
        }
        return helper(v, index, i) && helper(v, index+i, length-i-1);//检查分割出的左子树和右子树
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值