大家好,我是LvZi,今天带来
模拟算法系列|替换所有的问号|提莫攻击|种花问题|Z字形变换|兼具大小写的英文字母|删除字符使频率相同
一.基本概念
模拟算法就是根据题意
模拟出代码的过程,模拟算法的题意往往都很简单,考验的是将思路转化为代码的能力,十分的锻炼代码能力,且能很好的培养分类讨论的思想
关于模拟算法的提高,最重要的就是多刷题,多看题解
,积累足够多的经验
二.例题讲解
01.替换所有的问号
链接:https://leetcode.cn/problems/replace-all-s-to-avoid-consecutive-repeating-characters/
分析
- 题意很简单,就是将所有的
?
替换为其他字符,要求替换后不存在连续重复的字符 - 本题采用分类讨论的思想,假设
?
的下标为i
,根据i的位置
可以分为三类 i == 0
:成立条件:tmp != s[1]
i == n - 1
:成立条件:tmp != s[n - 2]
0 < i < n - 1
:成立条件:tmp != s[i - 1] && tmp != s[i + 1]
代码:
class Solution {
public String modifyString(String ss) {
// 模拟实现
int n = ss.length();
if (n == 1) return "a";
char[] s = ss.toCharArray();
for (int i = 0; i < n; i++) {
char ch = s[i];
if (ch == '?') {
for (char tmp = 'a'; tmp <= 'z'; tmp++) {
if (i == 0) {
if(tmp != s[i + 1]) s[i] = tmp;
} else if (i == n - 1) {
if(tmp != s[n - 2]) s[i] = tmp;
} else {
if(tmp != s[i - 1] && tmp != s[i + 1]) s[i] = tmp;
}
}
}
}
return new String(s);
}
}
02.提莫攻击
链接:https://leetcode.cn/problems/teemo-attacking/
分析
- 分类讨论,可分为两种情况
- 1.当前时间并未中毒 无需重置时间
- 2.当前时间处于中毒时间 需要重置时间
代码:
class Solution {
public int findPoisonedDuration(int[] timeSeries, int d) {
int n = timeSeries.length, ret = d;
for(int i = 1; i < n; i++) {
if(timeSeries[i] <= timeSeries[i - 1] + d) {
ret += (timeSeries[i] - timeSeries[i - 1]);// 中毒 重置时间
continue;
}
ret += d;// 未中毒
}
return ret;
}
}
03.种花问题
链接:https://leetcode.cn/problems/can-place-flowers/
分析
- 跳格子问题,为了尽可能的将所有花都种植,采用的策略应该是"找离得最近的可以种花的位置"
- 边界处理十分麻烦,但是我们可以
扩充数组
,类似于二维数组经常使用的扩增一行一列
的方式来进行初始化,减少边界条件的判断
代码:
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
// 为了简化判断的逻辑我们经常使用的优化技巧就是"扩增原数组"
// 最经常用于dp问题
// 在本题中有两个边界条件 i==0和i==m- 1
// 为了防止越界,前后各添加一个0
int[] a = new int[flowerbed.length + 2];
for(int i = 1; i <= flowerbed.length; i++) a[i] = flowerbed[i - 1];
for (int i = 1; i < a.length - 1; i++) {
if (a[i - 1] == 0 && a[i] == 0 && a[i + 1] == 0) {
a[i] = 1; // 种花!
n--;
}
}
return n <= 0;
}
}
04.Z字形变换
链接:https://leetcode.cn/problems/zigzag-conversion/description/
分析
- 找规律的题目
- 为了更方便起见,将字符串的长度+1
代码:
class Solution {
public String convert(String ss, int numRows) {
if(ss.length() == 1 || numRows == 1) return ss;// 长度为1 或者不需要进行Z字形变换
String s = " " + ss;
char[] str = s.toCharArray();
StringBuilder sb = new StringBuilder();
int stap = 2 * numRows - 2, n = str.length;
// 处理第一行
for(int i = 1; i < n; i += stap) sb.append(str[i]);
// 处理中间行
for(int i = 2; i <= numRows - 1; i++) {
int n1 = i, n2 = 2 * numRows - i;
while(n1 < n) {
sb.append(str[n1]);
if(n2 < n) sb.append(str[n2]);
n1 += stap; n2 += stap;
}
}
// 处理最后一行
for(int i = numRows; i < n; i += stap) sb.append(str[i]);
return sb.toString();
}
}
注意
:这里踩了一个坑,测试用例一直显示超出内存限制
,刚开始以为是写了死循环,一直检查循环的条件是否正确,但是发现并不存在错误,又打开idea进行测试,发现也没错误;看了题解才发现是由于特殊condition
的存在,比如只有一个字符
,或者numRows == 1
,第二种情况才是导致超出内存限制的罪魁祸首,此时stap == 0,所以会造成死循环,超出内存限制,写这种题目一定要考虑好特殊情况
05.兼具大小写的英文字母
链接:https://leetcode.cn/problems/greatest-english-letter-in-upper-and-lower-case/description/
分析
- 使用hash统计字符串s中每一个字符出现的次数,然后遍历判断每一个字符是否大小写都存在,如果存在,返回最大的那个
代码
class Solution {
public String greatestLetter(String s) {
// 不需要傻傻的去比较大小 直接从最大的字母Z开始遍历即可
int[] hash = new int[128];
for(char ch : s.toCharArray()) hash[ch]++;
for(char ch = 'Z'; ch >= 'A'; ch--)
if(hash[ch] >= 1 && hash[ch + 32] >= 1)
return "" + ch;
return "";
}
}
- 方法2:使用位运算
- 采用位图的思想,使用
mask1的二进制位上的0和1
表示小写字母是否出现过,使用mask2的二进制位上的0和1
表示大写字母是否出现过
代码
class Solution {
public String greatestLetter(String s) {
int mask1 = 0, mask2 = 0;
for(char ch : s.toCharArray()) {
if(ch >= 'a' && ch <= 'z') mask1 |= (1 << (ch - 'a'));
else mask2 |= (1 << (ch - 'A'));
}
int mask = mask1 & mask2;
for(int i = 31; i >= 0; i--) {
if(((1 << i) & mask) != 0) return "" + ((char)('A' + i));
}
return "";
}
}
06.删除字符使频率相同
链接:https://leetcode.cn/problems/remove-letter-to-equalize-frequency/
分析
思路:
- 统计每个字符出现的频率
- 建立
"频率--出现次数"
的映射关系 - 分类讨论
- 只有一种频率:此时仅有一种condition不符合条件,当频率为偶数且频率出现的次数也为偶数,形如
aabb
这种 - 存在两种频率:此时有两种符合条件的情况;
- 其中一个频率为1,且出现次数为1
- 其中一个频率比另一个频率大1,且次频率的出现次数也为1
- 其他频率:都不符合情况
- 只有一种频率:此时仅有一种condition不符合条件,当频率为偶数且频率出现的次数也为偶数,形如
代码:
class Solution {
public boolean equalFrequency(String word) {
// 1.统计所有字符出现的频率
int[] hash1 = new int[128];
for(char ch : word.toCharArray()) hash1[ch]++;
// 2.建立"频率--出现次数"的映射关系
Map<Integer, Integer> hash2 = new HashMap<>();
for(int x : hash1) {
if(x != 0)
hash2.put(x, hash2.getOrDefault(x, 0) + 1);
}
// 3.分类讨论结果
// 频率只有一种
if(hash2.size() == 1) {
for(Integer key : hash2.keySet()) {
if(key % 2 == 0 && hash2.get(key) > 1) return false;
return true;
}
}
// 频率有两种
if(hash2.size() == 2) {
int[] key = new int[2], val = new int[2];
int index = 0;
for(Map.Entry<Integer, Integer> entries : hash2.entrySet()) {
key[index] = entries.getKey();
val[index] = entries.getValue();
++index;
}
if((key[0] == 1 && val[0] == 1)||(key[1] == 1 && val[1] == 1)) return true;
if((key[0] - key[1] == 1 && val[0] == 1)||(key[1] - key[0] == 1 && val[1] == 1)) return true;
}
// 其余频率都不满足情况
return false;
}
}