当前位置: 首页 > article >正文

系统学习算法:专题六 模拟

模拟算法其实也不叫算法,这类题就是题目告诉你具体流程是怎么样的,让你用代码来实现,思路上不怎么难,主要考察代码能力,将想转化成做,模拟题目的操作步骤,这就是模拟

题目一:

思路:

题意很简单,就是让这个字符串中不能出现连续重复的字符,其中我们要做的就是替换所有的问号来达到前面这个要求

步骤其实也很简单,就是遍历整个字符串,如果出现了问号,就进行替换,这时有四种情况可以讨论

1.有前驱有后继,那么此时问号要替换的字符就不能等于前驱和后继,依次用a~z来比较找合适的即可,即 " abc?def " 这种情况

2.有前驱但没后继,那么此时问号要替换的字符就不能等于前驱即可,依次用a~z来比较找合适的,即 " abc? " 这种情况

3.有后继但没前驱,那么此时问号要替换的字符就不能等于后继即可,依次用a~z来比较找合适的,即 " ?abc " 这种情况

4.没前驱也没后继,其实就是" ? "这种情况,那么随便找一个字符替换就行,没有限制条件

那么接下来就将这个过程用代码进行模拟

代码:

class Solution {
    public String modifyString(String s) {
        //转化为字符数组,方便操作
        char[] ch=s.toCharArray();
        //用来替换的a~z
        String str="abcdefghigklmnopqrstuvwxyz";
        //遍历字符数组
        for(int i=0;i<ch.length;i++){
            //如果出现问号
            if(ch[i]=='?'){
                //有前驱有后继
                if((i-1>=0)&&(i+1<ch.length)){
                    for(int j=0;j<26;j++){//从a~z去找
                        if(str.charAt(j)!=ch[i-1]&&str.charAt(j)!=ch[i+1]){//符合条件就替换
                            ch[i]=str.charAt(j);
                            break;
                        }
                    }
                }else if(i+1<ch.length){//有后继没前驱
                    for(int j=0;j<26;j++){//从a~z去找
                        if(str.charAt(j)!=ch[i+1]){//符合条件就替换
                            ch[i]=str.charAt(j);
                            break;
                        }
                    }
                }else if(i-1>=0){//有前驱没后继
                    for(int j=0;j<26;j++){//从a~z去找
                        if(str.charAt(j)!=ch[i-1]){//符合条件就替换
                            ch[i]=str.charAt(j);
                            break;
                        }
                    }
                }else{//没前驱没后继
                    ch[i]='a';//随便替换一个即可
                }
            }
        }
        //返回替换后的字符串
        return new String(ch);
    }

题目二:

思路:

