Facebook Pixel

65. Valid Number

HardString
Leetcode Link

Problem Description

Given a string s, you need to determine whether it represents a valid number.

A valid number can be either:

  1. An integer number followed by an optional exponent
  2. A decimal number followed by an optional exponent

The components are defined as follows:

Integer number: An optional sign (+ or -) followed by one or more digits.

Decimal number: An optional sign (+ or -) followed by one of these formats:

  • Digits followed by a dot . (e.g., "3.")
  • Digits followed by a dot . followed by more digits (e.g., "3.14")
  • A dot . followed by digits (e.g., ".5")

Exponent: The letter e or E followed by an integer number (which can have its own sign).

Digits: One or more numeric characters (0-9).

Examples of valid numbers:

  • "2" - simple integer
  • "-0.1" - negative decimal
  • "4." - decimal with trailing dot
  • "-.9" - decimal starting with dot
  • "2e10" - integer with exponent
  • "3e+7" - exponent with explicit positive sign
  • "53.5e93" - decimal with exponent

Examples of invalid numbers:

  • "abc" - contains letters
  • "1e" - exponent without integer
  • "e3" - starts with exponent
  • "99e2.5" - exponent must be integer
  • "--6" - double sign
  • "95a54e53" - contains invalid character

The task is to return true if the string represents a valid number according to these rules, and false otherwise.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To validate whether a string represents a valid number, we need to carefully check if it follows the grammar rules for valid numbers. The key insight is to process the string character by character while tracking what we've seen so far.

Think of it like parsing the string from left to right and checking if each character is valid given what came before it. We need to enforce several rules:

  1. Sign handling: A sign (+ or -) can only appear at the beginning of the number or right after an exponent notation (e or E). We can't have multiple signs or signs in the wrong positions.

  2. Decimal point rules: We can have at most one decimal point, and it must appear before any exponent notation. The tricky part is that a decimal point alone (without any digits) is invalid, but .5 or 5. are both valid.

  3. Exponent rules: The exponent notation (e or E) can appear at most once, and it must be followed by a valid integer (which can have its own sign). The exponent cannot appear at the beginning or end of the string.

  4. Digit requirements: We need at least some digits in the string. Pure signs, dots, or exponent notations without digits are invalid.

The approach is to use a state-based validation: we track whether we've seen a decimal point (dot) and whether we've seen an exponent (e). As we scan through each character, we check if it's valid based on our current state. For example, if we encounter a decimal point but we've already seen one before or we've already seen an exponent, we know the string is invalid.

The special cases to handle are:

  • A string that's just a sign is invalid
  • A decimal point must have digits either before or after it (or both)
  • After an exponent notation, we need a valid integer (potentially with a sign)
  • Any non-numeric character (except valid special characters like ., e, E, +, -) makes the string invalid

Solution Approach

The implementation uses a character-by-character scanning approach with state tracking. Here's how the solution works:

Step 1: Handle the optional sign at the beginning

if s[i] in '+-':
    i += 1

We first check if the string starts with a sign. If it does, we advance the pointer i. If after this step we've reached the end of the string (meaning the string was just a sign), we return false.

Step 2: Special case for decimal point

if s[i] == '.' and (i + 1 == n or s[i + 1] in 'eE'):
    return False

We check if the current position is a decimal point with either nothing after it or immediately followed by an exponent. This handles invalid cases like "." or ".e5".

Step 3: Main scanning loop with state tracking We use two counters:

  • dot: tracks if we've seen a decimal point
  • e: tracks if we've seen an exponent notation

Starting from position j = i, we scan through each remaining character:

Case 1: Decimal point

if s[j] == '.':
    if e or dot:
        return False
    dot += 1

If we encounter a decimal point:

  • Check if we've already seen an exponent (decimal points can't appear after exponents)
  • Check if we've already seen a decimal point (only one allowed)
  • If both checks pass, increment the dot counter

Case 2: Exponent notation

elif s[j] in 'eE':
    if e or j == i or j == n - 1:
        return False
    e += 1
    if s[j + 1] in '+-':
        j += 1
        if j == n - 1:
            return False

If we encounter e or E:

  • Check if we've already seen an exponent (only one allowed)
  • Check if it's at the beginning (need digits before exponent)
  • Check if it's at the end (need integer after exponent)
  • Increment the e counter
  • Check if the next character is a sign (allowed after exponent)
  • If there's a sign, advance the pointer and ensure we're not at the end

Case 3: Non-numeric character

elif not s[j].isnumeric():
    return False

Any character that's not a digit, decimal point, exponent, or properly placed sign is invalid.

Step 4: Return result After scanning all characters without finding any violations, the string represents a valid number, so we return true.

