【剑指offer刷题记录4】

51、构建乘积数组

题目描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。
解题思路
最后要求的数组B如下图所示:
在这里插入图片描述
使用两个辅助数组,分别求每一行的上三角和下三角的乘积,最后将两者相乘得最后的结果。

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
        int length = A.size();
        vector<int> up(length,1),down(length,1),res(length,1);
        if(length == 0)
            return res;
        for(int i = length-2;i >= 0;--i){
            up[i] = up[i+1]*A[i+1];
        }
        for(int i = 1;i < length;++i){
            down[i] = down[i-1]*A[i-1];
        }
        for(int i = 0;i < length;++i){
            res[i] = up[i]*down[i];
        }
        return res;
    }
};

52、正则表达式匹配

题目描述
请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配
解题思路
按照自己的做法,写了一大堆判断条件,在牛客上发现一个很好的:

解这题需要把题意仔细研究清楚,反正我试了好多次才明白的。
首先,考虑特殊情况:
1>两个字符串都为空,返回true
2>当第一个字符串不空,而第二个字符串空了,返回false(因为这样,就无法
匹配成功了,而如果第一个字符串空了,第二个字符串非空,还是可能匹配成
功的,比如第二个字符串是“aaaa”,由于‘’之前的元素可以出现0次,
所以有可能匹配成功)
之后就开始匹配第一个字符,这里有两种可能:匹配成功或匹配失败。但考虑到pattern
下一个字符可能是‘
’, 这里我们分两种情况讨论:pattern下一个字符为‘’或
不为‘
’:
1>pattern下一个字符不为‘’:这种情况比较简单,直接匹配当前字符。如果
匹配成功,继续匹配下一个;如果匹配失败,直接返回false。注意这里的
“匹配成功”,除了两个字符相同的情况外,还有一种情况,就是pattern的
当前字符为‘.’,同时str的当前字符不为‘\0’。
2>pattern下一个字符为‘
’时,稍微复杂一些,因为‘’可以代表0个或多个。
这里把这些情况都考虑到:
a>当‘
’匹配0个字符时,str当前字符不变,pattern当前字符后移两位,
跳过这个‘’符号;
b>当‘
’匹配1个或多个时,str当前字符移向下一个,pattern当前字符
不变。(这里匹配1个或多个可以看成一种情况,因为:当匹配一个时,
由于str移到了下一个字符,而pattern字符不变,就回到了上边的情况a;
当匹配多于一个字符时,相当于从str的下一个字符继续开始匹配)
之后再写代码就很简单了。

其中在匹配一个或多个的判断时要考虑".*"这个结构,我的理解是这个结构可以匹配任意序列。

class Solution {
public:
    bool match(char* str, char* pattern)
    {
        if (*str == '\0' && *pattern == '\0')
            return true;
        if (*str != '\0' && *pattern == '\0')
            return false;
        if(*(pattern + 1) != '*'){
            if(*str == *pattern || (*str != '\0' && *pattern == '.')){
                return match(str+1, pattern+1);
            }else
                return false;
        }else{
            if(*str == *pattern || (*str != '\0' && *pattern == '.'))
                return match(str+1, pattern) || match(str, pattern+2);
            else
                return match(str,pattern+2);
        }
    }
};

53、表示数值的字符串

题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
解题思路:
扣各种条件,见注释:

class Solution {
public:
    bool isNumeric(char* string)
    {
        bool sign=false,point=false,e=false;//正负号、小数点、e出现的标志
        int i = 0;
        for(;i < strlen(string);i++){
            if(string[i] == 'e' ||string[i] == 'E'){//e出现的时候,不能有过e,且后续要有数
                if(e) return false;
                if(string[i+1] == '\0') return false;
                e=true;
            }
            else if(string[i] == '+'||string[i] == '-'){//正负号,曾出现过则必须紧接e,未出现过但不在第一位也必须紧跟e
                if(sign && string[i-1]!= 'e' && string[i-1]!= 'E') return false;
                if(!sign && i>0 && string[i-1]!= 'e' && string[i-1]!= 'E') return false;
                sign=true;
            }
            else if(string[i] == '.'){//小数点只能出现一次,且不能在e后
                if(e || point) return false;
                point = true;
            }
            else if(string[i] < '0' || string[i] > '9'){
                return false;
            }
        }
        return true;
    }
};

54、字符流中第一个不重复的数字

题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
解题思路:
利用一个vector来保存流的字符和出现次数,返回时查询

