【数学】【组合数学】1830. 使字符串有序的最少操作次数

简介: 【数学】【组合数学】1830. 使字符串有序的最少操作次数

本博文涉及知识点

数学 组合数学

LeetCode1830. 使字符串有序的最少操作次数

给你一个字符串 s (下标从 0 开始)。你需要对 s 执行以下操作直到它变为一个有序字符串:

找到 最大下标 i ,使得 1 <= i < s.length 且 s[i] < s[i - 1] 。

找到 最大下标 j ,使得 i <= j < s.length 且对于所有在闭区间 [i, j] 之间的 k 都有 s[k] < s[i - 1] 。

交换下标为 i - 1 和 j 处的两个字符。

将下标 i 开始的字符串后缀反转。

请你返回将字符串变成有序的最少操作次数。由于答案可能会很大,请返回它对 109 + 7 取余 的结果。

示例 1:

输入:s = “cba”

输出:5

解释:模拟过程如下所示:

操作 1:i=2,j=2。交换 s[1] 和 s[2] 得到 s=“cab” ,然后反转下标从 2 开始的后缀字符串,得到 s=“cab” 。

操作 2:i=1,j=2。交换 s[0] 和 s[2] 得到 s=“bac” ,然后反转下标从 1 开始的后缀字符串,得到 s=“bca” 。

操作 3:i=2,j=2。交换 s[1] 和 s[2] 得到 s=“bac” ,然后反转下标从 2 开始的后缀字符串,得到 s=“bac” 。

操作 4:i=1,j=1。交换 s[0] 和 s[1] 得到 s=“abc” ,然后反转下标从 1 开始的后缀字符串,得到 s=“acb” 。

操作 5:i=2,j=2。交换 s[1] 和 s[2] 得到 s=“abc” ,然后反转下标从 2 开始的后缀字符串,得到 s=“abc” 。

示例 2:

输入:s = “aabaa”

输出:2

解释:模拟过程如下所示:

操作 1:i=3,j=4。交换 s[2] 和 s[4] 得到 s=“aaaab” ,然后反转下标从 3 开始的后缀字符串,得到 s=“aaaba” 。

操作 2:i=4,j=4。交换 s[3] 和 s[4] 得到 s=“aaaab” ,然后反转下标从 4 开始的后缀字符串,得到 s=“aaaab” 。

示例 3:

输入:s = “cdbea”

输出:63

示例 4:

输入:s = “leetcodeleetcodeleetcode”

输出:982157772

提示:

1 <= s.length <= 3000

s 只包含小写英文字母。

组合数学

令 n= s.length

这个操作本质是:求前一字典序的s排列。本题   ⟺    \iff s的排列中有多少个字典序小于s。

i < j ,交换s[i]和s[j]。

image.png

令i1 < i2 ,i1 < j1 ,i2 < j2 s[i1] > s[j1] s[i2] > s[j2]

s交换s[i1]和s[j1]后 ,结果为t1。

s交换s[i2]和s[j2]后 ,结果为t2。

则t1< t2,因为t1[i1] < t2[i1]。

求排列的前一个字典序

计算最大下标i,使得存在s[x]<s[i],x∈ \in(i,n)。→ \rightarrow i1∈ \in(i,n) x1∈ \in(x1,n),不存在s[x1] < s[i1] → \rightarrow

s[i+1,⋯ \cdots…,n) 设升序排列。本题解的i就是题目的i-1。

寻找j,使得s[j] < s[i],有多个符合的s[j],取值最大的s[j]。由于s[i+1,… \dots,n)是升序,所以题解的j,就是题目的j。

交换s[i]和s[j]。

s[i]变小后,后面的字符无论如何变大,都是变小。故让s[i+1,n)变最大,即降序排列。目前是升序排列,反转即可达到目标。

求字典序小于s的排列数量

字典序列小于s的排列t有如下特征: 前k位相同,k∈ \in[0,n-1]。t[k] < s[k] ,t[k+1,⋯ \cdots,n)不限。假定后面有a个’a’,b个’b’,如果求数量?

假定’a’不同不,则共有(a+b)! 种排放, 考虑到‘a’ 是相同的则 a! 种是相同的,考虑到’b’是相同的,则b! 中不同。

!是阶乘,故:结果为 (a+b)!/a!/b!。

代码

核心代码

