牛客题解 | 正则表达式匹配

题目## 题目

题目链接

题目的主要信息:
  • 给出2个版本号version1和version2,比较它们的大小
  • 版本号是由修订号组成,修订号与修订号之间由一个"."连接
  • 修订号可能有前导0,按从左到右的顺序依次比较它们的修订号,比较修订号时,只需比较忽略任何前导零后的整数值
  • 如果版本号没有指定某个下标处的修订号,则该修订号视为0
  • 版本号中每一节可能超过int的表达范围
举一反三:

学习完本题的思路你可以解决如下题目:

BM91.反转字符串

BM88.判断是否为回文字符串

BM87.合并两个有序数组

方法一:双指针遍历截取(推荐使用)

知识点:双指针

双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。

思路:

既然是比较两个字符串每个点之间的数字是否相同,就直接同时遍历字符串比较,因此我们需要使用两个同向访问的指针各自访问一个字符串。

比较的时候,数字前导零不便于我们比较,因为我们不知道后面会出现多少前导零,因此应该将点之间的部分转化为数字再比较才方便。

while(i < n1 && version1[i] != '.'){ 
    num1 = num1 * 10 + (version1[i] - '0');
    i++;
}

具体做法:

  • step 1:利用两个指针表示字符串的下标,分别遍历两个字符串。
  • step 2:每次截取点之前的数字字符组成数字,即在遇到一个点之前,直接取数字,加在前面数字乘10的后面。(因为int会溢出,这里采用long记录数字)
  • step 3:然后比较两个数字大小,根据大小关系返回1或者-1,如果全部比较完都无法比较出大小关系,则返回0.

图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java实现代码:

import java.util.*;
public class Solution {
    public int compare (String version1, String version2) {
        int n1 = version1.length();
        int n2 = version2.length();
        int i = 0, j = 0;
        //直到某个字符串结束
        while(i < n1 || j < n2){
            long num1 = 0;
            //从下一个点前截取数字
            while(i < n1 && version1.charAt(i) != '.'){ 
                num1 = num1 * 10 + (version1.charAt(i) - '0');
                i++;
            }
            //跳过点
            i++; 
            long num2 = 0;
            //从下一个点前截取数字
            while(j < n2 && version2.charAt(j) != '.'){ 
                num2 = num2 * 10 + (version2.charAt(j) - '0');
                j++;
            }
            //跳过点
            j++; 
            //比较数字大小
            if(num1 > num2) 
                return 1;
            if(num1 < num2)
                return -1;
        }
        //版本号相同
        return 0; 
    }
}

C++实现代码:

class Solution {
public:
    int compare(string version1, string version2) {
        int n1 = version1.size();
        int n2 = version2.size();
        int i = 0, j = 0;
        //直到某个字符串结束
        while(i < n1 || j < n2){
            long long num1 = 0;
            //从下一个点前截取数字
            while(i < n1 && version1[i] != '.'){ 
                num1 = num1 * 10 + (version1[i] - '0');
                i++;
            }
            //跳过点
            i++; 
            long long num2 = 0;
            //从下一个点前截取数字
            while(j < n2 && version2[j] != '.'){ 
                num2 = num2 * 10 + (version2[j] - '0');
                j++;
            }
            //跳过点
            j++; 
            //比较数字大小
            if(num1 > num2) 
                return 1;
            if(num1 < num2)
                return -1;
        }
        //版本号相同
        return 0; 
    }
};

Python实现代码

class Solution:
    def compare(self , version1: str, version2: str) -> int:
        n1 = len(version1)
        n2 = len(version2)
        i, j = 0, 0
        # 直到某个字符串结束
        while i < n1 or j < n2: 
            num1 = 0
            # 从下一个点前截取数字
            while i < n1 and version1[i] != '.': 
                num1 = num1 * 10 + int(version1[i])
                i += 1
            # 跳过点
            i += 1 
            num2 = 0
            # 从下一个点前截取数字
            while j < n2 and version2[j] != '.': 
                num2 = num2 * 10 + int(version2[j])
                j += 1
            # 跳过点
            j += 1 
            # 比较数字大小
            if num1 > num2: 
                return 1
            if num1 < num2:
                return -1 
        # 版本号相同
        return 0

复杂度分析:

  • 时间复杂度: O ( m a x ( n , m ) ) O(max(n,m)) O(max(n,m)),其中 m m m n n n分别为两个字符串的长度,遍历两个字符串,复杂度选取较高值
  • 空间复杂度: O ( 1 ) O(1) O(1),常数级变量,无额外辅助空间
