We use cookies (including Google cookies) to personalize ads and analyze traffic. By continuing to use our site, you accept our Privacy Policy.

Palindrome Rearrangement Queries

Number: 3203

Difficulty: Hard

Paid? No

Companies: N/A


Problem Description

Given an even‑length string s and a set of queries, each query allows you to rearrange two specific substrings – one in the left half and one in the right half of s. For a query [a, b, c, d], you may arbitrarily reorder the characters in s[a:b] (with 0 ≤ a ≤ b < n/2) and in s[c:d] (with n/2 ≤ c ≤ d < n). The task is to decide whether, after these independent rearrangements, it is possible to convert s into a palindrome.


Key Insights

  • A palindrome of an even‑length string requires that for every index i in [0, n/2), the character s[i] equals s[n–1–i].
  • Only portions of the left and right halves are “modifiable.” The characters in the other (fixed) positions remain in place and must already satisfy the mirror property.
  • If both characters in a mirror pair are fixed then they must already match.
  • When one character of a mirror pair is fixed yet its partner is in an allowed (modifiable) segment, then that allowed segment must “supply” the needed character.
  • For mirror pairs where both positions are modifiable, the multiset of characters available in the allowed regions from the left and right halves must be equal (so that they can be rearranged into identical halves).
  • Pre‐computing prefix frequency arrays (for each half) and a “mismatch” array for fixed (non‑modifiable) positions allows each query to be answered in O(26) time, i.e. constant time per query.

Space and Time Complexity

Time Complexity: O(n + Q * 26) = O(n + Q) where n is the length of s and Q is the number of queries.
Space Complexity: O(n * 26) for the prefix frequency arrays (which is acceptable given the constant 26).


Solution

The idea is to “split” the string into two halves. For each query, we know exactly which segments in the left half (indices [a,b]) and right half (indices [c,d]) can be rearranged arbitrarily.
For every mirror pair (i, n–1–i) with i in the left half, there are three cases:

  1. Both positions are fixed. In this case the already‑present characters must match. We precompute a “mismatch” prefix so we can quickly count mismatches in any fixed segment.
  2. One position is fixed and the other modifiable. Then the modifiable part must supply the character from its fixed partner. In effect, we “demand” a certain frequency for that letter.
  3. Both positions are modifiable. Their characters are pooled in the allowed segments from the left and right parts. After subtracting the characters “used” in the fixed–modification pairs, the residual multiset in the modifiable left must equal that in the modifiable right. To support fast queries we precompute: – For the left half: a prefix frequency array (for a–z) over indices 0 to n/2–1.
    – For the right half: a prefix frequency array (for a–z) over indices n/2 to n–1.
    – A “mismatch” prefix array for mirror pairs. Then for each query we: • Identify the “modifiable” indices in the left half (given directly by [a,b]) and in the right half through their mirror mapping. When an index i in the left half has its mirror (n–1–i) in [c,d], then that left index belongs to the modifiable set from the right side (compute the mirror interval as [n–1–d, n–1–c]). • Use the prefix frequency arrays to extract counts fast for any subinterval. • Compute frequency “demands” for pairs where one side is fixed and the other is modifiable. • Check that the fixed–fixed pairs already match. • Finally, check that after meeting demands the remaining characters in the left and right allowed segments have exactly the same multiset. If all these conditions hold the answer for that query is true; otherwise false.

Code Solutions

Below are code examples in Python, JavaScript, C++, and Java. Each solution uses clear variable names and inline comments.

# Python solution example