template<int MOD = 1000000007>
class C1097Int
{
public:
  C1097Int(long long llData = 0) :m_iData(llData% MOD)
  {
  }
  C1097Int  operator+(const C1097Int& o)const
  {
    return C1097Int(((long long)m_iData + o.m_iData) % MOD);
  }
  C1097Int& operator+=(const C1097Int& o)
  {
    m_iData = ((long long)m_iData + o.m_iData) % MOD;
    return *this;
  }
  C1097Int& operator-=(const C1097Int& o)
  {
    m_iData = (m_iData + MOD - o.m_iData) % MOD;
    return *this;
  }
  C1097Int  operator-(const C1097Int& o)
  {
    return C1097Int((m_iData + MOD - o.m_iData) % MOD);
  }
  C1097Int  operator*(const C1097Int& o)const
  {
    return((long long)m_iData * o.m_iData) % MOD;
  }
  C1097Int& operator*=(const C1097Int& o)
  {
    m_iData = ((long long)m_iData * o.m_iData) % MOD;
    return *this;
  }
  bool operator<(const C1097Int& o)const
  {
    return m_iData < o.m_iData;
  }
  C1097Int pow(long long n)const
  {
    C1097Int iRet = 1, iCur = *this;
    while (n)
    {
      if (n & 1)
      {
        iRet *= iCur;
      }
      iCur *= iCur;
      n >>= 1;
    }
    return iRet;
  }
  C1097Int PowNegative1()const
  {
    return pow(MOD - 2);
  }
  int ToInt()const
  {
    return m_iData;
  }
private:
  int m_iData = 0;;
};
class Solution {
public:
  int makeStringSorted(string s) {
    int cnt[26] = { 0 };
    vector<C1097Int<>> fac(s.length() + 1, 1);
    for (int i = 2; i <= s.length(); i++)
    {
      fac[i] = fac[i - 1] * i;
    }
    C1097Int<> biRet;
    for (int i = s.length() - 1; i >= 0; i--)
    {
      const int n = s[i] - 'a';
      for (int ii = 0; ii < n; ii++)
      {
        if (0 == cnt[ii])
        {
          continue;
        }
        C1097Int<> biDiv = 1;
        for (int j = 0; j < 26; j++)
        {         
          biDiv *= fac[cnt[j]-(j == ii ) + ( j == n )];
        }
        biRet += fac[s.length() - 1 - i]  * biDiv.PowNegative1();
      }     
      cnt[n]++;
    }
    return biRet.ToInt();
  }
};
template<class T,class T2>
void Assert(const T& t1, const T2& 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 ;
  
  {
    Solution sln;
    s = "aabaa";
    auto res = sln.makeStringSorted(s);
    Assert(2, res);
  }
  {
    Solution sln;
    s =  "cdbea";
      auto res = sln.makeStringSorted(s);
    Assert(63, res);
  }
  {
    Solution sln;
    s = "leetcodeleetcodeleetcode";
    auto res = sln.makeStringSorted(s);
    Assert(982157772, res);
  }
  {
    Solution sln;
    s = "cba";
    auto res = sln.makeStringSorted(s);
    Assert(5, res);
  }
}

2023年9月

class Solution {
public:
int makeStringSorted(const string s) {
m_c = s.length();
vector<C1097Int<>> vRangeMul = { 1 };
for (int i = 1; i <= m_c; i++)
{
vRangeMul.emplace_back(vRangeMul.back() * i);
}
int nums[26] = { 0 };
C1097Int<> biRet = 0;
nums[s.back() - ‘a’]++;
for (int i = m_c - 1 - 1; i >= 0; i–)
{
const int iLessNum = std::accumulate(nums, nums + (s[i] - ‘a’), 0);
if (iLessNum > 0)
{//可以调整顺序
const int iRightNum = m_c - (i+1);
nums[s[i] - ‘a’]++;
C1097Int tmp = vRangeMul[iRightNum];
for (int k = 0; k < 26; k++)
{
tmp = vRangeMul[nums[k]].PowNegative1();
}
for (int j = 0; j < s[i]-‘a’; j++)
{
if (0 == nums[j])
{
continue;
}
biRet += tmp * vRangeMul[nums[j]] vRangeMul[nums[j] - 1].PowNegative1();
}
nums[s[i] - ‘a’]–;
}
nums[s[i] - ‘a’]++;
}
return biRet.ToInt();
}
int m_c;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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++**实现。

相关文章
|
3月前
|
算法 测试技术 C++
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
|
4月前
|
存储 C语言
牛客网刷题总结(1.有序序列判断,2.获得月份天数,3.矩阵相等判定,4.矩阵转换,5.井字棋判断输赢,6.递归进行进制转化)
牛客网刷题总结(1.有序序列判断,2.获得月份天数,3.矩阵相等判定,4.矩阵转换,5.井字棋判断输赢,6.递归进行进制转化)
39 0
|
21天前
|
算法 测试技术 C#
【位运算】【 数学】【 哈希映射】2857. 统计距离为 k 的点对
【位运算】【 数学】【 哈希映射】2857. 统计距离为 k 的点对
|
7月前
【Leetcode -643.子数组最大平均值Ⅰ -645.错误的集合】
【Leetcode -643.子数组最大平均值Ⅰ -645.错误的集合】
29 0
|
3月前
|
算法 测试技术 C++
【数论】【分类讨论】【C++算法】1611使整数变为 0 的最少操作次数
【数论】【分类讨论】【C++算法】1611使整数变为 0 的最少操作次数
|
4月前
|
算法 测试技术 C#
【数学】LeetCode1526: 形成目标数组的子数组最少增加次数
【数学】LeetCode1526: 形成目标数组的子数组最少增加次数
|
5月前
|
存储 算法 程序员
【算法训练-数组 一】【数组子集】:最长无重复子数组
【算法训练-数组 一】【数组子集】:最长无重复子数组
23 0
|
6月前
|
C语言
【C语言刷题】调整奇数偶数顺序、有序序列合并以及有序序列判断
【C语言刷题】调整奇数偶数顺序、有序序列合并以及有序序列判断
36 0