Facebook Pixel

3773. Maximum Number of Equal Length Runs πŸ”’

MediumHash TableStringCounting
LeetCode β†—

Problem Description

You are given a string s consisting of lowercase English letters.

A run in s is a non-empty substring made up of equal letters that cannot be extended any further (meaning the characters immediately before and after the run are different or don't exist). For example, the runs in "hello" are "h", "e", "ll", and "o".

You are allowed to select runs that all have the same length in s.

Your task is to return an integer representing the maximum number of runs you can select in s.

In other words, you need to group all the runs by their length, and find which length appears the most often. The count of runs sharing that most common length is your answer.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Hash Table / Counting?

This problem maps to Hash Table / Counting through a short path in the full flowchart.

Fastlookup orcounting?yesLinkedlist?noHash Table /Counting

Counting frequencies of run lengths across the string to find the most common length maps directly to hash table counting.

Open in Flowchart

Intuition

The key observation is that we need to find runs sharing the same length and pick as many of them as possible. Since selected runs must all have an identical length, the problem naturally turns into a counting question: among all the runs in s, which specific length appears most frequently?

To solve this, we first need to break the string into its individual runs. A run ends whenever the current character differs from the next one, so we can scan through s and group together consecutive equal characters. Each such group is one run, and we record its length.

Once we know the length of every run, the answer becomes clear. If we count how many runs there are for each possible length, the largest of these counts tells us the maximum number of runs we can select. This is because choosing the most common length gives us the most runs that share an equal length.

So the whole idea reduces to two simple steps: find the length of each run, then count occurrences of each length and return the highest count.

Solution Approach

Solution 1: Hash Table

We use a hash table cnt to record the number of occurrences of each run length. We traverse the string s, and for each run, we calculate its length m and increment cnt[m] by 1. Finally, the answer is the maximum value in cnt.

In the implementation, we rely on the groupby function to split the string into consecutive groups of equal characters. Each group produced by groupby corresponds to exactly one run. For every group g, we compute its length using len(list(g)), which gives us the run length m, and then we do cnt[m] += 1 to count how many runs have that length.

After processing all runs, the hash table cnt holds the frequency of each distinct run length. We simply return max(cnt.values()), which is the largest count and therefore the maximum number of runs that share the same length.

The time complexity is O(n), and the space complexity is O(n), where n is the length of the string s. This is because we scan the string once and the hash table stores at most O(n) distinct run lengths.

Example Walkthrough

Let's trace through the solution approach using the string s = "aaabbbccd".

Step 1: Break the string into runs using groupby

Scanning through s and grouping consecutive equal characters, we get the following runs:

RunCharactersLength m
1"aaa"3
2"bbb"3
3"cc"2
4"d"1

So the string splits into 4 runs with lengths [3, 3, 2, 1].

Step 2: Count occurrences of each length in the hash table cnt

As we process each run, we increment cnt[m] by 1:

  • Run "aaa" β†’ length 3 β†’ cnt = {3: 1}
  • Run "bbb" β†’ length 3 β†’ cnt = {3: 2}
  • Run "cc" β†’ length 2 β†’ cnt = {3: 2, 2: 1}
  • Run "d" β†’ length 1 β†’ cnt = {3: 2, 2: 1, 1: 1}

Step 3: Return the maximum count

Now the hash table tells us how many runs share each length:

  • Length 3 appears 2 times
  • Length 2 appears 1 time
  • Length 1 appears 1 time

The largest value among cnt.values() is 2, corresponding to length 3.

Result: We can select the two runs "aaa" and "bbb" (both of length 3), giving us a maximum of 2 runs. So the answer is max(cnt.values()) = 2.

This confirms the core idea: by grouping runs by length and picking the most frequent length, we maximize the number of equal-length runs we can select.

Solution Implementation

1from collections import Counter
2from itertools import groupby
3
4
5class Solution:
6    def maxSameLengthRuns(self, s: str) -> int:
7        # Counter to record how many runs share the same length
8        # key   -> length of a consecutive run of identical characters
9        # value -> number of runs that have this length
10        length_count = Counter()
11
12        # groupby splits the string into consecutive groups of identical chars.
13        # The first returned value (the character) is ignored with "_",
14        # and "group" is the iterator over that run's characters.
15        for _, group in groupby(s):
16            run_length = len(list(group))  # compute the length of this run
17            length_count[run_length] += 1  # tally one more run of this length
18
19        # Return the highest frequency among all run lengths,
20        # i.e. the maximum number of runs that share the same length.
21        return max(length_count.values())
22
1class Solution {
2    /**
3     * Finds the maximum number of consecutive-character runs that share the
4     * same length within the given string.
5     *
6     * Approach:
7     *   1. Scan the string and split it into maximal "runs" of identical
8     *      characters (e.g. "aaabb" -> runs "aaa" and "bb").
9     *   2. For each run, record its length in a frequency map.
10     *   3. Track the highest frequency seen for any single length, which is
11     *      the answer.
12     *
13     * @param s the input string
14     * @return the maximum count of runs having the same length
15     */
16    public int maxSameLengthRuns(String s) {
17        // Maps a run length -> how many runs of that length have appeared.
18        Map<Integer, Integer> lengthCount = new HashMap<>();
19        int maxRuns = 0;
20        int length = s.length();
21
22        // Iterate over the string, advancing run by run.
23        for (int start = 0; start < length; ) {
24            // Extend 'end' while the characters stay the same as s[start].
25            int end = start + 1;
26            while (end < length && s.charAt(end) == s.charAt(start)) {
27                ++end;
28            }
29
30            // Compute the current run's length.
31            int runLength = end - start;
32
33            // Increment the count for this run length and update the maximum.
34            int updatedCount = lengthCount.merge(runLength, 1, Integer::sum);
35            maxRuns = Math.max(maxRuns, updatedCount);
36
37            // Move to the start of the next run.
38            start = end;
39        }
40
41        return maxRuns;
42    }
43}
44
1class Solution {
2public:
3    int maxSameLengthRuns(string s) {
4        // Map from run length -> number of runs that have exactly this length
5        unordered_map<int, int> lengthCount;
6
7        // Tracks the maximum number of runs sharing the same length
8        int maxRuns = 0;
9
10        int n = s.size();
11
12        // Iterate over the string, processing one consecutive run at a time
13        for (int start = 0; start < n;) {
14            // Advance 'end' until the character differs from s[start]
15            int end = start + 1;
16            while (end < n && s[end] == s[start]) {
17                ++end;
18            }
19
20            // Length of the current run of identical characters
21            int runLength = end - start;
22
23            // Increment the count for this run length and update the answer
24            maxRuns = max(maxRuns, ++lengthCount[runLength]);
25
26            // Move to the start of the next run
27            start = end;
28        }
29
30        return maxRuns;
31    }
32};
33
1/**
2 * Finds the maximum number of consecutive-character "runs" that share
3 * the same length within the string.
4 *
5 * A "run" is a maximal block of identical consecutive characters.
6 * For example, in "aabbb" there are two runs: "aa" (length 2) and "bbb" (length 3).
7 *
8 * @param s - The input string to scan.
9 * @returns The highest frequency among all run lengths.
10 */
11function maxSameLengthRuns(s: string): number {
12    // Maps a run length to how many runs have that exact length.
13    const lengthCount: Record<number, number> = {};
14    const length = s.length;
15    let answer = 0;
16
17    // Iterate over the string; `start` marks the beginning of each run.
18    for (let start = 0; start < length; ) {
19        // Advance `end` until the character differs, capturing the full run.
20        let end = start + 1;
21        while (end < length && s[end] === s[start]) {
22            ++end;
23        }
24
25        // Compute the current run's length.
26        const runLength = end - start;
27
28        // Record this run length and update its frequency.
29        lengthCount[runLength] = (lengthCount[runLength] || 0) + 1;
30
31        // Track the maximum frequency seen so far.
32        answer = Math.max(answer, lengthCount[runLength]);
33
34        // Jump to the start of the next run.
35        start = end;
36    }
37
38    return answer;
39}
40

Time and Space Complexity

  • Time Complexity: O(n), where n is the length of the string s. The groupby function iterates through the entire string once to form consecutive groups, which takes O(n) time. For each group, len(list(g)) consumes the group's elements, and the total number of elements consumed across all groups equals n, so this also amounts to O(n) in total. Finally, max(cnt.values()) traverses the counter, whose size is at most the number of groups (bounded by n), taking O(n) time. Therefore, the overall time complexity is O(n).

  • Space Complexity: O(n), where n is the length of the string s. The Counter object cnt stores the frequencies of distinct run lengths, and in the worst case (e.g., all distinct run lengths) it can hold up to O(n) entries. Additionally, list(g) temporarily holds the elements of a single group, which is also bounded by O(n). Hence, the overall space complexity is O(n).

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Calling max() on an Empty Counter

The most common mistake is assuming length_count is always non-empty when calling max(length_count.values()). If the input string s is empty (""), then groupby produces no groups, leaving the Counter empty. Calling max() on an empty sequence raises a ValueError: max() arg is an empty sequence, crashing the program.

Solution: Guard against the empty case by either checking the Counter first or providing a default value to max():

from collections import Counter
from itertools import groupby


class Solution:
    def maxSameLengthRuns(self, s: str) -> int:
        length_count = Counter()
        for _, group in groupby(s):
            run_length = len(list(group))
            length_count[run_length] += 1

        # Use default=0 so an empty string safely returns 0
        return max(length_count.values(), default=0)

Pitfall 2: Exhausting the groupby Iterator Before Measuring It

groupby yields lazy iterators for each group, and these iterators share the same underlying position in the string. A subtle bug arises if you try to inspect the group in a way that advances it, or if you reorder operations. For example, the following loses the data:

for char, group in groupby(s):
    # WRONG: advancing the outer groupby invalidates the inner group iterator
    if some_condition(char):
        continue
    run_length = len(list(group))  # may now be 0 or incomplete

If you advance the outer groupby (e.g., by continuing to the next iteration) before consuming group, the inner iterator becomes invalid. The same problem occurs if you store all groups in a list first:

groups = list(groupby(s))          # stores (char, iterator) pairs
for char, group in groups:
    run_length = len(list(group))  # WRONG: all iterators are now empty -> length 0

Solution: Always consume each group iterator immediately within the loop body before moving to the next group. The original solution does this correctly with len(list(group)) right inside the loop. If you must materialize groups, capture their lengths at iteration time:

# Correct: measure length immediately while the iterator is still valid
run_lengths = [len(list(group)) for _, group in groupby(s)]
length_count = Counter(run_lengths)

Pitfall 3: Manual Run-Counting Off-by-One Errors

Developers who avoid groupby and write a manual scan often mishandle the last run. A typical loop increments a counter while characters match and finalizes a run only when a different character appearsβ€”forgetting to finalize the final run after the loop ends:

# WRONG: the last run is never recorded
count = 1
for i in range(1, len(s)):
    if s[i] == s[i - 1]:
        count += 1
    else:
        length_count[count] += 1
        count = 1
# missing: length_count[count] += 1  for the final run

Solution: After the loop, explicitly record the trailing run (and handle the empty-string edge case):

def maxSameLengthRuns(self, s: str) -> int:
    if not s:
        return 0
    length_count = Counter()
    count = 1
    for i in range(1, len(s)):
        if s[i] == s[i - 1]:
            count += 1
        else:
            length_count[count] += 1
            count = 1
    length_count[count] += 1  # finalize the last run
    return max(length_count.values())

Using groupby is preferable precisely because it eliminates these boundary-handling errors automatically.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More