【ps】本篇有 5 道 leetcode OJ。
目录
一、算法简介
模拟算法是指,根据题目表述进行筛选提取关键要素,按需求书写代码解决实际问题,换句话说其实就是“依葫芦画瓢”,但很考察对数据结构、容器和一些特殊数学方法的掌握程度。
模拟算法一般是一些很基础的题目,只要按题目要求来写代码就能解开题目。不过,模拟题也有相对困难的题目,而它们大部分的优化思路都是找规律。
二、相关例图
1)替换所有的问号
.1- 题目解析
“?”在字符串中间,需要考虑“?”两侧的字符;“?”在字符串两端,就只需要考虑“?”一侧的字符。
由此,对于“?”如何替换,就有了以后判断条件:
- “?”的左侧没有字符,则“?”的左侧无须判断与替换的字符是否相等。
- “?”的左侧有字符,则需判断与替换的字符字符是否相等。
- “?”的右侧没有字符,则“?”的右侧无须判断与替换的字符是否相等。
- “?”的右侧有字符,则需判断与替换的字符字符是否相等。
.2- 代码编写
class Solution {
public:
string modifyString(string s) {
int n=s.size();
for(int i=0;i<n;i++)
{
//遇到“?”就替换字符
if(s[i]=='?')
{
//遍历26个字符,找到合适的替换字符
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;
}
};
2)提莫攻击
.1- 题目解析
设 a、b、c 为 timeSeries 数组中的元素,含义为中毒的时刻,x 为相邻两元素的差值,d 即为中毒时间 duration,则存在以下规律:
所谓题目中的中毒持续时间会重置,其实就是 x 时间内又遭受了提莫攻击,且 x 时间小于 d 中毒时间,此时新的中毒时刻与上一次中毒时刻之间的差值 x,即为实际的中毒时间。
因此,当 x < d 时,中毒时间实际为 x,而当 x >= d时,中毒时间则为 d。
.2- 代码编写
class Solution {
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration) {
int ret=0;
for(int i=1;i<timeSeries.size();i++)
{
int x=timeSeries[i]-timeSeries[i-1];
//当 x < d 时,中毒时间实际为 x,而当 x >= d时,中毒时间则为 d。
if(x>=duration)ret+=duration;
else ret+=x;
}
return ret+duration; //最后一个时刻也遭受了中毒攻击,也需算上中毒时间
}
};
3)Z 字形变换
.1- 题目解析
由示例 1,一个字符串进行 Z 字变换后,形式如下:
我们可以把这个变换后的字符串看成是在一个二维矩阵中,由此 Z 字变换可以在一个二维矩阵中进行。设一个字符串为“abcdefg...”,且行数 n = 4,则可以得到以下二维矩阵:
但这样的话,代码的时间复杂度和空间复杂度都来到了O(字符串长度*n),是非常高的。因此,还需要对这样的思路进行优化,而优化的方式就是找规律。
如果填入的不是字符,而是字符在字符串数组中的下标,则可以得到以下二维矩阵:
不难发现,对于一行中的下标,它们之间的间隔都相同。设一行中相邻两元素下标的公差为 d,则:
- 对于第 0 行,第 0 行的下标依此为 0、0 + d、0 + 2d...0 + k*d。
- 对于第 i 行(第 1、2行),以第 i 行相邻的两字符为一组,它们的下标依此为 ( i,d - i )、( i + d,d - i + d)、( i + 2d,d - i + 2d)...( i + k*d,d - i + k*d)。
- 对于 n - 1 行(第 3 行),第 n - 1 行的下标依此为 n - 1、n - 1 + d、n - 1 + 2d...n - 1 + k*d。
当 n = 3 时,该规律仍正确:
.2- 代码编写
class Solution {
public:
string convert(string s, int numRows) {
if(numRows==1)return s; //处理边界情况
string ret;
int d=2*numRows-2,n=s.size();
//先处理第0行
for(int i=0;i<n;i+=d)
ret+=s[i];
//处理第i行
for(int k=1;k<numRows-1;k++)//先枚举行数
{
for(int i=k,j=d-k;i<n||j<n;i+=d,j+=d)//再枚举列
{
//以第 i 行相邻的两字符为一组,i表示前一个字符,j表示后一个字符
if(i<n)ret+=s[i];
if(j<n)ret+=s[j];
}
}
//处理第n-1行
for(int i=numRows-1;i<n;i+=d)
ret+=s[i];
return ret;
}
};
4)外观数列
.1- 题目解析
若 n = 5,则 countAndSay(1) 到 countAndSay(5) 分别为:
以“33341111554”为例,最终应压缩为“3314412514”,不难发现这个过程其实是对一段连续的数字进行计数, 然后描述这个数有多少个,例如压缩后的“3314412514”中的“33”,含义为“3 个 3”,而能够得到“3 个 3”,其实是因为对数组从左往后数出了 3 个 3。
由此,我们可以使用双指针来遍历数组,找出连续相同的数字构成的区间,并对数字的个数进行计数,以此完成字符串的压缩。
.2- 代码编写
class Solution {
public:
string countAndSay(int n) {
string ret="1";
//进行n-1次压缩
for(int i=1;i<n;i++)
{
string tmp; //保存本次压缩的结果
int len=ret.size(); //求压缩前的字符串长度
//双指针寻找连续相同的数字区间
for(int left=0,right=0;right<len;)
{
//找到连续区间后第一个不同的数字
while(ret[left]==ret[right]&&right<len)
right++;
//保存本次压缩的结果
tmp+=to_string(right-left)+ret[left];
//继续下一次压缩
left=right;
}
ret=tmp;
}
return ret;
}
};
5)数青蛙
.1- 题目解析
"coark"完整的为一声蛙叫。以示例 2 为例,当遍历字符串时,遍历至第一个“r”,只需“r”前面相应地存在一个“c”,就说明当前还可以构成一次蛙叫,可以继续遍历下去。;同理,遍历至第一个“o”,只需“o”前面相应地存在一个“r”。
由此,我们需要一个哈希表来对字符出现的情况进行标识。
每统计一遍完整的"coark",字符“k”在哈希表中对应的频次都将 + 1,直到遍历完字符串后,如果除k以外的字符在哈希表中的值全为 0,则可以返回字符“k”对应的出现频次,此时即返回了蛙叫的次数。
.2- 代码编写
//版本1
class Solution {
public:
int minNumberOfFrogs(string croakOfFrogs) {
// int c = 0, r = 0, o = 0, a = 0, k = 0;
vector<int> arr(6, 0); // 用下标1到5分别映射五个字母
for(auto& e : croakOfFrogs)
{
if(e == 'c')
{
if(arr[5] > 0)
--arr[5];
++arr[1];
}
if(e == 'r')
{
if(arr[1] > 0)
--arr[1];
else
return -1;
++arr[2];
}
if(e == 'o')
{
if(arr[2] > 0)
--arr[2];
else return -1;
++arr[3];
}
if(e == 'a')
{
if(arr[3] > 0)
--arr[3];
else return -1;
++arr[4];
}
if(e == 'k')
{
if(arr[4] > 0)
--arr[4];
else return -1;
++arr[5];
}
}
if(arr[1] > 0 || arr[5] == 0)
return -1;
return arr[5];
}
};
//版本2
class Solution {
public:
int minNumberOfFrogs(string croakOfFrogs) {
//前期准备
string t="croak";
int n=t.size();
vector<int> hash(n); //hash标识字符的出现频次
unordered_map<char,int> index; //index映射字符的下标,方便统一处理o、a、r、k
for(int i=0;i<n;i++)
index[t[i]]=i; //初始化 index
//统计蛙叫
for(auto ch:croakOfFrogs)
{
//遇到c
if(ch=='c')
{
if(hash[n-1]) //若最后一个字符k在哈希表中存在
hash[n-1]--;
hash[0]++;
}
//遇到o、a、r、k
else
{
int i=index[ch];
if(hash[i-1]==0) //若前驱字符不存在
return -1;
hash[i-1]--;
hash[i]++;
}
}
//检验hash,若除k以外的字符对应频次为0,则结果有效
for(int i=0;i<n-1;i++)
{
if(hash[i])
return -1;
}
//结果有效,则返回k对应的频次
return hash[n-1];
}
};