Facebook Pixel

165. Compare Version Numbers

Problem Description

You need to compare two version strings (version1 and version2) and determine their relative ordering.

A version string consists of revision numbers separated by dots (.). For example: "1.2.3" has three revisions: 1, 2, and 3.

Each revision should be treated as an integer, ignoring any leading zeros. For instance, "01" should be treated as 1, and "001" should also be treated as 1.

To compare the two version strings:

  1. Compare revision values from left to right, one revision at a time
  2. If one version string has fewer revisions than the other, treat the missing revisions as 0
  3. The comparison stops as soon as a difference is found

The function should return:

  • -1 if version1 is less than version2
  • 1 if version1 is greater than version2
  • 0 if both versions are equal

Example scenarios:

  • Comparing "1.2" with "1.2.0" would return 0 (equal, since missing revision is treated as 0)
  • Comparing "1.01" with "1.001" would return 0 (equal, since leading zeros are ignored)
  • Comparing "1.0" with "1.0.1" would return -1 (first is smaller)
  • Comparing "2.5" with "2.3" would return 1 (first is larger)
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to process both version strings simultaneously, comparing revision by revision. Since revisions are separated by dots, we can parse each revision on-the-fly as we traverse the strings.

Think of it like reading two numbers digit by digit, but instead of single digits, we're reading complete revision numbers between dots. When we encounter a dot or reach the end of a string, we know we've completed reading one revision.

The challenge is handling versions with different numbers of revisions. For example, when comparing "1.2" with "1.2.3", after processing the first two revisions, one string is exhausted while the other still has revisions left. The natural approach is to continue processing and treat any missing revision as 0.

To parse each revision while ignoring leading zeros, we can build the number character by character using the formula: number = number * 10 + current_digit. This automatically handles leading zeros because 0 * 10 + 5 = 5, effectively ignoring the leading zero.

Using two pointers (i and j) allows us to independently track our position in each version string. We continue the comparison process as long as at least one string has characters remaining to process. At each step:

  1. Extract the next revision from both strings (or 0 if one string is exhausted)
  2. Compare these revisions
  3. If they differ, we immediately know which version is larger
  4. If they're equal, move to the next revision

This approach avoids the need to split the strings or preprocess them into arrays, making it memory-efficient and allowing us to return early as soon as we find a difference.

Solution Approach

The implementation uses a two-pointer approach to traverse both version strings simultaneously. Here's how the algorithm works:

Initialize pointers and lengths:

  • Set i = 0 and j = 0 as pointers for version1 and version2 respectively
  • Store the lengths m = len(version1) and n = len(version2)

Main comparison loop: The outer loop continues while either string has unprocessed characters (i < m or j < n):

  1. Extract current revisions:

    • Initialize a = 0 and b = 0 to store the current revision values
    • For version1: While i < m and the current character isn't a dot, build the revision number using a = a * 10 + int(version1[i]), then increment i
    • For version2: Similarly, build revision b using b = b * 10 + int(version2[j]), then increment j

    This multiplication technique naturally handles leading zeros. For example, parsing "01" gives: 0 * 10 + 1 = 1

  2. Compare revisions:

    • If a != b, we've found a difference:
      • Return -1 if a < b (version1 is smaller)
      • Return 1 if a > b (version1 is larger)
  3. Move to next revision:

    • Increment both pointers by 1 to skip the dot separator: i = i + 1 and j = j + 1
    • If a pointer is already at or beyond the string length, it stays there, and subsequent revisions will be read as 0

Return equal: If the loop completes without finding any differences, the versions are equal, so return 0.

Time Complexity: O(max(m, n)) where m and n are the lengths of the version strings, as we traverse each string at most once.

Space Complexity: O(1) as we only use a constant amount of extra space for variables.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through comparing version1 = "1.2.3" and version2 = "1.10":

Initial Setup:

  • i = 0, j = 0
  • m = 5 (length of "1.2.3")
  • n = 4 (length of "1.10")

Iteration 1: Compare first revisions

  • Extract revision from version1:
    • Start with a = 0
    • Read '1': a = 0 * 10 + 1 = 1
    • Stop at '.', so i = 1
  • Extract revision from version2:
    • Start with b = 0
    • Read '1': b = 0 * 10 + 1 = 1
    • Stop at '.', so j = 1
  • Compare: a = 1, b = 1 β†’ Equal, continue
  • Skip dots: i = 2, j = 2

