须知
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对 C++算法 感兴趣的朋友,让我们一起进步!
模拟算法是一类通过模拟真实世界过程来解决问题的算法。它通过计算机模拟复杂系统的行为,帮助我们分析和解决那些难以通过解析方法直接求解的问题。模拟算法通常通过随机化、迭代、近似等技术,利用计算机的强大计算能力来获得问题的近似解。
模拟算法的核心思想通常包括以下几个方面:
题目链接:1576. 替换所有的问号 - 力扣(LeetCode)
题目描述:

'?',则需要进行替换操作。'?' 字符:
'?',我们尝试将它替换为字母 'a' 到 'z' 中的一个字母。为了避免与相邻的字符重复,只有当候选字母与前一个字符和后一个字符都不相同时,才选择该字母进行替换。'?' 位于字符串的第一个位置(i == 0),它不需要与前一个字符比较,只需要检查它是否与后一个字符相同。'?' 位于字符串的最后一个位置(i == s.size() - 1),它不需要与后一个字符比较,只需要检查它是否与前一个字符相同。'?' 字符后,返回修改后的字符串。class Solution
{
public:
string modifyString(string s)
{
// 遍历字符串
for(int i=0; i<s.size(); i++)
{
// 如果当前字符是 '?',则进行替换
if(s[i] == '?')
{
// 尝试从 'a' 到 'z' 中选择一个字符
for(char ch = 'a'; ch <= 'z'; ch++)
{
// 如果当前字符可以替换 '?',即与前后字符不相同
if((i == 0 || ch != s[i-1]) && (i == s.size()-1 || ch != s[i+1]))
{
s[i] = ch; // 替换字符
break; // 一旦找到合适的字符,跳出循环
}
}
}
}
return s; // 返回修改后的字符串
}
};for(int i = 0; i < s.size(); i++):
s 的每个字符。if(s[i] == '?'):
'?',则需要进行替换操作。for(char ch = 'a'; ch <= 'z'; ch++):
'a' 到 'z' 中尝试每一个字母。if((i == 0 || ch != s[i-1]) && (i == s.size()-1 || ch != s[i+1])):
ch 是否与相邻的字符重复: i == 0:如果当前字符是字符串的第一个字符,它只需要与后一个字符不相同。i == s.size() - 1:如果当前字符是字符串的最后一个字符,它只需要与前一个字符不相同。ch 需要与前后字符都不相同。s[i] = ch;:
'?'。break;:
for 循环,继续处理下一个 '?'。return s;:
思路: 另一种优化的思路是使用一个字符集 {'a', 'b', 'c'} 来替代 '?',只需要考虑三个字母就足够。因为只有三个字母,它们中任何一个都可以在符合条件的情况下替换 '?'。
步骤:
'?' 时,选择一个字母来替换。代码实现:
class Solution {
public:
string modifyString(string s) {
for (int i = 0; i < s.size(); i++) {
if (s[i] == '?') {
for (char ch : {'a', 'b', 'c'}) { // 只用 'a', 'b', 'c'
if ((i == 0 || ch != s[i-1]) && (i == s.size() - 1 || ch != s[i+1])) {
s[i] = ch;
break; // 找到合适的字符后退出循环
}
}
}
}
return s;
}
};时间复杂度:
O(n)。O(1)。总体时间复杂度: O(n)。
思路: 我们可以直接在遍历的过程中动态选择合适的字符来替换 '?',而不需要内部循环遍历所有字母。这个解法避免了重复遍历字母,减少了计算量。
步骤:
'?' 时,检查其前后的字符,并选择一个与前后字符都不相同的字母进行替换。代码实现:
class Solution {
public:
string modifyString(string s) {
for (int i = 0; i < s.size(); i++) {
if (s[i] == '?') {
// 判断当前 '?' 前后的字符
char prev = (i == 0) ? 0 : s[i-1];
char next = (i == s.size() - 1) ? 0 : s[i+1];
for (char ch = 'a'; ch <= 'z'; ch++) {
// 找一个既不与前一个字符重复也不与后一个字符重复的字母
if (ch != prev && ch != next) {
s[i] = ch;
break;
}
}
}
}
return s;
}
};时间复杂度:
O(n)。O(1)。总体时间复杂度: O(n)。
思路: 通过预先生成一个不重复的字符列表,然后根据条件动态选择下一个字符。这个方法的好处是避免了在每次遇到 '?' 时都进行字母检查。
步骤:
{'a', 'b', 'c'}。'?' 时,根据前后字符选一个合适的字母进行替换。代码实现:
class Solution {
public:
string modifyString(string s) {
vector<char> available = {'a', 'b', 'c'};
for (int i = 0; i < s.size(); i++) {
if (s[i] == '?') {
for (char ch : available) {
if ((i == 0 || ch != s[i-1]) && (i == s.size()-1 || ch != s[i+1])) {
s[i] = ch;
break;
}
}
}
}
return s;
}
};时间复杂度:
O(n)。O(1)。总体时间复杂度: O(n)。
这四种解法的时间复杂度和空间复杂度基本相同,都是 O(n) 和 O(1)。主要的差异在于内部选择字符的策略和细节上的实现。最重要的是,所有解法都依赖于贪心策略,确保每个 '?' 字符替换为一个与邻近字符不同的字母。
s,时间复杂度为 O(n),其中 n 是字符串的长度。'?',我们最多尝试26个字母。因此内层循环的时间复杂度为 O(26),即常数时间 O(1)。O(n),因为内层循环的次数是常数。O(1)。该算法通过遍历字符串并尝试替换每个 '?' 字符,确保替换后的字符不与相邻字符相同。它的时间复杂度为 O(n),空间复杂度为 O(1),是一种高效的解决方案。
题目描述:

