剑指offer(六)

51. 构建乘积数组

构建乘积数组_牛客题霸_牛客网 (nowcoder.com)

第一印象是求数组全部元素的乘积,然后每个位置都除以自己即可。但题目要求无法使用除法,只能另寻他法。

法一

啥也不管了,先暴力吧。

class Solution {
public:
    vector<int> multiply(vector<int>& A) {
        vector<int> ans(A.size(),0);
        for(int i = 0;i<A.size();i++){
            int mul = 1;
            for(int j = 0;j<A.size();j++){
                if(j != i) mul *= A[j];
            }
            ans[i] = mul;
        }
        return ans;
    }
};

法二

两次遍历。

乍看好像毫无头绪,但是细细琢磨发现b[i]等于a中除a[i]外所有元素的乘积,即b[i] = i左边的元素乘积 * i右边的元素乘积。而在一次遍历过程中左边或右边单独的乘积是可以累计的。

故,

  • 首先将b数组全部初始化为1。
  • 第一次遍历,用temp记录遍历过程中从左开始的a中元素的累积,并将结果与b[i]相乘。
  • 第二次遍历,依然用temp记录从右开始的a中元素的累积,并将结果与b[i]相乘。
  • 得到最终结果。
class Solution {
public:
    vector<int> multiply(vector<int>& A) {
        int len = A.size();
        vector<int> ans(len,1);
        int temp = 1;
        for(int i = 1;i<len;i++){
            temp *= A[i - 1];
            ans[i] *= temp;
        }
        temp = 1;
        for(int i = len - 2;i >= 0;i--){
            temp *= A[i + 1];
            ans[i] *= temp;
        }
        return ans;
    }
};

52. 正则表达式匹配

正则表达式匹配_牛客题霸_牛客网 (nowcoder.com)

看起来就是实现正则表达式里的.*。想不到怎么搞。

法一

C++11支持正则表达式了,但是牛客这里必须自己引入头文件,应该是后台的万能头文件不包括正则吧。

#include<regex>
class Solution {
public:
    bool match(string str, string pattern) {
        return regex_match(str,regex(pattern));
    }
};

法二

直接开抄

首先,.就代表一个万能字符做特殊判定即可,困难的是*

假设,abc与c*abc进行匹配,在从左向右匹配的过程中,首先a与c不匹配,发现不同但并不能做出“不匹配的判断”因为c后面还有一个*,代表字符可以出现任意次。所以,是否匹配还和当前字符后面跟着的符号有关。还需要去分情况讨论,很是困难。

但是,如果反过来看。从右边往左边匹配困难就少很多了。

假设str = aabpattern = c*a*b。每次匹配过程,结果只和当前匹配的字符和左边的字符 有关,不需要考虑右边是否还跟着其他字符。需要讨论的情况就少了很多,而且问题可以转换为子问题——当前字符是否匹配 + 之前的串是否匹配。可以考虑动规。

  • dp[i][j]:长度为istr与长度为jpattern是否匹配。

之后需要分情况讨论(分类依据是p中当前匹配字符是否是*,因为如果其是普通字符或者.可以直接比较):

  1. p[i-1]!= '*'
    1. 当前字符匹配成功,即s[i-1]==p[j-1] || p[j -1] == '*',此时dp[i][j] = dp[i-1][j-1](当前字符匹配成功,问题转换为之前的字符串是否匹配成功)
    2. 匹配不成功,dp[i][j] = false;
  2. p[i-1]== '*'比较s[i-1]p[j -2]*前的字符)
    1. 不匹配,即s[i - 1] != p[j - 2] && p[j - 2] != '.',此时dp[i][j] = dp[i][j - 2];(跳过p中不匹配的部分继续匹配)
    2. 匹配,又有三种情况
      1. 假设匹配0次,即匹配到了假装没匹配到直接跳过。此时dp[i][j] = dp[i][j - 2];
      2. 假设匹配1次,此时dp[i][j] = dp[i][j - 1];此时相当于p中多了一个*,其余可以直接匹配,故直接跳过*即可。
      3. 假设匹配多次,此时dp[i][j] = dp[i-1][j];相当于p中有a*,代表aaa*。匹配过程中假设s[i-1]为可以匹配到的最后一个a,故直接跳过s[i-1]继续去比。
