题目## 题目
题目的主要信息:
- 给出2个版本号version1和version2,比较它们的大小
- 版本号是由修订号组成,修订号与修订号之间由一个"."连接
- 修订号可能有前导0,按从左到右的顺序依次比较它们的修订号,比较修订号时,只需比较忽略任何前导零后的整数值
- 如果版本号没有指定某个下标处的修订号,则该修订号视为0
- 版本号中每一节可能超过int的表达范围
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:双指针遍历截取(推荐使用)
知识点:双指针
双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。
思路:
既然是比较两个字符串每个点之间的数字是否相同,就直接同时遍历字符串比较,因此我们需要使用两个同向访问的指针各自访问一个字符串。
比较的时候,数字前导零不便于我们比较,因为我们不知道后面会出现多少前导零,因此应该将点之间的部分转化为数字再比较才方便。
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是否能完全匹配
举一反三:
学习完本题的思路你可以解决如下题目:
方法:动态规划(推荐使用)
知识点:动态规划
动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果
思路:
如果是只有小写字母,那么直接比较字符是否相同即可匹配,如果再多一个’.‘,可以用它匹配任意字符,只要对应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][i−2]。
-
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[i−1][j−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[i−1][j]∣∣dp[i][j−2]- 不满足上述条件,只能不匹配,让前一个字符出现0次, d p [ i ] [ j ] = d p [ i ] [ j − 2 ] dp[i][j] = dp[i][j - 2] dp[i][j]=dp[i][j−2].
图示:

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的空间
922

被折叠的 条评论
为什么被折叠?



