须知
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对 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」问题的多种解法、毒药持续时间的计算方法,我们总结出贪心算法在处理时间重叠和区间问题中的高效应用。 贪心算法通过局部最优选择(如判断时间差、间隔计算等)来实现全局最优解,在解决连续区间、时间重叠、资源分配等问题时展现出了简洁和高效的特性。 在处理 区间重叠问题、最大化资源利用 以及 高效求解时间持续问题 等场景时,贪心算法可以显著简化问题求解的复杂度,尤其适用于 时间复杂度敏感 和 大数据规模 的应用场景。
路虽远,行则将至;事虽难,做则必成
亲爱的读者们,下一篇文章再会!!!