questions F-L
This commit is contained in:
188
backend/data/questions/greatest-common-divisor-of-strings.yaml
Normal file
188
backend/data/questions/greatest-common-divisor-of-strings.yaml
Normal file
@@ -0,0 +1,188 @@
|
||||
title: Greatest Common Divisor of Strings
|
||||
slug: greatest-common-divisor-of-strings
|
||||
difficulty: easy
|
||||
leetcode_id: 1071
|
||||
leetcode_url: https://leetcode.com/problems/greatest-common-divisor-of-strings/
|
||||
categories:
|
||||
- strings
|
||||
- math
|
||||
patterns:
|
||||
- greedy
|
||||
|
||||
description: |
|
||||
For two strings `s` and `t`, we say "`t` divides `s`" if and only if `s = t + t + t + ... + t` (i.e., `t` is concatenated with itself one or more times).
|
||||
|
||||
Given two strings `str1` and `str2`, return *the largest string* `x` *such that* `x` *divides both* `str1` *and* `str2`.
|
||||
|
||||
constraints: |
|
||||
- `1 <= str1.length, str2.length <= 1000`
|
||||
- `str1` and `str2` consist of English uppercase letters.
|
||||
|
||||
examples:
|
||||
- input: 'str1 = "ABCABC", str2 = "ABC"'
|
||||
output: '"ABC"'
|
||||
explanation: '"ABC" divides both strings. "ABCABC" = "ABC" + "ABC" and "ABC" = "ABC".'
|
||||
- input: 'str1 = "ABABAB", str2 = "ABAB"'
|
||||
output: '"AB"'
|
||||
explanation: '"AB" divides both strings. "ABABAB" = "AB" + "AB" + "AB" and "ABAB" = "AB" + "AB".'
|
||||
- input: 'str1 = "LEET", str2 = "CODE"'
|
||||
output: '""'
|
||||
explanation: "There is no string that divides both str1 and str2."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
This problem cleverly connects string manipulation to a fundamental mathematical concept: the **Greatest Common Divisor (GCD)**.
|
||||
|
||||
Think of it like this: if a string `x` can "divide" both `str1` and `str2`, then `x` repeated some number of times equals `str1`, and `x` repeated another number of times equals `str2`. This is exactly analogous to how a number `d` divides both `a` and `b` if `a = d * m` and `b = d * n` for some integers `m` and `n`.
|
||||
|
||||
The key insight is that **if a common divisor string exists, the length of the GCD string must be the GCD of the two string lengths**. Why? Because if `x` divides both strings, then `len(str1)` must be a multiple of `len(x)` and `len(str2)` must be a multiple of `len(x)`. The *largest* such length is exactly `gcd(len(str1), len(str2))`.
|
||||
|
||||
But there's one more critical check: **not all string pairs have a common divisor**. For example, `"LEET"` and `"CODE"` have no common divisor because they're fundamentally incompatible. The elegant way to check compatibility is: if `str1 + str2 == str2 + str1`, then a common divisor exists. If the strings are "made of the same building block," the order of concatenation doesn't matter.
|
||||
|
||||
approach: |
|
||||
We solve this using a **GCD-based Approach**:
|
||||
|
||||
**Step 1: Check if a common divisor exists**
|
||||
|
||||
- Concatenate `str1 + str2` and `str2 + str1`
|
||||
- If these are not equal, the strings have no common divisor — return an empty string
|
||||
- This check works because if both strings are built from the same repeating pattern, the order of concatenation won't matter
|
||||
|
||||
|
||||
|
||||
**Step 2: Calculate the GCD of the string lengths**
|
||||
|
||||
- Use the Euclidean algorithm to find `gcd(len(str1), len(str2))`
|
||||
- This gives us the length of the largest possible common divisor string
|
||||
|
||||
|
||||
|
||||
**Step 3: Return the GCD string**
|
||||
|
||||
- Return the prefix of `str1` (or `str2`) with length equal to the GCD
|
||||
- Since we've verified compatibility in Step 1, this prefix is guaranteed to divide both strings
|
||||
|
||||
|
||||
|
||||
The mathematical foundation makes this solution both elegant and efficient — we avoid brute-force checking of all possible divisor strings.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Brute Force All Prefixes
|
||||
description: |
|
||||
A naive approach might try every possible prefix of the shorter string and check if it divides both strings. For each candidate prefix of length `k`, you'd verify if `str1` and `str2` are composed entirely of that prefix.
|
||||
|
||||
While correct, this is unnecessarily slow. With strings up to length 1000, and checking each prefix by iterating through both strings, you could do up to O(n^2) work.
|
||||
|
||||
The GCD approach reduces this to O(n) string concatenation checks plus O(log(min(n, m))) for the GCD calculation.
|
||||
wrong_approach: "Try every prefix and check divisibility"
|
||||
correct_approach: "Use mathematical GCD on lengths after compatibility check"
|
||||
|
||||
- title: Forgetting the Compatibility Check
|
||||
description: |
|
||||
You might be tempted to just compute `gcd(len(str1), len(str2))` and return that prefix. But this fails for cases like `str1 = "LEET"`, `str2 = "CODE"`.
|
||||
|
||||
The GCD of 4 and 4 is 4, but `"LEET"` does not equal `"CODE"` — there's no common divisor string at all! The `str1 + str2 == str2 + str1` check catches this: `"LEETCODE"` ≠ `"CODELEET"`.
|
||||
wrong_approach: "Just return str1[:gcd(len(str1), len(str2))]"
|
||||
correct_approach: "First verify str1 + str2 == str2 + str1"
|
||||
|
||||
- title: Checking Wrong String for Prefix
|
||||
description: |
|
||||
After finding the GCD length, some might try to construct the result by repeating characters or using complex logic. Simply take a prefix of either string — since we've verified they're compatible, both strings start with the same pattern.
|
||||
wrong_approach: "Complex construction of the result string"
|
||||
correct_approach: "Return str1[:gcd_length] directly"
|
||||
|
||||
key_takeaways:
|
||||
- "**Mathematical insight**: String divisibility mirrors integer divisibility — the GCD concept transfers directly"
|
||||
- "**Compatibility check first**: The `str1 + str2 == str2 + str1` test elegantly verifies that a common pattern exists"
|
||||
- "**Euclidean algorithm**: The GCD of two numbers can be computed efficiently in O(log(min(a, b))) time"
|
||||
- "**Pattern recognition**: Look for mathematical analogies when problems involve repetition or divisibility"
|
||||
|
||||
time_complexity: "O(n + m). We perform two string concatenations of total length `n + m`, one equality check of length `n + m`, and a GCD calculation in O(log(min(n, m)))."
|
||||
space_complexity: "O(n + m). We create two concatenated strings of length `n + m` for the compatibility check."
|
||||
|
||||
solutions:
|
||||
- approach_name: GCD of Lengths
|
||||
is_optimal: true
|
||||
code: |
|
||||
from math import gcd
|
||||
|
||||
def gcd_of_strings(str1: str, str2: str) -> str:
|
||||
# Check if a common divisor pattern exists
|
||||
# If both strings are made of the same repeating unit,
|
||||
# concatenation order doesn't matter
|
||||
if str1 + str2 != str2 + str1:
|
||||
return ""
|
||||
|
||||
# The GCD string length must be the GCD of both lengths
|
||||
gcd_length = gcd(len(str1), len(str2))
|
||||
|
||||
# Return the prefix of that length
|
||||
return str1[:gcd_length]
|
||||
explanation: |
|
||||
**Time Complexity:** O(n + m) — String concatenation and comparison dominate.
|
||||
|
||||
**Space Complexity:** O(n + m) — For the concatenated strings.
|
||||
|
||||
This elegant solution leverages the mathematical relationship between string divisibility and integer GCD. The concatenation equality check `str1 + str2 == str2 + str1` is a brilliant way to verify that both strings share a common building block pattern.
|
||||
|
||||
- approach_name: Iterative GCD Check
|
||||
is_optimal: false
|
||||
code: |
|
||||
def gcd_of_strings(str1: str, str2: str) -> str:
|
||||
def gcd(a: int, b: int) -> int:
|
||||
# Euclidean algorithm
|
||||
while b:
|
||||
a, b = b, a % b
|
||||
return a
|
||||
|
||||
def divides(s: str, t: str) -> bool:
|
||||
# Check if s divides t (t is s repeated)
|
||||
if len(t) % len(s) != 0:
|
||||
return False
|
||||
times = len(t) // len(s)
|
||||
return s * times == t
|
||||
|
||||
# Find GCD length
|
||||
gcd_len = gcd(len(str1), len(str2))
|
||||
candidate = str1[:gcd_len]
|
||||
|
||||
# Verify it actually divides both strings
|
||||
if divides(candidate, str1) and divides(candidate, str2):
|
||||
return candidate
|
||||
return ""
|
||||
explanation: |
|
||||
**Time Complexity:** O(n + m) — Checking divisibility for both strings.
|
||||
|
||||
**Space Complexity:** O(gcd(n, m)) — For the candidate string and repeated copies.
|
||||
|
||||
This approach is more explicit: it computes the candidate GCD string, then verifies it actually divides both inputs. While correct and intuitive, it's slightly less elegant than the concatenation trick which handles the compatibility check in one comparison.
|
||||
|
||||
- approach_name: Brute Force
|
||||
is_optimal: false
|
||||
code: |
|
||||
def gcd_of_strings(str1: str, str2: str) -> str:
|
||||
# Try all possible prefix lengths, from largest to smallest
|
||||
min_len = min(len(str1), len(str2))
|
||||
|
||||
for length in range(min_len, 0, -1):
|
||||
# Skip if lengths aren't divisible
|
||||
if len(str1) % length != 0 or len(str2) % length != 0:
|
||||
continue
|
||||
|
||||
# Get candidate prefix
|
||||
candidate = str1[:length]
|
||||
|
||||
# Check if candidate divides both strings
|
||||
times1 = len(str1) // length
|
||||
times2 = len(str2) // length
|
||||
|
||||
if candidate * times1 == str1 and candidate * times2 == str2:
|
||||
return candidate
|
||||
|
||||
return ""
|
||||
explanation: |
|
||||
**Time Complexity:** O(min(n, m) * (n + m)) — For each candidate length, we check both strings.
|
||||
|
||||
**Space Complexity:** O(n + m) — For the repeated candidate strings.
|
||||
|
||||
This brute force approach tries every possible prefix length from largest to smallest. While it works, it's inefficient because it doesn't leverage the mathematical insight that the answer length must be `gcd(n, m)`. Included to illustrate why the GCD approach is superior.
|
||||
Reference in New Issue
Block a user