Valid Number
HardPrompt
Given a string s, return whether s is a valid number.
For example, all the following are valid numbers: "2", "0089", "-0.1", "+3.14", "4.", "-.9", "2e10", "-90E3", "3e+7", "+6e-1", "53.5e93", "-123.456e789", while the following are not valid numbers: "abc", "1a", "1e", "e3", "99e2.5", "--6", "-+3", "95a54e53".
Formally, a valid number is defined using one of the following definitions:
- An integer number followed by an optional exponent.
- A decimal number followed by an optional exponent.
An integer number is defined with an optional sign '-' or '+' followed by digits.
A decimal number is defined with an optional sign '-' or '+' followed by one of the following definitions:
- Digits followed by a dot
'.'. - Digits followed by a dot
'.'followed by digits. - A dot
'.'followed by digits.
An exponent is defined with an exponent notation 'e' or 'E' followed by an integer number.
The digits are defined as one or more digits.
Example 1:
Input: s = "0"
Output: true
Example 2:
Input: s = "e"
Output: false
Example 3:
Input: s = "."
Output: false
Constraints:
1 <= s.length <= 20sconsists of only English letters (both uppercase and lowercase), digits (0-9), plus'+', minus'-', or dot'.'.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a single regular expression to validate the entire string. Regular expressions provide a powerful and concise way to define and match string patterns, making the code short and declarative.
Algorithm
- The core idea is to construct a single regular expression that encapsulates all the rules for a valid number.
- The regex is built by defining patterns for each component of a valid number: an optional sign, the mantissa (integer or decimal part), and an optional exponent.
- The components are combined, and anchored with
^and$to ensure the entire string must match the pattern. - The final regex is
^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$. - The
String.matches()method in Java can be used to test the input string against this regex.
Walkthrough
A valid number can be broken down into three parts: an optional sign, a number part (mantissa), and an optional exponent part. We can construct a regex that captures all these rules.
- Overall Structure:
[sign][mantissa][exponent] - Sign:
[+-]?- An optional+or-at the beginning. - Mantissa:
(\d+(\.\d*)?|\.\d+)- This is the most complex part. It ensures there's at least one digit. It matches either:\d+(\.\d*)?: One or more digits, optionally followed by a dot and zero or more digits. This covers cases like"2","2.", and"2.3".|: OR\.\d+: A dot followed by one or more digits. This covers cases like".3".
- Exponent:
([eE][+-]?\d+)?- This matches the optional exponent part. It looks foreorE, followed by an optional sign, and then one or more digits. The?at the end makes the whole group optional.
Combining these parts and anchoring them with ^ (start of string) and $ (end of string) gives the final regex.
class Solution { public boolean isNumber(String s) { // The regex is constructed to match the formal definition of a valid number. // ^[+-]? # optional sign at the start // ( // \d+(\.\d*)? # a number with an optional decimal part (e.g., "2", "2.", "2.5") // | // \.\d+ # a decimal part without an integer part (e.g., ".5") // ) // ([eE][+-]?\d+)? # an optional exponent part (e.g., "e-10") // $ String regex = "^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?$"; return s.matches(regex); }}Complexity
Time
O(N)
Space
O(1)
Trade-offs
Pros
Very concise and often a one-line solution.
Highly readable for developers familiar with regular expressions.
Leverages powerful, optimized, and built-in features of the language.
Cons
Regex patterns can be complex to write, read, and debug, especially for intricate rules.
The performance might be slightly slower than a manual parser due to the overhead of the regex engine, although the asymptotic complexity is the same.
This approach involves a single, manual pass through the string, using several boolean flags to keep track of the components of a number that have been encountered (e.g., a digit, a decimal point, an exponent symbol). This method avoids the overhead of a regex engine and provides maximum performance and control.
Algorithm
- Initialize boolean flags
digitSeen,dotSeen, andeSeentofalse. - Iterate through the string character by character from left to right.
- For each character, apply a set of rules based on the current character and the flags:
- Digit: Set
digitSeentotrue. - Dot
.: Returnfalseif adotorehas already been seen. Otherwise, setdotSeentotrue. - Exponent
eorE: Returnfalseif anehas already been seen or if no digit has been seen before it. Otherwise, seteSeentotrueand resetdigitSeentofalse(to enforce that the exponent part must have digits). - Sign
+or-: Returnfalseif it's not at the beginning of the string and does not immediately follow aneorE. - Other characters: Return
falseimmediately.
- Digit: Set
- After the loop finishes, return the final value of
digitSeen. This final check correctly handles cases like"1e",".", or"+", which are invalid because they lack a required digit part.
Walkthrough
This implementation is a form of a state machine where the flags (digitSeen, dotSeen, eSeen) implicitly define the current state of the parsing process. We scan the string once, character by character, and validate the string's structure on the fly.
The logic ensures that all rules are followed. For instance, a dot . cannot appear after an e or another dot. An e must be preceded by a number and cannot appear more than once. A sign + or - is only allowed at the very beginning or immediately after an e. The final check on digitSeen is crucial to validate that the number or exponent part actually contains digits.
class Solution { public boolean isNumber(String s) { boolean digitSeen = false; boolean dotSeen = false; boolean eSeen = false; int n = s.length(); for (int i = 0; i < n; i++) { char c = s.charAt(i); if (Character.isDigit(c)) { digitSeen = true; } else if (c == '.') { if (dotSeen || eSeen) { // A dot cannot appear after another dot or after 'e' return false; } dotSeen = true; } else if (c == 'e' || c == 'E') { if (eSeen || !digitSeen) { // 'e' cannot appear after another 'e' or without a preceding number return false; } eSeen = true; digitSeen = false; // Reset digitSeen for the exponent part } else if (c == '+' || c == '-') { if (i != 0 && s.charAt(i - 1) != 'e' && s.charAt(i - 1) != 'E') { // A sign must be at the start or immediately follow 'e' return false; } } else { // Any other character is invalid return false; } } // The string is valid only if a digit was seen in the last component. // This handles cases like "1e", ".", "+", "1e+", which are invalid. return digitSeen; }}Complexity
Time
O(N)
Space
O(1)
Trade-offs
Pros
Most performant approach in practice due to direct character processing with no overhead.
Provides fine-grained control over the parsing logic, making it adaptable to complex or unusual rules.
The logic is self-contained and does not depend on any external libraries or complex language features.
Cons
The code can be more verbose compared to a regex solution.
The logic with multiple flags and conditions can become complex and prone to errors if edge cases are not handled systematically.
Solutions
Solution
using System.Text.RegularExpressions ; public class Solution { private readonly Regex _isNumber_Regex = new Regex ( @"^\s*[+-]?(\d+(\.\d*)?|\.\d+)([Ee][+-]?\d+)?\s*$" ); public bool IsNumber ( string s ) { return _isNumber_Regex . IsMatch ( s ); } }class Solution {public boolean isNumber(String s) { int n = s.length(); int i = 0; if (s.charAt(i) == '+' || s.charAt(i) == '-') { ++i; } if (i == n) { return false; } if (s.charAt(i) == '.' && (i + 1 == n || s.charAt(i + 1) == 'e' || s.charAt(i + 1) == 'E')) { return false; } int dot = 0, e = 0; for (int j = i; j < n; ++j) { if (s.charAt(j) == '.') { if (e > 0 || dot > 0) { return false; } ++dot; } else if (s.charAt(j) == 'e' || s.charAt(j) == 'E') { if (e > 0 || j == i || j == n - 1) { return false; } ++e; if (s.charAt(j + 1) == '+' || s.charAt(j + 1) == '-') { if (++j == n - 1) { return false; } } } else if (s.charAt(j) < '0' || s.charAt(j) > '9') { return false; } } return true; }}class Solution {public: bool isNumber(string s) { int n = s.size(); int i = 0; if (s[i] == '+' || s[i] == '-') ++i; if (i == n) return false; if (s[i] == '.' && (i + 1 == n || s[i + 1] == 'e' || s[i + 1] == 'E')) return false; int dot = 0, e = 0; for (int j = i; j < n; ++j) { if (s[j] == '.') { if (e || dot) return false; ++dot; } else if (s[j] == 'e' || s[j] == 'E') { if (e || j == i || j == n - 1) return false; ++e; if (s[j + 1] == '+' || s[j + 1] == '-') { if (++j == n - 1) return false; } } else if (s[j] < '0' || s[j] > '9') return false; } return true; }};class Solution: def isNumber(self, s: str) -> bool: n = len(s) i = 0 if s[i] in '+-': i += 1 if i == n: return False if s[i] == '.' and (i + 1 == n or s[i + 1] in 'eE'): return False dot = e = 0 j = i while j < n: if s[j] == '.': if e or dot: return False dot += 1 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 elif not s[j]. isnumeric(): return False j += 1 return TrueVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.