LeetCode 1147. 段式回文

题目链接:段式回文

PS:这道题的数据范围比较小,测试数据也比较弱,暴力枚举都能过。但分析的过程和优化的方法还是值得更深入理解的

题解:贪心 + 多项式哈希

  1. 贪心方法:在切割前后缀回文段时,优先切割最短的回文段,即对于原字符串,若同时存在AXA和ABYAB的情况(每个字母表示原串的一个子串),优先切割A而不是AB
  2. 贪心证明:
    1. 若B串长度大于A串
      ​​​​​
      如上图所示,AB可以分为AXA,而对于AXA,我们可以切割出三组回文段,明显更优
    2. 若B串长度小于A串,且B > A / 2

      如上图所示,A可以分为XYX,A本身可以被分为3组回文段,按我们的贪心策略,A会优先被切割,所以这种情况是不会存在的
    3. 若B串长度小于A串,且B < A / 2

      如上图所示,不看最后的一段B,`A与A面临的情况是相同的,即保持B的长度不变,不断地从*A中分割出一个B,最后必定会得到B > *A / 2,也就是第2种情况。
      由情况2可知:*A=XYX,B=YX,所以A划分的结果为:*ABBB...=XYX...XYX,与A同理,A本身可以被分为k组回文段,与贪心策略相悖,这种情况不存在
  3. 多项式哈希(基本知识见Oi WiKi的链接,讲解得比较详细):
    1. 按照原题的数据范围,暴力判断两子串是否相等也能过,时间复杂度大概是O(n^2),但如果n的范围扩大到1e5或者1e6,暴力的方法应该就不行了
    2. 这里我们可以用多项式哈希的方法预处理每位字符的哈希值,然后维护该哈希值的前缀,判断子串是否相等时我们可以通过前缀数组O(1)查询子串的哈希值
      PS:前缀子串和后缀子串的哈希值本身相差b^(k),b是选择的随机值,k是两子串头部的距离,b^(k)可以在预处理每位字符的哈希值时记录下来
    3. 多项式哈希冲突的概率为\frac{\left ( l-1 \right )}{M}(l为字符串的长度,M为选取的大质数)。一个大质数容易发生哈希冲突,一般选取两个大质数(冲突概率即为两个冲突概率的乘积)
      这里简单估计一下冲突的概率:假设我们选取的大质数均是1e9级别的,字符串长度为1e5级别的,查询次数也为1e5级别的,那么保证不发生冲突的概率为:\left ( 1-\left ( \frac{1e5}{1e9} \right )^{2} \right )^{1e5}\approx 99.9%

代码示例:
 

class Solution {
public:
    using ll = long long;
    
	// 两个大质数减小冲突概率
	const ll mod1 = 1e8 + 7, mod2 = 1e9 + 7;
	// hash值前缀和
	vector<ll> pre1, pre2;
	vector<ll> bn1, bn2;
	ll b;
	string_view str;
	int n;

	void Init(vector<ll>& pre, vector<ll>& bn, ll mod) {
		b = 2 + (rand() % mod);
		pre.resize(n + 1, 0);
		bn.resize(n + 1);
		bn[0] = 1;
		for (int i = 1; i <= n; i++) {
			int ch = str[i - 1];
			(pre[i] = ch * bn[i - 1] + pre[i - 1]) %= mod;
			bn[i] = (bn[i - 1] * b) % mod;
		}
	}

	// 判断子串是否相等
	inline bool IsSame(int p, int q, int len, vector<ll>& pre, vector<ll>& bn, ll mod) {
		++p, ++q;
		ll v1 = (pre[p + len - 1] - pre[p - 1] + mod) % mod;
		ll v2 = (pre[q] - pre[q - len] + mod) % mod;
		return v1 * bn[q - len + 1 - p] % mod == v2;
	}

	int longestDecomposition(string text) {
		srand(time(nullptr));
		n = text.size();
		str = text;
		Init(pre1, bn1, mod1);
		Init(pre2, bn2, mod2);
		int ans = 0;
		int l = 0, r = n - 1;
		for (int len = 1; l + len - 1 < r - len + 1; len++) {
			if (IsSame(l, r, len, pre1, bn1, mod1) && IsSame(l, r, len, pre2, bn2, mod2)) {
				ans += 2;
				l = l + len;
				r = r - len;
				len = 0;
			}
		}
		if (l <= r) ++ans;
		return ans;
	}
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IN0vation

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值