class Solution
{
public:
    vector<pair<char,int>> help;
  //Insert one char from stringstream
    void Insert(char ch)
    {
        bool hasch=false;
        for(int i = 0;i<help.size();++i){
            if(help[i].first == ch){
                help[i].second++;
                hasch= true;
            }
        }
        if(!hasch)
            help.push_back(make_pair(ch,1));
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        for(int i = 0;i<help.size();++i){
            if(help[i].second == 1)
                return help[i].first;
        }
        return '#';
    }
};

这个是方法比较好想但是空间复杂度高,下面是牛客网上的好方法
提供一个高效的算法:
思路:时间复杂度O(1),空间复杂度O(n)
1、用一个128大小的数组统计每个字符出现的次数
2、用一个队列,如果第一次遇到ch字符,则插入队列;其他情况不在插入
3、求解第一个出现的字符,判断队首元素是否只出现一次,如果是直接返回,否则删除继续第3步骤

class Solution
{
public:
  //Insert one char from stringstream
    void Insert(char ch)
    {  
        ++cnt[ch - '\0'];
        if(cnt[ch - '\0'] == 1)
           data.push(ch);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
      while(!data.empty() && cnt[data.front()] >= 2) data.pop();
        if(data.empty()) return '#';
        return data.front();
    }
    Solution()
    {
      memset(cnt, 0, sizeof(cnt));    
    }
private:
    queue<char> data;
    unsigned cnt[128];
};

55、链表环入口节点

题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路
设置快慢指针,都从链表头出发,快指针每次走两步,慢指针一次走一步,假如有环,一定相遇于环中某点(结论1)。接着让两个指针分别从相遇点和链表头出发,两者都改为每次走一步,最终相遇于环入口(结论2)。

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        ListNode*fast=pHead,*low=pHead;
        while(fast&&fast->next){
            fast=fast->next->next;
            low=low->next;
            if(fast==low)
                break;
        }
        if(!fast||!fast->next)return NULL;
        low=pHead;//low从链表头出发
        while(fast!=low){//fast从相遇点出发
            fast=fast->next;
            low=low->next;
        }
        return low;
    }
};

56、删除链表中的重复节点

题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
解题思路
见注释

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
    if( pHead == NULL ) return pHead;
 
    ListNode *pre = NULL; //指向前面最晚访问过的不重复结点
    ListNode *p = pHead; //指向当前处理的结点
    ListNode *q = NULL; //指向当前处理结点后面结点
 
    while( p != NULL )
    {
        //当前结点p,(其实是p指向当前结点),与它下一个结点p->next的val相同,说明要删掉有这个val的所有结点
        if( p->next != NULL && p->next->val == p->val )
        {
            q = p->next;
 
            //找到q,它指向最后一个与p val相同的结点,那p 到 q (包含) 都是要删除的
            while( q != NULL && q->next != NULL && q->next->val == p->val )
            {
                q = q->next;
            }
 
            //如果p指向链表中第一个元素,p -> ... -> q ->... , 要删除p到q, 将指向链表第一个元素的指针pHead指向q->next。
            if( p == pHead )
            {
                pHead = q->next;
            }
            else//如果p不指向链表中第一个元素,pre -> p ->...->q ->... ,要删除p到q,即pre->next = q->next
            {
                pre->next = q->next;
            }
            //当前处理的p要向链表尾部移动
            p = q->next;
        }
        else
        {
            pre = p;
            p = p->next;
        }
    }
    return pHead;
    }
};

57、二叉树的下一个节点

题目描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
解题思路
见注释

class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode == NULL)
            return pNode;
        TreeLinkNode* help;
        //有右子树 返回右孩子的最下层的左节点
        if(pNode -> right != NULL){
            help = pNode -> right;
            while(help -> left != NULL){
                help = help -> left;
            }
            return help;
        }//无右字数 分两种 是父节点的右孩子 或 是父节点的左孩子
        while(pNode -> next != NULL){
            if(pNode -> next -> left == pNode)//左孩子直接返回父节点
                return pNode -> next;
            pNode = pNode -> next;//否则继续向上寻找父节点 直到找到是父节点的左孩子的情况
        }
        return NULL;//若一直未找到,表示找到了根节点,则是树的最右下角的子节点,下一个节点为空
    }
};

58、对称二叉树

题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
解题思路
对于一个根节点,若左子树的左孩子等于右子树右孩子,且左子树右孩子等于右子树左孩子,则对称。将这个根节点向下移动,分别放到根节点的左孩子和右孩子上,递归此过程即可。

