Skip to content
advanced

Valid Number

A decimal point and an exponent are easy to track. The hard part is proving that every numeric component actually contains the digits the grammar requires.

Published 2026-09-07Updated 2026-09-1212 min read
Two ancient columns stand majestically against a serene hillside backdrop.
Two ancient columns stand majestically against a serene hillside backdrop. Photo by ROMAN ODINTSOV on Pexels.
Problem

Valid Number

Difficulty: HardAcceptance rate: 23.5%

Given a string s, determine whether it represents a valid number. A valid number is an optionally signed integer or decimal, optionally followed by an exponent consisting of 'e' or 'E' and an optionally signed integer. Integers require one or more digits; decimals may contain a dot with digits before it, after it, or on both sides, but must contain at least one digit.

String

Constraints

  • 1 <= s.length <= 20
  • s consists only of English letters, digits 0-9, '+', '-', or '.'

Important details

  • The exponent is optional and must use 'e' or 'E' followed by an integer number.
  • A sign may appear before the base number and after the exponent marker.
  • Decimal forms include digits followed by '.', digits followed by '.' and digits, or '.' followed by digits.
  • Return a boolean indicating validity.

A decimal point and an exponent are easy to track. The hard part is proving that every numeric component actually contains the digits the grammar requires.

Start With the Grammar

The cleanest Valid Number solution begins by treating the input as a small grammar, not as something to pass into a numeric conversion function.

A valid string has this structure:

optional sign
base number
optional exponent

The base number is either:

  • an integer: one or more digits
  • a decimal: a dot with at least one digit somewhere in the base

The exponent, when present, has this structure:

e or E
optional sign
one or more digits

So the complete grammar is:

number  := sign? (integer | decimal) exponent?
integer := digit+
decimal := digit+ "." digit*
         | "." digit+
exponent := ("e" | "E") sign? digit+

The decimal grammar permits digits on either side of the dot, but not zero digits on both sides. Therefore:

StringValid?Reason
4.YesDigits appear before the dot
-.9YesDigits appear after the dot
3.14YesDigits appear on both sides
.NoThe mantissa has no digits
2e10YesThe exponent contains digits
1eNoThe exponent is incomplete
99e2.5NoThe exponent must be an integer

The input contract allows English letters, decimal digits, +, -, and .. We validate that grammar exactly. We do not add whitespace trimming, hexadecimal syntax, underscores, or other formats.

The critical acceptance condition is also exact:

The entire string must be consumed, and the final component must contain at least one digit.

That last clause is what rejects a valid-looking prefix followed by an incomplete suffix.

Recognize the Parser State

This is a state-tracking problem. We scan once from left to right, and each character is legal only in the context created by earlier characters.

A first attempt often tracks only:

seen_dot
seen_exponent

That model is incomplete. It can tell us whether a dot or exponent has appeared, but it cannot enforce the digit obligations created by component boundaries.

For example:

  • e has no base digits.
  • 1e enters exponent mode but provides no exponent digits.
  • + has a sign but no number.
  • -. has a sign and dot but no mantissa digits.
  • 1e+ has an exponent sign but no exponent digits.

The missing state is not “have we seen a number somewhere?” It is:

Does the current numeric component contain at least one digit?

That component changes when we enter the exponent. The mantissa is one component; the exponent is another.

We need three flags:

  • seen_digit: whether the current component contains a digit
  • seen_dot: whether the mantissa already contains a dot
  • seen_exponent: whether the exponent has already begun

These flags implement a finite-state parser. We do not need to build a large transition table for this grammar, but the transition-table idea is useful: each character either moves the parser to a legal state or causes immediate rejection.

The important distinction is between conversion and validation. Calling float(s) asks Python whether it can interpret the string as a runtime floating-point value. This problem asks whether the string follows a specific character grammar. Conversion hides the transitions we need to reason about, and its accepted syntax need not match the problem contract exactly.

For an interview, explicit parser state is easier to inspect, explain, and prove.

Turn the Grammar Into Obligations

Each character class creates a local rule.

Digits complete the current component

A digit is always legal in the current number component.

When we see one, set:

seen_digit = True

This flag has two meanings at different points in the scan:

  • before the exponent: the mantissa contains at least one digit
  • after the exponent: the exponent contains at least one digit

That reuse is safe because entering the exponent resets the flag.

A dot belongs only to the mantissa

A dot is legal only when:

  • no earlier dot exists
  • the exponent has not started

So . is rejected if either seen_dot or seen_exponent is already true.

The dot itself does not satisfy the digit obligation. That is why . remains invalid, while 4. and -.9 are valid.

An exponent starts a new component

