Back to Stream Practice: String & Character
Q2Question 2 of 11

Second most repeated (Nth most frequent) character in a string: LinkedHashMap tie-break

Key Concepts
  • Build a frequency map with LinkedHashMap, sort entries by value descending, skip(n-1), then findFirst for Nth most frequent
  • LinkedHashMap ensures deterministic tie-breaking: tied characters maintain their original string order
  • Java's sort (TimSort) is stable: equal elements keep their relative order from the source
  • Without LinkedHashMap, ties break randomly because HashMap iteration order is nondeterministic
  • The pattern generalizes to any Nth-most-frequent query on any collection
  • Edge case: if fewer than N distinct characters exist, findFirst returns Optional.empty()

Definition

Build a frequency map with LinkedHashMap for stable ordering, sort entries by count descending, then skip(n-1) and findFirst to get the Nth most frequent character.
  • groupingBy(identity(), LinkedHashMap::new, counting()) builds a frequency map that preserves insertion order.
  • sorted(comparingByValue(reverseOrder())) sorts entries by frequency from highest to lowest.
  • skip(n-1).findFirst() skips the first n-1 entries and returns the Nth most frequent.
  • LinkedHashMap provides stable tie-breaking: when two characters have the same frequency, the one that appeared first in the string comes first because Java's sort is stable.
  • Without LinkedHashMap, ties between characters with equal frequency break randomly.

At National Bank of India, the fraud detection system analyzes character patterns in transaction descriptions. To find the second most common character, it counts each character, sorts by frequency, and picks the runner-up. When two characters tie (both appear 5 times), the system uses the order they first appeared in the text as the tiebreaker, just like LinkedHashMap preserves insertion order.

Understanding the Concepts

What is the Nth most frequent character?

The most frequent character is the one with the highest count. The second most frequent is the one with the next highest count. When two characters have the same count, the one that appears first in the original string ranks higher.

How do you find the Nth most frequent character?

JAVA
package com.bank.util;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class NthMostFrequentDemo {
    public static void main(String[] args) {
        String str = "AABABCCC";
        // A=3, B=2, C=3 -- A and C TIE at count 3

        Map<Character, Long> freq = str.chars()
            .mapToObj(c -> (char) c)
            .collect(Collectors.groupingBy(
                Function.identity(),
                LinkedHashMap::new,
                Collectors.counting()));

        System.out.println("Frequency (LinkedHashMap): " + freq);

        // Most frequent: sorted desc, findFirst
        Optional<Character> most = freq.entrySet().stream()
            .sorted(Map.Entry.<Character, Long>comparingByValue().reversed())
            .map(Map.Entry::getKey)
            .findFirst();

        System.out.println("Most frequent: " + most.orElse('?'));

        // 2nd most frequent: skip(1)
        Optional<Character> second = freq.entrySet().stream()
            .sorted(Map.Entry.<Character, Long>comparingByValue().reversed())
            .skip(1)
            .map(Map.Entry::getKey)
            .findFirst();

        System.out.println("2nd most frequent: " + second.orElse('?'));

        // Nth most frequent (generic)
        int n = 3;
        Optional<Character> nth = freq.entrySet().stream()
            .sorted(Map.Entry.<Character, Long>comparingByValue().reversed())
            .skip(n - 1)
            .map(Map.Entry::getKey)
            .findFirst();

        System.out.println(n + "rd most frequent: " + nth.orElse('?'));
    }
}
TEXT
Frequency (LinkedHashMap): {A=3, B=2, C=3}
Most frequent: A
2nd most frequent: C
3rd most frequent: B

Why does LinkedHashMap matter for tie-breaking?

A and C both have count 3. In the LinkedHashMap, A comes before C because A appeared first in the string. When sorted by value descending, TimSort is stable: elements with equal values keep their relative order from the source. Since the source is the LinkedHashMap (A before C), A stays ranked #1 and C stays ranked #2.

With HashMap, the iteration order could be {C=3, A=3, B=2} or {A=3, B=2, C=3}, making the tie-break unpredictable.

How is this different from finding the first non-repeating character?

| Question | Filter Condition | Sort Required? |
|----------|-----------------|----------------|
| First non-repeating | count == 1 | No (LinkedHashMap order suffices) |
| Nth most frequent | Rank by count descending | Yes (sorted by value) |
| Most frequent | Highest count | Yes (or just max) |

What if you want the Nth most frequent by distinct count?

Sometimes "second most frequent" means the second distinct frequency level, not the second character when sorted.

JAVA
// Distinct frequency levels: 3, 2 (two levels)
// Characters at frequency 3: A, C
// Characters at frequency 2: B
// "Second frequency level" = B
Optional<Character> secondLevel = freq.entrySet().stream()
    .sorted(Map.Entry.<Character, Long>comparingByValue().reversed())
    .map(Map.Entry::getValue)
    .distinct()
    .skip(1)
    .findFirst()
    .flatMap(targetCount -> freq.entrySet().stream()
        .filter(e -> e.getValue().equals(targetCount))
        .map(Map.Entry::getKey)
        .findFirst());

What are common mistakes?

WRONG: Using HashMap and expecting deterministic tie-breaking.

JAVA
// WRONG: HashMap -- ties break randomly
Map<Character, Long> wrongMap = str.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(
        Function.identity(),
        Collectors.counting()));  // HashMap!
// Tie between A(3) and C(3) -- who comes first? Random!

What should a senior engineer know?

  • Java's TimSort is stable, which means equal elements maintain their relative order. This stability combined with LinkedHashMap's insertion order is what produces deterministic tie-breaking.
  • For very long strings, the frequency map uses O(k) space where k is the number of distinct characters (at most 65,536 for char, or ~1.1 million for Unicode code points).
  • The sorted().skip(n-1).findFirst() pattern has O(k log k) time complexity for the sort step. For finding just the top-1 or top-2, using max or a partial sort would be more efficient.

Visual Overview

DIAGRAM
  "AABABCCC"
     │
     ▼
  LinkedHashMap: {A=3, B=2, C=3}
     │
     ▼
  sorted(comparingByValue().reversed())
  ┌───────────────────────────────────┐
  │ A=3  (first in LinkedHashMap)     │
  │ C=3  (second -- stable sort)      │
  │ B=2                               │
  └──────────────┬────────────────────┘
                 │
  skip(0) → A  (most frequent)
  skip(1) → C  (2nd most frequent)
  skip(2) → B  (3rd most frequent)

Key Takeaway

Finding the Nth most frequent character builds a frequency map with LinkedHashMap, sorts entries by count descending, and uses skip(n-1).findFirst(). The LinkedHashMap is essential for deterministic tie-breaking: when two characters have the same frequency, the one that appeared first in the string comes first because Java's TimSort is stable and preserves the relative order of equal elements.

In the string "AABABCCC", A and C both have count 3. With LinkedHashMap, A appeared first, so A is ranked #1 and C is ranked #2. With HashMap, the order would be random: sometimes C first, sometimes A. This subtlety is the exact "gotcha" interviewers look for.

Remember one sentence: LinkedHashMap plus stable sort gives deterministic tie-breaking for characters with equal frequency.

---

Share
Use ← → arrow keys to navigate