leetcode
leetcode 2501 ~ 2550
回文串重新排列查询

回文串重新排列查询

难度:

标签:

题目描述

You are given a 0-indexed string s having an even length n.

You are also given a 0-indexed 2D integer array, queries, where queries[i] = [ai, bi, ci, di].

For each query i, you are allowed to perform the following operations:

  • Rearrange the characters within the substring s[ai:bi], where 0 <= ai <= bi < n / 2.
  • Rearrange the characters within the substring s[ci:di], where n / 2 <= ci <= di < n.

For each query, your task is to determine whether it is possible to make s a palindrome by performing the operations.

Each query is answered independently of the others.

Return a 0-indexed array answer, where answer[i] == true if it is possible to make s a palindrome by performing operations specified by the ith query, and false otherwise.

  • A substring is a contiguous sequence of characters within a string.
  • s[x:y] represents the substring consisting of characters from the index x to index y in s, both inclusive.

 

Example 1:

Input: s = "abcabc", queries = [[1,1,3,5],[0,2,5,5]]
Output: [true,true]
Explanation: In this example, there are two queries:
In the first query:
- a0 = 1, b0 = 1, c0 = 3, d0 = 5.
- So, you are allowed to rearrange s[1:1] => abcabc and s[3:5] => abcabc.
- To make s a palindrome, s[3:5] can be rearranged to become => abccba.
- Now, s is a palindrome. So, answer[0] = true.
In the second query:
- a1 = 0, b1 = 2, c1 = 5, d1 = 5.
- So, you are allowed to rearrange s[0:2] => abcabc and s[5:5] => abcabc.
- To make s a palindrome, s[0:2] can be rearranged to become => cbaabc.
- Now, s is a palindrome. So, answer[1] = true.

Example 2:

Input: s = "abbcdecbba", queries = [[0,2,7,9]]
Output: [false]
Explanation: In this example, there is only one query.
a0 = 0, b0 = 2, c0 = 7, d0 = 9.
So, you are allowed to rearrange s[0:2] => abbcdecbba and s[7:9] => abbcdecbba.
It is not possible to make s a palindrome by rearranging these substrings because s[3:6] is not a palindrome.
So, answer[0] = false.

Example 3:

Input: s = "acbcab", queries = [[1,2,4,5]]
Output: [true]
Explanation: In this example, there is only one query.
a0 = 1, b0 = 2, c0 = 4, d0 = 5.
So, you are allowed to rearrange s[1:2] => acbcab and s[4:5] => acbcab.
To make s a palindrome s[1:2] can be rearranged to become abccab.
Then, s[4:5] can be rearranged to become abccba.
Now, s is a palindrome. So, answer[0] = true.

 

Constraints:

  • 2 <= n == s.length <= 105
  • 1 <= queries.length <= 105
  • queries[i].length == 4
  • ai == queries[i][0], bi == queries[i][1]
  • ci == queries[i][2], di == queries[i][3]
  • 0 <= ai <= bi < n / 2
  • n / 2 <= ci <= di < n
  • n is even.
  • s consists of only lowercase English letters.

代码结果

运行时间: 144 ms, 内存: 49.1 MB


/*
 思路:
 1. 对于每个查询,我们将指定范围内的子字符串进行重新排列。
 2. 检查重新排列后的字符串是否可以形成回文串。
 3. 判断一个字符串是否可以成为回文串,只需检查每个字符出现的次数,最多只能有一个字符出现奇数次。
 4. 使用Java Stream来优化代码结构。
*/

import java.util.*;
import java.util.stream.*;

public class Solution {
    public boolean[] canMakePalindrome(String s, int[][] queries) {
        return Arrays.stream(queries)
            .map(query -> s.substring(query[0], query[1] + 1) + s.substring(query[2], query[3] + 1))
            .map(this::canFormPalindrome)
            .mapToBoolean(Boolean::booleanValue)
            .toArray();
    }

    private boolean canFormPalindrome(String str) {
        long oddCount = str.chars()
            .mapToObj(c -> (char) c)
            .collect(Collectors.groupingBy(c -> c, Collectors.counting()))
            .values()
            .stream()
            .filter(count -> count % 2 != 0)
            .count();
        return oddCount <= 1;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        String s = "abcabc";
        int[][] queries = {{1, 1, 3, 5}, {0, 2, 5, 5}};
        System.out.println(Arrays.toString(solution.canMakePalindrome(s, queries)));
    }
}