Iteration 2: Compare second revisions

  • Extract revision from version1:
    • Start with a = 0
    • Read '2': a = 0 * 10 + 2 = 2
    • Stop at '.', so i = 3
  • Extract revision from version2:
    • Start with b = 0
    • Read '1': b = 0 * 10 + 1 = 1
    • Read '0': b = 1 * 10 + 0 = 10
    • Reached end of string, so j = 4
  • Compare: a = 2, b = 10 β†’ 2 < 10
  • Return -1 (version1 is smaller)

The algorithm correctly identifies that "1.2.3" < "1.10" because when comparing the second revision, 2 is less than 10.

Solution Implementation

1class Solution:
2    def compareVersion(self, version1: str, version2: str) -> int:
3        """
4        Compare two version strings and return:
5        -1 if version1 < version2
6        0 if version1 == version2
7        1 if version1 > version2
8        """
9        # Get lengths of both version strings
10        len1, len2 = len(version1), len(version2)
11      
12        # Initialize pointers for traversing both version strings
13        ptr1 = ptr2 = 0
14      
15        # Continue comparing while at least one version has remaining segments
16        while ptr1 < len1 or ptr2 < len2:
17            # Initialize revision numbers for current segment
18            revision1 = revision2 = 0
19          
20            # Parse the current revision number from version1
21            # Continue until we hit a dot or reach the end
22            while ptr1 < len1 and version1[ptr1] != '.':
23                revision1 = revision1 * 10 + int(version1[ptr1])
24                ptr1 += 1
25          
26            # Parse the current revision number from version2
27            # Continue until we hit a dot or reach the end
28            while ptr2 < len2 and version2[ptr2] != '.':
29                revision2 = revision2 * 10 + int(version2[ptr2])
30                ptr2 += 1
31          
32            # Compare current revision numbers
33            if revision1 != revision2:
34                return -1 if revision1 < revision2 else 1
35          
36            # Skip the dot separator and move to next segment
37            ptr1 += 1
38            ptr2 += 1
39      
40        # All revision segments are equal
41        return 0
42
1class Solution {
2    /**
3     * Compares two version strings.
4     * @param version1 The first version string (e.g., "1.2.3")
5     * @param version2 The second version string (e.g., "1.2.4")
6     * @return -1 if version1 < version2, 1 if version1 > version2, 0 if equal
7     */
8    public int compareVersion(String version1, String version2) {
9        int version1Length = version1.length();
10        int version2Length = version2.length();
11      
12        int index1 = 0;
13        int index2 = 0;
14      
15        // Process both version strings simultaneously
16        while (index1 < version1Length || index2 < version2Length) {
17            // Parse the current revision number from version1
18            int revision1 = 0;
19            while (index1 < version1Length && version1.charAt(index1) != '.') {
20                revision1 = revision1 * 10 + (version1.charAt(index1) - '0');
21                index1++;
22            }
23          
24            // Parse the current revision number from version2
25            int revision2 = 0;
26            while (index2 < version2Length && version2.charAt(index2) != '.') {
27                revision2 = revision2 * 10 + (version2.charAt(index2) - '0');
28                index2++;
29            }
30          
31            // Compare the current revision numbers
32            if (revision1 != revision2) {
33                return revision1 < revision2 ? -1 : 1;
34            }
35          
36            // Skip the dot separator for next iteration
37            index1++;
38            index2++;
39        }
40      
41        // All revisions are equal
42        return 0;
43    }
44}
45
1class Solution {
2public:
3    int compareVersion(string version1, string version2) {
4        int version1Length = version1.size();
5        int version2Length = version2.size();
6      
7        int index1 = 0;
8        int index2 = 0;
9      
10        // Process both version strings until we reach the end of both
11        while (index1 < version1Length || index2 < version2Length) {
12            // Parse the next revision number from version1
13            int revision1 = 0;
14            while (index1 < version1Length && version1[index1] != '.') {
15                revision1 = revision1 * 10 + (version1[index1] - '0');
16                index1++;
17            }
18          
19            // Parse the next revision number from version2
20            int revision2 = 0;
21            while (index2 < version2Length && version2[index2] != '.') {
22                revision2 = revision2 * 10 + (version2[index2] - '0');
23                index2++;
24            }
25          
26            // Compare the current revision numbers
27            if (revision1 < revision2) {
28                return -1;
29            }
30            if (revision1 > revision2) {
31                return 1;
32            }
33          
34            // Skip the dot separator if present
35            if (index1 < version1Length) {
36                index1++;  // Skip '.'
37            }
38            if (index2 < version2Length) {
39                index2++;  // Skip '.'
40            }
41        }
42      
43        // All revisions are equal
44        return 0;
45    }
46};
47
1/**
2 * Compares two version strings and returns their relative order
3 * @param version1 - First version string (e.g., "1.2.3")
4 * @param version2 - Second version string (e.g., "1.2.4")
5 * @returns -1 if version1 < version2, 1 if version1 > version2, 0 if equal
6 */
7function compareVersion(version1: string, version2: string): number {
8    // Split version strings into arrays of revision numbers
9    const versionParts1: string[] = version1.split('.');
10    const versionParts2: string[] = version2.split('.');
11  
12    // Determine the maximum length between both version arrays
13    const maxLength: number = Math.max(versionParts1.length, versionParts2.length);
14  
15    // Compare each revision number from left to right
16    for (let index: number = 0; index < maxLength; index++) {
17        // Convert string to number, treating undefined/missing parts as 0
18        const revisionNumber1: number = Number(versionParts1[index]) || 0;
19        const revisionNumber2: number = Number(versionParts2[index]) || 0;
20      
21        // If current revision of version1 is smaller, version1 is earlier
22        if (revisionNumber1 < revisionNumber2) {
23            return -1;
24        }
25      
26        // If current revision of version1 is larger, version1 is later
27        if (revisionNumber1 > revisionNumber2) {
28            return 1;
29        }
30      
31        // If equal, continue to next revision
32    }
33  
34    // All revisions are equal, versions are the same
35    return 0;
36}
37

