IMO this is no small part of why C still has its reputation for speed. Think how you'd write this in C. Maybe
/*
Recursively removes doublets from input. A doublet is a pair of adjacent chars,
identical or differing only in capitalization (eg. "aa" or "aA"). The result is
written to output, which must be at least input_length bytes long.
Returns the number of bytes in the result.
*/
size_t remove_doublets(char * output, const char * input, size_t input_length);
and the body is a dozen lines of pointer pushing and tolower. Do you faff around with malloc? unicode case conversions? hashmaps??? Of course not, that would be too hard, so you just write the stupid C code. For all its faults, the C idiom really does push you towards the simple, stupid answer to code like this.