3.1 算法思路:
duration 时间。duration,那么毒药的持续时间会部分重叠,重叠部分不重复计算。duration,则没有重叠,应该完全计算毒药的持续时间。timeSeries 数组中的每个时间点,计算每两个连续时间点之间的时间差。duration,则毒药的持续时间不会重叠,应该加上 duration。duration,则只有部分时间是新的持续时间,应该加上时间差。duration。class Solution {
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration) {
int n = timeSeries.size(); // 获取时间序列的长度
int ret = 0; // 结果变量,用来累加毒药的持续时间
for (int i = 0; i < n - 1; i++) { // 遍历时间序列,直到倒数第二个元素
// 如果下一个释放时间与当前释放时间的间隔大于等于 duration
if (timeSeries[i + 1] - timeSeries[i] >= duration) {
ret += duration; // 没有重叠,累加整个 duration
} else {
// 否则,间隔小于 duration,累加两次释放之间的间隔
ret += timeSeries[i + 1] - timeSeries[i];
}
}
// 最后一次释放毒药的持续时间
ret += duration;
return ret;
}
};timeSeries 数组:
timeSeries 中的每个时间点,除了最后一个时间点。每次释放毒药都会持续 duration 时间,除非后续释放的毒药与当前的释放时间有重叠。timeSeries[i] 和 timeSeries[i + 1],我们计算它们之间的时间差: duration,说明两次释放毒药没有重叠,所以当前毒药持续的时间是 duration,我们将其加到总时间中。duration,说明两次释放的毒药存在部分重叠,因此我们只能加上时间差,即毒药的持续时间为 timeSeries[i + 1] - timeSeries[i]。ret += duration; 确保了最后一次释放的毒药持续时间被正确计算。ret 就是所有毒药持续时间的总和。duration。timeSeries 数组: duration),则毒药持续时间是 duration。duration。代码实现:
class Solution {
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration) {
int n = timeSeries.size();
int ret = 0;
for (int i = 0; i < n - 1; i++) {
// 如果当前释放时间和下次释放时间的间隔大于等于duration
if (timeSeries[i + 1] - timeSeries[i] >= duration) {
ret += duration;
} else {
// 如果下次释放时间早于当前毒药的持续时间,则加上间隔时间
ret += timeSeries[i + 1] - timeSeries[i];
}
}
// 最后一次释放的毒药持续duration
if (n > 0) {
ret += duration;
}
return ret;
}
};时间复杂度:
O(n),其中 n 是 timeSeries 数组的长度。我们只遍历了一次 timeSeries 数组。O(1),我们只用了几个常数空间。poisoned数组)。但总体效率是相同的。poisoned 数组,虽然能清晰模拟过程,但开销较大,特别是当时间序列的时间跨度很大时。O(n),并且没有额外的空间开销,因此是最优解。O(n),但空间复杂度可能较高,不太适合大规模的输入。timeSeries 数组,因此时间复杂度为 O(n),其中 n 是 timeSeries 数组的长度。O(1)。该算法通过遍历 timeSeries 数组并计算相邻时间点的时间差,确保了正确地计算每次毒药释放的持续时间。对于不重叠的部分,完全加上 duration,对于重叠部分,计算实际的时间差。最后,单独加上最后一次释放毒药的持续时间。
timeSeries 数组,计算每两个释放时刻之间的时间差。duration,则毒药的持续时间为 duration。duration,则毒药的持续时间为时间差。O(n),其中 n 是 timeSeries 数组的长度。O(1),只用了常数空间。poisoned 标记每个时刻是否中毒。O(n),但空间复杂度较高,O(m),其中 m 是时间跨度。总结:
O(n),且空间复杂度为 O(1),效率高且简洁。过上面几个例题,如「Teemo Attacking」问题的多种解法、毒药持续时间的计算方法,我们总结出贪心算法在处理时间重叠和区间问题中的高效应用。 贪心算法通过局部最优选择(如判断时间差、间隔计算等)来实现全局最优解,在解决连续区间、时间重叠、资源分配等问题时展现出了简洁和高效的特性。 在处理 区间重叠问题、最大化资源利用 以及 高效求解时间持续问题 等场景时,贪心算法可以显著简化问题求解的复杂度,尤其适用于 时间复杂度敏感 和 大数据规模 的应用场景。
路虽远,行则将至;事虽难,做则必成
亲爱的读者们,下一篇文章再会!!!