LeetCode 3744 - Find Kth Character in Expanded String
The problem gives us a string s containing one or more lowercase words separated by single spaces. From this original string, we conceptually build a new string t using a special expansion rule.
Difficulty: 🟡 Medium
Topics: String
Solution
LeetCode 3744 - Find Kth Character in Expanded String
Problem Understanding
The problem gives us a string s containing one or more lowercase words separated by single spaces. From this original string, we conceptually build a new string t using a special expansion rule.
For each word, the first character appears once, the second character appears twice, the third character appears three times, and so on. Spaces between words remain as single spaces in the expanded string.
For example:
Word: hello
h -> repeated 1 time -> h
e -> repeated 2 times -> ee
l -> repeated 3 times -> lll
l -> repeated 4 times -> llll
o -> repeated 5 times -> ooooo
Expanded word: heelllllllooooo
For the input:
s = "hello world"
the expanded string becomes:
t = "heelllllllooooo woorrrllllddddd"
We are given an index k, and we must return the character located at position k in the expanded string t.
The important observation is that the constraints allow:
1 <= s.length <= 100000
This means the expanded string can become much larger than the original string. If a word has length m, its expanded length is:
1 + 2 + 3 + ... + m
which equals:
m(m + 1) / 2
For large words, explicitly constructing the entire expanded string may be unnecessarily expensive.
The problem guarantees that:
0 <= k < t.length
so the requested position always exists.
Important edge cases include a single-character word, strings containing only one word, positions that land exactly on a space between words, and positions that fall inside large repeated blocks of a character.
Approaches
Brute Force
The most direct solution is to actually construct the expanded string.
We scan each word. For the character at position i within the word, we append that character i + 1 times. Between words, we append a single space.
After building the complete expanded string t, we simply return:
t[k]
This approach is obviously correct because it exactly follows the definition of the expansion process.
The drawback is that the expanded string may become much larger than the input. Creating and storing the entire string costs time proportional to the size of the expanded result and requires the same amount of memory.
Optimal Approach
The key observation is that we do not need the entire expanded string. We only need the character at one position.
Instead of constructing t, we can walk through the original string and keep track of how many characters the expansion contributes.
For a word:
c0 c1 c2 ...
the expanded blocks have lengths:
1, 2, 3, ...
As we process each character, we know exactly how many positions it occupies in the expanded string.
Suppose the current character contributes a block of length len.
If:
k < len
then the desired position lies inside this block, so that character is the answer.
Otherwise, we subtract:
k -= len
and continue searching.
Spaces are handled similarly. Each space contributes exactly one character to the expanded string.
By consuming portions of k as we traverse the original string, we locate the answer without ever constructing the expanded result.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O( | t | ) |
| Optimal | O(n) | O(1) | Scans original string only, no expansion stored |
Here, n = len(s).
Algorithm Walkthrough
- Initialize a pointer
i = 0to scan the original string. - While
iis within the string:
-
If
s[i]is a space: -
The expanded string contributes exactly one character.
-
If
k == 0, return the space. -
Otherwise decrement
kby one and continue.
- If
s[i]starts a word:
- Record the start of the word.
- Traverse the word character by character until a space or the end of the string is reached.
- For each character inside the word:
- Let its position within the word be
pos, starting from0. - Its expansion length is:
pos + 1
- If:
k < pos + 1
then the desired index falls inside this repeated block, so return that character.
- Otherwise subtract:
k -= (pos + 1)
and continue.
5. Continue processing words and spaces until the answer is found.
6. The problem guarantees that k is valid, so a character will always be returned.
Why it works
At every step, k represents the position we still need to locate within the remaining unprocessed portion of the expanded string. Whenever we skip a block of length L, subtracting L from k removes exactly those positions from consideration. When k becomes smaller than the current block length, the target index must lie inside that block, and since every position in the block contains the same character, returning that character is correct.
Python Solution
class Solution:
def kthCharacter(self, s: str, k: int) -> str:
n = len(s)
i = 0
while i < n:
if s[i] == ' ':
if k == 0:
return ' '
k -= 1
i += 1
continue
position_in_word = 0
while i < n and s[i] != ' ':
block_length = position_in_word + 1
if k < block_length:
return s[i]
k -= block_length
position_in_word += 1
i += 1
return ""
Implementation Explanation
The algorithm scans the original string exactly once.
Whenever a space is encountered, it contributes a single character to the expanded string. If the desired position is that space, we return it immediately. Otherwise we decrement k and continue.
For characters inside a word, we track their index within that word using position_in_word. A character at position p expands into a block of length p + 1.
Before skipping the block, we check whether the remaining target index k falls inside it. If it does, that character is the answer. Otherwise we subtract the block length from k and move forward.
Because we never build the expanded string, the solution uses constant extra memory.
Go Solution
func kthCharacter(s string, k int64) byte {
n := len(s)
i := 0
for i < n {
if s[i] == ' ' {
if k == 0 {
return ' '
}
k--
i++
continue
}
positionInWord := int64(0)
for i < n && s[i] != ' ' {
blockLength := positionInWord + 1
if k < blockLength {
return s[i]
}
k -= blockLength
positionInWord++
i++
}
}
return 0
}
Go-Specific Notes
The Go signature uses int64 for k, so all arithmetic involving block lengths is also performed using int64 to avoid unnecessary conversions.
The algorithm itself is identical to the Python version. Since the input contains only lowercase English letters and spaces, indexing the string by byte is safe and efficient.
Worked Examples
Example 1
s = "hello world"
k = 0
Expanded string:
heelllllllooooo woorrrllllddddd
^
Processing:
| Character | Block Length | k Before | Result |
|---|---|---|---|
| h | 1 | 0 | 0 < 1, return 'h' |
Answer:
"h"
Example 2
s = "hello world"
k = 15
Processing the first word:
| Character | Block Length | k Before | k After |
|---|---|---|---|
| h | 1 | 15 | 14 |
| e | 2 | 14 | 12 |
| l | 3 | 12 | 9 |
| l | 4 | 9 | 5 |
| o | 5 | 5 | 0 |
The first word contributes:
1 + 2 + 3 + 4 + 5 = 15
positions.
Next character is the space:
| Character | Block Length | k Before |
|---|---|---|
| space | 1 | 0 |
Since k == 0, return the space.
Answer:
" "
Additional Example
s = "abc"
k = 4
Expansion:
abbccc
Processing:
| Character | Block Length | k Before | k After |
|---|---|---|---|
| a | 1 | 4 | 3 |
| b | 2 | 3 | 1 |
| c | 3 | 1 | inside block |
Answer:
'c'
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each character of the original string is visited once |
| Space | O(1) | Only a few variables are used |
The algorithm never constructs the expanded string. Instead, it directly determines which expansion block contains the requested index. Since every character of the original input is processed at most once, the running time is linear in the length of s, and the extra memory usage remains constant.
Test Cases
sol = Solution()
assert sol.kthCharacter("hello world", 0) == "h" # first example
assert sol.kthCharacter("hello world", 15) == " " # space between words
assert sol.kthCharacter("a", 0) == "a" # single character word
assert sol.kthCharacter("ab", 0) == "a" # first block
assert sol.kthCharacter("ab", 1) == "b" # first occurrence of b
assert sol.kthCharacter("ab", 2) == "b" # second occurrence of b
assert sol.kthCharacter("abc", 3) == "c" # start of c block
assert sol.kthCharacter("abc", 5) == "c" # end of c block
assert sol.kthCharacter("a b", 1) == " " # space handling
assert sol.kthCharacter("a b", 2) == "b" # character after space
assert sol.kthCharacter("zzz", 5) == "z" # repeated same letter
assert sol.kthCharacter("cat dog", 6) == " " # exact boundary at space
assert sol.kthCharacter("abcd", 9) == "d" # last position of expansion
Test Summary
| Test | Why |
|---|---|
"hello world", 0 |
First character of expanded string |
"hello world", 15 |
Position lands on space |
"a", 0 |
Smallest valid input |
"ab", 1 |
Inside repeated block |
"ab", 2 |
End of repeated block |
"abc", 3 |
Beginning of larger block |
"abc", 5 |
End of larger block |
"a b", 1 |
Space handling |
"a b", 2 |
Transition from space to next word |
"zzz", 5 |
Same character repeated many times |
"cat dog", 6 |
Boundary immediately before second word |
"abcd", 9 |
Final valid position |
Edge Cases
Single Character Word
A word of length one expands to itself. A common bug is assuming every character has a repeated block larger than one. In this case the only block length is 1. The implementation naturally handles this because the first character receives a block length of position + 1 = 1.
Target Position Falls on a Space
Spaces are preserved exactly once between words. It is easy to focus only on word expansions and accidentally ignore spaces. The implementation explicitly processes spaces as blocks of length one, allowing positions such as the second example to return ' ' correctly.
Target Position at the End of a Large Repetition Block
Suppose the target is the final occurrence of a repeated character, such as the last 'c' in:
abbccc
The condition:
if k < block_length:
correctly identifies any position inside the block, including its last position. This avoids off-by-one errors that often occur when working with ranges of repeated characters.
Very Large Input String
The original string can contain up to 100000 characters. Building the entire expanded string could require substantially more memory. The optimal solution never creates the expansion and instead processes contribution lengths directly, keeping memory usage constant regardless of the expanded size.