算法(6)——模拟

一、什么是模拟

模拟是对真实事物或者过程的虚拟。在编程时为了实现某个功能,可以用语言来模拟那个功能,模拟成功也就相应地表示编程成功。

二、模拟算法的思路

模拟算法是一种基本的算法思想,可用于考查程序员的基本编程能力,其解决方法就是根据题目给出的规则对题目要求的相关过程进行编程模拟,说白了,就是题目说什么就做什么。在解决模拟类问题时,需要注意字符串处理、特殊情况处理和对题目意思的理解。

在C语言中,通常使用函数 srand() 和 rand() 来生成随机数。其中,函数 srand() 用于初始化随机数发生器,然后使用函数 rand() 来生成随机数。如果要使用上述两个函数,则需要在源程序头部包含 time.h 文件。在程序设计过程中,可使用随机函数来模拟自然界中发生的不可预测情况。在解题时,需要仔细分析题目给出的规则,要尽可能地做到全面考虑所有可能出现的情况,这是解模拟类问题的关键点之一。

三、模拟算法的特点和技巧

特点:

        代码量大

        操作多

        思路复杂

        较为复杂的模拟题出错后难定位错误

技巧:

       1、在动手写代码之前,在草纸上尽可能地写好要实现的流程.

       2、在代码中,尽量把每个部分模块化,写成函数、结构体或类

       3、对于一些可能重复用到的概念,可以统一转化,方便处理:如,某题给你"YY-MM-DD 时:分" 把它抽取到一个函数,处理成秒,会减少概念混淆

       4、调试时分块调试。模块化的好处就是可以方便的单独调某一部分

       5、写代码的时候一定要思路清晰,不要想到什么写什么,要按照落在纸上的步骤写

三、模拟OJ题

3.1、替换所有的问号

1576. 替换所有的问号 - 力扣(LeetCode)

        题目描述

        算法思路

纯模拟。从前往后遍历整个字符串,找到问号之后,就⽤ a ~ z 的每⼀个字符去尝试替换即可。
        算法代码 
class Solution {
public:
    string modifyString(string s) 
    {
        for(int i=0;i<s.size();i++)
        {
            if(s[i]=='?')
            {
                for(char j='a';j<='z';j++)
                {
                    if((i==0||j!=s[i-1])&&(i==s.size()-1||j!=s[i+1]))
                    {
                        s[i]=j;
                    }
                }
            }
        }
        return s;
    }
};

3.2、提莫攻击

495. 提莫攻击 - 力扣(LeetCode)

        题目描述

        算法思路

模拟 + 分情况讨论。

计算相邻两个时间点的差值:

        i. 如果差值⼤于等于中毒时间,说明上次中毒可以持续 duration 秒;

        ii. 如果差值⼩于中毒时间,那么上次的中毒只能持续两者的差值。
        代码实现
class Solution {
public:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) 
    {
        int n=timeSeries.size();
        int sum=0;
        for(int i=1;i<n;i++)
        {
            if(timeSeries[i]-timeSeries[i-1]>=duration)
            {
                sum+=duration;
            }
            else
            {
                sum+=timeSeries[i]-timeSeries[i-1];
            }
        }
        return sum+duration;
    }
};

3.3、N字形变换

6. Z 字形变换 - 力扣(LeetCode)

        题目描述

        算法思路

这个 Z 字型其实是这样的:

对于前面的  3 行的  示例1 , 它的字符数分布是这样的

对于前面的 4 行的 示例2 , 它的字符数分布是这样的:

那么对于 n 行的字符数分布是这样的:

如上图所示,我们可以发现:

1.当前行 curRow 为 0 或 n-1 时,箭头发生反向转折。
方法一: 从左到右按箭头方向迭代 s ,将每个字符添加到合适的行。之后从上到下遍历行即可。

我们假定 n=numRows :

