
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在算法体系的浩瀚星河中,模拟算法始终是最具"烟火气"的基础存在------它不依赖复杂的数学推导,不追求精妙的优化技巧,核心逻辑朴素而直白:题目让你做什么,你就用代码忠实还原什么.很多初学者乃至资深开发者,都容易陷入"模拟 = 简单"的认知误区,将其等同于"按图索骥"的代码堆砌,却忽略了这一基础算法背后,藏着优选算法体系中最核心的解题思维与底层逻辑.作为五大基础算法之一,模拟算法是连接自然语言描述与计算机求解的直接桥梁,更是优选算法学习的入门基石.它的核心价值,从来不是"完成代码实现",而是"精准建模、细节把控、逻辑拆解"的能力------从问题抽象、状态定义,到规则执行、边界处理,每一步推演都在锤炼我们将复杂场景转化为可执行流程的能力,这正是优选算法所强调的"高效解题、深度理解"的核心要义.本文立足优选算法视角,以实战推演为核心,跳出"单纯代码实现"的局限,深入挖掘模拟算法的蕴含深意.我们将通过经典案例的一步步推演,拆解模拟算法的实现逻辑、细节陷阱与优化思路,让读者明白:模拟算法不仅是编程竞赛中的"签到题利器"、工程开发中的"业务逻辑实现工具",更是培养逻辑思维、夯实算法基础的关键载体------它教会我们的,是如何用严谨的代码,还原问题的本质.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
1.模拟算法背景介绍
模拟算法(Simulation Algorithm)是一种基础且通用的算法思想,核心是通过计算机程序忠实还原问题描述的规则或现实世界的系统行为,按照既定逻辑逐步推进并最终得到结果的程序化实现方法.它强调 "无需复杂优化,严格按规则执行",本质是将抽象问题的逻辑流程转化为可执行的代码步骤,是连接问题描述与计算机求解的直接桥梁.随着算法理论体系的完善,模拟算法成为五大基础算法之一(其他四种为枚举、递归、分治、贪心),广泛应用于编程竞赛、工程开发和科学研究中.它既是独立的解题方法,也是其他复杂算法的辅助模块.模拟算法虽看似简单,却是算法能力的重要体现,掌握模拟算法不仅能解决实际问题,更能培养严谨的逻辑思维和代码实现能力,为深入学习其他复杂算法奠定坚实基础.
2.替换所有的问号(OJ题)

算法思路:解法(模拟)
纯模拟.从前往后遍历整个字符串,找到问号之后,就用 a ~ z 的每一个字符去尝试替换即可.
核心代码
cpp
class Solution
{
public:
string modifyString(string s)
{
int n = s.size();
for (int i = 0; i < n; i++)
{
if (s[i] == '?') //替换
{
for (char ch = 'a'; ch <= 'z'; ch++)
{
if ((i == 0 || ch != s[i - 1]) &&(i == n - 1 || ch != s[i + 1]))
{
s[i] = ch;
break;
}
}
}
}
return s;
}
};
完整测试代码
cpp
#include <iostream>
#include <string>
using namespace std;
class Solution
{
public:
string modifyString(string s)
{
int n = s.size();
for (int i = 0; i < n; i++)
{
if (s[i] == '?') //遇到问号,开始替换
{
//从a到z遍历,找到符合条件的字符
for (char ch = 'a'; ch <= 'z'; ch++)
{
//核心判断:不和左边、右边的字符相同
if ((i == 0 || ch != s[i - 1]) &&(i == n - 1 || ch != s[i + 1]))
{
s[i] = ch;
break;
}
}
}
}
return s;
}
};
int main() {
Solution sol;
string s1 = "?";
cout << "输入:" << s1 << " 输出:" << sol.modifyString(s1) << endl;
string s2 = "?zs";
cout << "输入:" << s2 << " 输出:" << sol.modifyString(s2) << endl;
string s3 = "a?b";
cout << "输入:" << s3 << " 输出:" << sol.modifyString(s3) << endl;
string s4 = "??";
cout << "输入:" << s4 << " 输出:" << sol.modifyString(s4) << endl;
string s5 = "j?qg??";
cout << "输入:" << s5 << " 输出:" << sol.modifyString(s5) << endl;
string s6 = "abc";
cout << "输入:" << s6 << " 输出:" << sol.modifyString(s6) << endl;
return 0;
}