 题意很简单,就是算中毒时间之和,中毒时间只有两种情况

第一种:在中毒结束前没有新的攻击,则中毒时间全部加上

第二种:在中毒结束前受到新的攻击,则中毒时间加上两次攻击时间的差值

其中最后一次攻击肯定是第一种情况,所以只用遍历到数组倒数第二位即可

代码:

class Solution {
    public int findPoisonedDuration(int[] timeSeries, int duration) {
        //中毒时间之和
        int sum=0;
        //遍历到倒数第二个数
        for(int i=0;i<timeSeries.length-1;i++){
            //如果中毒状态没重置,全部执行完
            if(timeSeries[i]+duration-1<timeSeries[i+1]){
                sum+=duration;
            }else if(timeSeries[i]+duration-1>=timeSeries[i+1]){//有新的攻击,重置
                sum+=timeSeries[i+1]-timeSeries[i];//加上两次攻击时间差值
            }
        }
        //最后一次攻击中毒时间肯定是全部执行完
        return sum+duration;
    }
}

题目三:

思路:

就是按照题目要求,将原字符串按照Z字排列,然后返回新的字符串

这种变换的题有行有列,那么就需要二维矩阵来方便操作,因此第一步根据题目要求,大致算出矩阵所需的行数和列数,这道题的行数为numRows,列数需要根据周期性来计算,但是太麻烦了,我们直接就用字符串的长度作为列数,保证够用只是会浪费

然后我们就开始按照要求填数就行了,从(0,0)开始填,一开始是从上到下,然后是斜着的从左下往右上,这两步为一个周期,循环下去就行了

对此画图,因为我们不关心真实构成字符串的各个字符,只关心下标走到字符串的哪里了,所以用012来代表对应的字符

很容易找到规律,竖着i+1,j不变,斜着i-1,j+1

根据规律我们就可以将字符串的字符全部填入矩阵

接下来就是遍历二维数组,如果是大小写字母,逗号或句号就拼接到要返回的字符串里

这就是比较简单的模拟思路

代码1:

class Solution {
    public String convert(String s, int numRows) {
        //如果行数为1,那么直接返回
        if(numRows==1){
            return s;
        }
        //构建矩阵
        char[][] ch=new char[numRows][s.length()];
        //循环
        for(int i=0,j=0,index=0;index<s.length();){
            //竖着
            while(i<numRows&&index<s.length()){
                ch[i++][j]=s.charAt(index++);
            }
            //调整i的位置
            i--;
            //斜着
            while(i>1&&j<s.length()-1&&index<s.length()){
                ch[--i][++j]=s.charAt(index++);
            }
            //调整ij的位置
            i--;
            j++;
        }
        //要返回的新字符串
        String str=new String();
        //遍历矩阵
        for(int i=0;i<numRows;i++){
            for(int j=0;j<s.length();j++){
                //如果有填入的字符就拼接到字符串
                if((ch[i][j]>='a'&&ch[i][j]<='z')||(ch[i][j]>='A'&&ch[i][j]<='Z')
                ||ch[i][j]==','||ch[i][j]=='.'){
                    str+=ch[i][j];
                }
            }
        }
        //返回字符串
        return str;
    }
}

其中有个特例需要解决,那就是行数为1,此时原字符串就是答案,直接返回就行

但是这种方法时间复杂度和空间复杂度都很高

直接就来到末尾了 ,因此我们就需要优化

而模拟算法的题想要优化,基本都是找规律,然后根据规律直接找到答案

有了前面的分析步骤,我们就思考能不能得到下标的规律

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

