LeetCode 3365 - Rearrange K Substrings to Form Target String
This problem asks whether it is possible to rearrange k contiguous equal-length substrings of a string s to form another string t, given that s and t are anagrams.
Difficulty: 🟡 Medium
Topics: Hash Table, String, Sorting
Solution
Problem Understanding
This problem asks whether it is possible to rearrange k contiguous equal-length substrings of a string s to form another string t, given that s and t are anagrams. In simpler terms, we are splitting s into k pieces of the same size, then permuting these pieces in any order to see if we can produce t. The input consists of two strings s and t, and an integer k. The output is a boolean indicating whether the rearrangement is possible.
The constraints are crucial: s and t have lengths up to 2 * 10^5, and s.length is divisible by k, so each substring has length n = len(s) / k. The fact that s and t are guaranteed to be anagrams ensures that the characters themselves are compatible; the only challenge is arranging substrings of the correct size. Edge cases include k = 1 (entire string is one substring, so only exact match works) and k = len(s) (each character is a substring, so any permutation works).
Approaches
The brute-force approach is to generate all permutations of the k substrings of s and check if any concatenation equals t. While this guarantees correctness, it is infeasible for large strings, as there are k! permutations, and each concatenation check costs O(n) time. For k around 10^5, this is astronomically slow.
The optimal approach relies on the insight that we do not need to check all permutations explicitly. Since substrings are of equal length, the problem reduces to checking whether the multiset of substrings from s matches the multiset of substrings from t. By breaking both s and t into k equal-length chunks and counting how many times each substring occurs, we can compare the counts. If they match, a rearrangement exists; otherwise, it does not. We can efficiently implement this using a hash map (dictionary in Python, map in Go).
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(k! * n) | O(k) | Generate all permutations of substrings and check concatenation |
| Optimal | O(n) | O(n) | Use hash map to count frequency of substrings and compare |
Algorithm Walkthrough
- Compute the length of each substring as
sub_len = len(s) // k. - Initialize a hash map to count the frequency of each substring of length
sub_lenins. - Iterate over
sin steps ofsub_len, extract each substring, and increment its count in the hash map. - Repeat the same process for
t, decrementing counts in the hash map for each substring. - After processing both strings, check the hash map. If all counts are zero, it means
sandtcontain the same substrings with the same frequencies, so returntrue. Otherwise, returnfalse.
Why it works: The hash map comparison ensures that the multiset of substrings in s matches that in t. Since the order of substrings can be rearranged arbitrarily, having the same multiset guarantees that a valid rearrangement exists.
Python Solution
class Solution:
def isPossibleToRearrange(self, s: str, t: str, k: int) -> bool:
n = len(s)
sub_len = n // k
count = {}
for i in range(0, n, sub_len):
sub_s = s[i:i+sub_len]
count[sub_s] = count.get(sub_s, 0) + 1
for i in range(0, n, sub_len):
sub_t = t[i:i+sub_len]
if sub_t not in count:
return False
count[sub_t] -= 1
if count[sub_t] == 0:
del count[sub_t]
return not count
In the Python code, we first calculate the length of each substring. We then iterate over s and store the frequency of each substring in a dictionary. Next, we iterate over t, decrementing the counts, and immediately return false if a substring is missing. If all counts reach zero, we return true.
Go Solution
func isPossibleToRearrange(s string, t string, k int) bool {
n := len(s)
subLen := n / k
count := make(map[string]int)
for i := 0; i < n; i += subLen {
subS := s[i:i+subLen]
count[subS]++
}
for i := 0; i < n; i += subLen {
subT := t[i:i+subLen]
if count[subT] == 0 {
return false
}
count[subT]--
if count[subT] == 0 {
delete(count, subT)
}
}
return len(count) == 0
}
In Go, we use a map to count substrings. The logic mirrors the Python solution, with attention to Go syntax and the use of delete to remove entries from the map once the count reaches zero.
Worked Examples
Example 1: s = "abcd", t = "cdab", k = 2
Substring length = 2
Substrings of s: ["ab", "cd"] → count = {"ab":1, "cd":1}
Substrings of t: ["cd", "ab"] → decrement: "cd"→0, "ab"→0 → map empty → return True
Example 2: s = "aabbcc", t = "bbaacc", k = 3
Substring length = 2
Substrings of s: ["aa", "bb", "cc"] → count = {"aa":1, "bb":1, "cc":1}
Substrings of t: ["bb", "aa", "cc"] → decrement: "bb"→0, "aa"→0, "cc"→0 → map empty → return True
Example 3: s = "aabbcc", t = "bbaacc", k = 2
Substring length = 3
Substrings of s: ["aab", "bcc"] → count = {"aab":1, "bcc":1}
Substrings of t: ["bba", "acc"] → "bba" not in map → return False
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We iterate over both s and t once in steps of sub_len, processing each character exactly twice |
| Space | O(n) | We store counts for up to k substrings, each of length n/k, so total space is O(n) |
The linear time complexity ensures the solution is efficient even for the maximum input size of 2 * 10^5. The hash map guarantees constant-time insertions and lookups on average.
Test Cases
# Provided examples
assert Solution().isPossibleToRearrange("abcd", "cdab", 2) == True # basic reorder
assert Solution().isPossibleToRearrange("aabbcc", "bbaacc", 3) == True # multiple substrings
assert Solution().isPossibleToRearrange("aabbcc", "bbaacc", 2) == False # mismatch in substring grouping
# Edge cases
assert Solution().isPossibleToRearrange("abc", "abc", 1) == True # entire string as one substring
assert Solution().isPossibleToRearrange("abc", "cba", 3) == True # each char is a substring
assert Solution().isPossibleToRearrange("aabb", "abab", 2) == True # repeated letters, split works
assert Solution().isPossibleToRearrange("aaaa", "aaaa", 2) == True # identical substrings
# Large input
s = "ab" * 100000
t = "ba" * 100000
assert Solution().isPossibleToRearrange(s, t, 200000) == True # stress test large input
| Test | Why |
|---|---|
"abcd", "cdab", 2 |
Valid rearrangement of 2 substrings |
"aabbcc", "bbaacc", 3 |
Valid rearrangement of 3 substrings |
"aabbcc", "bbaacc", 2 |
Substring lengths do not match t |
"abc", "abc", 1 |
Entire string as a single substring |
"abc", "cba", 3 |
Each character is a substring, full permutation allowed |
"aabb", "abab", 2 |
Substrings with repeated letters, still works |
"aaaa", "aaaa", 2 |
Identical substrings, edge case |
| Large repeated string | Stress test for performance |
Edge Cases
One important edge case is when k = 1. Here, the entire string is a single substring, so s must exactly equal t. The implementation handles this