给你一个长度为 n
的字符串 word
和一个整数 k
,其中 k
是 n
的因数。
在一次操作中,你可以选择任意两个下标 i
和 j
,其中 0 <= i, j < n
,且这两个下标都可以被 k
整除,然后用从 j
开始的长度为 k
的子串替换从 i
开始的长度为 k
的子串。也就是说,将子串 word[i..i + k - 1]
替换为子串 word[j..j + k - 1]
。
返回使 word
成为 K 周期字符串 所需的 最少 操作次数。
如果存在某个长度为 k
的字符串 s
,使得 word
可以表示为任意次数连接 s
,则称字符串 word
是 K 周期字符串 。例如,如果 word == "ababab"
,那么 word
就是 s = "ab"
时的 2 周期字符串 。
示例 1:
输入:word = "leetcodeleet", k = 4
输出:1
解释:可以选择 i = 4 和 j = 0 获得一个 4 周期字符串。这次操作后,word 变为 "leetleetleet" 。
示例 2:
输入:word = "leetcoleet", k = 2
输出:3
解释:可以执行以下操作获得一个 2 周期字符串。
i | j | word |
---|---|---|
0 | 2 | etetcoleet |
4 | 0 | etetetleet |
6 | 0 | etetetetet |
提示:
1 <= n == word.length <= 105
1 <= k <= word.length
k
能整除word.length
。word
仅由小写英文字母组成。
C#代码:
public class Solution {
public int MinimumOperationsToMakeKPeriodic(string word, int k) {
Dictionary<string, int> map = new Dictionary<string, int>();
int n = word.Length;
for (int i = k; i <= n; i += k) { //[(i - k)..i]
string sub = word.Substring(i - k, k);
if (map.ContainsKey(sub)) {
map[sub]++;
} else {
map[sub] = 1;
}
}
int sum = 0, mx = 0;
foreach (var kvp in map) {
sum += kvp.Value;
mx = Math.Max(mx, kvp.Value);
}
return sum - mx;
}
}
因为C#有字典结构,作起来比较方便,可以参考思路;
C语言代码:
注意:样例过大,应该使用哈希表进行相关操作,错误例子:
// 用于存储子串及其出现的频率
typedef struct {
char sub[100];
int count;
} SubstringFreq;
int minimumOperationsToMakeKPeriodic(char* word, int k) {
int n = strlen(word);
int numSubstrings = n / k;
SubstringFreq *freqs = (SubstringFreq *)calloc(numSubstrings, sizeof(SubstringFreq));
int distinctSubstrings = 0;
// 统计子串的频率
for (int i = 0; i <= n - k; i += k) {
char sub[100];
strncpy(sub, word + i, k);
sub[k] = '\0';
int found = 0;
for (int j = 0; j < distinctSubstrings; j++) {
if (strcmp(freqs[j].sub, sub) == 0) {
freqs[j].count++;
found = 1;
break;
}
}
if (!found) {
strcpy(freqs[distinctSubstrings].sub, sub);
freqs[distinctSubstrings].count = 1;
distinctSubstrings++;
}
}
// 找到出现频率最高的子串
int maxFreq = 0;
for (int i = 0; i < distinctSubstrings; i++) {
if (freqs[i].count > maxFreq) {
maxFreq = freqs[i].count;
}
}
free(freqs);
return numSubstrings - maxFreq;
}
可以看见样例比较小的时候还是可以通过的;
但是提交的时候会给出以下警告:
意思就是数据过大就执行错误。
所以必须借助数据结构哈希表这样操作:
// 定义哈希表项结构
typedef struct {
int key; // 哈希键,表示子串的哈希值
int val; // 哈希值,表示该子串的出现次数
UT_hash_handle hh; // 使结构体成为哈希表的一部分
} HashItem;
// 查找哈希表中的项,返回指向该项的指针
HashItem *hashFindItem(HashItem **obj, int key) {
HashItem *pEntry = NULL;
HASH_FIND_INT(*obj, &key, pEntry); // 在哈希表中查找键为key的项
return pEntry;
}
// 添加新项到哈希表,如果该项已存在,则返回false
bool hashAddItem(HashItem **obj, int key, int val) {
if (hashFindItem(obj, key)) { // 如果该键已经存在
return false;
}
HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem)); // 为新项分配内存
pEntry->key = key;
pEntry->val = val;
HASH_ADD_INT(*obj, key, pEntry); // 将新项添加到哈希表
return true;
}
// 设置哈希表项的值,如果不存在则添加
bool hashSetItem(HashItem **obj, int key, int val) {
HashItem *pEntry = hashFindItem(obj, key);
if (!pEntry) {
hashAddItem(obj, key, val); // 如果项不存在则添加
} else {
pEntry->val = val; // 如果项存在则更新其值
}
return true;
}
// 获取哈希表项的值,如果不存在则返回默认值
int hashGetItem(HashItem **obj, int key, int defaultVal) {
HashItem *pEntry = hashFindItem(obj, key);
if (!pEntry) {
return defaultVal; // 如果项不存在则返回默认值
}
return pEntry->val; // 如果项存在则返回其值
}
// 释放哈希表中的所有项
void hashFree(HashItem **obj) {
HashItem *curr = NULL, *tmp = NULL;
HASH_ITER(hh, *obj, curr, tmp) { // 遍历哈希表中的所有项
HASH_DEL(*obj, curr);
free(curr); // 释放每一项的内存
}
}
// 计算字符串s从start到end的哈希值
int hash(char *s, int start, int end) {
long long base = 31;
long long mod = 1e9 + 7;
long long res = 0;
for (int i = start; i <= end; i++) {
res = (res * base + s[i] - 'a') % mod; // 计算子串的哈希值
}
return res;
}
// 计算将字符串变为k周期所需的最小操作数
int minimumOperationsToMakeKPeriodic(char* word, int k) {
int n = strlen(word), res = INT_MAX;
HashItem *count = NULL;
for (int i = 0; i < n; i += k) {
int part = hash(word, i, i + k - 1); // 计算子串的哈希值
hashSetItem(&count, part, hashGetItem(&count, part, 0) + 1); // 更新哈希表中该子串的出现次数
res = fmin(res, n / k - hashGetItem(&count, part, 0)); // 计算需要的最小操作数
}
hashFree(&count); // 释放哈希表
return res;
}
代码说明:
- 哈希表:使用
uthash
库来实现哈希表,以记录每个子串的出现次数。 - hash 函数:计算给定范围内字符串的哈希值,使用模运算来避免整数溢出。
- 最小操作数计算:遍历字符串,以步长
k
计算每个子串的哈希值,并更新哈希表中的出现次数。最终结果是将字符串变为k
周期所需的最小操作数。 - 内存管理:确保在使用结束后释放所有分配的内存,防止内存泄漏。
这段代码应该可以正确处理较大规模的输入。
提交结果:
然后根据上面C语言代码,我发现还能优化一下:
// 定义哈希表项结构
typedef struct {
int key; // 哈希键,表示子串的哈希值
int val; // 哈希值,表示该子串的出现次数
UT_hash_handle hh; // 使结构体成为哈希表的一部分
} HashItem;
// 查找或添加哈希表项,如果已存在则返回该项,否则新建并返回新项
HashItem* hashSetItem(HashItem **obj, int key) {
HashItem *pEntry = NULL;
HASH_FIND_INT(*obj, &key, pEntry); // 在哈希表中查找键为key的项
if (!pEntry) { // 如果项不存在,则创建新项并添加到哈希表
pEntry = (HashItem *)malloc(sizeof(HashItem));
pEntry->key = key;
pEntry->val = 0;
HASH_ADD_INT(*obj, key, pEntry);
}
return pEntry;
}
// 释放哈希表中的所有项
void hashFree(HashItem **obj) {
HashItem *curr = NULL, *tmp = NULL;
HASH_ITER(hh, *obj, curr, tmp) { // 遍历哈希表中的所有项
HASH_DEL(*obj, curr);
free(curr); // 释放每一项的内存
}
}
// 计算字符串s从start到end的哈希值
int hash(char *s, int start, int end) {
long long base = 31;
long long mod = 1e9 + 7;
long long res = 0;
for (int i = start; i <= end; i++) {
res = (res * base + s[i] - 'a') % mod; // 计算子串的哈希值
}
return res;
}
// 计算将字符串变为k周期所需的最小操作数
int minimumOperationsToMakeKPeriodic(char* word, int k) {
int n = strlen(word), res = INT_MAX;
HashItem *count = NULL;
for (int i = 0; i < n; i += k) {
int part = hash(word, i, i + k - 1); // 计算子串的哈希值
HashItem *pEntry = hashSetItem(&count, part); // 查找或创建哈希项
pEntry->val++; // 增加该子串的出现次数
res = fmin(res, n / k - pEntry->val); // 计算需要的最小操作数
}
hashFree(&count); // 释放哈希表
return res;
}
提交结果:
也是在c领域乱杀了。
然而C++更简单一点,直接有哈希库可以使用:
class Solution {
public:
// 计算将字符串变为k周期所需的最小操作数
int minimumOperationsToMakeKPeriodic(string word, int k) {
unordered_map<string, int> cnt; // 定义一个哈希表,键为子串,值为该子串的出现次数
int n = word.size(); // 获取字符串的长度
int mx = 0; // 用于记录出现次数最多的子串的次数
// 遍历字符串,以步长k遍历,提取每个长度为k的子串
for (int i = 0; i < n; i += k) {
mx = max(mx, ++cnt[word.substr(i, k)]);
}
return n / k - mx;
}
};
提交结果:
虽然没那么无敌,但也能过,效率还是比较高的。
好了,本期就先到这,希望对小伙伴有所帮助,博主也是边写边学的,也是这两个星期才写算法的,如果有什么不足还请私信博主,欢迎探讨。
题目来源:3137. K 周期字符串需要的最少操作次数 - 力扣(LeetCode)
C代码(第一个C)与思路来源:
C#代码与思路来源:b站