An e or E is legal only when:

  • no earlier exponent exists
  • the mantissa already contains a digit

After accepting the exponent marker, reset seen_digit:

seen_digit = False

This reset is the central transition in the parser. It says: the mantissa is complete, but the exponent still owes us a digit.

Without the reset, 1e would incorrectly inherit the digit from the mantissa and pass.

A sign appears only at a component boundary

A sign is legal in exactly two positions:

  • at index 0, before the base number
  • immediately after e or E, before the exponent integer

A sign does not count as a digit. It only changes the grammar state in which digits may follow.

This rejects:

  • --6
  • -+3
  • 1-2
  • 1e--4

It accepts:

  • -6
  • +3.14
  • 3e+7
  • 6E-1

Every other character is invalid

Letters other than e and E are invalid. So are spaces, repeated signs, and any symbol outside the contract.

Rejecting immediately keeps the parser honest. Once a transition violates the grammar, no later character can repair it.

Derive the One-Pass Scan

Flowchart of a one-pass numeric parser: digits mark the current component as complete, a dot is allowed only once before the exponent, an exponent requires mantissa digits and resets the digit flag, signs are allowed at component boundaries, invalid characters reject, and the scan accepts only when the final component contains a digit.
The key transition is entering the exponent: it resets the digit obligation, so an exponent marker or sign cannot finish the number by itself.

Initialize all state to false:

seen_digit = False
seen_dot = False
seen_exponent = False

Then process each character:

  1. Digit
    Mark the current component complete with respect to its digit obligation.

  2. Dot
    Reject after another dot or after an exponent. Otherwise record the dot.

  3. Exponent marker
    Reject if an exponent already exists or if the mantissa has no digit. Otherwise record the exponent and reset seen_digit.

  4. Sign
    Accept only at the beginning or immediately after an exponent marker. Do not mark a digit.

  5. Anything else
    Reject immediately.

After the loop, return seen_digit.

That final return is not a convenience check. It is the accepting state of the parser. It rejects an unfinished exponent, a bare sign, or a dot-only mantissa.

The sign rule is easiest to express with the previous character:

i == 0 or s[i - 1] in "eE"

This works because the exponent branch has already rejected malformed placement before the sign is examined.

Prove the Accepting State

Use this invariant while scanning:

After consuming any accepted prefix, seen_dot and seen_exponent describe the structural markers already used, and seen_digit records whether the current numeric component contains at least one digit.

Now check each transition.

  • A digit sets seen_digit to true, so the current component satisfies its digit obligation.
  • A dot is accepted only once and only before the exponent. It changes the mantissa structure but does not pretend to be a digit.
  • An exponent is accepted only after a valid mantissa digit. Resetting seen_digit transfers the digit obligation to the new exponent component.
  • A sign is accepted only at the beginning of a component. Since it does not set seen_digit, a sign cannot complete an integer or exponent by itself.
  • Any other character has no legal grammar transition and is rejected.

If the scan finishes with seen_digit == False, the final component is incomplete. This catches:

+
.
-.
1e
1e+

If the scan finishes with seen_digit == True, every component that was opened has supplied at least one digit, and every structural marker was used legally.

This is why accepting a valid prefix is insufficient. For 1e, the prefix 1 is valid, and the exponent marker is structurally allowed. But the complete string ends before the exponent component reaches its accepting state.

The parser must consume the whole input. A valid prefix is not a valid number.

Trace the Failure Boundaries

State traces expose the bugs that ordinary examples hide.

Valid: -6.5e+2

Characterseen_digitseen_dotseen_exponentAction
-FalseFalseFalseSign at index 0
6TrueFalseFalseMantissa digit
.TrueTrueFalseFirst mantissa dot
5TrueTrueFalseMantissa digit
eFalseTrueTrueStart exponent; reset digit state
+FalseTrueTrueSign after exponent
2TrueTrueTrueExponent digit

The scan ends with seen_digit == True, so the string is accepted.

Invalid: 1e

The 1 satisfies the mantissa. The e is legal because the mantissa is complete, but entering exponent mode resets seen_digit to false. The scan ends immediately, so the exponent never receives a digit.

Invalid: 1e+

This follows the same path, with one additional accepted transition for the exponent sign. The sign still does not count as a digit. The final state remains incomplete.

Valid: -.9

The initial sign is legal. The dot is legal and does not complete the mantissa. The 9 supplies the required digit after the dot.

Requiring digits on both sides of the dot would incorrectly reject this valid form.

Valid: 4.

The 4 completes the mantissa before the dot. The trailing dot is therefore legal, even though no digit follows it.

Invalid: 99e2.5

The exponent begins after valid mantissa digits. The 2 completes the exponent. The following dot is rejected because dots belong only to the mantissa.

