LeetCode 249: Group Shifted Strings Solution in Python – A Step-by-Step Guide

Imagine you’re sorting a pile of words where some are just shifted versions of others—like "abc" and "bcd" being related because each letter slides forward by one. That’s the essence of LeetCode 249: Group Shifted Strings! This medium-level problem asks you to group strings that can be transformed into each other by shifting letters, wrapping around the alphabet as needed. Using Python, we’ll explore two solutions: the Best Solution, a hash map with shift normalization, and an Alternative Solution, a brute force comparison method. With detailed examples, clear code, and beginner-friendly breakdowns—especially for the best solution—this guide will help you master string grouping and boost your coding skills. Let’s shift those letters and sort them out!

What Is LeetCode 249: Group Shifted Strings?

In LeetCode 249: Group Shifted Strings, you’re given a list of strings, and your task is to group them into lists where each group contains strings that are "shifted" versions of one another. A string is a shifted version if each character can be transformed into the next by adding the same number (modulo 26) to its position in the alphabet. This problem is a creative twist on string manipulation, related to LeetCode 242: Valid Anagram, but focusing on shifts rather than character counts.

Problem Statement

  • Input: A list of strings strings containing lowercase letters.
  • Output: A list of lists, where each sublist contains strings that are shift-equivalent.
  • Rules: Shifting wraps around (e.g., 'z' + 1 = 'a'); all strings in a group must be the same length.

Constraints

  • List length: 1 to 200.
  • String length: 0 to 100.
  • Characters: Lowercase English letters only.

Examples

  • Input: strings = ["abc", "bcd", "acef", "xyz", "az", "ba", "a", "z"]
    • Output: [["abc", "bcd", "xyz"], ["acef"], ["az", "ba"], ["a"], ["z"]]
  • Input: strings = ["a"]
    • Output: [["a"]]

Understanding the Problem: Grouping by Shifts

To solve LeetCode 249: Group Shifted Strings in Python, we need to group strings that can be aligned by shifting their letters by a consistent amount. For example, "abc" shifts to "bcd" (each letter +1), and "bcd" shifts to "cde" (another +1). The key is to identify a "shift pattern" that’s unique to each group, despite the actual letters. We’ll use two methods: 1. Best Solution (Hash Map with Normalization): O(n * k) time—fast and clever. 2. Alternative Solution (Brute Force Comparison): O(n² * k) time—simple but slow.

Let’s dive into the best solution with extra detail for beginners.

Best Solution: Hash Map with Shift Normalization

Why This Is the Best Solution

The hash map with shift normalization approach is the top pick for LeetCode 249 because it runs in O(n * k) time (where n is the number of strings and k is the max string length) by converting each string into a unique "shift key" and grouping them efficiently. It uses minimal extra space and scales well, making it the go-to solution.

How It Works

Think of this solution as giving each string a secret code based on how its letters shift relative to each other, then putting all strings with the same code in a box together. The trick is to create a code that stays the same no matter where the string starts in the alphabet. Here’s how it works, step-by-step, explained simply:

  • Shift Pattern: For "abc" to "bcd", each letter shifts by 1 ('a'→'b', 'b'→'c', 'c'→'d'). The differences between consecutive letters tell us the pattern.
  • Normalization:
    • Take each string and calculate the difference between adjacent characters.
    • Adjust for wrapping (e.g., 'z' to 'a' is 1, not -25) by adding 26 and taking modulo 26.
    • For "abc": 'b'-'a' = 1, 'c'-'b' = 1 → pattern "1,1".
    • For "bcd": 'c'-'b' = 1, 'd'-'c' = 1 → same "1,1".
  • Hash Map:
    • Use the pattern (e.g., "1,1") as a key in a dictionary.
    • Store all strings with that pattern in a list under that key.
  • Handle Single Letters: For "a" or "z", no differences exist, so use an empty pattern.
  • Group: Each dictionary value is a group of shift-equivalent strings.

It’s like sorting friendship bracelets by their bead patterns—same pattern, same group!

Step-by-Step Example

Example: strings = ["abc", "bcd", "acef", "xyz", "az", "ba", "a", "z"]

  • Process Each String:
    • "abc": Differences = (b-a, c-b) = (1, 1) → key "1,1".
    • "bcd": (c-b, d-c) = (1, 1) → key "1,1".
    • "acef": (c-a, e-c, f-e) = (2, 2, 1) → key "2,2,1".
    • "xyz": (y-x, z-y) = (1, 1) → key "1,1".
    • "az": (z-a) = (25) → key "25".
    • "ba": (a-b) = (-1 + 26 = 25) → key "25".
    • "a": No differences → key "".
    • "z": No differences → key "".
  • Group in Hash Map:
    • "1,1": ["abc", "bcd", "xyz"].
    • "2,2,1": ["acef"].
    • "25": ["az", "ba"].
    • "": ["a", "z"].
  • Result: [["abc", "bcd", "xyz"], ["acef"], ["az", "ba"], ["a", "z"]].