class Solution {
public:
    bool match(string s, string p) {
        int n = s.size();
        int m = p.size();
        vector<vector<bool>> dp(n + 1,vector<bool>(m + 1,false));
        dp[0][0] = true;
        dp[0][1] = false;
        for(int j = 2;j<=m;j++){
            if(p[j - 1] == '*') dp[0][j] = dp[0][j - 2];
        }
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=m;j++){
                if(p[j - 1] != '*'){
                    if(s[i - 1] == p[j - 1] || p[j - 1] == '.') dp[i][j] = dp[i - 1][j - 1];
                    else dp[i][j] = false;
                }else{      //p[j - 1] == '*'
                    if(s[i - 1] != p[j - 2] && p[j - 2] != '.') dp[i][j] = dp[i][j - 2];
                    else dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j];
                }
            }
        }
        return dp[n][m];
    }
};
  • 代码中,凡是出现在dp后面的ij都表示字符串长度,凡是sp后面的则都表示索引。

53. 表示数值的字符串

LCR 138. 有效数字 - 力扣(LeetCode)

看不懂,直接开抄。

有效数字(按顺序)可以分成以下几个部分:

  1. 若干空格
  2. 一个 小数 或者 整数
  3. (可选)一个 'e''E' ,后面跟着一个 整数
  4. 若干空格

将所有能正确表示数值的字符串的形势总结如下:

  • 空格只能出现在开头和结尾。
  • .出现的正确情况: 只出现一次,且出现在e的前面。
  • e出现的正确情况:只出现一次,且出现前有数字。
  • +-出现的正确情况:只能在开头或e后一位。
class Solution {
public:
    bool validNumber(string s) {
        // 去掉首尾空格
        int start = s.find_first_not_of(' ');
        if (start == string::npos) return false;
        int end = s.find_last_not_of(' ');
        s = s.substr(start, end - start + 1);

        bool numFlag = false;
        bool dotFlag = false;
        bool eFlag = false;

        for (int i = 0; i < s.size(); i++) {
            if (isdigit(s[i])) {
                numFlag = true;
            } 
            //判断'.'的出现是否合法
            else if (s[i] == '.' && !dotFlag && !eFlag) {
                dotFlag = true;    
            } 
            // 判断'e'的出现是否合法
            else if ((s[i] == 'e' || s[i] == 'E') && !eFlag && numFlag) {
                eFlag = true;
                numFlag = false; // 'e'后面必须跟着整数
            } 
            // 判断正负号出现是否合法
            else if ((s[i] == '+' || s[i] == '-') && (i == 0 || s[i - 1] == 'e' || s[i - 1] == 'E')) {
                // 正确的位置不做处理
            } 
            // 其他情况都不合法
            else {
                return false;
            }
        }
        // 确保'e'或'E'后有数字
        return numFlag;
    }
};

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

字符流中第一个不重复的字符_牛客题霸_牛客网 (nowcoder.com)

初看懵逼的很,不明白insert这个函数有啥好用。后来才发现,这个是把字符串当作字符流。用insert在遍历字符串的同时,不断显示第一个不重复字符。

这就好说了。

法一

巧用库函数

