Talentd
FleetCode
Make and follow sheets.Sign in

Expressive Words solution and explanation

MediumArrayStringTwo Pointers8 min read46.8% acceptanceUpdated 9 Apr 2026
Leetcode - 809 - Expressive WordsAlgo Process · 15:11 · 7K viewsWatch on YouTube
9 more videos on this problem
  1. Leetcode - 809 - Expressive WordsAlgo Process · 15:11 · 7K views
  2. LeetCode 809. Expressive Words, mockingeazyleet · 26:32 · 2K views
  3. LeetCode 809. Expressive Words - Python刷题codingya · 11:20 · 1K views
  4. Code Review: LeetCode 809: Expressive WordsRoel Van de Paar · 2:59 · 330 views
  5. Expressive WordsOwen Wu · 14:40 · 190 views
  6. Expressive WordsMeso Jab · 0:08 · 77 views
  7. [LIVE] Leetcode 809 Expressive Wordscookedcoder67 · 21:10 · 50 views
  8. No Sound Leetcode 809: Expressive WordsExtremeProgrammer · 3:07 · 49 views
  9. LeetCode Live - 809. Expressive WordsNate · 21:41 · 40 views
  10. Expressive Words (LeetCode)Daiki - Road to $400K - · 26:39 · 36 views

The problem

Sometimes people repeat letters to represent extra feeling. For example:

  • "hello" -> "heeellooo"
  • "hi" -> "hiiii"

In these strings like "heeellooo", we have groups of adjacent letters that are all the same: "h", "eee", "ll", "ooo".

You are given a string s and an array of query strings words. A query word is stretchy if it can be made to be equal to s by any number of applications of the following extension operation: choose a group consisting of characters c, and add some number of characters c to the group so that the size of the group is three or more.

  • For example, starting with "hello", we could do an extension on the group "o" to get "hellooo", but we cannot get "helloo" since the group "oo" has a size less than three. Also, we could do another extension like "ll" -> "lllll" to get "helllllooo". If s = "helllllooo", then the query word "hello" would be stretchy because of these two extension operations: query = "hello" -> "hellooo" -> "helllllooo" = s.

Return the number of query strings that are stretchy.

Example 1:

Input: s = "heeellooo", words = ["hello", "hi", "helo"]
Output: 1
Explanation: 
We can extend "e" and "o" in the word "hello" to get "heeellooo".
We can't extend "helo" to get "heeellooo" because the group "ll" is not size 3 or more.

Example 2:

Input: s = "zzzzzyyyyy", words = ["zzyy","zy","zyy"]
Output: 3

Constraints:

  • 1 <= s.length, words.length <= 100
  • 1 <= words[i].length <= 100
  • s and words[i] consist of lowercase letters.

Approach overview

Problem Overview: You get a base string s and a list of words. A word is considered expressive if you can stretch some character groups in the word so it becomes exactly s. Stretching means repeating characters in a group to length ≥ 3 without changing the character order. The task is to count how many words can transform into s.

Approach 1: Two-Pointer Approach with Grouping (O(n + totalWordsLength) time, O(1) space)

This approach scans both strings using two pointers. The idea is to compare groups of the same character instead of individual characters. Move pointers through s and the candidate word, count the length of each consecutive group (for example "eee" vs "e"). If the characters differ, the word cannot match. If the counts are equal, the group matches directly. If the count in s is ≥ 3 and larger than the word's group, stretching is allowed. Otherwise the word is invalid. This method processes each character once, making it efficient for large inputs and a natural fit for problems involving grouped characters in string processing.

The key insight: only groups with length ≥ 3 in s can absorb extra characters. Smaller groups must match exactly. Iterating with grouped counts avoids unnecessary comparisons and keeps the algorithm linear.

Approach 2: Regex-Based Matching (O(n + totalWordsLength) average time, O(n) space)

Another option converts s into a flexible regular expression. Each character group in s becomes a regex pattern: groups with length ≥ 3 allow a range like e{1,3} or more flexible repetition, while shorter groups require exact counts. After building the regex pattern, test every word using a regex match. This approach leverages the built-in regex engine instead of manually implementing group comparisons.

The regex method is concise and expressive but introduces overhead from pattern compilation and matching. Regex engines also hide the internal complexity, which may lead to slower performance compared to a direct algorithm. It’s still a practical solution when readability is preferred or when working in languages with strong regex support.

Recommended for interviews: The two-pointer grouping solution is what interviewers usually expect. It demonstrates control over array-style traversal, careful handling of character groups, and linear-time reasoning. The regex approach shows creativity but relies on library behavior rather than algorithm design. Showing the grouping idea first and implementing it with two pointers signals strong problem-solving skills.

Approach 1: Two-Pointer Approach with Grouping

This approach involves breaking down the target string 's' and each candidate word into groups of consecutive same characters. We then compare these groups using two pointers, ensuring that a word can be stretched to match 's' by checking the compatibility of their respective character groups.

