Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
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 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:
'.'.'.' followed by digits.'.' 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 <= 20s consists of only English letters (both uppercase and lowercase), digits (0-9), plus '+', minus '-', or dot '.'.Problem summary: 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
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: General problem-solving
"0"
"e"
"."
string-to-integer-atoi)class Solution {
public boolean isNumber(String s) {
s = s.trim();
if (s.isEmpty()) return false;
boolean seenDigit = false;
boolean seenDot = false;
boolean seenExp = false;
boolean digitAfterExp = true;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (Character.isDigit(ch)) {
seenDigit = true;
if (seenExp) digitAfterExp = true;
} else if (ch == '+' || ch == '-') {
// Sign is valid only at start or right after exponent.
if (i > 0 && s.charAt(i - 1) != 'e' && s.charAt(i - 1) != 'E') return false;
} else if (ch == '.') {
// Dot allowed only once and only before exponent.
if (seenDot || seenExp) return false;
seenDot = true;
} else if (ch == 'e' || ch == 'E') {
// Exponent allowed once and only after at least one digit.
if (seenExp || !seenDigit) return false;
seenExp = true;
digitAfterExp = false;
} else {
return false;
}
}
return seenDigit && digitAfterExp;
}
}
func isNumber(s string) bool {
i, j := 0, len(s)-1
for i <= j && s[i] == ' ' {
i++
}
for j >= i && s[j] == ' ' {
j--
}
if i > j {
return false
}
s = s[i : j+1]
seenDigit := false
seenDot := false
seenExp := false
digitAfterExp := true
for idx := 0; idx < len(s); idx++ {
ch := s[idx]
if ch >= '0' && ch <= '9' {
seenDigit = true
if seenExp {
digitAfterExp = true
}
} else if ch == '+' || ch == '-' {
if idx > 0 && s[idx-1] != 'e' && s[idx-1] != 'E' {
return false
}
} else if ch == '.' {
if seenDot || seenExp {
return false
}
seenDot = true
} else if ch == 'e' || ch == 'E' {
if seenExp || !seenDigit {
return false
}
seenExp = true
digitAfterExp = false
} else {
return false
}
}
return seenDigit && digitAfterExp
}
class Solution:
def isNumber(self, s: str) -> bool:
s = s.strip()
if not s:
return False
seen_digit = False
seen_dot = False
seen_exp = False
digit_after_exp = True
for i, ch in enumerate(s):
if ch.isdigit():
seen_digit = True
if seen_exp:
digit_after_exp = True
elif ch in '+-':
if i > 0 and s[i - 1] not in 'eE':
return False
elif ch == '.':
if seen_dot or seen_exp:
return False
seen_dot = True
elif ch in 'eE':
if seen_exp or not seen_digit:
return False
seen_exp = True
digit_after_exp = False
else:
return False
return seen_digit and digit_after_exp
impl Solution {
pub fn is_number(s: String) -> bool {
let s = s.trim();
if s.is_empty() {
return false;
}
let bytes = s.as_bytes();
let mut seen_digit = false;
let mut seen_dot = false;
let mut seen_exp = false;
let mut digit_after_exp = true;
for i in 0..bytes.len() {
let ch = bytes[i];
if ch.is_ascii_digit() {
seen_digit = true;
if seen_exp {
digit_after_exp = true;
}
} else if ch == b'+' || ch == b'-' {
if i > 0 && bytes[i - 1] != b'e' && bytes[i - 1] != b'E' {
return false;
}
} else if ch == b'.' {
if seen_dot || seen_exp {
return false;
}
seen_dot = true;
} else if ch == b'e' || ch == b'E' {
if seen_exp || !seen_digit {
return false;
}
seen_exp = true;
digit_after_exp = false;
} else {
return false;
}
}
seen_digit && digit_after_exp
}
}
function isNumber(s: string): boolean {
s = s.trim();
if (s.length === 0) return false;
let seenDigit = false;
let seenDot = false;
let seenExp = false;
let digitAfterExp = true;
for (let i = 0; i < s.length; i++) {
const ch = s[i];
if (ch >= '0' && ch <= '9') {
seenDigit = true;
if (seenExp) digitAfterExp = true;
} else if (ch === '+' || ch === '-') {
if (i > 0 && s[i - 1] !== 'e' && s[i - 1] !== 'E') return false;
} else if (ch === '.') {
if (seenDot || seenExp) return false;
seenDot = true;
} else if (ch === 'e' || ch === 'E') {
if (seenExp || !seenDigit) return false;
seenExp = true;
digitAfterExp = false;
} else {
return false;
}
}
return seenDigit && digitAfterExp;
}
Use this to step through a reusable interview workflow for this problem.
Two nested loops check every pair or subarray. The outer loop fixes a starting point, the inner loop extends or searches. For n elements this gives up to n²/2 operations. No extra space, but the quadratic time is prohibitive for large inputs.
Most array problems have an O(n²) brute force (nested loops) and an O(n) optimal (single pass with clever state tracking). The key is identifying what information to maintain as you scan: a running max, a prefix sum, a hash map of seen values, or two pointers.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.