LeetCode Daily Challenge-3304. Find the K-th Character in String Game I(Easy)

Anni HuangAnni Huang
6 min read
  • LeetCode Link
  • Date: 3rd July 2025
  • Topics: Math, Bit Manipulation, Recursion, Simulation

Problem Understanding

LeetCode 3304 presents an interesting string transformation game where Alice starts with "a" and repeatedly applies an operation: for each character in the current string, append its next character in the alphabet to create a new string.

Problem Statement

  • Initial state: word = "a"
  • Operation: For each character c in word, append next(c) to word
  • Goal: Find the k-th character (1-indexed) after sufficient operations
  • Key rule: 'z' wraps around to 'a'

Example Walkthrough

Initially: word = "a"
Round 1: "a""ab" (append 'b')
Round 2: "ab""abbc" (append 'b' then 'c')
Round 3: "abbc""abbcbccd" (append 'b','c','c','d')

For k = 5: The 5th character is 'b'

Two Solution Approaches

Approach 1: Pattern Recognition (Elegant)

The elegant solution recognizes that the answer is simply 'a' + popcount(k - 1) where popcount(x) counts the number of 1s in the binary representation of x.

Approach 2: Recursive Divide and Conquer (Our Focus)

The provided C solution uses a recursive approach that mirrors the binary structure of the problem.

The C Solution Deep Dive

char dfs(int k, int level);

char kthCharacter(int k) {
    int level = 0;
    int len = 1;
    // Find the level such that total length >= k
    while (len < k) {
        len *= 2;
        level++;
    }
    return dfs(k, level);
}

char dfs(int k, int level) {
    if (level == 0) return 'a'; // base case: word = "a"

    int half = 1 << (level - 1); // length of W(level - 1)

    if (k <= half) {
        return dfs(k, level - 1); // first half
    } else {
        char c = dfs(k - half, level - 1); // second half (shifted)
        return c == 'z' ? 'a' : c + 1;
    }
}

Algorithm Breakdown

Step 1: Find the Required Level

int level = 0;
int len = 1;
while (len < k) {
    len *= 2;
    level++;
}

Purpose: Determine the minimum number of rounds needed to have at least k characters.

Logic: Each round doubles the string length:

  • Level 0: length = 1 ("a")
  • Level 1: length = 2 ("ab")
  • Level 2: length = 4 ("abbc")
  • Level 3: length = 8 ("abbcbccd")

Step 2: Recursive Structure Analysis

The key insight is that at each level, the string has a binary tree structure:

Level 0: "a"
         |
Level 1: "a" + "b"
         |     |
Level 2: "ab" + "bc"
         |       |
Level 3: "abbc" + "bccd"

Step 3: Divide and Conquer Logic

int half = 1 << (level - 1); // length of W(level - 1)

if (k <= half) {
    return dfs(k, level - 1); // first half
} else {
    char c = dfs(k - half, level - 1); // second half (shifted)
    return c == 'z' ? 'a' : c + 1;
}

Key Insights:

  1. First Half: Characters 1 to half are identical to the previous level
  2. Second Half: Characters half+1 to 2*half are the "next" characters of the previous level
  3. Position Mapping: Position k in second half corresponds to position k-half in previous level

Visual Example: Finding the 5th Character

Target: k = 5
Level needed: 3 (since 2^3 = 8 >= 5)

Level 3: "abbcbccd" (length 8)
         positions: 1 2 3 4 5 6 7 8

half = 4, k = 5 > 4, so we're in the second half
Position 5 maps to position 1 in the previous level

Level 2: "abbc" (length 4)
         positions: 1 2 3 4

Position 1 → character 'a'
Since we're in the second half, we increment: 'a' + 1 = 'b'

Step-by-Step Execution

Let's trace dfs(5, 3):

  1. dfs(5, 3):

    • half = 4, k = 5 > 4 → second half
    • Call dfs(5-4, 2) = dfs(1, 2)
    • Result will be incremented
  2. dfs(1, 2):

    • half = 2, k = 1 ≤ 2 → first half
    • Call dfs(1, 1)
  3. dfs(1, 1):

    • half = 1, k = 1 ≤ 1 → first half
    • Call dfs(1, 0)
  4. dfs(1, 0):

    • Base case: return 'a'
  5. Back to dfs(1, 1): returns 'a'

  6. Back to dfs(1, 2): returns 'a'
  7. Back to dfs(5, 3): increments 'a' → returns 'b'

Time and Space Complexity

Time Complexity

  • Finding level: O(log k)
  • Recursive calls: O(log k) levels, each with O(1) work
  • Total: O(log k)

Space Complexity

  • Recursion stack: O(log k)
  • Variables: O(1)
  • Total: O(log k)

Key Optimizations

1. Level Pre-computation

Instead of building the entire string, we calculate the minimum level needed upfront.

2. Binary Structure Exploitation

Each level has exactly double the length of the previous level, allowing efficient position mapping.

3. Character Wrapping

return c == 'z' ? 'a' : c + 1;

Handles the wrap-around from 'z' to 'a' elegantly.

Alternative Implementations

Iterative Version

char kthCharacter(int k) {
    int shifts = 0;
    k--; // Convert to 0-indexed

    while (k > 0) {
        if (k & 1) shifts++; // Count 1s in binary
        k >>= 1;
    }

    return 'a' + (shifts % 26);
}

Mathematical One-liner

char kthCharacter(int k) {
    return 'a' + __builtin_popcount(k - 1);
}

Common Pitfalls and Debug Tips

1. Off-by-One Errors

// Remember: problem uses 1-based indexing
int half = 1 << (level - 1);
if (k <= half) {
    return dfs(k, level - 1);
} else {
    return dfs(k - half, level - 1); // Correct offset
}

2. Character Wrapping

// Handle 'z' → 'a' transition
return c == 'z' ? 'a' : c + 1;

3. Level Calculation

// Ensure we have enough characters
while (len < k) {
    len *= 2;
    level++;
}

Test Cases

void test_solution() {
    assert(kthCharacter(1) == 'a'); // Base case
    assert(kthCharacter(2) == 'b'); // First transformation
    assert(kthCharacter(5) == 'b'); // Example from problem
    assert(kthCharacter(10) == 'c'); // Larger case
}

Why This Approach Works

1. Binary Tree Structure

The string growth follows a perfect binary tree pattern where:

  • Each node represents a character
  • Left child = same character
  • Right child = next character

2. Recursive Decomposition

Any position k can be decomposed into:

  • Which half it belongs to
  • Its relative position within that half
  • The transformation applied to reach that half

3. Efficient Navigation

Instead of building the entire string, we navigate directly to the target position using the binary structure.

Conclusion

The recursive divide-and-conquer approach elegantly solves this problem by recognizing the underlying binary tree structure. While the mathematical solution (popcount) is more concise, the recursive approach provides deeper insights into the problem's structure and is more intuitive for understanding the transformation process.

Key Takeaways:

  1. Pattern Recognition: Look for binary/tree structures in growth problems
  2. Divide and Conquer: Break down positions into manageable subproblems
  3. Recursive Thinking: Each level builds upon the previous in a predictable way
  4. Mathematical Insight: Sometimes elegant patterns hide behind complex transformations

The solution demonstrates how a seemingly complex string transformation can be solved efficiently through structural analysis and recursive decomposition.

0
Subscribe to my newsletter

Read articles from Anni Huang directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anni Huang
Anni Huang

I am Anni HUANG, a software engineer with 3 years of experience in IDE development and Chatbot.