3.提莫攻击(OJ题)

算法思路:解法(模拟 + 分情况讨论)
模拟+分情况讨论,计算相邻两个时间点的差值:
- 如果差值大于等于中毒时间,说明上次中毒可以持续
duration秒; - 如果差值小于中毒时间,那么上次的中毒只能持续两者的差值.
核心代码
cpp
class Solution
{
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration)
{
int ret = 0;
for (int i = 1; i < timeSeries.size(); i++)
{
int tmp = timeSeries[i] - timeSeries[i - 1];
if (tmp >= duration)
ret += duration;
else
ret += tmp;
}
return ret + duration;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration)
{
int ret = 0;
for (int i = 1; i < timeSeries.size(); i++)
{
int tmp = timeSeries[i] - timeSeries[i - 1];
if (tmp >= duration)
ret += duration;
else
ret += tmp;
}
return ret + duration; //最后一次中毒一定完整持续duration秒
}
};
int main() {
Solution sol;
vector<int> t1 = {1, 4};
int d1 = 2;
cout << "测试用例1:timeSeries = [1,4], duration=2 → 中毒总时长:" << sol.findPoisonedDuration(t1, d1) << "(预期:4)" << endl;
vector<int> t2 = {1, 2};
int d2 = 2;
cout << "测试用例2:timeSeries = [1,2], duration=2 → 中毒总时长:" << sol.findPoisonedDuration(t2, d2) << "(预期:3)" << endl;
vector<int> t3 = {1, 3, 5};
int d3 = 2;
cout << "测试用例3:timeSeries = [1,3,5], duration=2 → 中毒总时长:" << sol.findPoisonedDuration(t3, d3) << "(预期:6)" << endl;
vector<int> t4 = {10};
int d4 = 5;
cout << "测试用例4:timeSeries = [10], duration=5 → 中毒总时长:" << sol.findPoisonedDuration(t4, d4) << "(预期:5)" << endl;
vector<int> t5 = {1, 2, 3};
int d5 = 3;
cout << "测试用例5:timeSeries = [1,2,3], duration=3 → 中毒总时长:" << sol.findPoisonedDuration(t5, d5) << "(预期:5)" << endl;
return 0;
}

4.Z字形变换(OJ题)

算法思路:解法(模拟+找规律)
找规律,用 row 代替行数,row = 4 时画出的 Z 字形如下:
0 2row - 2 4row - 4
1 2row - 3 2row - 1 4row - 5 4row - 3
2 2row-4 2row 4row - 6 4row - 2
3 2row + 1 4row - 1
不难发现,数据是以 2row - 2 为一个周期进行规律变换的.将所有数替换成用周期来表示的变量:
- 第一行的数是:
0, 2row - 2, 4row - 4; - 第二行的数是:
1, (2row - 2) - 1, (2row - 2) + 1, (4row - 4) - 1, (4row - 4) + 1; - 第三行的数是:
2, (2row - 2) - 2, (2row - 2) + 2, (4row - 4) - 2, (4row - 4) + 2; - 第四行的数是:
3, (2row - 2) + 3, (4row - 4) + 3。
可以观察到,第一行、第四行为差为 2row - 2 的等差数列;第二行、第三行除了第一个数取值为行数,每组下标为 (2n - 1, 2n) 的数围绕 (2row - 2) 的倍数左右取值.以此规律,我们可以写出迭代算法.