def expressive_words(s, words):
    def get_char_groups(word):
        groups = []
        last = word[0]
        count = 0
        for char in word:
            if char == last:
                count += 1
            else:
                groups.append((last, count))
                last = char
                count = 1
        groups.append((last, count))
        return groups

    def is_stretchy(s_group, word_group):
        if len(s_group) != len(word_group):
            return False
        for (s_char, s_count), (w_char, w_count) in zip(s_group, word_group):
            if s_char != w_char:
                return False
            if s_count < w_count:
                return False
            if s_count < 3 and s_count != w_count:
                return False
        return True

    s_group = get_char_groups(s)
    result = 0
    for word in words:
        word_group = get_char_groups(word)
        if is_stretchy(s_group, word_group):
            result += 1
    return result
function expressiveWords(s, words) {
    const getCharGroups = (word) => {
        const groups = [];
        let last = word[0];
        let count = 0;
        for (let char of word) {
            if (char === last) {
                count++;
            } else {
                groups.push([last, count]);
                last = char;
                count = 1;
            }
        }
        groups.push([last, count]);
        return groups;
    };

    const isStretchy = (sGroup, wordGroup) => {
        if (sGroup.length !== wordGroup.length) return false;
        for (let i = 0; i < sGroup.length; i++) {
            const [sChar, sCount] = sGroup[i];
            const [wChar, wCount] = wordGroup[i];
            if (sChar !== wChar) return false;
            if (sCount < wCount) return false;
            if (sCount < 3 && sCount !== wCount) return false;
        }
        return true;
    };

    const sGroup = getCharGroups(s);
    let result = 0;
    for (let word of words) {
        const wordGroup = getCharGroups(word);
        if (isStretchy(sGroup, wordGroup)) {
            result++;
        }
    }
    return result;
}

The Python solution defines a helper function, get_char_groups, to break a word into groups of consecutive identical characters. The function returns a list of tuples, each containing a character and its count. A second helper function, is_stretchy, compares the character groups of 's' and a word. This function checks if each group of 's' can be formed by stretching the word's corresponding group, ensuring the conditions of stretchy transformations are adhered to. For each word in words, the solution compares it with 's' and increments the count of stretchy words if the conditions are met.

Complexity

Time Complexity: O(N * M), where N is the length of the string 's' and M is the total length of all words combined. Space Complexity: O(N + M) for the storage of character groups.

Try this approach in the editor

Approach 2: Regex-Based Matching

This approach leverages regular expressions to match the pattern of the original word 's' with each word. By converting 's' into a regex pattern, we can efficiently determine if a word can be transformed into 's'.

import java.util.regex.*;

public class Solution {
    public int expressiveWords(String S, String[] words) {
        String pattern = getPattern(S);
        int result = 0;
        for (String word: words) {
            if (isStretchy(word, pattern))
                result++;
        }
        return result;
    }

    private String getPattern(String s) {
        StringBuilder pattern = new StringBuilder();
        int i = 0;
        while (i < s.length()) {
            char c = s.charAt(i);
            int start = i;
            while (i < s.length() && s.charAt(i) == c)
                i++;
            int count = i - start;
            if (count > 2) {
                pattern.append(c).append("{1,").append(count).append("}");
            } else {
                pattern.append(c).append("{").append(count).append("}");
            }
        }
        return pattern.toString();
    }

    private boolean isStretchy(String word, String pattern) {
        return Pattern.matches(pattern, word);
    }
}
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Solution {
    public int ExpressiveWords(string S, string[] words) {
        var pattern = GetPattern(S);
        int result = 0;
        foreach (var word in words) {
            if (IsStretchy(word, pattern))
                result++;
        }
        return result;
    }

    private string GetPattern(string s) {
        var groupedPattern = new List<string>();
        int i = 0;
        while (i < s.Length) {
            char c = s[i];
            int start = i;
            while (i < s.Length && s[i] == c)
                i++;
            int length = i - start;
            if (length > 2) {
                groupedPattern.Add(c + "{1," + length + "}");
            }
            else {
                groupedPattern.Add(c + "{" + length + "}");
            }
        }
        return string.Join(string.Empty, groupedPattern);
    }

    private bool IsStretchy(string word, string pattern) {
        return Regex.IsMatch(word, "^" + pattern + "$");
    }
}

The Java solution uses a similar regex-based approach to match stretchy words. The pattern is dynamically constructed to allow flexibility in character repetition for groups in 's' that can be extended. Each word is tested against the regex pattern to determine if it can transform into 's'.

Complexity

Time Complexity: O(N + L*W), where L is the maximum length of a word and W is the number of words. Space Complexity: O(N) for the regex pattern used.

Try this approach in the editor

Approach 3: Traversal Counting + Two Pointers

We can traverse the array words, and for each word t in the array, check if t can be expanded to obtain s. If it can, increment the answer by one.

Therefore, the key problem is to determine if the word t can be expanded to obtain s. We use a function check(s, t) to determine this. The implementation logic of the function is as follows:

First, check the length relationship between s and t. If the length of t is greater than s, return false directly. Otherwise, use two pointers i and j to point to s and t, respectively, both initially set to 0.

If the characters pointed to by i and j are different, then t cannot be expanded to obtain s, and we return false. Otherwise, we need to check the relationship between the consecutive occurrence counts of the characters pointed to by i and j, denoted as c_1 and c_2. If c_1 \lt c_2 or c_1 \lt 3 and c_1 neq c_2, then t cannot be expanded to obtain s, and we return false. Otherwise, move i and j to the right by c_1 and c_2 times, respectively, and continue checking.

If both i and j reach the end of the strings, then t can be expanded to obtain s, and we return true. Otherwise, we return false.

The time complexity is O(n times m + sum_{i=0}^{m-1} w_i), where n and m are the lengths of the string s and the array words, respectively, and w_i is the length of the i-th word in the array words.

class Solution {
public:
    int expressiveWords(string s, vector<string>& words) {
        auto check = [](string& s, string& t) -> int {
            int m = s.size(), n = t.size();
            if (n > m) return 0;
            int i = 0, j = 0;
            while (i < m && j < n) {
                if (s[i] != t[j]) return 0;
                int k = i;
                while (k < m && s[k] == s[i]) ++k;
                int c1 = k - i;
                i = k, k = j;
                while (k < n && t[k] == t[j]) ++k;
                int c2 = k - j;
                j = k;
                if (c1 < c2 || (c1 < 3 && c1 != c2)) return 0;
            }
            return i == m && j == n;
        };

        int ans = 0;
        for (string& t : words) ans += check(s, t);
        return ans;
    }
};
func expressiveWords(s string, words []string) (ans int) {
	check := func(s, t string) bool {
		m, n := len(s), len(t)
		if n > m {
			return false
		}
		i, j := 0, 0
		for i < m && j < n {
			if s[i] != t[j] {
				return false
			}
			k := i
			for k < m && s[k] == s[i] {
				k++
			}
			c1 := k - i
			i, k = k, j
			for k < n && t[k] == t[j] {
				k++
			}
			c2 := k - j
			j = k
			if c1 < c2 || (c1 != c2 && c1 < 3) {
				return false
			}
		}
		return i == m && j == n
	}
	for _, t := range words {
		if check(s, t) {
			ans++
		}
	}
	return ans
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Two-Pointer Approach with Grouping

Time Complexity: O(N * M), where N is the length of the string 's' and M is the total length of all words combined. Space Complexity: O(N + M) for the storage of character groups.

Regex-Based Matching

Time Complexity: O(N + L*W), where L is the maximum length of a word and W is the number of words. Space Complexity: O(N) for the regex pattern used.

Traversal Counting + Two Pointers–

Complexity analysis

ApproachTimeSpaceWhen to Use
Two-Pointer Approach with GroupingO(n + totalWordsLength)O(1)Best general solution. Efficient linear scan with explicit control over character groups.
Regex-Based MatchingO(n + totalWordsLength) averageO(n)Useful when regex support is strong and implementation speed matters more than raw performance.

Frequently asked questions

Is Expressive Words easy or hard?

Expressive Words is considered a medium-level problem. The difficulty comes from correctly handling grouped characters and the rule that only groups of length three or more can stretch. Once the grouping insight is clear, the implementation becomes straightforward.

Expressive Words Python/Java solution

Python and Java solutions typically implement the two-pointer grouping technique. Iterate through both strings, count consecutive characters, and verify the stretching conditions. The same logic also works in JavaScript, C#, and other languages with simple loops.

How to solve Expressive Words in O(n)?

Process character groups instead of individual characters. Use two pointers to walk through the base string and the candidate word while counting consecutive characters. A word matches if each group either has the same length or the base string group has length ≥ 3 and is longer than the word’s group.

What is the best approach for Expressive Words?

The two-pointer grouping approach is the most efficient and commonly expected solution. It compares groups of identical characters in the base string and each word while validating stretch rules. The algorithm runs in O(n + total word length) time and O(1) extra space, making it optimal for interview settings.

Is Expressive Words asked at Google/Amazon/Meta?

Expressive Words appears in coding interview preparation sets for companies like Google and Meta because it tests string processing, group counting, and careful edge-case handling. The problem evaluates whether candidates can design linear-time algorithms using two pointers.

What data structure is used in Expressive Words?

The solution mainly uses two pointers and group counting over strings. No complex data structures are required—just sequential traversal and counters for character groups. This makes it a classic string and two-pointer problem.

What is the time complexity of Expressive Words?

The optimal solution runs in O(n + totalWordsLength) time, where n is the length of the base string and totalWordsLength is the sum of lengths of all words. Each character in both strings is processed at most once when counting character groups. Space complexity is O(1) for the two-pointer approach.

Comments

Now write it yourselfClose this page and solve Expressive Words in the editor, without looking.
Practise Expressive Words