专栏:算法的魔法世界
个人主页:手握风云
目录
一、模拟
二、例题讲解
2.1. 替换所有的问号
2.2. 提莫攻击
2.3. Z字形变换
2.4. 外观数列
2.5. 数青蛙
一、模拟
模拟算法说简单点就是照葫芦画瓢,现在草稿纸上模拟一遍算法过程,再把算法过程转化为代码。
二、例题讲解
2.1. 替换所有的问号
方法中所给的字符串s只含小写字母和问号,我们需要把这个问号转化为某个小写字母,得到一个连续重复字符的新字符串。
我们先将字符串转化为字符数组,然后从前遍历这个字符数组。当遇到"?"时,从26个英文字母找到第一个既不等与它的前一位有不等于它的后一位的字符。当我们还需要注意一下边界情况,比如"?"在数组的第0位上,前一位不存在,只需要比较后面一位即可。
完整代码实现:
class Solution {
public String modifyString(String s) {
char[] ch = s.toCharArray();
int n = s.length();
for (int i = 0; i < n; i++) {
if(ch[i] == '?'){
for(char c = 'a';c <= 'z';c++){
if((i == 0 || c != ch[i - 1]) && (i == n - 1 || c != ch[i+1])){
ch[i] = c;
break;
}
}
}
}
return String.valueOf(ch);
}
}
2.2. 提莫攻击
艾希受到提莫的攻击,会对艾希造成持续两秒的中毒效果,如果在中毒期间再次受到攻击,那么中毒时间还会重置,题目要求我们输出中毒的总时长。
·我们需要遍历一边数组,计算每两个相邻元素的差x,如果x大于等于中毒持续时间duration,则中毒时间ret加上duration;如果x小于duration,则ret直接加上x。但比较容易忽略的一点是数组的最后一个元素没有下一个元素,所以最后的返回值再要加上一个duration。
完整代码实现:
class Solution {
public int findPoisonedDuration(int[] timeSeries, int duration) {
int ret = 0;
for (int i = 1; i < timeSeries.length; i++) {
int x = timeSeries[i] - timeSeries[i - 1];
if(x >= duration) ret += duration;
else ret += x;
}
return ret + duration;
}
}
2.3. Z字形变换
题目要求我们将给定的字符串变为倒Z排列,然后再以从左到右的顺序输出,得到一个新字符串。我们要想成功模拟出这个过程,需要借助一个矩阵来储存字符。我们以示例1为例,先创建一个n行的矩阵,先向下填充,当x=n时,开始右上填充,当x=0时,又开始向下填充……。假设字符串长度为len,因为我们记得遍历字符串又得遍历矩阵,所以时间复杂度和空间复杂度都为。
我们接下来可以通过找规律来对算法进行优化。如果矩阵里面填充的是字符的下标,那我们就可以发现规律了。第0行和第n-1行的数字正好构成了等差数列,且公差d为2n-2。接下来枚举第1行到第n-1行,1、5、9、13依然是构成了等差数列,3、7、11构成等差数列,(1,3)->(5,7)->(9,11)。
完整代码:
class Solution {
public String convert(String s, int numRows) {
//处理一下边界情况
if(numRows == 1) return s;
int d = 2 * numRows - 2, n = s.length();
StringBuilder ret = new StringBuilder();
//1.处理第一行
for (int i = 0; i < n; i += d)
ret.append(s.charAt(i));
//2.处理中间行
for (int k = 1; k < numRows - 1; k++) {//一次枚举中间行
for (int i = k, j = d - i; i < n || j < n; i += d, j += d) {
if (i < n) ret.append(s.charAt(i));
if (j < n) ret.append(s.charAt(j));
}
}
//3.处理最后一行
for (int i = numRows - 1; i < n; i += d) {
ret.append(s.charAt(i));
}
return ret.toString();
}
}
2.4. 外观数列
本题的输出结果就是对第n-1的解释,当n=1时,字符串s="1";当n=2时,前一项是1个1,记为"11";当n=3时,前一项是2个1,记为"21";当n=4时,前一项时1个2和1个1,记为"1211"……规律就是对上一项连续相同的字符元素进行分类。
我们可以利用双指针进行模拟。先初始化两个指针left和right,比较left和right是否相等,相等right就向右移动,当right的指向与left不相等时,right就停止,此时的left直接移动到right的位置。以此循环,直到right移动到字符串的结尾的后一位。前一类元素个数的计算right-1-left+1,作为对上一类元素的解释。
完整代码实现:
class Solution {
public String countAndSay(int n) {
String ret = "1";//先初始化n=1时的字符串
for (int i = 1; i < n; i++) {
StringBuilder tmp = new StringBuilder();
int len = ret.length();
for (int left = 0,right = 0;right < len;){
while(right < len && (ret.charAt(left) == ret.charAt(right))) right++;
tmp.append(Integer.toString(right - left));
tmp.append(ret.charAt(left));
left = right;
}
ret = tmp.toString();
}
return ret;
}
}
2.5. 数青蛙
题目要求我们根据给定的字符串,计算出最少需要多少只青蛙才能完成鸣叫的过程。鸣叫的过程必须按照"croak"的顺序进行。比如"croakcroak",一只青蛙先叫完1声,还可以接着叫第2声;"crcoakroak","cr"后面是"c",就需要另一只青蛙开始叫。
我们先从头到尾遍历一遍字符串,,比如我们遍历到a时,只需确定a的前面是不是r,所以我们还需要一个哈希表来记录每个字符出现的情况。当遍历到c时,哈希表中c加1;如果遍历到r时,检查前一个是不是c,如果是,则c--,r++。重复以上操作,
如上图,k后面又是c,我们还需要给c记录上1,而我们要求的是所需不同青蛙的最少数目,所以我们从k里面借一个1,再去遍历最后一个"croak",最终k里面存的就是最终结果。当遍历完字符串之后,k前面还有非0元素,说明还有青蛙没有叫完,则返回-1。
还有一种情况,如果给定的字符串是"crroak",遍历到第二个r时,哈希表中的c为0,直接返回-1。所以我们可以总结出:当r、o、a、k的前驱字符再哈希表时,前驱个数--,当前字符++,不存在直接返回-1;如果是c,找k是否存在于哈希表中,存在前驱个数--,当前字符++,不存在直接返回-1。
完整代码实现:
class Solution {
public int minNumberOfFrogs(String croakOfFrogs) {
char[] croakOf = croakOfFrogs.toCharArray();
String t = "croak";
int n = t.length();
int[] hash = new int[5];
Map<Character,Integer> index = new HashMap<>();
for (int i = 0; i < n; i++)
index.put(t.charAt(i),i);
for (char ch : croakOf) {
if(ch == t.charAt(0)) {
if(hash[n - 1] != 0) hash[n - 1]--;
hash[0]++;
}else {
int i = index.get(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];
}
}