 以这个为例,我们可以发现第一行和最后一行的排布一样,其中第一行的第一个数为0,第二个数为6,我们就来思考什么样的公式能得到0,6,12

先找到公差d=6,6又是怎么来到,因为前面0~5在前三列,5假设也移到第2列,就相当于有两列,其中第二列缺2个,因此d=2n-2,n为行数

这个例子符合了,不保证其他例子符合,所以要再举个例,假设为3行,会发现第一行是0,4,

2n-2=4,公式正确

然后就是中间的行,可以将两个数看作一对,1,5一对,7,11一对,1到7为公差d=6,5到11为公差d=6,依次类推

最后一行和第一行的规律一样,接下来就将规律转化成代码即可

代码2:

class Solution {
    public String convert(String s, int numRows) {
        //特殊处理一下
        if(numRows==1){
            return s;
        }
        //stringbuffer的append比string的+=快很多,且线程安全
        StringBuffer ret=new StringBuffer();
        //求出公差
        int d=2*numRows-2;
        //处理第一行
        for(int i=0;i<s.length();i+=d){
            ret.append(s.charAt(i));
        }
        //处理第2到k行
        for(int k=1;k<numRows-1;k++){
            //添加第k行的所有元素
            for(int i=k,j=d-k;i<s.length()||j<s.length();i+=d,j+=d){
                if(i<s.length()){
                    ret.append(s.charAt(i));
                }
                if(j<s.length()){
                    ret.append(s.charAt(j));
                }
            }
        }
        //处理最后一行
        for(int i=numRows-1;i<s.length();i+=d){
            ret.append(s.charAt(i));
        }
        return ret.toString();
    }
}

内存上根本没有创建矩阵,时间上复杂度也很低 

题目四:

思路:

题意就是输入一个整数n代表递归次数,然后通过递归公式,返回经过n次递归后的字符串结果

其中递归公式的操作步骤就是将一个字符串化为“个数”+“字符”

比如题目举例的3322251

就是遍历该字符串

33为2个“3”,所以返回23

222为3个“2”,所以返回32

5为1个“5”,所以返回15

1为1个“1”,所以返回11

所以最后要返回的字符串为23321511

那么接下来的关键就是如何用代码模拟上述操作

其中递归结束的条件为n==1,然后操作就是遍历字符串,然后用一个变量count记录该字符有多少个,初始值为1,因为被遍历到那么至少为1个,接下来就循环往后一个字符进行比较,如果相同就count++,下标也往后移动,直到出现不同,这时就将个数和字符拼接到字符串,然后继续扫描后面的字符串,最后返回这次递归的字符串

代码1(递归):

class Solution {
    //递归方法
    public String dg(int n){
        //递归结束条件
        if(n==1){
            return "1";
        }else{//递归
            //拿到上一次递归的结果
            String s = dg(n-1);
            StringBuffer str=new StringBuffer();
            //扫描字符串
            for(int i=0;i<s.length();i++){
                //count代表个数
                int count=1;
                //扫描有多少个相同的字符
                while(i<s.length()-1 && s.charAt(i)==s.charAt(i+1)){
                    count++;
                    i++;
                }
                //拼接个数+字符
                str.append(Integer.toString(count));
                str.append(s.charAt(i));
            }
            //返回这次递归的结果
            return str.toString();
        }
    }
    public String countAndSay(int n) {
        //调用递归方法
        return dg(n);
    }
}

递归是从后往前递,然后再从前往后归,还有一个与它相似的方法,就是迭代,即从前往后迭代,通常通过循环就可以解决

思路也是一样的就不多赘述了,上面的count记录个数可以改用双指针来计算,就当多练习一下其他算法

代码2(迭代):

class Solution {
    public String countAndSay(int n) {
        //初始为"1"
        String ret = "1";
        //第一层循环代表第i次迭代
        for (int i = 2; i <= n; i++) {
            //记录这一次迭代的字符串结果
            StringBuffer str = new StringBuffer();
            //这一层循环代表遍历整一个字符串
            for (int left = 0, right = 0; right < ret.length();) {
                //这一层循环代表扫描该字符出现了多少个
                while (right < ret.length() && ret.charAt(left) == ret.charAt(right)) {
                    right++;
                }
                //拼接个数+字符
                str.append(Integer.toString(right - left));
                str.append(ret.charAt(left));
                //换到下一个字符
                left = right;
            }
            //更新这一次迭代结果
            ret=str.toString();
        }
        //返回迭代n次的结果
        return ret;
    }
}

题目五:

 思路:

题意也比较好理解,就是要判断是否是有效组合,如果是则返回至少需要多少只青蛙,不是则返回-1

这个不能简单的数出现了多少次croak就返回有多少只青蛙,因为一只青蛙叫完还可以继续叫,比如示例1

可以用哈希表来记录当前叫声的出现次数,判断是否是有效组合,只用看看该字符的前驱有没有出现,如果没有出现就无效,返回-1,如果有出现就前驱次数--,当前次数++,如此循环

叫到k的时候代表有一只青蛙叫完了,就变为闲置状态,而这时如果又遍历到了c,表示又有一次新的叫声,那么此时就可以让闲置的青蛙去叫,此时k--,c++,如果k为0,说明没有闲置的青蛙,是一只新的青蛙来叫了,c++就行

最后如果c~a还有次数,则说明不是有效组合,因为如果是有效的话,最后叫声全部叫完,都应该堆积在k位置

代码1:

class Solution {
    public int minNumberOfFrogs(String croakOfFrogs) {
        //创建哈希表
        HashMap<Character,Integer> map=new HashMap<>();
        char[] arr=croakOfFrogs.toCharArray();
        //遍历字符串
        for(int i=0;i<arr.length;i++){
            //如果为一开始的叫声
            if(arr[i]=='c'){
                //如果没有青蛙处于休闲状态
                if(map.getOrDefault('k',0)==0){
                    //又有新的青蛙加入
                    map.put('c',map.getOrDefault('c',0)+1);
                }else{//有青蛙闲置,再次利用
                    map.put('k',map.get('k')-1);
                    map.put('c',map.getOrDefault('c',0)+1);
                }
            }else if(arr[i]=='r'){
                //如果前面没有对应的叫声,则不是有效组合
                if(map.getOrDefault('c',0)==0){
                    return -1;
                }else{//前面对应叫声减1,来到当前叫声
                    map.put('c',map.get('c')-1);
                    map.put('r',map.getOrDefault('r',0)+1);
                }
            }else if(arr[i]=='o'){//全部类似上述操作,复制修改
                if(map.getOrDefault('r',0)==0){
                    return -1;
                }else{
                    map.put('r',map.get('r')-1);
                    map.put('o',map.getOrDefault('o',0)+1);
                }
            }else if(arr[i]=='a'){
                if(map.getOrDefault('o',0)==0){
                    return -1;
                }else{
                    map.put('o',map.get('o')-1);
                    map.put('a',map.getOrDefault('a',0)+1);
                }
            }else{
                if(map.getOrDefault('a',0)==0){
                    return -1;
                }else{
                    map.put('a',map.get('a')-1);
                    map.put('k',map.getOrDefault('k',0)+1);
                }
            }
        }
        //如果还有剩余叫声没叫完(叫了一半)
        if(map.getOrDefault('c',0)!=0||map.getOrDefault('r',0)!=0||
        map.getOrDefault('o',0)!=0||map.getOrDefault('a',0)!=0){
            return -1;
        }else{//返回至少有多少只青蛙
            return map.getOrDefault('k',0);
        }
    }
}

虽然这么看比较清晰明了,通俗易懂,但是还是不够简洁,且如果叫声不是croak五个字符,而是更长的字符串,那么if else就为字符串的长度,太冗杂了,于是搞一个比较通用的方法,根据总结的分为两种情况

代码2:

class Solution {
    public int minNumberOfFrogs(String c) {
        char[] croakOfFrogs = c.toCharArray();
        String t = "croak";
        int n = t.length();
        int[] hash = new int[n]; // 数组模拟哈希表
        Map<Character, Integer> index = new HashMap<>(); // [x, x这个字符对应的下标]
        //放入每一个字符对应的下标
        for (int i = 0; i < n; i++){
            index.put(t.charAt(i), i);
        }
        //遍历字符串
        for (char ch : croakOfFrogs) {
            //如果是c
            if (ch == t.charAt(0)) {
                //如果有多余的k
                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;
            }            
        }
        //返回k位置的次数(即至少有多少只青蛙) 
        return hash[n - 1];
    }
}

总结

到此模拟算法就学习完了,就是锻炼代码能力,模拟题目要求的操作,如果空间时间复杂度高了,那么基本就要去找规律,根据规律来简化


http://www.kler.cn/a/519285.html

相关文章:

  • 非根目录部署 nextjs 项目,资源文件 请求404 的问题
  • 从0到1:C++ 开启游戏开发奇幻之旅(一)
  • 2025年1月22日(什么是扫频)
  • 在Qt中实现点击一个界面上的按钮弹窗到另一个界面
  • cuda reductionreduce
  • xss靶场
  • Linux_线程控制
  • TCP协议(网络)
  • Vue3在img标签中绑定数据模型中的url图片无法显示问题
  • 奇安信 2022 Zteam 面试(详细答案版)
  • 扣子平台音频功能:让声音也能“智能”起来
  • Solon Cloud Gateway 开发:Route 的匹配检测器及定制
  • 集群IB网络扫描
  • 使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化
  • 【架构面试】二、消息队列和MySQL和Redis
  • 批量提取多个 Excel 文件内指定单元格的数据
  • linux如何定位外部攻击并进行防御处理
  • Visual Studio Code修改terminal字体
  • 【pytorch】norm的使用
  • 9【如何面对他人学习和生活中的刁难】
  • 破解浏览器渲染“死锁”:CSS与JS如何影响页面加载速度?
  • GCC之编译(8)AR打包命令
  • 【初阶数据结构】逆流的回环链桥:双链表
  • 【单链表算法实战】解锁数据结构核心谜题——相交链表
  • 解决使用Selenium时ChromeDriver版本不匹配问题
  • [b01lers2020]Life on Mars1