解释

方法:

题解的核心思路是预处理字符串 s,通过对比字符串的前半部和后半部的字符频率,找到最早和最晚的不匹配点。利用这些不匹配点,我们可以高效地回答每个查询。首先,我们创建一个字符频率数组 count 来统计从字符串两端向中心移动时,左右字符出现的差异。然后,针对查询,首先判断是否整个字符串已经满足回文结构。如果不满足,我们需要进一步确定最小的子字符串区间,使得这一区间内的所有字符重新排列后能够和其对应的镜像位置字符匹配。通过逐步检查从两端向中心移动过程中字符的匹配情况,我们建立了两个索引数组 leftIndices 和 rightIndices,这两个数组帮助我们迅速定位任意查询范围内是否可能通过排列实现回文结构。最后,对于每个查询,我们根据这些预处理的信息来判断是否能够重排成回文串。

时间复杂度:

O(n + m)

空间复杂度:

O(n + m)

代码细节讲解

🦆
如何确定题解中提到的‘最早和最晚的不匹配点’,并且这些点如何影响到整个算法的查询处理过程?
在题解中,‘最早和最晚的不匹配点’是通过比较字符串从两端向中心的字符来确定的。从字符串的首尾开始,逐对字符比较,第一对不相同的字符的位置被标记为‘最早的不匹配点’(start),最后一对不相同的字符的位置被标记为‘最晚的不匹配点’(end)。这两个点的确定是为了辅助判断字符串的某个子区间是否可以通过字符重新排列来形成回文。在处理查询时,如果查询的区间包含了这两个点,那么该区间内的字符需要进行详细检查,以确认是否所有字符都可以通过适当的重新排列来匹配其镜像位置的字符。
🦆
题解中提到的预处理步骤中用到的字符频率数组 count 是如何初始化和更新的?具体是基于什么逻辑来调整 count 数组中的值?
字符频率数组 count 用来记录字符串从两端到中心过程中字符出现的差异。数组初始化为全0,表示没有任何字符差异。随着从两端向中心遍历字符串,每次遍历时,左侧字符的频率在对应的 count 数组中增加,而右侧字符的频率减少。这种增减反映了在尝试构建回文时两侧字符出现的不平衡。如果数组中所有的值都能回归到0,说明可以通过某种排列形成回文串。这个数组的更新是基于每个字符出现次数的不平衡程度,从而帮助我们在后续的查询中快速判断重排的可能性。
🦆
为什么在处理每个查询时,需要依据 start 和 end 索引来判断子字符串是否可以通过重排成为回文结构?
start 和 end 索引标记了整个字符串中第一次和最后一次字符不匹配的位置。这意味着在这两个索引之间的区域是最有可能需要通过字符重排来达成回文的区域。在处理查询时,如果查询的区间完全包含从 start 到 end 的区间,这表明在考虑的子字符串中包含了所有潜在的不匹配字符。因此,只有当这部分区间可以通过重新排列来匹配其对称部分时,整个子字符串才可能被重排为回文。
🦆
题解最后判断单个查询是否能重排成回文串时,使用了复合条件语句,能否详细解释其中每个条件判断的意义和必要性?
复合条件语句用于判断查询的子区间是否可以通过重排成为回文结构。具体条件如下: 1. `a <= start && b >= end`:查询区间包含了从 start 到 end 的全部字符,这是重排成回文的必要条件之一。 2. `c <= n - 1 - end && d >= n - 1 - start`:确保子区间的镜像对称部分也包含在查询中,这对于形成对称的回文结构是必要的。 3. `a <= start && b >= start && c <= n - 1 - end && d >= rightIndices[b]`:确保左侧的查询区间至少包括到 start 且右侧的查询区间延伸到正确匹配右侧的相关索引,意味着这部分区间可以通过重排来匹配其镜像对称区域。 4. `c <= n - 1 - start && d >= n - 1 - start && a <= leftIndices[c] && b >= end`:确保右侧的查询区间至少包括到最远的不匹配点,而左侧的查询区间可以延伸到对应的左侧索引,这同样是为了保证可以通过重排来实现对称。 以上条件判断的组合确保了在回文构建过程中,查询的区间能够在逻辑上和结构上满足重排成回文的要求。

相关问题