This approach ensures all the grammar rules are enforced: proper sign placement, single decimal point before any exponent, single exponent with valid integer following it, and at least some digits present in the string.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the validation of "-.9e2" step by step:

Initial Setup:

  • String: "-.9e2", length n = 5
  • i = 0 (starting position)

Step 1: Handle optional sign

  • s[0] = '-' is a sign
  • Increment i to 1
  • String is not just a sign (i < n), continue

Step 2: Check special decimal point case

  • s[1] = '.' is a decimal point
  • Check if nothing after or immediately followed by 'e'/'E'
  • s[2] = '9' (not 'e' or 'E'), so this check passes

Step 3: Main scanning loop

  • Initialize: j = 1, dot = 0, e = 0

Iteration 1 (j = 1):

  • s[1] = '.'
  • Is it a decimal? Yes
  • Have we seen an exponent (e > 0)? No
  • Have we seen a decimal (dot > 0)? No
  • Valid! Increment dot to 1
  • j++ → j = 2

Iteration 2 (j = 2):

  • s[2] = '9'
  • Is it numeric? Yes
  • Valid! Continue
  • j++ → j = 3

Iteration 3 (j = 3):

  • s[3] = 'e'
  • Is it an exponent? Yes
  • Have we seen an exponent (e > 0)? No
  • Is it at the beginning (j == i)? No (3 ≠ 1)
  • Is it at the end (j == n-1)? No (3 ≠ 4)
  • Valid! Increment e to 1
  • Check next character: s[4] = '2' (not a sign)
  • Don't advance extra
  • j++ → j = 4

Iteration 4 (j = 4):

  • s[4] = '2'
  • Is it numeric? Yes
  • Valid! Continue
  • j++ → j = 5

Step 4: Loop ends (j = 5 = n)

  • No violations found
  • Return true

The string "-.9e2" represents a valid number: a negative decimal (.9) with exponent (e2).


Now let's see an invalid example: "e3"

Initial Setup:

  • String: "e3", length n = 2
  • i = 0

Step 1: Handle optional sign

  • s[0] = 'e' is not a sign
  • i remains 0

Step 2: Check special decimal point case

  • s[0] = 'e' is not a decimal point
  • Skip this check

Step 3: Main scanning loop

  • Initialize: j = 0, dot = 0, e = 0

Iteration 1 (j = 0):

  • s[0] = 'e'
  • Is it an exponent? Yes
  • Check conditions:
    • Have we seen an exponent (e > 0)? No
    • Is it at the beginning (j == i)? Yes (0 == 0) ❌
  • This violates the rule: exponent cannot be at the beginning
  • Return false

The string "e3" is invalid because it starts with an exponent notation without any preceding digits.

Solution Implementation