方法二:分割截取(思路扩展)

思路:

既然方法一都是每次以点为界限,将字符转换为数字,那我们是不是可以尝试提前就把它们分割好呢?分割也不难,可以借助Java或者Pyhton的split函数直接按照点为间隔划分开。C++没有这么方便的split函数了,但是我们还有流输入istringstream,只需要用一个字符型变量承接点,其他部分就是逐渐输入数组中。

具体做法:

  • step 1:使用split函数或者字符串流输入,按照点将两个原始字符串分割,使每个修订号的数字单独呈现在数组中。
  • step 2:遍历数组,每次各自取出一个数字比较,较短的版本号没有可取的数字了,就直接取0。
  • step 3:遍历取出的数字字符串,将其转换成数字,然后比较数字大小。根据大小关系返回1或者-1,如果全部比较完都无法比较出大小关系,则返回0.

Java实现代码:

import java.util.*;
public class Solution {
    public int compare (String version1, String version2) {
        //按照.划分
        String[] nums1 = version1.split("\\."); 
        String[] nums2 = version2.split("\\.");
        for(int i = 0; i < nums1.length || i < nums2.length; i++){
            //较短的版本号后续都取0
            String str1 = i < nums1.length ? nums1[i] : "0"; 
            String str2 = i < nums2.length ? nums2[i] : "0";
            long num1 = 0;
            //字符串转数字
            for(int j = 0; j < str1.length(); j++) 
                num1 = num1 * 10 + (str1.charAt(j) - '0');
            long num2 = 0;
            for(int j = 0; j < str2.length(); j++)
                num2 = num2 * 10 + (str2.charAt(j) - '0');
            //比较数字大小
            if(num1 > num2) 
                return 1;
            if(num1 < num2)
                return -1;
        }
        //版本相同
        return 0;
    }
}

C++实现代码:

class Solution {
public:
    int compare(string version1, string version2) {
        vector<string> nums1;
        vector<string> nums2;
        istringstream sin1(version1);
        istringstream sin2(version2);
        string temp;
        //流输入分割
        while(getline(sin1, temp, '.')) 
            nums1.push_back(temp);
        while(getline(sin2, temp, '.'))
            nums2.push_back(temp);
        for(int i = 0; i < nums1.size() || i < nums2.size(); i++){
            //较短的版本号取0
            string s1 = i < nums1.size() ? nums1[i] : "0"; 
            string s2 = i < nums2.size() ? nums2[i] : "0";
            long long num1 = 0;
            //字符串转数字
            for(int j = 0; j < s1.length(); j++) 
                num1 = num1 * 10 + (s1[j] - '0');
            long long num2 = 0;
            for(int j = 0; j < s2.length(); j++)
                num2 = num2 * 10 + (s2[j] - '0');
            //比较数字大小
            if(num1 > num2) 
                return 1;
            if(num1 < num2)
                return -1;
        }
        //版本号相同
        return 0;
    }
};

Python实现代码

class Solution:
    def compare(self , version1: str, version2: str) -> int:
        #分割
        nums1 = version1.split('.') 
        nums2 = version2.split('.')
        for i in range(max([len(nums1), len(nums2)])):
            #较短的版本号后续都取0,字符串转数字
            num1 = int(nums1[i]) if i < len(nums1) else 0 
            num2 = int(nums2[i]) if i < len(nums2) else 0
            #比较数字大小
            if num1 > num2: 
                return 1
            if num1 < num2:
                return -1
        #版本相同
        return 0

复杂度分析:

  • 时间复杂度: O ( m a x ( n , m ) ) O(max(n,m)) O(max(n,m)),其中 m m m n n n分别为两个字符串的长度,流输入和split相当于遍历字符串,复杂度选取较高值
  • 空间复杂度: O ( m a x ( n , m ) ) O(max(n,m)) O(max(n,m)),使用了记录分割后修订号的数组,最坏长度为二者原本字符串长度最大值

题目链接

题目主要信息:
  • 一个正常字符串str,可能为空,只包含小写字母
  • 一个模式串pattern,可能为空,只包含小写字母和‘*’与‘.’
  • 模式中的字符’.‘表示任意一个字符,而’*'表示它前面的字符可以出现任意次(包含0次)
  • 求str与pattern是否能完全匹配
举一反三:

学习完本题的思路你可以解决如下题目:

BM65 最长公共子序列(二)

BM66.最长公共子串

BM71.最长上升子序列(一)

BM73 最长回文子串