Invalid: 1.2.3

The first dot is legal. The second dot violates the one-dot restriction and is rejected immediately.

Invalid: 1a

The 1 is valid state progress. The a has no transition, so the parser rejects it at once. No numeric conversion is needed, and no later recovery is possible.

Implement the Python Validator

Here is the interview-ready valid number parser in Python. The branch order follows the derivation: digits, dot, exponent, sign, then invalid input.

class Solution:
    def isNumber(self, s: str) -> bool:
        seen_digit = False
        seen_dot = False
        seen_exponent = False

        for i, char in enumerate(s):
            if char.isdigit():
                seen_digit = True

            elif char == ".":
                # A dot is allowed only once, and only in the mantissa.
                if seen_dot or seen_exponent:
                    return False
                seen_dot = True

            elif char in "eE":
                # The mantissa must be complete before the exponent starts.
                if seen_exponent or not seen_digit:
                    return False

                seen_exponent = True
                # The exponent is a new component with its own digit obligation.
                seen_digit = False

            elif char in "+-":
                # A sign is allowed at the start or immediately after e/E.
                if i > 0 and s[i - 1] not in "eE":
                    return False

            else:
                return False

        # The final component must contain at least one digit.
        return seen_digit

The code intentionally does not call float(), strip whitespace, or accept alternate numeric formats. Those operations would expand or change the contract. This validator checks the supplied grammar directly.

One subtle point deserves emphasis: char.isdigit() is broader than the problem's stated ASCII input alphabet in some Python contexts. Under the given contract, the input contains only 0 through 9, so this is sufficient. If the input contract were widened, use an explicit ASCII check such as:

"0" <= char <= "9"

That is a contract decision, not a parser-state decision.

Complexity and Interview Checks

Let n be the length of the string.

  • Time: O(n), because each character is inspected once.
  • Auxiliary space: O(1), because the parser stores only three boolean flags and the loop index.

There is no repeated substring parsing, backtracking, or numeric computation. The work is proportional to the input because the parser must inspect every character before it can certify complete-string validity.

Before submitting, test the boundaries rather than only ordinary decimals:

CategoryExamples
Basic integers0, 0089, -6, +3
Decimals4., -.9, 3.14
Exponents2e10, -90E3, 3e+7, 6E-1
Missing base digits+, -, ., -.
Missing exponent digits1e, 1e+, 1e-
Repeated markers1..2, 1e2e3, 1e2.5
Bad signs--6, -+3, 1-2, 1e--4
Invalid symbolsabc, 1a, 95a54e53

The common implementation failures are predictable:

  • returning true after a valid prefix
  • allowing signs anywhere
  • allowing a dot after exponent entry
  • forgetting to reset seen_digit after e or E
  • requiring digits on both sides of the dot
  • treating a sign or dot as a digit
  • silently adding whitespace or alternate syntax rules

The technique has a clear boundary. It fits a restricted numeric grammar with bounded parser state. It does not decide whether a value is mathematically finite, whether a runtime conversion will overflow, or whether a locale-specific format should be accepted. Those are different contracts with different state and policy requirements.

The transferable rule is simple:

When each symbol changes what may legally follow, derive one state variable per grammar obligation. Reset completion state when a new component begins, reject illegal transitions immediately, and require an accepting state after the entire input is consumed.

Do not memorize seen_digit, seen_dot, and `seen_exponent as magic flag names. Derive them from the grammar. Read the error, trace the state, fix the assumption.

References

  1. Valid Number - LeetCodeleetcode.com
8sources checked
8source domains
5searches run

Research updated Sep 7, 2026

Related sites

Strengthen the language foundations behind the solution

Use LearnPyFast and LearnJSFast when you want to reinforce the language mechanics that support interview implementations.

Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast
JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast

Keep grinding

Related coding interview problems

Continue with nearby problems that reuse the same data-structure, invariant, or optimization pattern.

Lush green pine tree with a vibrant blue sky background, perfect for nature-themed projects.
beginner
11 min read

Add Binary

You receive two binary strings, a and b, and must return their sum as another binary string. The inputs contain only '0' and '1', have lengths from 1 to…

View solution
A person working on a laptop with a red notebook and glasses on a white table.
intermediate
10 min read

Add Two Numbers

The lists already expose digits in the order addition needs. Scan both lists together, track one carry, and keep going until there is no digit or carry…

View solution
A stylish workspace featuring a laptop, plant, and smartphone on a desk.
intermediate
10 min read

Count and Say

The Count and Say solution is a repeated state transition: start with "1", scan the current string into maximal consecutive runs, and emit each run as…

View solution