class Solution
{
public:
  //Insert one char from stringstream
    void Insert(char ch) {
         vec.push_back(ch);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce() {
        if(vec.empty()) return '#';
        for(auto& ch : vec){
            if(count(vec.begin(),vec.end(),ch) == 1) return ch;
        }
        return '#';
    }
private:
    vector<int> vec;

};

法二

不用库函数,用哈希表自己数。空间复杂度会高一点,但应该会稍微快一点。

class Solution
{
public:
  //Insert one char from stringstream
    void Insert(char ch) {
         vec.push_back(ch);
         mp[ch]++;
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce() {
        if(vec.empty()) return '#';
        for(auto& ch : vec){
            if(mp[ch] == 1) return ch;
        }
        return '#';
    }
private:
    vector<int> vec;
    unordered_map<char, int> mp;

};

55. 链表中环的入口结点

LCR 022. 环形链表 II - 力扣(LeetCode)

直接开抄。代码随想录 (programmercarl.com)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast && fast->next){
            fast = fast->next->next;
            slow = slow->next;
            if(slow == fast){
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while(index1 != index2){
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return nullptr;
    }
};

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

82. 删除排序链表中的重复元素 II - 力扣(LeetCode)

法一:

83. 删除排序链表中的重复元素 - 力扣(LeetCode)很像。只不过本题重复的节点一个也不保留。这时候需要考虑第一个元素的处理问题。因为如果保留一个重复节点的话,第一个元素即使是重复的也需要保留,就不需要考虑了。但如果一个也不保留的话,第一个节点也有需要删除的可能性。

这时候,只要在原来代码的基础上新增加一个虚拟头节点,这样第一个节点就成了第二个,两个问题就统一起来了。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        while(cur->next && cur->next->next){
            if(cur->next->val == cur->next->next->val){
                int temp = cur->next->val;
                while( cur->next && cur->next->val == temp){
                    cur->next = cur->next->next;
                }
            }else{
                cur = cur->next;
            }
        }
        return dummyHead->next;
    }
};

法二:

迭代法。将问题分解为当前节点的去留问题和后继链表的去除重复节点问题。

  • 递归出口:当前节点为空或没有后继节点。
  • 递归问题:
    • 当前节点值与后继节点值不一致:当前节点需要保留,故head->next = deleteDuplicates(head->next);然后返回。
    • 当前节点值与后继节点值一样:当前节点不需要保留,需要找到与当前节点值不一样的某个后继节点,从该节点开始继续迭代 deleteDuplicates(temp)
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;
        if(head->val != head->next->val){
            head->next = deleteDuplicates(head->next);
            return head;
        }else{
            ListNode* temp = head->next;
            while( temp && temp->val == head->val){
                temp = temp->next;
            }
            return deleteDuplicates(temp);
        }   
        return nullptr;
    }
};

57. 二叉树的下一个结点

面试题 04.06. 后继者 - 力扣(LeetCode)

法一:

很直观的一种写法。找中序遍历的下一个节点,那就中序遍历一次,把遍历顺序存在一个数组里,之后遍历一次数组就找到下一个了。

class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        inorder(root);
        for(int i = 0;i<nodes.size();i++){
            if(nodes[i] == p && i + 1 < nodes.size()) return nodes[i + 1]; 
        }
        return nullptr;
    }
    void inorder(TreeNode* root){
        if(root == nullptr) return ;
        inorder(root->left);
        nodes.push_back(root);
        inorder(root->right);
    }
private:
    vector<TreeNode*> nodes;
};

法二:

法一好像有点浪费空间,其实不需要把所有的遍历结果都存一遍,只要记录两个就行了。pre和cur。若pre == p,那么cur就是后继。

class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        stack<TreeNode*> st;
        TreeNode* pre = nullptr;
        TreeNode* cur = root;
        while(cur != nullptr || !st.empty()){
            if(cur != nullptr){
                st.push(cur);
                cur = cur->left;
            }else{
                cur = st.top();
                st.pop();
                if(pre == p) return cur;
                pre = cur;
                cur = cur->right;
            }
        }
        return nullptr;
    }
};

二叉树的下一个结点_牛客题霸_牛客网 (nowcoder.com)