核心代码
cpp
class Solution
{
public:
string convert(string s, int numRows)
{
//处理边界情况
if (numRows == 1)
return s;
string ret;
int d = 2 * numRows - 2, n = s.size();
//1.先处理第⼀⾏
for (int i = 0; i < n; i += d)
ret += s[i];
//2.处理中间⾏
for (int k = 1; k < numRows - 1; k++) //枚举每⼀⾏
{
for (int i = k, j = d - k; i < n || j < n; i += d, j += d)
{
if (i < n)
ret += s[i];
if (j < n)
ret += s[j];
}
}
//3.处理最后⼀⾏
for (int i = numRows - 1; i < n; i += d)
ret += s[i];
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <string>
using namespace std;
class Solution
{
public:
string convert(string s, int numRows)
{
//处理边界情况:只有1行时,直接返回原字符串
if (numRows == 1)
return s;
string ret;
//周期长度:2*行数-2
int d = 2 * numRows - 2, n = s.size();
//1.先处理第一行
for (int i = 0; i < n; i += d)
ret += s[i];
//2.处理中间行
for (int k = 1; k < numRows - 1; k++) //枚举每一行
{
for (int i = k, j = d - k; i < n || j < n; i += d, j += d)
{
if (i < n)
ret += s[i];
if (j < n)
ret += s[j];
}
}
//3.处理最后一行
for (int i = numRows - 1; i < n; i += d)
ret += s[i];
return ret;
}
};
int main() {
Solution sol;
string s1 = "PAYPALISHIRING";
int rows1 = 3;
cout << "输入:s = \"" << s1 << "\", numRows = " << rows1 << endl;
cout << "输出:" << sol.convert(s1, rows1) << endl;
cout << "预期:PAHNAPLSIIGYIR" << endl << endl;
string s2 = "PAYPALISHIRING";
int rows2 = 4;
cout << "输入:s = \"" << s2 << "\", numRows = " << rows2 << endl;
cout << "输出:" << sol.convert(s2, rows2) << endl;
cout << "预期:PINALSIGYAHRPI" << endl << endl;
string s3 = "LEETCODE";
int rows3 = 1;
cout << "输入:s = \"" << s3 << "\", numRows = " << rows3 << endl;
cout << "输出:" << sol.convert(s3, rows3) << endl;
cout << "预期:LEETCODE" << endl << endl;
string s4 = "ABCDE";
int rows4 = 5;
cout << "输入:s = \"" << s4 << "\", numRows = " << rows4 << endl;
cout << "输出:" << sol.convert(s4, rows4) << endl;
cout << "预期:ABCDE" << endl << endl;
string s5 = "AB";
int rows5 = 3;
cout << "输入:s = \"" << s5 << "\", numRows = " << rows5 << endl;
cout << "输出:" << sol.convert(s5, rows5) << endl;
cout << "预期:AB" << endl;
return 0;
}

5.外观数列(OJ题)

算法思路:解法(模拟+双指针)
所谓外观数列,其实只是依次统计字符串中连续且相同的字符的个数.依照题意,依次模拟即可.
核心代码
cpp
class Solution
{
public:
string countAndSay(int n)
{
string ret = "1";
for (int i = 1; i < n; i++) //解释n - 1次ret即可
{
string tmp;
int len = ret.size();
for (int left = 0, right = 0; right < len;)
{
while (right < len && ret[left] == ret[right])
right++;
tmp += to_string(right - left) + ret[left];
left = right;
}
ret = tmp;
}
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <string>
using namespace std;
class Solution
{
public:
string countAndSay(int n)
{
string ret = "1";
//循环 n-1 次,生成第 n 项
for (int i = 1; i < n; i++)
{
string tmp;
int len = ret.size();
//双指针:统计连续相同字符的数量
for (int left = 0, right = 0; right < len;)
{
//右指针移动到第一个不同的字符位置
while (right < len && ret[left] == ret[right])
right++;
//拼接:数量 + 字符
tmp += to_string(right - left) + ret[left];
left = right;
}
ret = tmp;
}
return ret;
}
};
int main() {
Solution sol;
cout << "n = 1 → 输出:" << sol.countAndSay(1) << " 预期:1" << endl;
cout << "n = 2 → 输出:" << sol.countAndSay(2) << " 预期:11" << endl;
cout << "n = 3 → 输出:" << sol.countAndSay(3) << " 预期:21" << endl;
cout << "n = 4 → 输出:" << sol.countAndSay(4) << " 预期:1211" << endl;
cout << "n = 5 → 输出:" << sol.countAndSay(5) << " 预期:111221" << endl;
cout << "n = 6 → 输出:" << sol.countAndSay(6) << " 预期:312211" << endl;
return 0;
}

6.数青蛙(OJ题)

算法思路:解法(模拟 + 分情况讨论)
模拟⻘蛙的叫声.
- 当遇到'r' 'o' 'a' 'k' 这四个字符的时候,我们要去看看每⼀个字符对应的前驱字符,有没有⻘蛙叫出来.如果有⻘蛙叫出来,那就让这个⻘蛙接下来喊出来这个字符;如果没有,直接返回-1;
- 当遇到'c'这个字符的时候,我们去看看'k'这个字符有没有⻘蛙叫出来.如果有,就让这个⻘蛙继续去喊 'c' 这个字符;如果没有的话,就重新搞⼀个⻘蛙.
- 用状态计数模拟青蛙的鸣叫过程:把croak拆成5个状态(0=c,1=r,2=o,3=a,4=k),用数组记录每个状态的青蛙数量,叫完 k 的青蛙可以复用,最终统计最大并发数(最少青蛙数).
- 青蛙鸣叫是线性流转:c(0) → r(1) → o(2) → a(3) → k(4)
代码中:叫r必须先有 c;叫o必须先有r;叫a必须先有o;叫k必须先有a;不满足直接返回 -1. - 一只青蛙叫完k后,可以立刻重新叫c:遇到新的c时,先检查有没有空闲青蛙(hash[4])有就复用(hash[4]--),不用新增青蛙,这是求最少青蛙数的关键!
- 遍历结束后,所有青蛙必须叫完k:如果c/r/o/a还有数量→鸣叫中断→非法;只有k有数量→全部完成 →合法
核心代码
cpp
class Solution
{
public:
int minNumberOfFrogs(string croakOfFrogs)
{
string t = "croak";
int n = t.size();
vector<int> hash(n); //⽤数组来模拟哈希表
unordered_map<char, int> index; //[x, x这个字符对应的下标]
for (int i = 0; i < n; i++)
index[t[i]] = i;
for (auto ch : croakOfFrogs)
{
if (ch == 'c')
{
if (hash[n - 1] != 0)
hash[n - 1]--;
hash[0]++;
}
else
{
int i = index[ch];
if (hash[i - 1] == 0)
return -1;
hash[i - 1]--;
hash[i]++;
}
}
for (int i = 0; i < n - 1; i++)
if (hash[i] != 0)
return -1;
return hash[n - 1];
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>
using namespace std;
class Solution
{
public:
int minNumberOfFrogs(string croakOfFrogs)
{
string t = "croak";
int n = t.size();
vector<int> hash(n); //用数组模拟哈希表,记录每个字符的状态数量
unordered_map<char, int> index; //建立字符到下标的映射:c=0, r=1, o=2, a=3, k=4
for (int i = 0; i < n; i++)
index[t[i]] = i;
for (auto ch : croakOfFrogs)
{
if (ch == 'c')
{
//如果有青蛙叫完了k,复用这只青蛙,减少k的数量
if (hash[n - 1] != 0)
hash[n - 1]--;
hash[0]++; //新增一只青蛙开始叫c
}
else
{
int i = index[ch];
//前一个字符没有,说明序列非法
if (hash[i - 1] == 0)
return -1;
hash[i - 1]--; //前一个状态减一
hash[i]++; //当前状态加一
}
}
//检查除了k之外,其他字符是否都完成了鸣叫
for (int i = 0; i < n - 1; i++)
if (hash[i] != 0)
return -1;
//最终k的数量就是最少需要的青蛙数
return hash[n - 1];
}
};
int main() {
Solution sol;
string s1 = "croakcroak";
cout << "测试用例1:" << s1 << " → 最少青蛙数:" << sol.minNumberOfFrogs(s1) << "(预期:1)" << endl;
string s2 = "crcoakroak";
cout << "测试用例2:" << s2 << " → 最少青蛙数:" << sol.minNumberOfFrogs(s2) << "(预期:2)" << endl;
string s3 = "croakcroa";
cout << "测试用例3:" << s3 << " → 最少青蛙数:" << sol.minNumberOfFrogs(s3) << "(预期:-1)" << endl;
string s4 = "c";
cout << "测试用例4:" << s4 << " → 最少青蛙数:" << sol.minNumberOfFrogs(s4) << "(预期:-1)" << endl;
string s5 = "croaakc";
cout << "测试用例5:" << s5 << " → 最少青蛙数:" << sol.minNumberOfFrogs(s5) << "(预期:-1)" << endl;
return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容:【优选算法】(实战掌握分治思想的使用方法)
每日心灵鸡汤:你越静,你越赢!
你越在意什么,就会被什么折磨,万事从心起,看淡一身轻.生气,是因为你不够大度;嫉妒,是因为你不够优秀;悲伤,是因为你不够坚强郁;闷,是因为你不够豁达;焦虑,是因为你不够从容.心平能愈三千疾,心静可通万事理.这世界最好的放生就是放过自己,越安静,越简单,别让脾气挡住了你的福气.心静了,福气就来了,放下的越多,得到的也就越多.
