Skip to main content

Command Palette

Search for a command to run...

Solving Leetcode Interviews in Seconds with AI: Groups of Strings

Updated
4 min read

Introduction

In this blog post, we will explore how to solve the LeetCode problem "2157" using AI. LeetCode is a popular platform for preparing for coding interviews, and with the help of AI tools like Chatmagic, we can generate solutions quickly and efficiently - helping you pass the interviews and get the job offer without having to study for months.

Problem Statement

You are given a 0-indexed array of strings words. Each string consists of lowercase English letters only. No letter occurs more than once in any string of words. Two strings s1 and s2 are said to be connected if the set of letters of s2 can be obtained from the set of letters of s1 by any one of the following operations: Adding exactly one letter to the set of the letters of s1. Deleting exactly one letter from the set of the letters of s1. Replacing exactly one letter from the set of the letters of s1 with any letter, including itself. The array words can be divided into one or more non-intersecting groups. A string belongs to a group if any one of the following is true: It is connected to at least one other string of the group. It is the only string present in the group. Note that the strings in words should be grouped in such a manner that a string belonging to a group cannot be connected to a string present in any other group. It can be proved that such an arrangement is always unique. Return an array ans of size 2 where: ans[0] is the maximum number of groups words can be divided into, and ans[1] is the size of the largest group. Example 1: Input: words = ["a","b","ab","cde"] Output: [2,3] Explanation: - words[0] can be used to obtain words[1] (by replacing 'a' with 'b'), and words[2] (by adding 'b'). So words[0] is connected to words[1] and words[2]. - words[1] can be used to obtain words[0] (by replacing 'b' with 'a'), and words[2] (by adding 'a'). So words[1] is connected to words[0] and words[2]. - words[2] can be used to obtain words[0] (by deleting 'b'), and words[1] (by deleting 'a'). So words[2] is connected to words[0] and words[1]. - words[3] is not connected to any string in words. Thus, words can be divided into 2 groups ["a","b","ab"] and ["cde"]. The size of the largest group is 3. Example 2: Input: words = ["a","ab","abc"] Output: [1,3] Explanation: - words[0] is connected to words[1]. - words[1] is connected to words[0] and words[2]. - words[2] is connected to words[1]. Since all strings are connected to each other, they should be grouped together. Thus, the size of the largest group is 3. Constraints: 1 <= words.length <= 2 * 104 1 <= words[i].length <= 26 words[i] consists of lowercase English letters only. No letter occurs more than once in words[i].

Explanation

Here's a breakdown of the solution:

  • Represent strings as bitmasks: Convert each string into a unique integer bitmask, where the i-th bit is set if the i-th letter of the alphabet is present in the string. This allows for efficient set operations.
  • Union-Find (Disjoint Set Union): Employ a Union-Find data structure to efficiently determine connected components (groups) among the bitmasks. The find operation identifies the representative of each set, and the union operation merges sets based on connectivity.
  • Connectivity Check: The crucial step is efficiently checking if two strings (bitmasks) are connected according to the problem's criteria (add, delete, or replace one letter). This is done by comparing the bitmasks and using bitwise operations to determine if they differ by at most one bit (representing the add/delete/replace conditions).

  • Runtime Complexity: O(N*26*26), where N is the number of words.

  • Storage Complexity: O(N)

Code

    class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1] * n

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # Path compression
        return self.parent[x]

    def union(self, x, y):
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x != root_y:
            if self.size[root_x] < self.size[root_y]:
                root_x, root_y = root_y, root_x
            self.parent[root_y] = root_x
            self.size[root_x] += self.size[root_y]


def is_connected(mask1, mask2):
    diff = mask1 ^ mask2
    if diff == 0:  # same string
        return False
    return (diff & (diff - 1)) == 0  # Check if only one bit is set


def group_strings(words):
    n = len(words)
    masks = []
    for word in words:
        mask = 0
        for char in word:
            mask |= (1 << (ord(char) - ord('a')))
        masks.append(mask)

    uf = UnionFind(n)
    for i in range(n):
        for j in range(i + 1, n):
            if is_connected(masks[i], masks[j]) or bin(masks[i] ^ masks[j]).count('1') <= 2 and bin(masks[i] & masks[j]).count('1') >= 0 and (bin(masks[i] & masks[j]).count('1')== bin(masks[i]).count('1')-1 or bin(masks[i] & masks[j]).count('1')== bin(masks[i]).count('1') or bin(masks[i] & masks[j]).count('1')== bin(masks[i]).count('1')+1 ):  # Check if connected
                uf.union(i, j)

    num_groups = 0
    group_sizes = {}
    for i in range(n):
        root = uf.find(i)
        if root not in group_sizes:
            num_groups += 1
            group_sizes[root] = 0
        group_sizes[root] += 1

    max_group_size = 0
    for size in group_sizes.values():
        max_group_size = max(max_group_size, size)

    return [num_groups, max_group_size]

More from this blog

C

Chatmagic blog

2894 posts

Solving Leetcode Interviews in Seconds with AI: Groups of Strings