【前缀和】【分类讨论】【二分查找】2983:回文串重新排列查询

简介: 【前缀和】【分类讨论】【二分查找】2983:回文串重新排列查询

回文串重新排列查询

给你一个长度为 偶数 n ,下标从 0 开始的字符串 s 。

同时给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [ai, bi, ci, di] 。

对于每个查询 i ,你需要执行以下操作:

将下标在范围 0 <= ai <= bi < n / 2 内的 子字符串 s[ai:bi] 中的字符重新排列。

将下标在范围 n / 2 <= ci <= di < n 内的 子字符串 s[ci:di] 中的字符重新排列。

对于每个查询,你的任务是判断执行操作后能否让 s 变成一个 回文串 。

每个查询与其他查询都是 独立的 。

请你返回一个下标从 0 开始的数组 answer ,如果第 i 个查询执行操作后,可以将 s 变为一个回文串,那么 answer[i] = true,否则为 false 。

子字符串 指的是一个字符串中一段连续的字符序列。

s[x:y] 表示 s 中从下标 x 到 y 且两个端点 都包含 的子字符串。

示例 1:

输入:s = “abcabc”, queries = [[1,1,3,5],[0,2,5,5]]

输出:[true,true]

解释:这个例子中,有 2 个查询:

第一个查询:

  • a0 = 1, b0 = 1, c0 = 3, d0 = 5
  • 你可以重新排列 s[1:1] => abcabc 和 s[3:5] => abcabc 。
  • 为了让 s 变为回文串,s[3:5] 可以重新排列得到 => abccba 。
  • 现在 s 是一个回文串。所以 answer[0] = true 。
    第二个查询:
  • a1 = 0, b1 = 2, c1 = 5, d1 = 5.
  • 你可以重新排列 s[0:2] => abcabc 和 s[5:5] => abcabc 。
  • 为了让 s 变为回文串,s[0:2] 可以重新排列得到 => cbaabc 。
  • 现在 s 是一个回文串,所以 answer[1] = true 。
    示例 2:

输入:s = “abbcdecbba”, queries = [[0,2,7,9]]

输出:[false]

解释:这个示例中,只有一个查询。

a0 = 0, b0 = 2, c0 = 7, d0 = 9.

你可以重新排列 s[0:2] => abbcdecbba 和 s[7:9] => abbcdecbba 。

无法通过重新排列这些子字符串使 s 变为一个回文串,因为 s[3:6] 不是一个回文串。

所以 answer[0] = false 。

示例 3:

输入:s = “acbcab”, queries = [[1,2,4,5]]

输出:[true]

解释:这个示例中,只有一个查询。

a0 = 1, b0 = 2, c0 = 4, d0 = 5.

你可以重新排列 s[1:2] => acbcab 和 s[4:5] => acbcab 。

为了让 s 变为回文串,s[1:2] 可以重新排列得到 => abccab 。

然后 s[4:5] 重新排列得到 abccba 。

现在 s 是一个回文串,所以 answer[0] = true 。

提示:

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 是一个偶数。

s 只包含小写英文字母。

前缀和+分类讨论

令s1是s的前半部分,即s[0,n),s2是s的后半部分颠倒顺序。代码中的a,b和题意中的ab相同。c,d和题意不同,是s2的下标。

c = s.length() - 1 - v[3], d = s.length() - 1 - v[2]。这样s是回文,等同与s1等于s2。

vPreSumLeft vPreSumLeft[i]表示s1中’a’+i 的数量前缀和
vPreSumRight vPreSumRight[i]表示s2中’a’+i 的数量前缀和
IsSame 如果s1[a,b]和s2[a,b]中各字符数量相等,返回true,否则返回false
vNotSame 记录所有s1[i]!=s2[i]的下标
[iCrossLeft,iCrossRight] 表示线段[a,b]和[c,d]相交部分
CanVilid vNotSame[a,b]直接有多少个元素,使用二分查找实现。
线段[iUnion1,iUnion2] 包括线段[a,b] [c,d]的最小线段

一维线段的关系

相离:没有交点。

相交分以下情况:

  • 相交部分左都有点,属于一条线段。如:[1,2] [0,3] ,分类为包括。
  • 相交部分左都有点,属于不同的线段,如[1,3],[2,4],分类为侠义的相交,或者说不包括的相交。
  • 相交部分左边(或右边右点),[1,3] 和[2,3],分类为包括。
  • 相交部分左右都无点,分类为重合,因为和包括的处理相同。所以当包扩处理。

