【算法】模拟

【ps】本篇有 5 道 leetcode OJ。 

目录

一、算法简介

二、相关例图

1)替换所有的问号

 .1- 题目解析

.2- 代码编写

2)提莫攻击

 .1- 题目解析

.2- 代码编写

3)Z 字形变换

 .1- 题目解析

.2- 代码编写

4)外观数列

 .1- 题目解析

.2- 代码编写

5)数青蛙

 .1- 题目解析

.2- 代码编写


一、算法简介

        模拟算法是指,根据题目表述进行筛选提取关键要素,按需求书写代码解决实际问题,换句话说其实就是“依葫芦画瓢”,但很考察对数据结构、容器和一些特殊数学方法的掌握程度。

        模拟算法一般是一些很基础的题目,只要按题目要求来写代码就能解开题目。不过,模拟题也有相对困难的题目,而它们大部分的优化思路都是找规律。

二、相关例图

1)替换所有的问号

1576. 替换所有的问号

 

 .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)提莫攻击

495. 提莫攻击

 

 .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 字形变换

6. 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)外观数列

38. 外观数列

 

 .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)数青蛙

1419. 数青蛙

 

 .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];
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值