Prerequisites

Before attempting this problem, you should be comfortable with:

  • Hash Maps - Grouping elements by a computed key and efficiently storing/retrieving collections
  • String Manipulation - Iterating through characters and computing relationships between adjacent characters
  • Modular Arithmetic - Handling wrap-around cases (like 'z' shifting to 'a') using modulo 26 operations

1. Hashing

Intuition

Two strings belong to the same shifting sequence if the relative differences between consecutive characters are identical. For example, "abc" and "xyz" both have differences of +1 between each pair of adjacent letters. By computing these differences for each string and using them as a hash key, we can group all strings that share the same shifting pattern together.

The key insight is that we normalize differences using modulo 26 to handle wrap-around cases (like 'z' shifting to 'a'). This way, strings that can be shifted into one another will produce the same hash key.

Algorithm

  1. For each string, compute a hash key by calculating the difference between consecutive characters. Use modulo 26 to handle wrap-around.
  2. Use a hash map where the key is this computed hash and the value is a list of strings that share this pattern.
  3. Iterate through all input strings, compute their hash key, and add each string to the corresponding group in the hash map.
  4. Return all the grouped lists from the hash map.
class Solution:
    def groupStrings(self, strings: List[str]) -> List[List[str]]:

        def get_hash(string: str):
            key = []
            for a, b in zip(string, string[1:]):
                key.append(chr((ord(b) - ord(a)) % 26 + ord('a')))
            return ''.join(key)
        
        # Create a hash value (hash_key) for each string and append the string
        # to the list of hash values i.e. mapHashToList["cd"] = ["acf", "gil", "xzc"]
        groups = collections.defaultdict(list)
        for string in strings:
            hash_key = get_hash(string)
            groups[hash_key].append(string)
        
        # Return a list of all of the grouped strings
        return list(groups.values())

Time & Space Complexity

  • Time complexity: O(NK)O(N \cdot K)
  • Space complexity: O(NK)O(N \cdot K)

Where NN is the length of strings and KK is the maximum length of a string in strings.


Common Pitfalls

Forgetting to Handle Negative Differences with Modulo

When computing the difference between consecutive characters, s[i] - s[i-1] can be negative (e.g., going from 'z' to 'a'). Simply using modulo is not enough in some languages; you must add 26 before taking modulo to ensure a positive result: (s[i] - s[i-1] + 26) % 26. Failing to do this produces different hash keys for strings that should be grouped together.

Not Handling Single-Character Strings

Single-character strings have no consecutive character pairs, producing an empty hash key. All single-character strings should be grouped together since any single character can shift to any other. Ensure your hash function handles this case correctly by returning a consistent empty or default key.

Using Absolute Difference Instead of Directional Difference

The shifting sequence is directional: "az" and "za" have different patterns. Using abs(s[i] - s[i-1]) would incorrectly group these together. The correct approach uses (s[i] - s[i-1] + 26) % 26 to preserve the direction of the shift while handling wrap-around.