65. Valid Number
Problem Description
Given a string s
, you need to determine whether it represents a valid number.
A valid number can be either:
- An integer number followed by an optional exponent
- 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.
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:
-
Sign handling: A sign (
+
or-
) can only appear at the beginning of the number or right after an exponent notation (e
orE
). We can't have multiple signs or signs in the wrong positions. -
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
or5.
are both valid. -
Exponent rules: The exponent notation (
e
orE
) 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. -
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 pointe
: 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 EvaluatorExample 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
, orE
: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:
- There are digits in the mantissa (before any exponent)
- If an exponent exists, there are digits after it
- Decimal points can appear with digits on either or both sides
How many times is a tree node visited in a depth first search?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!