牛课上这个差不多的题目给出的形式还不一样。牛客只给了一个节点,没有给出根节点所以不能直接遍历。但牛客上的树有一个next指针指向父亲节点,所以也好办。直接分情况讨论即可:

  • p是空节点:返回nullptr
  • p有右孩子:中序遍历,所以有右孩子的话,下一个节点就是从右节点作为基准,开始找最左边的孩子。
  • p没有右孩子:
    • 若p的父亲节点是根节点的话:直接返回其父亲节点即可。
    • 若p的父亲节点不是根节点的话:此时,按照左根右的遍历顺序来看,p的父亲节点一定是已经遍历过了。需要找到当前节点是其父节点的左子节点的那个父节点。(左根右体遍历顺序中,假设父亲节点为pre,当前节点为p。只要p是pre的右孩子,pre就一定已经遍历过了。当p是pre的左孩子时,p的下一个就是pre。)
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* p) {
        if( p == nullptr) return p;
        if(p->right != nullptr){
            TreeLinkNode* cur = p->right;
            while(cur->left)
                cur = cur->left;
            return cur;
        }
        while(p->next != nullptr){
            TreeLinkNode* pre = p->next;
            if(pre->left == p) return pre;
            p = p->next;
        }
        return nullptr;
    }
};

58. 判断对称的二叉树

LCR 145. 判断对称二叉树 - 力扣(LeetCode)

比较简单的一道题。注意对称判定的时候是左孩子的左子树和右孩子的右子树判定(其他类推)即可。

class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right){
        if(left == nullptr && right == nullptr) return true;
        else if(left == nullptr || right == nullptr) return false;
        else if(left->val != right->val) return false;
        else{
            return compare(left->left,right->right) && compare(left->right,right->left); 
        }
    }
    bool checkSymmetricTree(TreeNode* root) {
        if(root == nullptr) return true;
        return compare(root->left,root->right);
    }
};

59. 按之字形顺序打印二叉树

按之字形顺序打印二叉树_牛客题霸_牛客网 (nowcoder.com)

法一

第一印象,直接层序遍历,然后将偶数位置的数组翻转一下。

class Solution {
public:
    vector<vector<int> > Print(TreeNode* root) {
        vector<vector<int>> ans;
        queue<TreeNode*> que;
        if(root) que.push(root);
        while(!que.empty()){
            int size = que.size();
            vector<int> vec;
            for(int i = 0;i<size;i++){
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            ans.push_back(vec);
        }
        for(int i = 1;i<ans.size();i+=2){
            reverse(ans[i].begin(),ans[i].end());
        }
        return ans;
    }
};

法二

也可以用一个标志位,在插入时就直接改变插入方向。这里就用odd作为奇数行标志位

class Solution {
public:
    vector<vector<int> > Print(TreeNode* root) {
        vector<vector<int>> ans;
        queue<TreeNode*> que;
        if(root) que.push(root);
        bool odd = true;
        while(!que.empty()){
            int size = que.size();
            vector<int> vec;
            for(int i = 0;i<size;i++){
                TreeNode* node = que.front();
                que.pop();
                if(odd)
                    vec.push_back(node->val);
                else
                    vec.insert(vec.begin(), node->val);
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            odd = !odd;  // 遍历完一行了,标志一下方向变了
            ans.push_back(vec);
        }
        return ans;
    }
};

60. 把二叉树打印成多行

把二叉树打印成多行_牛客题霸_牛客网 (nowcoder.com)

这不是一个标标准准的层序遍历吗

class Solution {
public:
    vector<vector<int> > Print(TreeNode* root) {
        vector<vector<int>> ans;
        queue<TreeNode*> que;
        if(root) que.push(root);
        while(!que.empty()){
            int size = que.size();
            vector<int> vec;
            for(int i = 0;i<size;i++){
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            ans.push_back(vec);
        }
        return ans;
    }
};
  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

记与思

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

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

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

打赏作者

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

抵扣说明:

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

余额充值