1class Solution:
2    def isNumber(self, s: str) -> bool:
3        """
4        Validates if a string represents a valid number.
5        Valid numbers include integers, decimals, and scientific notation.
6      
7        Args:
8            s: Input string to validate
9          
10        Returns:
11            bool: True if string is a valid number, False otherwise
12        """
13        n = len(s)
14        index = 0
15      
16        # Check for optional sign at the beginning
17        if s[index] in '+-':
18            index += 1
19      
20        # String cannot be just a sign
21        if index == n:
22            return False
23      
24        # Check for invalid cases: lone decimal point or decimal point followed by e/E
25        if s[index] == '.' and (index + 1 == n or s[index + 1] in 'eE'):
26            return False
27      
28        # Initialize flags for decimal point and exponent
29        has_decimal = 0
30        has_exponent = 0
31        current_index = index
32      
33        # Parse the rest of the string
34        while current_index < n:
35            if s[current_index] == '.':
36                # Decimal point cannot appear after exponent or if already present
37                if has_exponent or has_decimal:
38                    return False
39                has_decimal += 1
40              
41            elif s[current_index] in 'eE':
42                # Check for invalid exponent placement
43                # Cannot have multiple exponents, at the start, or at the end
44                if has_exponent or current_index == index or current_index == n - 1:
45                    return False
46                has_exponent += 1
47              
48                # Check for optional sign after exponent
49                if s[current_index + 1] in '+-':
50                    current_index += 1
51                    # Sign cannot be the last character
52                    if current_index == n - 1:
53                        return False
54                      
55            elif not s[current_index].isnumeric():
56                # Any non-numeric character (except . and e/E) is invalid
57                return False
58              
59            current_index += 1
60          
61        return True
62
1class Solution {
2    public boolean isNumber(String s) {
3        int length = s.length();
4        int index = 0;
5      
6        // Check for optional sign at the beginning
7        if (s.charAt(index) == '+' || s.charAt(index) == '-') {
8            index++;
9        }
10      
11        // If string ends after sign, it's invalid
12        if (index == length) {
13            return false;
14        }
15      
16        // Check for invalid decimal point cases:
17        // - Decimal point at the end of string
18        // - Decimal point immediately followed by 'e' or 'E'
19        if (s.charAt(index) == '.' && 
20            (index + 1 == length || s.charAt(index + 1) == 'e' || s.charAt(index + 1) == 'E')) {
21            return false;
22        }
23      
24        // Track occurrences of decimal point and exponent
25        int decimalCount = 0;
26        int exponentCount = 0;
27      
28        // Iterate through the remaining characters
29        for (int currentIndex = index; currentIndex < length; currentIndex++) {
30            char currentChar = s.charAt(currentIndex);
31          
32            if (currentChar == '.') {
33                // Decimal point is invalid after exponent or if there's already a decimal
34                if (exponentCount > 0 || decimalCount > 0) {
35                    return false;
36                }
37                decimalCount++;
38            } else if (currentChar == 'e' || currentChar == 'E') {
39                // Exponent is invalid if:
40                // - There's already an exponent
41                // - It's at the beginning (right after sign)
42                // - It's at the end of string
43                if (exponentCount > 0 || currentIndex == index || currentIndex == length - 1) {
44                    return false;
45                }
46                exponentCount++;
47              
48                // Check for optional sign after exponent
49                if (s.charAt(currentIndex + 1) == '+' || s.charAt(currentIndex + 1) == '-') {
50                    currentIndex++;
51                    // If sign is the last character, it's invalid
52                    if (currentIndex == length - 1) {
53                        return false;
54                    }
55                }
56            } else if (currentChar < '0' || currentChar > '9') {
57                // Any non-digit character (except handled cases) is invalid
58                return false;
59            }
60        }
61      
62        return true;
63    }
64}
65
1class Solution {
2public:
3    bool isNumber(string s) {
4        int length = s.size();
5        int index = 0;
6      
7        // Handle optional leading sign (+/-)
8        if (s[index] == '+' || s[index] == '-') {
9            ++index;
10        }
11      
12        // String cannot be just a sign
13        if (index == length) {
14            return false;
15        }
16      
17        // Check if string is just a decimal point or decimal point followed by e/E
18        if (s[index] == '.' && (index + 1 == length || s[index + 1] == 'e' || s[index + 1] == 'E')) {
19            return false;
20        }
21      
22        int decimalCount = 0;  // Count of decimal points encountered
23        int exponentCount = 0; // Count of exponent markers (e/E) encountered
24      
25        // Iterate through the remaining characters
26        for (int j = index; j < length; ++j) {
27            if (s[j] == '.') {
28                // Decimal point cannot appear after exponent or if already encountered
29                if (exponentCount || decimalCount) {
30                    return false;
31                }
32                ++decimalCount;
33            } 
34            else if (s[j] == 'e' || s[j] == 'E') {
35                // Exponent cannot be repeated, at the start, or at the end
36                if (exponentCount || j == index || j == length - 1) {
37                    return false;
38                }
39                ++exponentCount;
40              
41                // Handle optional sign after exponent
42                if (s[j + 1] == '+' || s[j + 1] == '-') {
43                    ++j; // Skip the sign
44                    // Sign cannot be the last character
45                    if (j == length - 1) {
46                        return false;
47                    }
48                }
49            } 
50            else if (s[j] < '0' || s[j] > '9') {
51                // Invalid character (not a digit)
52                return false;
53            }
54        }
55      
56        return true;
57    }
58};
59
1function isNumber(s: string): boolean {
2    const length = s.length;
3    let index = 0;
4  
5    // Handle optional leading sign (+/-)
6    if (s[index] === '+' || s[index] === '-') {
7        index++;
8    }
9  
10    // String cannot be just a sign
11    if (index === length) {
12        return false;
13    }
14  
15    // Check if string is just a decimal point or decimal point followed by e/E
16    if (s[index] === '.' && (index + 1 === length || s[index + 1] === 'e' || s[index + 1] === 'E')) {
17        return false;
18    }
19  
20    let decimalCount = 0;  // Count of decimal points encountered
21    let exponentCount = 0; // Count of exponent markers (e/E) encountered
22  
23    // Iterate through the remaining characters
24    for (let j = index; j < length; j++) {
25        if (s[j] === '.') {
26            // Decimal point cannot appear after exponent or if already encountered
27            if (exponentCount || decimalCount) {
28                return false;
29            }
30            decimalCount++;
31        } 
32        else if (s[j] === 'e' || s[j] === 'E') {
33            // Exponent cannot be repeated, at the start, or at the end
34            if (exponentCount || j === index || j === length - 1) {
35                return false;
36            }
37            exponentCount++;
38          
39            // Handle optional sign after exponent
40            if (s[j + 1] === '+' || s[j + 1] === '-') {
41                j++; // Skip the sign
42                // Sign cannot be the last character
43                if (j === length - 1) {
44                    return false;
45                }
46            }
47        } 
48        else if (s[j] < '0' || s[j] > '9') {
49            // Invalid character (not a digit)
50            return false;
51        }
52    }
53  
54    return true;
55}
56