Time and Space Complexity

Time Complexity: O(max(m, n)) where m is the length of version1 and n is the length of version2.

The algorithm uses two pointers to traverse through both version strings simultaneously. In the worst case, we need to traverse through every character of both strings exactly once. The outer while loop continues until both pointers reach the end of their respective strings. The inner while loops parse individual revision numbers between dots, but each character is still only visited once throughout the entire execution. Therefore, the total time complexity is linear with respect to the combined length of both input strings.

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space. The variables used are:

  • Two integer pointers i and j for traversing the strings
  • Two integer variables a and b for storing the current revision numbers being compared
  • Two integer variables m and n for storing the lengths of the input strings

Since we don't create any additional data structures that grow with the input size, and we're parsing the version numbers on-the-fly without storing them, the space complexity is constant.

Common Pitfalls

1. Incorrectly Handling Leading Zeros

A common mistake is trying to strip leading zeros as a preprocessing step or using string comparison directly. This can lead to incorrect parsing or comparison logic.

Pitfall Example:

# Wrong approach - comparing strings directly
if version1[i:next_dot] < version2[j:next_dot]:  # "10" < "9" gives wrong result
    return -1

Solution: Convert each revision segment to an integer during parsing. The multiplication method (revision = revision * 10 + int(char)) naturally handles leading zeros without special cases.

2. Not Handling Different Version Lengths

Failing to properly handle versions with different numbers of revisions, especially when one version has trailing zeros that should be ignored.

Pitfall Example:

# Wrong - stops when shorter version ends
while ptr1 < len1 and ptr2 < len2:  # Misses remaining revisions
    # comparison logic

Solution: Use or instead of and in the while condition: while ptr1 < len1 or ptr2 < len2. This ensures all revisions are processed, with missing revisions defaulting to 0.

3. Pointer Management After Parsing

Forgetting to skip the dot separator or incorrectly incrementing pointers can cause infinite loops or skipped revisions.

Pitfall Example:

# Wrong - might increment past string bounds without checking
while version1[ptr1] != '.':  # No bounds check
    revision1 = revision1 * 10 + int(version1[ptr1])
    ptr1 += 1
ptr1 += 1  # Could go beyond string length unexpectedly

Solution: Always check bounds before accessing characters: while ptr1 < len1 and version1[ptr1] != '.'. The final increment (ptr1 += 1) after the inner loops is safe because subsequent iterations check ptr1 < len1.

4. Using Split Method Incorrectly

While using split('.') seems simpler, it can lead to memory inefficiency for very long version strings or issues with edge cases.

Pitfall Example:

# Potentially inefficient for very long strings
revisions1 = version1.split('.')
revisions2 = version2.split('.')
# Need to pad shorter list or handle index out of bounds

Solution: The two-pointer approach avoids creating additional arrays and handles different lengths naturally by treating missing revisions as 0 without explicit padding.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which two pointer techniques do you use to check if a string is a palindrome?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More