剑指offer 刷题记录(21~30题)

继续接上篇博客的刷题!

21.输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

思路:借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是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
….
依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。
参考大佬的写法orz

//运行时间:4ms 占用内存:488k
class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.size() == 0) 
            return false;
            
        vector<int> stack;
        for(int i = 0,j = 0 ;i < pushV.size();) {
            stack.push_back(pushV[i++]);
            while(j < popV.size() && stack.back() == popV[j]) {
                stack.pop_back(); 
                j++;
            }
        }
        return stack.empty();
    }
};

22.从上往下打印出二叉树的每个节点,同层节点从左至右打印。

在这里插入图片描述

//运行时间:23ms 占用内存:504k
/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> result;
        if(root==nullptr)
            return result;
        queue<TreeNode*> dequeTreeNode;
        dequeTreeNode.push(root);
        while(dequeTreeNode.size()) {
            result.push_back(dequeTreeNode.front()->val);
            if(dequeTreeNode.front()->left!=NULL)
                dequeTreeNode.push(dequeTreeNode.front()->left);
            if(dequeTreeNode.front()->right!=NULL)
                dequeTreeNode.push(dequeTreeNode.front()->right);
            dequeTreeNode.pop();
        }
        return result;
    }
};

23.输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

//递归
class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
 
        int size = sequence.size();
        if(0==size)
        {
            return false;
        }
 
        return isLastOrder(sequence, 0, size-1);
    }
 
private:
    bool isLastOrder(vector<int>& sequece, int b, int e)
    {
        if(b==e)
        {
            return true;
        }
        int mid = b;
        while(sequece[mid++]<sequece[e] && mid<e);
 
        int tmp = mid;
        while (sequece[tmp++]>sequece[e] && tmp<e);
        if(tmp<e)
        {
            return false;
        }
 
        if(mid==b || mid==e)
        {
            return isLastOrder(sequece, b, e-1);
        }
        else
        {
            return (isLastOrder(sequece, b, mid-1) && isLastOrder(sequece, mid, e-1));
        }
    }
};

非递归方法:
非递归也是一个基于递归的思想:左子树一定比右子树小,因此去掉根后,数字分为left,right两部分,right部分的,最后一个数字是右子树的根他也比左子树所有值大,因此我们可以每次只看有子树是否符合条件即可,即使到达了左子树左子树也可以看出由左右子树组成的树还想右子树那样处理,对于左子树回到了原问题,对于右子树,左子树的所有值都比右子树的根小可以暂时把他看出右子树的左子树,只需看看右子树的右子树是否符合要求即可。

//运行时间:3ms 占用内存:476k
class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        int length=sequence.size();
        if(length<=0)
            return false;
        int i=0;
        while(--length) {
            while(sequence[i++]<sequence[length]);
            while(sequence[i++]>sequence[length]);
            if(i<length)
                return false;
            i=0;
        }
        return true;
    }
};

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

非递归法:后序遍历
1.进栈时候,把值同时压入路径的向量数组,修正路径的和
2.出栈时候,先判断和是否相等,且该节点是否是叶节点,
判断完成后保持和栈一致,抛出路径,修改路径的和
3.向量数组和栈的操作要保持一致

//运行时间:4ms 占用内存:372k
/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        stack<TreeNode*> s;
        vector<int> v;
        vector<vector<int> > res;
        while (root || !s.empty()) {
            while (root) {
                s.push(root); v.push_back(root->val); expectNumber -= root->val;
                //能左就左,否则向右
                root = root->left ? root->left : root->right;
            }
            root = s.top();
            if (expectNumber == 0 && root->left == nullptr && root->right == nullptr)
                res.push_back(v);
            s.pop(); v.pop_back(); expectNumber += root->val;
            //右子数没遍历就遍历,如果遍历就强迫出栈
            if (!s.empty() && s.top()->left == root)
                root = s.top()->right;
            else
                root = nullptr;//强迫出栈
        }
        return res;
    }
};

