Compare Version Numbers
Given two version strings v1 and v2, compare them. If v1 > v2 return 1; if v1 < v2 return -1; otherwise return 0. Treat missing segments as 0.
- 1 <= v1.length, v2.length <= 500
- v1/v2 consist of digits and '.'
v1 = "1.01", v2 = "1.001"0v1 = "1.0", v2 = "1.0.0"0v1 = "1.10", v2 = "1.2"1A version string like "1.10.2" is a list of numbers separated by dots. Comparing two of them means walking the numbers left to right — the first level that differs decides the whole comparison — and returning 1 if v1 is larger, -1 if smaller, 0 if they are equal. Two rules make this trickier than it first looks, and both come straight from what a version number means: a leading zero inside a level is cosmetic ("01" is just the number 1), and a version that runs out of levels behaves as if the missing levels were 0 ("1.0" and "1" are the same version).
The tempting shortcut is to compare v1 and v2 as plain strings, or to compare each dot-separated level as a string. It breaks immediately. Take "1.10" versus "1.2". Character by character the strings agree on "1." and then compare '1' against '2', so string comparison declares "1.10" the smaller. But version 1.10 comes after 1.2: numerically the second level is 10 versus 2, and 10 is larger. String ordering compares digit characters left to right, which has nothing to do with numeric size once two numbers have different digit counts. Leading zeros wreck it from the other direction: "1.01" and "1.1" are the same version, yet as strings "01" and "1" differ. The conclusion is unambiguous — each level must be compared as an integer, not as text.
So the plan writes itself. Split each version on the dots into its list of levels, turn each level into an integer (which erases leading zeros for free), and compare the two lists position by position. At the first position where the integers differ, we have the answer. If one list is shorter, its missing levels count as 0 — so we compare up to the longer length, reading a 0 whenever a list has run out.
def compare(version1, version2):
a = version1.split(".")
b = version2.split(".")
for i in range(max(len(a), len(b))):
x = int(a[i]) if i < len(a) else 0 # missing level -> 0
y = int(b[i]) if i < len(b) else 0
if x != y:
return 1 if x > y else -1
return 0max(len(a), len(b)), not the shorter length, reading a 0 for any level that has run out. Stopping at the shorter length would call "1.0.1" and "1" equal — it would never look at the trailing 1 that makes the first one larger. Padding the short version with zeros is exactly what makes "trailing zeros don't matter, but a trailing non-zero does" come out right.If you would rather not allocate the split lists, the same logic runs with two pointers that build each level's integer on the fly and reset at every dot — trading the O(N) list memory for O(1) extra space, at the cost of a little more code. The comparison rule is identical.
Now an unequal-length case, v1 = "1.0", v2 = "1.0.0":
- Levels 0 and 1 tie (1 = 1, 0 = 0). At level 2, v1 has run out so we read 0, and v2 has 0 — equal. No difference anywhere, so return 0.
Every character of both strings is read a constant number of times — once to parse, once to skip a dot — so the whole comparison is O(N + M) in the two lengths, with O(1) extra space in the pointer form. The transferable idea is smaller than the algorithms on other pages but appears constantly: when data arrives as text yet means something else — a number, a date, an IP address — compare it in the domain it means, not as raw characters. Here that means parse each level to an integer and pad the shorter list with zeros; the two bugs everyone hits (leading zeros, and mismatched level counts) both vanish the moment you do.
version delta analyzer
Comparing Version 1 ("1.0.1") and Version 2 ("1").