剑指offer刷题记录(C++版本)(之三)

21.栈的压入,弹出序列

  • 题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
  • 思路:首先要理解题意,为何(序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。)弹出序列即出栈的一种方式,4,5,3,2,1即在进栈后位1,2,3,4的情况下先出栈4,再进栈5成为1,2,3,5然后在依次出栈得到的序列。而4,3,5,1,2的最后1,2这个序列是无法实现的,2必须在1之前才正确。

因此借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。

举例:
入栈1,2,3,4,5
出栈4,5,3,2,1
首先1入辅助栈,此时栈顶1≠4,继续入栈2
此时栈顶2≠4,继续入栈3
此时栈顶3≠4,继续入栈4
此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
此时栈顶3≠5,继续入栈5
此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
….
依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。

class Solution {
public:
    bool IsPopOrder(vector pushV,vector popV) {
        if(pushV.empty() || popV.empty() || pushV.size()!=popV.size())
            return false;
        stack s;
        int j=0;
        for(int i=0;i

22.从上往下打印二叉树

  • 题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
  • 思路:使用两个队列一个存放节点,一个存放值。先将根节点加入到队列中,然后遍历队列中的元素,遍历过程中,访问该元素的左右节点,再将左右子节点加入到队列中来
class Solution {
public:
    vector PrintFromTopToBottom(TreeNode* root) {
        vector result;
        if(NULL == root)
            return result;
         
        queue q;
        q.push(root);
        while(!q.empty())
        {
            TreeNode* temp = q.front();
            q.pop();
            result.push_back(temp->val);
            if(NULL != temp->left)
                q.push(temp->left);
            if(NULL != temp->right)
                q.push(temp->right);
        }
        return result;
    }
};

23.二叉搜索树的后续遍历序列

  • 题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
  • 知识点后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点

    后序遍历结果:DEBFCA
    已知前序遍历和中序遍历,就能确定后序遍历

二叉搜索树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

  • 思路

已知条件后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。
1、确定root;
2、遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;
3、遍历右子树,若发现有小于root的值,则直接返回false;
4、分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。

class Solution {
public:
    bool VerifySquenceOfBST(vector sequence) {
        vector leftTree,rightTree;
        int root; // 根结点
        if(sequence.empty()) return false;
        int index = 0; // 标记左右子树界限
        int len = sequence.size();
        root = sequence[len-1];
        int i=0;
        for(;iroot) break; // 找到第一个大于根结点的位置,则左边为左子树,右边为右子树
        }
        for(int j=i;j1) VerifySquenceOfBST(leftTree);
        if(rightTree.size()>1) VerifySquenceOfBST(rightTree);
         
        return true;
    }
};

24.二叉树中和位某一值的路径

  • 题目:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
  • 思路

1.按先序遍历把当前节点cur的左子树依次入栈同时保存当前节点,每次更新当前路径的和sum;
2.判断当前节点是否是叶子节点以及sum是否等于expectNumber,如果是,把当前路径放入结果中。
3.遇到叶子节点cur更新为NULL,此时看栈顶元素,如果栈顶元素的把栈顶元素保存在last变量中,同时弹出栈顶元素,当期路径中栈顶元素弹出,sum减掉栈顶元素,这一步骤不更改cur的值;
4.如果步骤3中的栈顶元素的右子树存在且右子树之前没有遍历过,当前节点cur更新为栈顶的右子树,此时改变cur=NULL的情况。

class Solution {
public:
    vector > FindPath(TreeNode* root,int expectNumber) {
        vector > res;   
    if (root == NULL)
        return res;
    stack s;
    s.push(root);
    int sum = 0; //当前和
    vector curPath; //当前路径
    TreeNode *cur = root; //当前节点
    TreeNode *last = NULL; //保存上一个节点
    while (!s.empty()){
        if (cur == NULL){
            TreeNode *temp = s.top();
            if (temp->right != NULL && temp->right != last){
                cur = temp->right; //转向未遍历过的右子树
            }else{
                last = temp; //保存上一个已遍历的节点
                s.pop();
                curPath.pop_back(); //从当前路径删除
                sum -= temp->val;
            }  }
        else{
            s.push(cur);
            sum += cur->val;
            curPath.push_back(cur->val);
            if (cur->left == NULL && cur->right == NULL && sum == expectNumber){
                res.push_back(curPath);
            }
            cur = cur->left; //先序遍历,左子树先于右子树
        }
    }
    return res;
    }
};

25.复杂链表的复制

  • 题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
  • 思路
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(!pHead) return NULL;
        RandomListNode *currNode = pHead;
        while(currNode){
            RandomListNode *node = new RandomListNode(currNode->label);
            node->next = currNode->next;
            currNode->next = node;
            currNode = node->next;
        }
        currNode = pHead;
        while(currNode){
            RandomListNode *node = currNode->next;
            if(currNode->random){               
                node->random = currNode->random->next;
            }
            currNode = node->next;
        }
        //拆分
        RandomListNode *pCloneHead = pHead->next;
        RandomListNode *tmp;
        currNode = pHead;
        while(currNode->next){
            tmp = currNode->next;
            currNode->next =tmp->next;
            currNode = tmp;
        }
        return pCloneHead;
    }
};

