Skip to content
← All posts

Second Largest Digit in a String: Single-Pass Tracking

5 min read
leetcodeproblemeasystringshash-map

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" returns 2 (digits are 1, 2, 3, so second largest is 2)
  • "abc1111" returns -1 (only digit is 1, no second largest exists)
Scanning "dfa12321afd"d0f1a21324352617a8f9d10Extracted digits (unique, sorted):321largest2nd largest
Digit characters are highlighted. Non-digit characters are dimmed. The unique digits are {3, 2, 1}, so the second largest is 2.

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 current largest becomes second, and the digit becomes the new largest.
  • If the digit is strictly between second and largest (less than largest but greater than second), update second.
  • 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 &lt; largest and d &gt; 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

Tracing second_highest("dfa12321afd"):

Step 1: char = 'd' (index 0)

char ="d"(not a digit, skip)
largest =-1second =-1

'd' is not a digit. Skip it. largest = -1, second = -1.

Step 2: char = 'f' (index 1)

char ="f"(not a digit, skip)
largest =-1second =-1

'f' is not a digit. Skip it. No changes.

Step 3: char = 'a' (index 2)

char ="a"(not a digit, skip)
largest =-1second =-1

'a' is not a digit. Skip it. No changes.

Step 4: char = '1' (index 3)

char ="1"digit =1
largest =1second =-1

'1' is a digit with value 1. Since 1 > largest (-1), shift: second = -1, largest = 1.

Step 5: char = '2' (index 4)

char ="2"digit =2
largest =2second =1

'2' is a digit with value 2. Since 2 > largest (1), shift: second = 1, largest = 2.

Step 6: char = '3' (index 5)

char ="3"digit =3
largest =3second =2

'3' is a digit with value 3. Since 3 > largest (2), shift: second = 2, largest = 3.

Step 7: char = '2' (index 6)

char ="2"digit =2
largest =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)

char ="1"digit =1
largest =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)

char ="a,f,d"(not a digit, skip)
largest =3second =2

Remaining characters are all non-digit. Skip all of them. Final: largest = 3, second = 2.

All characters processed. Return second = 2.

The largest digit is 3, and the second largest is 2.

Complexity analysis

ApproachTimeSpace
Single passO(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. Both largest and second stay at -1.
  • Only one unique digit: "abc1111" returns -1. largest becomes 1, but second never gets updated because no digit is strictly less than 1 and greater than -1 at the same time (1 is not less than 1, so the elif never fires for duplicate values).
  • All ten digits present: "0123456789" returns 8. The cascade updates through every digit, and by the end largest = 9, second = 8.
  • Digits 0 and 0 only: "a0b0" returns -1. There is only one unique digit value.
  • Two adjacent digits: "a91" returns 1. After seeing 9, largest = 9. After seeing 1, since 1 < 9 and 1 > -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

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.