class Solution {
public:
	string convert(string s, int numRows) {

		if (numRows == 1) return s;

		vector<string> rows(min(numRows, int(s.size()))); // 防止s的长度小于行数
		int curRow = 0;
		bool goingDown = false;

		for (char c : s) {
			rows[curRow] += c;
			if (curRow == 0 || curRow == numRows - 1) {// 当前行curRow为0或numRows -1时,箭头发生反向转折
				goingDown = !goingDown;
			}
			curRow += goingDown ? 1 : -1;
		}

		string ret;
		for (string row : rows) {// 从上到下遍历行
			ret += row;
		}

		return ret;
	}
};

3.4、外观数列

38. 外观数列 - 力扣(LeetCode)

        题目描述

        算法思路

所谓「外观数列」,其实只是依次统计字符串中连续且相同的字符的个数。依照题意,依次模拟即
可。

        代码实现

class Solution {
public:
    string countAndSay(int n) 
    {
        string ret="1";
        for(int i=1;i<n;i++)  //翻译n-1次
        {
            string temp;
            int len =ret.size();
            for(int left=0,right=0;right<len;)
            {
                while(right<len&&ret[right]==ret[left]) right++;
                temp+=(right-left)+'0';
                temp+=ret[left];
                left=right;
            }
            ret=temp;
        }
        return ret;
    }
};

3.5、数青蛙

        题目描述

        算法思路

(快排思想 - 三指针法使数组分三块)

类⽐数组分两块的算法思想,这⾥是将数组分成三块,那么我们可以再添加⼀个指针,实现数组分
三块。
设数组⼤⼩为 n ,定义三个指针 left, cur, right :
        ◦ left :⽤来标记 0 序列的末尾,因此初始化为 -1
        ◦ cur :⽤来扫描数组,初始化为 0
        ◦ right :⽤来标记 2 序列的起始位置,因此初始化为 n
cur 往后扫描的过程中,保证:
        ◦ [0, left] 内的元素都是 0
        ◦ [left + 1, cur - 1] 内的元素都是 1

        [cur, right - 1] 内的元素是待定元素    

        [right, n] 内的元素都是 2

        算法流程:
a. 初始化 cur = 0 left = -1 right = numsSize
b. cur < right 的时候(因为 right 表⽰的是 2 序列的左边界,因此当 cur 碰到right 的时候,说明已经将所有数据扫描完毕了),⼀直进⾏下⾯循环:根据 nums[cur] 的值,可以分为下⾯三种情况:
        i. nums[cur] == 0 ;说明此时这个位置的元素需要在 left + 1 的位置上,因此交换 left + 1 cur 位置的元素,并且让 left++ (指向 0 序列的右边界),cur++ (为什么可以 ++ 呢,是因为 left + 1 位置要么是 0 ,要么是 cur ,交换完毕之后,这个位置的值已经符合我们的要求,因此 cur++ 
        ii. nums[cur] == 1 ;说明这个位置应该在 left cur 之间,此时⽆需交换,直接让 cur++ ,判断下⼀个元素即可;
        iii. nums[cur] == 2 ;说明这个位置的元素应该在 right - 1 的位置,因此交换right - 1 与 cur 位置的元素,并且让 right-- (指向 2 序列的左边界),cur 不变(因为交换过来的数是没有被判断过的,因此需要在下轮循环中判断)
c. 当循环结束之后:
        [0, left] 表⽰ 0 序列;
        [left + 1, right - 1] 表⽰ 1 序列;
        [right, numsSize - 1] 表⽰ 2 序列。

        代码实现

class Solution {
public:
    int minNumberOfFrogs(string croakOfFrogs) 
    {
        string t="croak";
        int n=t.size();
        vector<int> hash(n);
        unordered_map<char ,int > index;
        for(int i=0;i<n;i++)
        {
            index[t[i]]=i;
        }
        for(auto ch:croakOfFrogs)
        {
            if(ch=='c')
            {
                if(hash[n-1]!=0) hash[n-1]--;
                hash[0]++;
            }
            else
            {
                int i=index[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];
    }
};
  • 74
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值