Code with Detailed Line-by-Line Explanation

Here’s the Python code, explained for beginners:

class Solution:
    def groupStrings(self, strings: List[str]) -> List[List[str]]:
        # Step 1: Set up our grouping box (dictionary)
        shift_groups = {}

        # Step 2: Process each string
        for s in strings:
            # Step 3: Create the shift pattern (key)
            if len(s) == 1:
                key = ""  # Single letters have no differences
            else:
                # Calculate differences between consecutive letters
                diffs = []
                for i in range(1, len(s)):
                    # Get ASCII difference, adjust for wrap-around
                    diff = (ord(s[i]) - ord(s[i-1])) % 26
                    diffs.append(str(diff))
                # Join differences into a string key
                key = ",".join(diffs)

            # Step 4: Add string to its group
            if key in shift_groups:
                shift_groups[key].append(s)
            else:
                shift_groups[key] = [s]

        # Step 5: Return all groups
        return list(shift_groups.values())
  • Time Complexity: O(n * k)—processing n strings of max length k.
  • Space Complexity: O(n)—storing all strings in the hash map.

This solution is like a smart librarian sorting books by their secret codes—quick and efficient!

Alternative Solution: Brute Force Comparison

Why an Alternative Approach?

The brute force comparison approach checks every pair of strings to see if they’re shift-equivalent, grouping them manually. It’s slower but shows the concept clearly, making it a great starting point for beginners before tackling the optimized method.

How It Works

  • For each string, compare it with all others by shifting one to match the other.
  • Group strings that match after shifting.

Step-by-Step Example

Example: strings = ["abc", "bcd", "xyz"]

  • Compare "abc":
    • To "bcd": Shift "abc" by 1 → "bcd", matches → group.
    • To "xyz": Shift "abc" by 23 → "xyz", matches → group.
  • Check Others: "bcd" to "xyz" (shift 22) → matches.
  • Result: [["abc", "bcd", "xyz"]].

Code for Brute Force Approach

class Solution:
    def groupStrings(self, strings: List[str]) -> List[List[str]]:
        # Step 1: Helper to check if two strings are shift-equivalent
        def is_shifted(s1, s2):
            if len(s1) != len(s2):
                return False
            if not s1:
                return True
            diff = (ord(s2[0]) - ord(s1[0])) % 26
            for i in range(1, len(s1)):
                if (ord(s2[i]) - ord(s1[i])) % 26 != diff:
                    return False
            return True

        # Step 2: Group strings
        result = []
        used = set()
        for i in range(len(strings)):
            if i in used:
                continue
            group = [strings[i]]
            used.add(i)
            for j in range(i + 1, len(strings)):
                if j not in used and is_shifted(strings[i], strings[j]):
                    group.append(strings[j])
                    used.add(j)
            result.append(group)

        return result
  • Time Complexity: O(n² * k)—comparing all pairs.
  • Space Complexity: O(n)—storing groups.

It’s intuitive but inefficient.

Comparing the Two Solutions

  • Best Solution (Hash Map):
    • Pros: O(n * k) time, scalable, elegant.
    • Cons: Requires pattern understanding.
  • Alternative Solution (Brute Force):
    • Pros: Simple logic, visible steps.
    • Cons: O(n² * k) time, slow for large lists.

Hash map wins for speed.

Additional Examples and Edge Cases

Single Letters

  • ["a", "b"][["a"], ["b"]]

Empty Strings

  • [""][[""]]

Mixed Lengths

  • ["ab", "bc", "a"][["ab", "bc"], ["a"]]

Both handle these well.

Complexity Breakdown

  • Hash Map:
    • Time: O(n * k)—linear processing.
    • Space: O(n)—group storage.
  • Brute Force:
    • Time: O(n² * k)—pairwise checks.
    • Space: O(n)—results.

Hash map is faster.

Key Takeaways for Beginners

  • Normalization: Use differences to identify patterns.
  • Hash Map: Group efficiently with keys.
  • Brute Force: Compare everything—good for learning.
  • Python Tip: ord() unlocks letter math—see [Python Basics](/python/basics).

Final Thoughts: Group Strings Like a Pro

LeetCode 249: Group Shifted Strings in Python is a fun twist on string grouping. The hash map solution offers O(n * k) brilliance, while brute force provides a clear starting point. Want more? Try LeetCode 242: Valid Anagram or LeetCode 438: Find All Anagrams in a String. Ready to shift? Head to Solve LeetCode 249 on LeetCode and group those strings today!