周期字符串需要的最少操作次数----leetcode

给你一个长度为 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 周期字符串。

ijword
02etetcoleet
40etetetleet
60etetetetet

提示:

  • 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站

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值