Back to Stream Practice: String & Character
Q10Question 10 of 11

Find the SECOND non-repeating character in a string

Key Concepts
  • Count characters into a LinkedHashMap so unique characters come out in first-appearance order
  • Filter entries with count == 1 to get non-repeating characters in order
  • skip(1).findFirst() drops the first unique character and returns the second as an Optional
  • LinkedHashMap is essential because 'second' only makes sense with a defined ordering
  • Generalizes to the Nth non-repeating character by using skip(n-1)
  • Optional.empty() is returned honestly when fewer than two unique characters exist

Definition

Count characters into a LinkedHashMap, filter to count == 1, then skip(1).findFirst() to get the second non-repeating character.
  • LinkedHashMap preserves first-appearance order, which defines what 'second' means.
  • groupingBy(c -> c, LinkedHashMap::new, counting()) builds an ordered frequency map.
  • filter(count == 1) keeps only characters that appear exactly once.
  • skip(1).findFirst() drops the first unique character and returns the next as an Optional.
  • Generalizable to the Nth non-repeating character by changing skip to n-1.

Priya at the National Bank of India lists every character that appears only once in a reference code, in the order she first saw them, then ignores the first unique letter and picks the next one.

Understanding the Concepts

What does this question ask?

The interviewer wants the second character that appears exactly once in a string. This tests LinkedHashMap for ordered frequency counting, entry filtering, and skip-then-findFirst.

How do you find the second non-repeating character?

JAVA
package com.bank.core;

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

public class SecondNonRepeating {
    public static Optional<Character> secondNonRepeating(String s) {
        Map<Character, Long> freq = s.chars()
            .mapToObj(c -> Character.toLowerCase((char) c))
            .filter(Character::isLetter)
            .collect(Collectors.groupingBy(
                c -> c,
                LinkedHashMap::new,
                Collectors.counting()));

        return freq.entrySet().stream()
            .filter(e -> e.getValue() == 1)
            .map(Map.Entry::getKey)
            .skip(1)
            .findFirst();
    }

    public static void main(String[] args) {
        String code = "NATIONAL BANK";
        Optional<Character> result = secondNonRepeating(code);
        System.out.println("Second non-repeating: "
            + result.map(String::valueOf).orElse("none"));
    }
}
TEXT
Second non-repeating: i

In "national bank" (lowercase), the unique characters in first-seen order are t, i, o, l, b, k. The second one is 'i'.

Why is LinkedHashMap essential?

With a plain HashMap, iteration order is arbitrary, so 'second' would be meaningless.

JAVA
// WRONG: HashMap gives arbitrary order
Map<Character, Long> unordered = s.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
// skip(1) on an arbitrary order gives an arbitrary character

// CORRECT: LinkedHashMap preserves insertion (first-seen) order
Map<Character, Long> ordered = s.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(
        c -> c, LinkedHashMap::new, Collectors.counting()));

How do you generalize to the Nth non-repeating character?

JAVA
public static Optional<Character> nthNonRepeating(String s, int n) {
    if (n < 1) throw new IllegalArgumentException("n must be >= 1");

    Map<Character, Long> freq = s.chars()
        .mapToObj(c -> Character.toLowerCase((char) c))
        .filter(Character::isLetter)
        .collect(Collectors.groupingBy(
            c -> c, LinkedHashMap::new, Collectors.counting()));

    return freq.entrySet().stream()
        .filter(e -> e.getValue() == 1)
        .map(Map.Entry::getKey)
        .skip(n - 1)
        .findFirst();
}

What are the edge cases?

JAVA
// All characters repeat: no unique characters at all
Optional<Character> allRepeat = secondNonRepeating("aabbcc");
System.out.println(allRepeat.isPresent());  // false

// Only one unique character: second does not exist
Optional<Character> oneUnique = secondNonRepeating("aabbc");
System.out.println(oneUnique.isPresent());  // false

// Empty string: no characters at all
Optional<Character> empty = secondNonRepeating("");
System.out.println(empty.isPresent());  // false

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

| Operation | Pipeline ending | Result |
|---|---|---|
| First non-repeating | filter(count == 1).findFirst() | First unique in appearance order |
| Second non-repeating | filter(count == 1).skip(1).findFirst() | Second unique in appearance order |
| Nth non-repeating | filter(count == 1).skip(n-1).findFirst() | Nth unique in appearance order |

The only difference is the skip value.

What are common mistakes?

Mistake: using HashMap instead of LinkedHashMap.

JAVA
// WRONG: HashMap has no ordering guarantee
Map<Character, Long> wrong = s.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
// "second" is meaningless without order

// CORRECT: LinkedHashMap preserves first-seen order
Map<Character, Long> correct = s.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(
        c -> c, LinkedHashMap::new, Collectors.counting()));

Mistake: filtering for count > 1 instead of count == 1.

JAVA
// WRONG: count > 1 gives DUPLICATE characters, not unique ones
.filter(e -> e.getValue() > 1)

// CORRECT: count == 1 gives NON-REPEATING characters
.filter(e -> e.getValue() == 1)

What about performance?

The approach is linear overall: one pass to count, one pass to filter and skip. The LinkedHashMap's iteration is O(n) where n is the number of distinct characters, which is at most the alphabet size. For very long strings, this is efficient.

What are common interview follow-up questions?

  • "How do you find the first repeating character?" Filter for count > 1 and take findFirst from the LinkedHashMap.
  • "How do you handle Unicode characters?" Use codePoints() instead of chars().
  • "What if you need both the character and its position?" Track positions in a custom data structure.

Visual Overview

DIAGRAM
  String: "NATIONAL BANK" (lowercased, letters only)
       │
       │  groupingBy(LinkedHashMap, counting)
       ▼
  ┌────────────────────────────────┐
  │  n → 3   a → 3   t → 1       │
  │  i → 1   o → 1   l → 1       │
  │  b → 1   k → 1               │
  └────────────────────────────────┘
       │
       │  filter(count == 1)
       ▼
  Unique in order: [t, i, o, l, b, k]
       │
       │  skip(1).findFirst()
       ▼
  Second non-repeating: 'i'

Key Takeaway

Finding the second non-repeating character builds on the first-non-repeating pattern by adding skip(1) before findFirst. Count characters into a LinkedHashMap so that iteration order matches first-appearance order, filter entries with count equal to one, and the unique characters come out in reading order. skip(1) drops the first unique character and findFirst returns the second.

The LinkedHashMap is the critical correctness detail, because the word 'second' only has meaning when the unique characters are enumerated in their first-appearance order. With a plain HashMap, the result would be arbitrary. At the National Bank of India, Rajesh used this in a layered uniqueness check over reference codes, verifying that codes had enough unique characters.

The pattern generalizes immediately to the Nth non-repeating character by using skip(n-1), and the Optional honestly handles strings with fewer than n unique characters by returning empty.

Remember one sentence: count into a LinkedHashMap, filter for count == 1, then skip(1).findFirst() gives the second unique character in first-seen order.

---

Share
Use ← → arrow keys to navigate