25.输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

这题还是很有难度的。不过offer书上讲的还是很详细的。等有空再来具体写一下。这里先参考一下别人的代码。

//运行时间:5ms 占用内存:504k
/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
    if (!pHead) 
        return NULL;
    nodeClone(pHead);
    connectRandom(pHead);
    return reconnect(pHead);
    }
     
     
//[1]复制结点,插入到原结点后方
void nodeClone(RandomListNode *head) {
    RandomListNode *pNode = head;
    while (pNode != NULL) {
        RandomListNode *pClone = new RandomListNode(pNode->label);
        pClone->next = pNode->next;
        pNode->next = pClone;
        pNode = pClone->next;
    }
}
 
//[2]还原新结点的random指针
void connectRandom(RandomListNode *head) {
    RandomListNode *pNode = head;
     
    while (pNode != NULL) {
        RandomListNode *pClone = pNode->next;
        if (pNode->random) {
            pClone->random = pNode->random->next;
        }
        pNode = pClone->next;
    }
}
 
//[3]拆分
RandomListNode *reconnect(RandomListNode *head) {
    RandomListNode *pNode = head;
    RandomListNode *result = head->next;
    while (pNode != NULL) {
        RandomListNode *pClone = pNode->next;
        pNode->next = pClone->next;
        pNode = pNode->next;
        if (pNode != NULL)
            pClone->next = pNode->next;         
    }
    return result;
} 
};

26.输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

直接用中序遍历即可。

//运行时间:3ms 占用内存:460k
/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
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。

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

//运行时间:7ms 占用内存:548k
class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> result;
        if(str.empty()) return result;
         
        Permutation(str,result,0);
         
        // 此时得到的result中排列并不是字典顺序,可以单独再排下序
        sort(result.begin(),result.end());
         
        return result;
    }
     
    void Permutation(string str,vector<string> &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<str.size();++i) {
                swap(str[i],str[begin]);
                Permutation(str,result,begin+1);
                swap(str[i],str[begin]); // 复位,用以恢复之前字符串顺序,达到第一位依次跟其他位交换的目的
            }
        }
    }
     
    void swap(char &first,char &second)
    {
        char temp = first;
        first = second;
        second = temp;
    }
};

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

如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。

//运行时间:4ms 占用内存:488k
class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty())
            return 0;
        // 遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1
        int result = numbers[0];
        int times = 1; // 次数

        for(int i=1;i<numbers.size();++i) {
            if(times == 0) {
                // 更新result的值为当前元素,并置次数为1
                result = numbers[i];
                times = 1;
            }
            else if(numbers[i] == result)
                ++times; // 相同则加1
            else
                --times; // 不同则减1
        }    
        // 判断result是否符合条件,即出现次数大于数组长度的一半
        times = 0;
        for(int i=0;i<numbers.size();++i){
            if(numbers[i] == result)
                ++times;
        }

        return (times > numbers.size()/2) ? result : 0;
    }
};

29.输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

全排序 时间复杂度O(logn),代码如下:

//运行时间:4ms 占用内存:492k
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> result;
        if(input.empty()||k>input.size()) return result;

        sort(input.begin(),input.end());//排序

        for(int i=0;i<k;i++)
            result.push_back(input[i]);

        return result;
    }
};

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

这题目描述,有OJ内味了(逃

//运行时间:6ms 占用内存:484k
class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int length=array.size();
        if(length <= 0) {
            return 0;
        }
        
        int nCurSum = 0;
        int nGreatestSum = 0x80000000;
        for(int i = 0; i < length; ++i) {
            if(nCurSum <= 0)
                nCurSum = array[i];
            else
                nCurSum += array[i];

            if(nCurSum > nGreatestSum)
                nGreatestSum = nCurSum;
        }
        return nGreatestSum;
    }
};
发布了36 篇原创文章 · 获赞 8 · 访问量 1570

猜你喜欢

转载自blog.csdn.net/weixin_43619346/article/details/103680590