Approach (high level)- Treat each UTF‑8 codepoint as an atomic unit. Parse the bytearray to find each character boundary (lead byte + length), store the start indices (int per char). Then reverse the order of characters in-place by swapping the byte ranges for characters using those indices. Finally, reverse the order of characters inside each word (identified by space 0x20) to produce words in reversed order but with characters preserved in each word.- Note on space: words are separated by single ASCII space (0x20), so it is always a single byte and easy to detect.- This implementation trades O(C) extra integers (C = number of codepoints) for simplicity and correctness. If strict O(1) extra space is required, you can implement an O(1) rotate-swap of variable-length segments (more complex); I describe that trade-off at the end.Code (Python)python
def reverse_words_utf8_inplace(buf: bytearray):
"""
In-place reverse order of words in a UTF-8 encoded mutable bytearray `buf`.
Uses O(C) extra integers where C is number of UTF-8 codepoints.
"""
n = len(buf)
if n == 0:
return
# Helper: given a lead byte, determine UTF-8 length
def utf8_len(b):
if b & 0x80 == 0:
return 1
if b & 0xE0 == 0xC0:
return 2
if b & 0xF0 == 0xE0:
return 3
if b & 0xF8 == 0xF0:
return 4
raise ValueError("Invalid UTF-8 leading byte: %02x" % b)
# 1) Scan and collect character start indices
starts = []
i = 0
while i < n:
b = buf[i]
# lead bytes are bytes that are NOT continuation (0b10xxxxxx)
if (b & 0xC0) == 0x80:
raise ValueError("Invalid UTF-8: unexpected continuation byte at %d" % i)
L = utf8_len(b)
# validate continuation bytes
for j in range(1, L):
if i + j >= n or (buf[i + j] & 0xC0) != 0x80:
raise ValueError("Invalid UTF-8 continuation at %d" % (i + j))
starts.append(i)
i += L
# Build lengths array for convenience (optional)
lengths = []
for idx in starts:
lengths.append(utf8_len(buf[idx]))
C = len(starts)
if C == 0:
return
# 2) Reverse all characters by swapping their byte spans
def swap_spans(a_start, a_len, b_start, b_len):
# simple swap when equal length
if a_len == b_len:
for k in range(a_len):
buf[a_start + k], buf[b_start + k] = buf[b_start + k], buf[a_start + k]
return
# when lengths differ, perform rotation using a small temp buffer up to 4 bytes
# copy a to tempA, b to tempB and perform three writes:
tempA = buf[a_start:a_start + a_len]
tempB = buf[b_start:b_start + b_len]
# move tempB into a_start.., then tempA into the right place
# need careful move because spans may overlap; to be safe, do by creating copies (small)
buf[a_start:a_start + b_len] = tempB
# move middle bytes between a_end and b_start appropriately
mid_start = a_start + b_len
mid_end = b_start + b_len # old end of B
# compute remaining bytes that should shift; easiest approach: reconstruct slice
# full reconstruction of the region from a_start..b_start+b_len using concatenation
region = tempB + buf[a_start + a_len:b_start] + tempA
buf[a_start: b_start + b_len] = region
left = 0
right = C - 1
while left < right:
a_start = starts[left]
a_len = lengths[left]
b_start = starts[right]
b_len = lengths[right]
swap_spans(a_start, a_len, b_start, b_len)
# after swapping, the bytes at these positions correspond to swapped segments,
# but starts/lengths mapping becomes invalid for the inner indices. The easiest
# fix: rebuild starts/lengths for the mutated buffer region.
# For simplicity, after each outer swap we rebuild starts/lengths entirely.
# This still runs in O(C^2) worst case if we rebuild every time; to keep O(C),
# we instead perform all swaps by working on starts/lengths arrays and then
# physically rearranging bytes once. For clarity and correctness in an interview
# setting we will rebuild starts/lengths here (acceptable for moderate sizes).
# Rebuild:
starts = []
lengths = []
i = 0
while i < n:
b = buf[i]
if (b & 0xC0) == 0x80:
raise ValueError("Invalid UTF-8 after swap at %d" % i)
L = utf8_len(b)
starts.append(i)
lengths.append(L)
i += L
C = len(starts)
left += 1
right -= 1
# 3) Now we have characters reversed. Next: within each word, reverse the sequence
# of characters to restore character order inside each word (so words themselves are reversed).
# Identify words by ASCII space (0x20).
# Build list of character indices again (they are current after swaps)
starts = []
lengths = []
i = 0
while i < n:
b = buf[i]
L = utf8_len(b)
starts.append(i)
lengths.append(L)
i += L
C = len(starts)
word_char_indices = [] # indices of characters in current word
def flush_word(indices):
# reverse characters by swapping spans using swap_spans on character-level positions
i = 0
j = len(indices) - 1
while i < j:
a_idx = indices[i]
b_idx = indices[j]
swap_spans(starts[a_idx], lengths[a_idx], starts[b_idx], lengths[b_idx])
# rebuild starts/lengths (simple safe approach)
tmp_starts = []
tmp_lengths = []
k = 0
while k < n:
Lk = utf8_len(buf[k])
tmp_starts.append(k)
tmp_lengths.append(Lk)
k += Lk
nonlocal starts, lengths
starts, lengths = tmp_starts, tmp_lengths
i += 1
j -= 1
# Map from char index to whether it's a space (single byte 0x20)
for ci in range(C):
si = starts[ci]
L = lengths[ci]
if L == 1 and buf[si] == 0x20:
# flush current word
if word_char_indices:
flush_word(word_char_indices)
word_char_indices = []
else:
word_char_indices.append(ci)
if word_char_indices:
flush_word(word_char_indices)
Key points and reasoning- UTF-8 detection: leading bytes are those where top two bits != 10 (i.e. (b & 0xC0) != 0x80). The leading byte pattern identifies length: 0xxxxxxx (1), 110xxxxx (2), 1110xxxx (3), 11110xxx (4). Continuation bytes always match 10xxxxxx.- We must operate on whole codepoints; swapping individual bytes would corrupt multi-byte characters.- This implementation stores character start indices (ints) — extra memory O(C). That avoids very tricky in-place variable-length segment rotation logic and is clearer and safer.- Time complexity: O(n * rebuilds) in the naive rebuild-on-each-swap implementation above; with careful implementation (swap using starts/lengths arrays and updating those arrays instead of rebuilding) you can make it O(n). Space complexity: O(C) extra for starts/lengths (C ≤ n).Edge cases- Empty buffer, leading/trailing spaces, invalid UTF-8 sequences, single-character words.- Single-byte ASCII words work normally.Trade-offs / alternatives- Strict O(1) extra space: you can implement an in-place algorithm that swaps variable-length segments with a sequence of byte-range reversals/rotations (cyclic rotations). That is more complex but possible: swap two non-adjacent segments by reversing three regions (A, middle, B) appropriately. For interview/code-challenge clarity and correctness, the starts/lengths approach is acceptable; mention if O(1) space is mandatory and you want, I can provide the rotation-only version.