26.二叉搜索树与双向链表???

  • 题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
  • 知识点
    双向链表图解
  • 思路

1.核心是中序遍历的非递归算法。
2.修改当前遍历节点与前一遍历节点的指针指向。

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
    if(pRootOfTree == nullptr) return nullptr;
        TreeNode* pre = nullptr;
         
        convertHelper(pRootOfTree, pre);
         
        TreeNode* res = pRootOfTree;
        while(res ->left)
            res = res ->left;
        return res;
    }
     
    void convertHelper(TreeNode* cur, TreeNode*& pre)
    {
        if(cur == nullptr) return;
         
        convertHelper(cur ->left, pre);
         
        cur ->left = pre;
        if(pre) pre ->right = cur;
        pre = cur;
         
        convertHelper(cur ->right, pre);
    }
};

27.字符串的排列

  • 题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

  • 思路

递归法,问题转换为先固定第一个字符,求剩余字符的排列;求剩余字符排列时跟原问题一样。
(1) 遍历出所有可能出现在第一个位置的字符(即:依次将第一个字符同后面所有字符交换);
(2) 固定第一个字符,求后面字符的排列(即:在第1步的遍历过程中,插入递归进行实现)。
需要注意的几点:
(1) 先确定递归结束的条件,例如本题中可设begin == str.size() - 1;
(2) 形如 abaaa等特殊测试用例的情况,vector在进行push_back时是不考虑重复情况的,需要自行控制;
(3) 输出的排列可能不是按字典顺序排列的,可能导致无法完全通过测试用例,考虑输出前排序,或者递归之后取消复位操作。

class Solution {
public:
    vector Permutation(string str) {
        vector result;
        if(str.empty()) return result;
         
        Permutation(str,result,0);
         
        // 此时得到的result中排列并不是字典顺序,可以单独再排下序
        sort(result.begin(),result.end());
         
        return result;
    }
     
    void Permutation(string str,vector &result,int begin)
    {
        if(begin == str.size()-1) // 递归结束条件:索引已经指向str最后一个元素时
        {
            if(find(result.begin(),result.end(),str) == result.end())
            {
                // 如果result中不存在str,才添加;避免aa和aa重复添加的情况
                result.push_back(str);
            }
        }
        else
        {
            // 第一次循环i与begin相等,相当于第一个位置自身交换,关键在于之后的循环,
            // 之后i != begin,则会交换两个不同位置上的字符,直到begin==str.size()-1,进行输出;
            for(int i=begin;i

28.数组中出现次数超过一半的数字

  • 题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
  • 思路

数组排序后,如果符合条件的数存在,则一定是数组中间那个数。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)
这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;

class Solution {
public:
    int MoreThanHalfNum_Solution(vector numbers) {
// 因为用到了sort,时间复杂度O(NlogN),并非最优
        if(numbers.empty()) return 0;       
        sort(numbers.begin(),numbers.end()); // 排序,取数组中间那个数
        int middle = numbers[numbers.size()/2];    
        int count=0; // 出现次数
        for(int i=0;inumbers.size()/2) ? middle :  0;
    }
};

29.最小的K个数

  • 题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
  • 思路

基于堆排序算法,构建最大堆。时间复杂度为O(nlogk)
如果用快速排序,时间复杂度为O(nlogn)
如果用冒泡排序,时间复杂度为O(n*k)

全排列 时间复杂度O(nlogn)

class Solution {
public:
    vector GetLeastNumbers_Solution(vector input, int k) {
        vector res;
        if(input.empty()||k>input.size()) return res;
         
        sort(input.begin(),input.end());
         
        for(int i=0;i

30.连续子数组的最大和

  • 题目:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
  • 思路

首先要搞懂这题目的意思,连续子序列并不一定从第0个开始,因此最大子序列很有可能是冲中间开始计算
因此遍历求和,遇到负和抛弃之前的结果,重新积累,期间保留最大值

class Solution {
public:
    int FindGreatestSumOfSubArray(vector array) {
        if(array.empty())
            return 0;
         
        int cSum = 0;
        int result = array[0]; // result存储最大和,不能初始为0,存在负数
        for(int i = 0;i result) // 存储最大结果
                result = cSum;
        }
        return result;
    }
};

你可能感兴趣的:(剑指offer刷题记录(C++版本)(之三))