Time and Space Complexity

Time Complexity: O(n)

The algorithm iterates through the string once using a while loop that goes from index j = i to n. Each character is visited at most once during this iteration. Inside the loop, all operations are constant time:

  • Checking if a character is ., e, or E: O(1)
  • Checking if a character is numeric using isnumeric(): O(1)
  • Incrementing counters and indices: O(1)

Even when we encounter e or E and potentially skip one additional character for the sign (+ or -), each character is still processed at most once. Therefore, the overall time complexity is O(n), where n is the length of the string.

Space Complexity: O(1)

The algorithm uses only a fixed amount of extra space:

  • Integer variables: n, i, j, dot, e
  • No additional data structures that grow with input size

All variables use constant space regardless of the input string length, resulting in O(1) space complexity.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Not Requiring Digits Before or After Decimal Point

The Problem: The current implementation doesn't ensure that there are actual digits present in the number. It would incorrectly accept strings like ".", "+.", or "e5" as valid numbers, when they should be rejected because they lack the required digits.

Example of Failure:

  • Input: "." - Just a decimal point with no digits
  • Current code behavior: Would pass the initial decimal point check only if followed by nothing or e/E, but doesn't verify digits exist elsewhere
  • Expected: false
  • Current result might be: true (depending on implementation details)

Solution: Add a flag to track whether we've seen at least one digit:

def isNumber(self, s: str) -> bool:
    n = len(s)
    index = 0
  
    # Check for optional sign at the beginning
    if s[index] in '+-':
        index += 1
  
    if index == n:
        return False
  
    # Track if we've seen required components
    has_decimal = False
    has_exponent = False
    has_digit = False  # NEW: Track if we've seen any digit
  
    current_index = index
  
    while current_index < n:
        if s[current_index].isnumeric():
            has_digit = True  # Mark that we've seen a digit
          
        elif s[current_index] == '.':
            if has_exponent or has_decimal:
                return False
            has_decimal = True
          
        elif s[current_index] in 'eE':
            # Must have seen at least one digit before exponent
            if not has_digit or has_exponent or current_index == n - 1:
                return False
            has_exponent = True
          
            # Reset digit flag for the exponent part
            has_digit = False
          
            # Handle optional sign after exponent
            if current_index + 1 < n and s[current_index + 1] in '+-':
                current_index += 1
                if current_index == n - 1:
                    return False
                  
        else:
            return False
          
        current_index += 1
  
    # Must have seen at least one digit (either in mantissa or after exponent)
    return has_digit

Pitfall 2: Incorrect Validation of Decimal-Only Numbers

The Problem: The special case check if s[index] == '.' and (index + 1 == n or s[index + 1] in 'eE') is too restrictive. It would reject valid numbers like "4." (decimal with trailing dot) while trying to catch invalid cases.

Example of Failure:

  • Input: "4." - Valid decimal number with trailing dot
  • Current code behavior: If the string is exactly "4.", the check wouldn't trigger (since there's a digit before the dot), but the logic might not properly handle this edge case
  • Input: ".5" - Valid decimal starting with dot
  • The initial check might incorrectly flag this as invalid

Solution: Remove the overly restrictive initial check and rely on proper digit validation:

def isNumber(self, s: str) -> bool:
    n = len(s)
    index = 0
  
    if s[index] in '+-':
        index += 1
  
    if index == n:
        return False
  
    has_decimal = False
    has_exponent = False
    has_digit_before_e = False  # Digits before exponent
    has_digit_after_e = False   # Digits after exponent
  
    current_index = index
  
    while current_index < n:
        if s[current_index].isnumeric():
            if has_exponent:
                has_digit_after_e = True
            else:
                has_digit_before_e = True
              
        elif s[current_index] == '.':
            if has_exponent or has_decimal:
                return False
            has_decimal = True
          
        elif s[current_index] in 'eE':
            if has_exponent or not has_digit_before_e:
                return False
            has_exponent = True
          
            if current_index + 1 < n and s[current_index + 1] in '+-':
                current_index += 1
          
            if current_index == n - 1:
                return False
              
        else:
            return False
          
        current_index += 1
  
    # If we have exponent, must have digits after it
    # Otherwise, just need digits before it
    return has_digit_after_e if has_exponent else has_digit_before_e

This approach properly validates that:

  1. There are digits in the mantissa (before any exponent)
  2. If an exponent exists, there are digits after it
  3. Decimal points can appear with digits on either or both sides
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More