BM75 编辑距离(一)

BM77 最长的括号子串

方法:动态规划(推荐使用)

知识点:动态规划

动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果

思路:

如果是只有小写字母,那么直接比较字符是否相同即可匹配,如果再多一个’.‘,可以用它匹配任意字符,只要对应str中的元素不为空就行了。但是多了’*'字符,它的情况有多种,涉及状态转移,因此我们用动态规划。

具体做法:

  • step 1:设dp[i][j]表示str前i个字符和pattern前j个字符是否匹配。(需要注意这里的i,j是长度,比对应的字符串下标要多1)

  • step 2: (初始条件) 首先,毋庸置疑,两个空串是直接匹配,因此 d p [ 0 ] [ 0 ] = t r u e dp[0][0]=true dp[0][0]=true。然后我们假设str字符串为空,那么pattern要怎么才能匹配空串呢?答案是利用’*‘字符出现0次的特性。遍历pattern字符串,如果遇到’*'意味着它前面的字符可以出现0次,要想匹配空串也只能出现0,那就相当于考虑再前一个字符是否能匹配,因此 d p [ 0 ] [ i ] = d p [ 0 ] [ i − 2 ] dp[0][i] = dp[0][i - 2] dp[0][i]=dp[0][i2]

  • step 3: (状态转移) 然后分别遍历str与pattern的每个长度,开始寻找状态转移。首先考虑字符不为’*‘的简单情况,只要遍历到的两个字符相等,或是pattern串中为’.‘即可匹配,因此最后一位匹配,即查看二者各自前一位是否能完成匹配,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i - 1][j - 1] dp[i][j]=dp[i1][j1]。然后考虑’*'出现的情况:

    1. pattern[j - 2] == '.' || pattern[j - 2] == str[i - 1]:即pattern前一位能够多匹配一位,可以用’*'让它多出现一次或是不出现,因此有转移方程 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] ∣ ∣ d p [ i ] [ j − 2 ] dp[i][j] = dp[i - 1][j] || dp[i][j - 2] dp[i][j]=dp[i1][j]∣∣dp[i][j2]
    2. 不满足上述条件,只能不匹配,让前一个字符出现0次, d p [ i ] [ j ] = d p [ i ] [ j − 2 ] dp[i][j] = dp[i][j - 2] dp[i][j]=dp[i][j2].

图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java实现代码:

import java.util.*;
public class Solution {
    public boolean match (String str, String pattern) {
        int n1 = str.length();
        int n2 = pattern.length();
        //dp[i][j]表示str前i个字符和pattern前j个字符是否匹配
        boolean[][] dp = new boolean[n1 + 1][n2 + 1]; 
        //遍历str每个长度
        for(int i = 0; i <= n1; i++){  
            //遍历pattern每个长度
            for(int j = 0; j <= n2; j++){ 
                //空正则的情况
                if(j == 0){ 
                    dp[i][j] = (i == 0 ? true : false);
                //非空的情况下 星号、点号、字符
                }else{ 
                    if(pattern.charAt(j - 1) != '*'){
                        //当前字符不为*,用.去匹配或者字符直接相同
                        if(i > 0 && (str.charAt(i - 1) == pattern.charAt(j - 1) || pattern.charAt(j - 1) == '.')){
                            dp[i][j] = dp[i - 1][j - 1];
                        }
                    }else{
                        //碰到*
                        if(j >= 2){
                            dp[i][j] |= dp[i][j - 2];
                        }
                        //若是前一位为.或者前一位可以与这个数字匹配
                        if(i >= 1 && j >= 2 && (str.charAt(i - 1) == pattern.charAt(j - 2) || pattern.charAt(j - 2) == '.')){
                            dp[i][j] |= dp[i - 1][j];
                        }
                    }
                }
            }
        }
        return dp[n1][n2];
  }
}

C++实现代码:

class Solution {
public:
    bool match(string str, string pattern) {
        int n1 = str.length();
        int n2 = pattern.length();
        //dp[i][j]表示str前i个字符和pattern前j个字符是否匹配
        vector<vector<bool> > dp(n1 + 1, vector<bool>(n2 + 1, false)); 
        //两个都为空串自然匹配
        dp[0][0] = true; 
        //初始化str为空的情况,字符串下标从1开始
        for(int i = 2; i <= n2; i++){ 
            //可以让自己前面个字符重复0次
            if(pattern[i - 1] == '*') 
                //与再前一个能够匹配空串有关
                dp[0][i] = dp[0][i - 2]; 
        }
        //遍历str每个长度
        for(int i = 1; i <= n1; i++){ 
            //遍历pattern每个长度
            for(int j = 1; j <= n2; j++){ 
                //当前字符不为*,用.去匹配或者字符直接相同
                if(pattern[j - 1] != '*' && (pattern[j - 1] == '.' || pattern[j - 1] == str[i - 1])){ 
                      dp[i][j] = dp[i - 1][j - 1];
                //当前的字符为*
                }else if(j >= 2 && pattern[j - 1] == '*'){ 
                    //若是前一位为.或者前一位可以与这个数字匹配
                    if(pattern[j - 2] == '.' || pattern[j - 2] == str[i - 1]) 
                        //转移情况
                        dp[i][j] = dp[i - 1][j] || dp[i][j - 2];  
                    else
                        //不匹配
                        dp[i][j] = dp[i][j - 2]; 
                }
            }
        }
        return dp[n1][n2];
    }
};

Python代码实现:

class Solution:
    def match(self , str: str, pattern: str) -> bool:
        n1 = len(str)
        n2 = len(pattern)
        #dp[i][j]表示str前i个字符和pattern前j个字符是否匹配
        dp = [[False] * (n2 + 1) for i in range(n1 + 1)]
        #两个都为空串自然匹配
        dp[0][0] = True 
        #初始化str为空的情况,字符串下标从1开始
        for i in range(2, n2 + 1): 
            #可以让自己前面个字符重复0次
            if pattern[i - 1] == '*': 
                #与再前一个能够匹配空串有关
                dp[0][i] = dp[0][i - 2] 
        #遍历str每个长度
        for i in range(1, n1 + 1): 
            #遍历pattern每个长度
            for j in range(n2 + 1): 
                #当前字符不为*,用.去匹配或者字符直接相同
                if pattern[j - 1] != '*' and (pattern[j - 1] == '.' or pattern[j - 1] == str[i - 1]): 
                      dp[i][j] = dp[i - 1][j - 1]
                #当前的字符为*
                elif j >= 2 and pattern[j - 1] == '*': 
                    #若是前一位为.或者前一位可以与这个数字匹配
                    if pattern[j - 2] == '.' or pattern[j - 2] == str[i - 1]: 
                        #转移情况
                        dp[i][j] = dp[i - 1][j] or dp[i][j - 2] 
                    else:
                        #不匹配
                        dp[i][j] = dp[i][j - 2] 
        return dp[n1][n2]

复杂度分析:

  • 时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m n n n分别为字符串和模版串的长度,初始化遍历矩阵一边,状态转移遍历整个dp矩阵
  • 空间复杂度: O ( m n ) O(mn) O(mn),动态规划辅助数组dp的空间
### C++ 中处理后缀表达式的解析与实现 #### 解析原理 后缀表达式,又称逆波兰表示法,是一种不需要括号来表示操作顺序的算术表达式形式。这种表达方式使得计算机更容易理解和计算复杂的数学表达式。 对于给定的一个字符串类型的后缀表达式,在C++中可以通过栈这一数据结构来进行求解。具体来说,当遇到数字时将其压入栈内;而一旦碰到运算符,则弹出相应数量的操作数执行该运算,并把结果重新压回栈中等待后续可能存在的进一步计算直到整个过程结束只留下最终的结果于栈底[^1]。 #### 实现代码示例 以下是基于上述逻辑编写的用于评估简单四则运算(加减乘除)组成的后缀表达式的函数: ```cpp #include <iostream> #include <stack> #include <sstream> using namespace std; // 计算两个数值之间的基本运算 double calculate(double a, double b, char op){ switch(op){ case '+': return a + b; case '-': return a - b; case '*': return a * b; case '/': if(b != 0) return a / b; else { cout << "Error: Division by zero!" << endl; exit(1); } } } int main(){ string expression = "34+"; // 示例输入:3 + 4 stack<double> numStack; stringstream ss(expression); string token; while(ss >> token){ if(isdigit(token[0])){ numStack.push(stod(token)); }else{ double operand2 = numStack.top();numStack.pop(); double operand1 = numStack.top();numStack.pop(); char operation = token[0]; double result = calculate(operand1, operand2, operation); numStack.push(result); } } cout << "Result of the postfix expression is : " << numStack.top() << endl; return 0; } ``` 此程序能够读取由空格分隔开来的单字符或多位整数构成的后缀表达式,并对其进行求值输出。注意这里假设所有的输入都是有效的并且至少含有一个二元运算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值