Second Largest Digit in a String: Single-Pass Tracking
Given an alphanumeric string s, return the second largest numerical digit that appears in s, or -1 if it does not exist. A digit's "second largest" means the largest digit that is strictly smaller than the maximum digit found in the string.
This is LeetCode 1796: Second Largest Digit in a String.
Examples:
"dfa12321afd"returns2(digits are 1, 2, 3, so second largest is 2)"abc1111"returns-1(only digit is 1, no second largest exists)
Why this problem matters
This problem is a clean exercise in filtering and tracking. You are given a mixed string of letters and digits, and you need to extract just the digits, then find the second largest among them. It forces you to think about how to maintain a "top two" running state without storing every value you have seen.
The technique generalizes beyond this specific problem. Any time you need the k-th largest element from a stream or a single pass over data, you use the same approach: maintain a small sorted window of the top-k values and update it as new values arrive. For k=2, that means two variables.
This also tests your ability to handle edge cases cleanly. What if there are no digits at all? What if every digit is the same? Getting the boundary conditions right on a problem like this is exactly the kind of precision that matters in interviews.
The key insight
You do not need a set or a sorted list. You only need two variables: largest and second. As you scan each character, check if it is a digit. If it is, compare it against your two trackers:
- If the digit is greater than
largest, the currentlargestbecomessecond, and the digit becomes the newlargest. - If the digit is strictly between
secondandlargest(less thanlargestbut greater thansecond), updatesecond. - Otherwise, do nothing.
Initialize both to -1. At the end, second holds your answer. If no second largest exists, it stays at -1, which is exactly what the problem asks you to return.
The solution
def second_highest(s: str) -> int:
largest = -1
second = -1
for ch in s:
if ch.isdigit():
d = int(ch)
if d > largest:
second = largest
largest = d
elif d < largest and d > second:
second = d
return second
The loop does constant work per character: one isdigit() check, one int() conversion for digits, and at most two comparisons. The whole function runs in a single pass with no extra data structures.
The elif condition requires both d < largest and d > second. The first check ensures you do not accidentally demote largest when you see a duplicate of it. The second check ensures you only update second when you genuinely have a better candidate.
Visual walkthrough
second_highest("dfa12321afd"):Step 1: char = 'd' (index 0)
-1second =-1'd' is not a digit. Skip it. largest = -1, second = -1.
Step 2: char = 'f' (index 1)
-1second =-1'f' is not a digit. Skip it. No changes.
Step 3: char = 'a' (index 2)
-1second =-1'a' is not a digit. Skip it. No changes.
Step 4: char = '1' (index 3)
1second =-1'1' is a digit with value 1. Since 1 > largest (-1), shift: second = -1, largest = 1.
Step 5: char = '2' (index 4)
2second =1'2' is a digit with value 2. Since 2 > largest (1), shift: second = 1, largest = 2.
Step 6: char = '3' (index 5)
3second =2'3' is a digit with value 3. Since 3 > largest (2), shift: second = 2, largest = 3.
Step 7: char = '2' (index 6)
3second =2'2' is a digit with value 2. 2 is not > largest (3), but 2 is not > second (2) either (equal, not greater). No change.
Step 8: char = '1' (index 7)
3second =2'1' is a digit with value 1. 1 is not > largest (3) and not > second (2). No change.
Step 9: chars 'a', 'f', 'd' (indices 8-10)
3second =2Remaining characters are all non-digit. Skip all of them. Final: largest = 3, second = 2.
The largest digit is 3, and the second largest is 2.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Single pass | O(n) | O(1) |
Time: O(n) where n is the length of the string. You visit each character exactly once. The work per character is O(1): a character type check, a possible integer conversion, and up to two comparisons.
Space: O(1). You store only two integer variables regardless of input size. No sets, no arrays, no hash maps.
The building blocks
1. Character filtering
The first piece is deciding which characters matter. In this problem, you skip anything that is not a digit. The pattern of iterating over a string and selectively processing certain characters appears everywhere: counting vowels, extracting numbers from mixed text, validating input formats.
for ch in s:
if ch.isdigit():
d = int(ch)
2. Top-two tracking
The second piece is maintaining the top two values in a single pass. This is a miniature version of the "k-th largest element" pattern. For k=2, you keep two variables and update them with a specific cascade: when a new value beats the champion, the champion drops to second place.
if d > largest:
second = largest
largest = d
elif d < largest and d > second:
second = d
This same logic scales. For top-three, you would add a third variable and extend the cascade. For arbitrary k, you would use a min-heap of size k. But for k=2, two variables and an if-elif chain are all you need.
Edge cases
- No digits at all:
"abc"returns-1. Bothlargestandsecondstay at-1. - Only one unique digit:
"abc1111"returns-1.largestbecomes 1, butsecondnever gets updated because no digit is strictly less than 1 and greater than-1at the same time (1 is not less than 1, so theelifnever fires for duplicate values). - All ten digits present:
"0123456789"returns8. The cascade updates through every digit, and by the endlargest = 9,second = 8. - Digits 0 and 0 only:
"a0b0"returns-1. There is only one unique digit value. - Two adjacent digits:
"a91"returns1. After seeing9,largest = 9. After seeing1, since1 < 9and1 > -1,second = 1.
From understanding to recall
You now see the solution clearly: scan the string, filter for digits, and track the top two values with a simple cascade. The logic is small enough to hold in your head. But will you remember it next time you see a "k-th largest in a stream" variant?
That is where practice matters. The two building blocks here, character filtering and top-k tracking, are patterns you will use in dozens of other problems. CodeBricks isolates each one so you can drill them independently. After a few rounds of spaced repetition, the cascade logic for tracking two values becomes automatic. You stop thinking about the mechanics and focus on the problem-specific details.
Related posts
- Find the Index of the First Occurrence in a String - String scanning fundamentals where you iterate character by character and apply a matching condition
- Excel Sheet Column Number - Character-to-number conversion patterns, turning letters into numeric values during a left-to-right scan
- Happy Number - Digit extraction and tracking, where you repeatedly process individual digits and watch for a termination condition
Each of these problems shares the core loop of "scan, convert, decide." The differences are in what you track and what condition you check. Master the loop, and the variations become easy.