1. 讲解
是什么?
模拟题的本质是 比葫芦画瓢,即按照题目给定的规则和步骤,直接转化为代码实现。
-
题目通常会描述一个具体的场景或流程(如Z字形变换、螺旋矩阵、数青蛙等)
-
不需要复杂的算法思想(如动态规划、贪心),只需忠实模拟每一步操作
-
特点:思路比较简单,但细节往往比较繁琐
什么时候用?
当题目直接描述了一个可以通过固定规则推演的过程时,就可以考虑使用模拟。
适用条件通常包括:
-
题目给出了明确的状态转移规则或操作步骤
-
不需要复杂的数据结构或高级算法优化
-
容易在实现过程中遗漏边界条件或特殊情况
怎么用?
模拟题的通用解题步骤:
-
纸上模拟 先在演草纸模拟一遍流程,走通题目的逻辑,否则极其容易忽略掉细节。
-
转化为代码 将纸上推演的步骤一步一步翻译为代码,注意变量边界和循环条件。
-
优化(找规律) 模拟题想要优化,99.9%都是靠找规律来的。
-
画图:找规律的时候,最好就是把原始的模拟结果画出来(比如把矩阵画出来)
-
反推:根据画出来的最终结果去反推规律(如推导下标公式)
-
验证:当推出来一个规律后,再换一种情况来验证一下这个规律是否正确
-
兜底 :同时考虑是否有特殊情况需要单独考虑(如
numRows == 1等边界情况)
-
例题讲解:
我们以LeetCode的替换所有的问号为例
1. 题目描述
给你一个仅包含小写英文字母和 '?' 字符的字符串 s,请你将所有的 '?' 转换为若干小写字母,使最终的字符串不包含任何连续重复 的字符。你不能修改非 '?' 字符。
示例 1:
输入:s = "?zs"
输出:"azs"
解释:该示例共有 25 种解决方案。只有 "z" 是无效的,因为字符串 "zzs" 中有连续重复的两个 'z'。
2. 解析与代码演化
-
第一步:纸上推演 从前往后扫描数组,当遇到
?时,就用a~z依次尝试,直到找到一个字符放在?的位置,使得它和它前面一个字母不同和后面一个字母也不同。细节:在开头仅需判断和后面一个字母是否相同;在结尾仅需判断和前面一个字母是否相同即可。
-
第二步:代码实现(基础版,尝试
a~z)
java
class Solution {
public String modifyString(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
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 String.valueOf(s);
}
}
- 第三步:优化(尝试
a~c)
java
class Solution {
public String modifyString(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
for (int i = 0; i < n; i++) {
if (s[i] == '?') { // 替换
for (char ch = 'a'; ch <= 'c'; ch++) { // 优化:只需尝试 a,b,c
if ((i == 0 || ch != s[i - 1]) && (i == n - 1 || ch != s[i + 1])) {
s[i] = ch;
break;
}
}
}
}
return String.valueOf(s);
}
}
总结
-
模拟题是思路最简单 但细节最繁琐的题型,适合作为基础训练
-
解题核心在于:纸上推演 -> 转换代码 -> 处理边界
-
模拟题的优化捷径是找规律 ,找规律的关键在于画图反推 -> 多情况验证 -> 考虑特殊情况
-
时间复杂度从暴力模拟的
O(n²)或O(n*m)优化至 O(n) 或 O(1)
2. 习题
替换所有的问号
题目链接:1576.替换所有的问号
提莫攻击
题目链接:495.提莫攻击
外观数列
题目链接:38.外观数列
数青蛙
题目链接:1419.数青蛙