【LeetCode 每日一题】165. 比较版本号
Problem: 165. 比较版本号
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N + M)
- 空间复杂度:O(N + M)
整体思路
这段代码的目的是比较两个版本号字符串 version1
和 version2
的大小。版本号由点 .
分隔的非负整数修订号组成(例如 “1.0.1”, “7.5.2.4”)。比较规则是从左到右逐个比较修订号,直到找到第一个不同的修订号,或者比较完所有修订号。
该算法采用了一种非常清晰和健壮的策略:分割 -> 逐段比较 -> 补零处理。
-
第一步:分割版本号
String[] vs1 = version1.split("\\.");
String[] vs2 = version2.split("\\.");
- 算法首先使用
split("\\.")
方法,将两个版本号字符串按照点.
分割成字符串数组。"\\."
是因为.
在正则表达式中是特殊字符,需要转义。 vs1
和vs2
分别存储了两个版本号的所有修订号(以字符串形式)。
-
第二步:同步遍历与比较
for (int i = 0; i < n || i < m; i++)
: 这是算法的核心。它使用一个for
循环来同步地从左到右遍历两个修订号数组。- 循环条件
i < n || i < m
非常关键。它确保了循环会一直持续到两个版本号中较长的那个也完全被处理完为止。这优雅地处理了版本号长度不等的情况(例如,比较 “1.0” 和 “1.0.1”)。
-
第三步:处理不等长与补零
- 在循环内部,需要获取第
i
个位置的修订号进行比较。 int v1 = i < n ? Integer.parseInt(vs1[i]) : 0;
int v2 = i < m ? Integer.parseInt(vs2[i]) : 0;
- 这两行代码巧妙地处理了当一个版本号比另一个短时的情况。
- 如果索引
i
在数组的有效范围内(i < n
或i < m
),就正常地解析该位置的修订号字符串为整数。 - 如果索引
i
超出了某个数组的范围(例如,i >= n
),就认为该位置的修订号是0
。这符合版本号的比较规则,例如 “1.0” 和 “1.0.0” 是相等的。
- 在循环内部,需要获取第
-
第四步:比较并提前返回
if (v1 != v2)
: 在获取了两个整数形式的修订号v1
和v2
之后,立即进行比较。- 如果它们不相等,说明已经找到了决定版本大小的第一个差异点。
return v1 < v2 ? -1 : 1;
: 根据v1
和v2
的大小关系,直接返回-1
(version1 < version2) 或1
(version1 > version2),并终止整个函数。
-
第五步:处理相等情况
- 如果
for
循环正常结束而没有触发任何return
,说明在遍历的所有位置上,两个版本号的修订号都完全相同(包括补零的部分)。 - 这意味着两个版本号是相等的,因此最后返回
0
。
- 如果
完整代码
class Solution {/*** 比较两个版本号字符串的大小。* @param version1 第一个版本号字符串* @param version2 第二个版本号字符串* @return 如果 version1 < version2, 返回 -1;* 如果 version1 > version2, 返回 1;* 如果 version1 == version2, 返回 0。*/public int compareVersion(String version1, String version2) {// 步骤 1: 将版本号字符串按 '.' 分割成修订号数组String[] vs1 = version1.split("\\.");String[] vs2 = version2.split("\\.");int n = vs1.length;int m = vs2.length;// 步骤 2: 同步遍历两个修订号数组,直到最长的那个结束for (int i = 0; i < n || i < m; i++) {// 步骤 3: 获取当前位置的修订号,对短的版本号进行“补零”处理// 如果索引 i 超出数组范围,则视该修订号为 0int v1 = i < n ? Integer.parseInt(vs1[i]) : 0;int v2 = i < m ? Integer.parseInt(vs2[i]) : 0;// 步骤 4: 比较修订号if (v1 != v2) {// 如果发现差异,立即返回比较结果return v1 < v2 ? -1 : 1;}}// 步骤 5: 如果循环结束都没有发现差异,说明版本号相等return 0;}
}
时空复杂度
- 设
N
是version1
的长度,M
是version2
的长度。 - 设
P1
是version1
中的修订号数量,P2
是version2
中的修订号数量。
时间复杂度:O(N + M)
- 分割字符串:
version1.split("\\.")
需要遍历整个version1
字符串,时间复杂度为 O(N)。version2.split("\\.")
需要遍历整个version2
字符串,时间复杂度为 O(M)。
- 遍历与比较:
for
循环的执行次数是max(P1, P2)
。- 在循环内部,
Integer.parseInt()
的时间复杂度与修订号字符串的长度成正比。 - 所有
parseInt
操作的总时间,加起来等于对所有修订号数字字符的遍历,这与原始字符串version1
和version2
的总长度相关。 - 因此,整个循环部分的时间复杂度也可以认为是 O(N + M)。
综合分析:
总的时间复杂度由字符串分割和循环解析共同决定,为 O(N) + O(M) + O(N+M),最终简化为 O(N + M)。
空间复杂度:O(N + M)
- 主要存储开销:算法创建了两个字符串数组
vs1
和vs2
来存储分割后的修订号。 vs1
数组:存储了version1
的所有修订号。这些字符串的总长度加上数组本身的开销,空间复杂度为 O(N)。vs2
数组:同理,空间复杂度为 O(M)。
综合分析:
算法所需的额外空间主要由这两个新创建的数组决定。因此,总的空间复杂度为 O(N + M)。