前言
模拟算法通俗地来解释就是 "照葫芦画瓢", 通常这类题的题目中就说明了这道题应该怎么做, 要做的就是把题目的意思转化为代码, 这类题的特点是思路比较简单, 考查的是代码能力.
1. 模拟算法流程, 最好在演草纸上过一遍流程, 凭空想象可能会忽略一些细节
2. 把流程转化为代码
题目1: 替换所有的问号(easy)
从前往后遍历字符串, 遇到 '?' 就替换为 a~z 中符合题意的字母, 也就是每次替换需要和前后的字符进行比较判断是否符合题意, 注意开头和结尾位置的字母判断.
class Solution {
public:
string modifyString(string s)
{
for(int i = 0; i < s.length(); i++)
{
if(s[i] == '?')
{
char c = 'a';
s[i] = c;
//默认都替换为a, 如果和前面字母相等或者和后面字母相等就换字母
while((i>=1 && s[i] == s[i-1]) || (i <= s.length()-1 && s[i] == s[i+1]))
s[i] = ++c;
}
}
return s;
}
};
题目2: 提莫攻击(easy)
此题问题关键在于判断 相邻两次攻击的时间间隔 和 duration 的大小关系:
1. 如果是最后一次攻击, 直接加上duration即可.
2. 普通情况需要单独判断, 如果时间间隔>=duration, 直接+=duratio; 如果时间间隔<duration, += 时间间隔.
class Solution {
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration)
{
int ret = 0;
int n = timeSeries.size();
for(int i = 0; i < n; i++)
{
if(i == n-1)
{
ret += duration;
break;
}
int gap = timeSeries[i+1] - timeSeries[i];
if(gap >= duration)
ret += duration;
else
ret += gap;
}
return ret;
}
};
题目3: N 字形变换(medium)
可以通过找规律来优化, 第1行和第n-1行发现每两个元素间距是固定的, 设为d, d=2*n-2
第k行则多了两个元素, 下标分别为 k 和 d-k, 每两个元素间距依然是d:
class Solution {
public:
string convert(string s, int numRows)
{
vector<vector<char>> arr(numRows);
int d = 2*numRows-2;
int n = s.size();
//n=1的时候, d=0, 直接返回原字符串即可, 否则死循环
if(d == 0)
return s;
for(int i = 0; i < numRows; i++)
{
//处理第一行
if(i == 0)
for(int j = i; j < n; j+=d)
arr[i].push_back(s[j]);
//处理第k行
else if(i == numRows-1)
for(int j = i; j < n; j+=d)
arr[i].push_back(s[j]);
//处理第k行
else
{
for(int j = i, k = d-j; j < n; j+=d,k+=d)
{
arr[i].push_back(s[j]);
if(k < n) arr[i].push_back(s[k]);
}
}
}
string ret;
for(const auto& v:arr)
for(const auto& c:v)
ret+=c;
return ret;
}
};
题目4: 外观数列
利用双指针记录 被描述字符 和 它的长度, 然后依次描述即可:
class Solution {
public:
string countAndSay(int n)
{
string ret = "1";
//描述n-1即可
for(int i = 1; i < n; i++)
{
string tmp;
int n = ret.size();
int left = 0, right = 0;
while(right < n)
{
while(right < n && ret[right] == ret[left])
right++;
tmp += to_string(right-left) + ret[left];//添加描述的字符
left = right;//更新left
}
ret = tmp;
}
return ret;
}
};
题目5: 数青蛙
从前往后遍历crokaOFFrogs字符串, 如果遇到 'c' 说明有一个青蛙开始叫, 如果遇到 'r' 只需去查找前面的字符串中是否有 'c', 所以可以用哈希表来存储字符出现的次数, 也就是青蛙的个数, 遍历到 'r' 的时候, c字符出现个数>=1, 说明有青蛙可以叫出来'r', hash['c']--, hash['r']++, 代表有一个青蛙叫到 'r', 其它字符也是同理. 叫到'k'说明青蛙叫完了.
注意:
1. 我们寻找的是最小青蛙的个数, 如果hash['k']>=1, 说明有青蛙叫完了, 再遇到'c'的时候可以让叫完的青蛙去叫, 所以hash['k']--, hash['c']++
2. 如果字符串遍历结束k之前的字符有>=1的情况, 说明有青蛙没叫完, 返回-1
if else 写法:
class Solution {
public:
int minNumberOfFrogs(string croakOfFrogs)
{
unordered_map<char,int> hash;
int n = croakOfFrogs.size();
for(int i = 0; i < n; i++)
{
if(croakOfFrogs[i] == 'c')
{
if(hash['k'])
hash['k']--;
hash['c']++;
}
else if(croakOfFrogs[i] == 'r')
{
if(hash['c'])
{
hash['c']--;
hash['r']++;
}
else
return -1;
}
else if(croakOfFrogs[i] == 'o')
{
if(hash['r'])
{
hash['r']--;
hash['o']++;
}
else
return -1;
}
else if(croakOfFrogs[i] == 'a')
{
if(hash['o'])
{
hash['o']--;
hash['a']++;
}
else
return -1;
}
else if(croakOfFrogs[i] == 'k')
{
if(hash['a'])
{
hash['a']--;
hash['k']++;
}
else
return -1;
}
else
return -1;
}
if(hash['c'] || hash['r'] || hash['o'] || hash['a'])
return -1;
else
return hash['k'];
}
};
哈希映射写法:
class Solution {
public:
int minNumberOfFrogs(string croakOfFrogs)
{
string voice = "croak";
int n = voice.size();
vector<int> hash(n);//用数组作为哈希表
//建立<字符,下标>的哈希映射
unordered_map<char, int> index;
for(int i = 0; i < voice.size(); i++)
index[voice[i]] = i;
for(int i = 0; i < croakOfFrogs.size(); i++)
{
if(croakOfFrogs[i] == 'c')
{
if(hash[index['k']] != 0) hash[index['k']]--;//挑出一只叫完的青蛙去叫
hash[index['c']]++;
}
else
{
int x = index[croakOfFrogs[i]];
//检查叫声是否合法
if(hash[x-1] == 0)
return -1;
//正常
else
{
hash[x-1]--;
hash[x]++;
}
}
}
//检查是否有未叫完的青蛙
for(int i = 0; i < n-1; i++)
{
if(hash[i] != 0)
return -1;
}
//k的个数即是最少青蛙的个数
return hash[index['k']];
}
};