[a,b]包括[c,d]的判断标准:a等于iUnion1,b等于iUnion2

相离

s1[a,b] 和s2[a,b]的字符数量相等, s1[c,d] 和s2[c,d]的字符数量相等。除[a,b] [c,d]外没有字符不相等。

由于可以任意排列,所以只要字符数量相等,就可以排列成相同。

包括

s1[iUnion1,iUnion2] 和s2[iUnion1,iUnion2]的字符数量相等,除[iUnion1,iUnion2]外,没有字符不等。

侠义相交

针对a,b有两种情况:

a等于iCrossLeft ,这时非重合部分为[iCrossRight+1,b]

b等于iCrossRight,这时非重合部分为[a,iCrossLeft-1]

s1[a,b]中必须有非重合部分的所有的字符,且数量足够。否则无法让s1相等。

c,d类似。

以下几个条件:

  • 一,[a,b]有非重合部分所有字符,[c,d]也是。
  • 二,s1[iUnion1,iUnion2] 和s2[iUnion1,iUnion2]的字符数量相等。
  • 三,除[iUnion1,iUnion2]外,没有字符不等。

代码

核心代码

class Solution {
public:
  vector<bool> canMakePalindromeQueries(string s, vector<vector<int>>& queries) {
    const int n2 = s.length() / 2;
    vector<vector<int>> vPreSumLeft(26, vector<int>(1)), vPreSumRight(26, vector<int>(1));
    vector<int> vNotSame;
    for (int i = 0; i < n2; i++)
    {
      for (int j = 0; j < 26; j++)
      {
        vPreSumLeft[j].emplace_back(vPreSumLeft[j].back() + (j + 'a' == s[i]));
        vPreSumRight[j].emplace_back(vPreSumRight[j].back() + (j + 'a' == s[s.length() - 1 - i]));
      }
      if (s[i] != s[s.length() - 1 - i])
      {
        vNotSame.emplace_back(i);
      }
    }
    auto IsSame = [&](int a, int b)
    {
      for (int i = 0; i < 26; i++)
      {
        if (vPreSumLeft[i][b + 1] - vPreSumLeft[i][a] != vPreSumRight[i][b + 1] - vPreSumRight[i][a])
        {
          return false;
        }
      }
      return true;
    };
    auto NotSameCount = [&](int a, int b)
    {
      return std::upper_bound(vNotSame.begin(), vNotSame.end(), b) - std::lower_bound(vNotSame.begin(), vNotSame.end(), a);
    };    
    vector<bool> vRet;
    for (const auto& v : queries)
    {
      const int a = v[0], b = v[1], c = s.length() - 1 - v[3], d = s.length() - 1 - v[2];
      const int iCrossLeft = max(a, c), iCrossRight = min(b, d);
      const int iCrossLen = iCrossRight - iCrossLeft + 1;
      auto Has = [&](const int a, const int b,const vector<int>& vPreSum, const vector<int>& vPreSumOther)
      {//[a,b]可以任意调整顺序的范围,[c,d]是非交叉范围
        int c = a, d = iCrossLeft-1;
        if (a == iCrossLeft)
        {
          c = iCrossRight+1;
          d = b;
        }
        return (vPreSum[b + 1] - vPreSum[a] - (vPreSumOther[d + 1] - vPreSumOther[c])) >= 0;
      };
      if (iCrossLen <= 0)
      {//两者没有交叉
        const int iNotSameCount = NotSameCount(a, b) + NotSameCount(c, d);
        vRet.emplace_back(IsSame(a, b) && IsSame(c, d) && (iNotSameCount == vNotSame.size()));
      }
      else
      {
        const int iUnion1 = min(a, c),  iUnion2 = max(b, d);
        auto IsInclude =[&](const int a, const int b)
        {
          return (iUnion1 == a) && (iUnion2 == b);
        };
        if (IsInclude(a, b) || IsInclude(c, d))
        {
          vRet.emplace_back(IsSame(iUnion1, iUnion2) && (NotSameCount(iUnion1, iUnion2) == vNotSame.size() ));
          continue;
        }
        bool bHas = true;
        for (int i = 0; i < 26; i++)
        {
          bHas &= Has(a,b, vPreSumLeft[i], vPreSumRight[i]);
          bHas &= Has(c, d, vPreSumRight[i], vPreSumLeft[i]);
        }
        vRet.emplace_back(bHas&& IsSame(iUnion1, iUnion2) && (NotSameCount(iUnion1, iUnion2) == vNotSame.size()));
      }
    }
    return vRet;
  }
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{
  string s, p;
  vector<vector<int>>queries;
  {
    Solution sln;
    s = "fxdqcfqdxc", queries = { {1,1,7,8},{1,1,5,9},{2,4,8,8},{0,4,6,8},{2,3,7,8},{2,4,5,9},{1,4,9,9} };
    auto res = sln.canMakePalindromeQueries(s, queries);
    Assert(vector<bool>{false, true, false, true, false, true, false}, res);
  }
  {
    Solution sln;
    s = "dbaabd", queries = { {0, 1, 5, 5}, { 1,2,4,5 } };
    auto res = sln.canMakePalindromeQueries(s, queries);
    Assert(vector<bool>{true,true}, res);
  }
  {
    Solution sln;
    s = "ceddceddcc", queries = { {0,1,6,8} };
    auto res = sln.canMakePalindromeQueries(s, queries);
    Assert(vector<bool>{false}, res);
  }
  {
    Solution sln;
    s = "acbcab", queries = { {1,2,4,5} };
    auto res = sln.canMakePalindromeQueries(s, queries);
    Assert(vector<bool>{true}, res);
  }
  {
    Solution sln;
    s = "abbcdecbba", queries = { {0,2,7,9} };
    auto res = sln.canMakePalindromeQueries(s, queries);
    Assert(vector<bool>{false}, res);
  }
  {
    Solution sln;
    s = "abcabc", queries = { {1,1,3,5},{0,2,5,5} };
    auto res = sln.canMakePalindromeQueries(s, queries);
    Assert(vector<bool>{true, true}, res);
  }
  {
    Solution sln;
    s = "odaxusaweuasuoeudxwa", queries = { {0,5,10,14} };
    auto res = sln.canMakePalindromeQueries(s, queries);
    Assert(vector<bool>{false}, res);
  }
}


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 **C+

+17**

如无特殊说明,本算法用**C++**实现。

相关文章
|
1月前
|
人工智能 分布式计算 算法
【动态规划】【二分查找】【去重】730. 统计不同回文子序列
【动态规划】【二分查找】【去重】730. 统计不同回文子序列
|
1月前
|
测试技术 Perl
【动态规划】【记忆化搜索】【回文】1312让字符串成为回文串的最少插入次数
【动态规划】【记忆化搜索】【回文】1312让字符串成为回文串的最少插入次数
|
22天前
|
存储 人工智能 BI
小苯的九宫格,小苯的好数组(排序),小苯的数字合并(字典树,前缀和)
小苯的九宫格,小苯的好数组(排序),小苯的数字合并(字典树,前缀和)
18 3
|
17天前
|
人工智能 BI
【动态规划】最长非降子序列 01背包 插入加号
1. 计算给定整数序列的最长非升子序列。 2. 解决 0-1 背包问题,找出使总价值最大的物品组合。 3. 找出在整数中插入加号的方法,使得加号后的整数和最小。
13 0
|
1月前
|
算法 测试技术 C#
【字符串】【贪心】【 树状数组】2193. 得到回文串的最少操作次数
【字符串】【贪心】【 树状数组】2193. 得到回文串的最少操作次数
|
1月前
|
算法 测试技术 C#
二分查找|滑动窗口|前缀和|LeetCode209: 长度最小的子数组
二分查找|滑动窗口|前缀和|LeetCode209: 长度最小的子数组
|
1月前
|
存储 编译器 C语言
Day2 排序子序列、倒置字符串
Day2 排序子序列、倒置字符串
32 0
|
1月前
|
算法
回溯-求出数组的所有子序列【学习算法】
回溯-求出数组的所有子序列【学习算法】
34 0
|
6月前
|
人工智能 算法 C++
C++ 二分查找算法:山脉数组中查找目标值
C++ 二分查找算法:山脉数组中查找目标值
|
11月前
|
存储 算法 C语言
【前缀和】1310.子数组异或查询
本篇将学习前缀和OJ题子数组异或查询相关知识。
62 0