def canFormPalindrome(s, queries):
    n = len(s)
    half = n // 2

    # Precompute prefix frequency arrays for left and right halves.
    # left_freq[i][ch] will store frequency for left half indices [0, i-1]
    left_freq = [[0]*26 for _ in range(half+1)]
    for i in range(half):
        ch_index = ord(s[i]) - ord('a')
        for j in range(26):
            left_freq[i+1][j] = left_freq[i][j]
        left_freq[i+1][ch_index] += 1

    # right half prefix frequency; right part spans indices [half, n-1]
    right_freq = [[0]*26 for _ in range(half+1)]
    for i in range(half):
        ch_index = ord(s[half+i]) - ord('a')
        for j in range(26):
            right_freq[i+1][j] = right_freq[i][j]
        right_freq[i+1][ch_index] += 1

    # Precompute mismatch array for fixed mirror pairs.
    mismatch = [0]*(half)
    for i in range(half):
        if s[i] != s[n-1-i]:
            mismatch[i] = 1
    # Build prefix sum for mismatches
    mismatch_ps = [0]*(half+1)
    for i in range(half):
        mismatch_ps[i+1] = mismatch_ps[i] + mismatch[i]

    results = []
    # Function: get frequency count in left half interval [l, r] inclusive.
    def get_left_freq(l, r):
        res = [0]*26
        for ch in range(26):
            res[ch] = left_freq[r+1][ch] - left_freq[l][ch]
        return res

    # Function: get frequency count in right half interval [l, r] inclusive.
    def get_right_freq(l, r):
        res = [0]*26
        for ch in range(26):
            res[ch] = right_freq[r+1][ch] - right_freq[l][ch]
        return res

    for (a, b, c, d) in queries:
        valid = True

        # Compute mirror indices for the allowed right region.
        # s[c:d] are allowed positions in right half (indices from half to n-1).
        # Their mirror indices in left half: i such that:
        #   j = n-1-i is in [c, d]  => i in [n-1-d, n-1-c]
        left_allowed_mirror_start = n - 1 - d
        left_allowed_mirror_end = n - 1 - c

        # For each mirror pair index i in the left-half [0, half-1], we partition into 4 groups:
        # Group A: i not in [a, b] AND i not in [left_allowed_mirror_start, left_allowed_mirror_end]
        #         => both positions are fixed; they must match.
        # Use our mismatch prefix array to count mismatches in these fixed indices.
        # To avoid iterating over individual indices, we can subtract counts from allowed intervals.
        # For simplicity in this demonstration we iterate over the fixed region.
        # (In practice, one would merge intervals to get constant time.)
        fixedMismatch = 0
        # Check i in [0, half)
        # (A more efficient solution would precompute and query prefix sums over the complement intervals.)
        for i in range(half):
            in_left_allowed = (a <= i <= b)
            in_right_allowed = (left_allowed_mirror_start <= i <= left_allowed_mirror_end)
            if (not in_left_allowed and not in_right_allowed) and (s[i] != s[n-1-i]):
                fixedMismatch += 1
                break
        if fixedMismatch > 0:
            results.append(False)
            continue

        # Now, for fixed vs. modifiable pairs we compute required frequencies.
        req_right = [0]*26  # characters needed in right allowed region (mirror of fixed left)
        req_left = [0]*26   # characters needed in left allowed region (mirror of fixed right)

        # Group B: left i fixed (i not in [a, b]) but mirror allowed (i in [left_allowed_mirror_start, left_allowed_mirror_end])
        for i in range(left_allowed_mirror_start, left_allowed_mirror_end+1):
            if 0 <= i < half:
                if not (a <= i <= b):  # left fixed 
                    ch = s[i]
                    req_right[ord(ch) - ord('a')] += 1

        # Group C: left allowed (i in [a, b]) but mirror fixed (i not in [left_allowed_mirror_start, left_allowed_mirror_end])
        for i in range(a, b+1):
            if not (left_allowed_mirror_start <= i <= left_allowed_mirror_end):
                # then mirror index is fixed; required char is s[n-1-i]
                ch = s[n-1-i]
                req_left[ord(ch) - ord('a')] += 1

        # Group D: Intersection: both allowed are free and will be balanced later.
        # Compute allowed region frequencies using prefix arrays.
        left_allowed_freq = get_left_freq(a, b)  # left allowed segment frequency from left half
        # right allowed segment is given by indices [c-half, d-half] in right_freq.
        right_allowed_freq = get_right_freq(c - half, d - half)  # note: adjust index inside right half

        # Check that the allowed regions can satisfy the fixed-to-modifiable demands.
        possible = True
        for ch in range(26):
            if left_allowed_freq[ch] < req_left[ch] or right_allowed_freq[ch] < req_right[ch]:
                possible = False
                break
            # Subtract these demanded characters; the leftovers (Group D) must match exactly.
            left_allowed_freq[ch] -= req_left[ch]
            right_allowed_freq[ch] -= req_right[ch]
            if left_allowed_freq[ch] != right_allowed_freq[ch]:
                possible = False
                break

        results.append(possible)

    return results

# Example usage:
s = "abcabc"
queries = [[1,1,3,5],[0,2,5,5]]
print(canFormPalindrome(s, queries))  # Expected output: [True, True]
← Back to All Questions