class Solution {
public:
    bool help(TreeNode* p1, TreeNode* p2){
        if(p1 == nullptr && p2 == nullptr)
            return true;
        if(p1 == nullptr || p2 == nullptr)
            return false;
        if(p1 -> val != p2 -> val)
            return false;
        return help(p1 -> left, p2 -> right) && help(p2 -> left, p1 -> right);
    }
    bool isSymmetrical(TreeNode* pRoot)
    {
        return help (pRoot, pRoot);
    }
};

59、之字形打印二叉树

题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
解题思路
在层次遍历的基础上修改,在每一层遍历的时候进行vector的构造,加入一个布尔变量判断是奇数行还是偶数行,如果为奇数行则使用pushback,如果是偶数行则使用insert前向插入元素。
code:

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> res;
        if(pRoot == nullptr)
            return res;
        queue<TreeNode*> qt;
        qt.push(pRoot);
        bool isOdd = true;
        while(!qt.empty()){
            vector<int> temp;
            const int length = qt.size();
            for(int i = 0;i<length; ++i){
                TreeNode* help = qt.front();
                qt.pop();
                if(isOdd)
                    temp.push_back(help->val);
                else
                    temp.insert(temp.begin(),help->val);
                if(help->left != nullptr)
                    qt.push(help->left);
                if(help->right != nullptr)
                    qt.push(help->right);
            }
            res.push_back(temp);
            isOdd = !isOdd;
        }
        return res;
    }
};

60、二叉树打印成多行

题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
题目解析
感觉是上一题的简化版。。。层次遍历输出即可

class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            vector<vector<int> > res;
            if(pRoot == nullptr){
                return res;
            }
            queue<TreeNode*> qt;
            qt.push(pRoot);
            while(!qt.empty()){
                vector<int> temp;
                const int length = qt.size();
                for(int i = 0;i<length;i++){
                    TreeNode* help = qt.front();
                    qt.pop();
                    temp.push_back(help->val);
                    if(help->left != nullptr)
                        qt.push(help->left);
                    if(help->right != nullptr)
                        qt.push(help->right);
                }
                res.push_back(temp);
            }
            return res;
        }
    
};

61、序列化二叉树

62、二叉搜索树第k小的节点

题目描述
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
解题思路
利用二叉搜索树中序遍历是依次升序的特点,遍历到第k个即可
code

class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        stack<TreeNode*> mid;
        int count = 0;
        while(pRoot != nullptr || !mid.empty()){
            if (pRoot != nullptr) {
				mid.push(pRoot);
				pRoot = pRoot->left;
			} else { //pNode == null && !stack.isEmpty()
				TreeNode* node = mid.top();
                mid.pop();
                count++;
                if(count == k)
                    return node;
				pRoot = node->right;
			}
        }
        return nullptr;
    }
};

63、数据流中的中位数

题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
解题思路
建立一个大根堆和一个小根堆(C++中有优先级队列结构就是大根堆),将较小的一半数放入大根堆,较大的一半数放入小根堆,最后如果两个堆数量相等就取两个堆顶的中位数,否则返回数量较大的堆顶。
code

class Solution {
    priority_queue<int, vector<int>, less<int> > p;//大根堆 通过修改元素比较方式的参数,第三个参数来改变排序规则
    priority_queue<int, vector<int>, greater<int> > q;//小根堆
     
public:
    void Insert(int num){
        if(p.empty() || num <= p.top()) p.push(num);//较小数放入大根堆
        else q.push(num);//较大数放入小根堆
        if(p.size() == q.size() + 2){
            q.push(p.top());
            p.pop();
        } //调整使两者的数量相等,且大根堆的数量永远比小根堆大或等
        if(p.size() + 1 == q.size()){
            p.push(q.top());
            q.pop();
        }
    }
    double GetMedian(){ 
      return p.size() == q.size() ? (p.top() + q.top()) / 2.0 : p.top();
    }
};

64、滑动窗口最大值

题目描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
解题思路

code

vector<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
        vector<int> res;
        deque<int> qi;
        for(unsigned int i = 0; i < num.size();++i){
            while(!qi.empty()&&(num[qi.back()] <= num[i]))
                qi.pop_back();
            while(qi.size() && i-qi.front()+1 > size)
                qi.pop_front();
            qi.push_back(i);
            if(size&&i+1>=size)
                res.push_back(num[qi.front()]);
        }
        return res;
    }

65、

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值