模拟算法实例讲解:从理论到实践的编程之旅

目录

1、模拟算法简介

2、替换所有问号

3、提莫攻击

4、Z字形变换

5、外观数列

6、数青蛙


1、模拟算法简介

模拟算法是一种基本的算法设计方法,它的核心思想是按照问题描述的规则,逐步模拟问题的发展过程,从而得到问题的解决方案。这种算法通常不依赖于复杂的数学公式或高级的数据结构,而是通过直接模拟现实世界中的操作或规则来解决问题。

2、替换所有问号

1576. 替换所有的问号

从前往后遍历整个字符串,找到问号之后,就用 a ~ z 的每⼀个字符去尝试替换即

cpp 复制代码
class Solution {
public:
    string modifyString(string s) 
    { // 模拟,算法流程
        int n = s.size();
        for(int i = 0;i < n;i++)
        { // 遍历数组,找到?,替换
            if(s[i] == '?')
            {
                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;
    }
};

3、提莫攻击

495. 提莫攻击

cpp 复制代码
class Solution {
public:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) 
    {
        int T = 0;
        for(int i = 1;i < timeSeries.size();i++)
        {   // 计算两次中毒的时间间隔
            int time = timeSeries[i] - timeSeries[i-1];
            // 间隔大于标准中毒时间,按标准计时 
            if(time > duration) T += duration;
            // 否则,计时会重置,所以只记录间隔时间
            else T += time;
        }
        // 别忘了最后一次中毒
        T += duration;
        return T;
    }
};

4、Z字形变换

6. Z 字形变换

**方法一:用****字符串数组vector<string>**模拟Z字形存入数据,再将字符串数组中的字符串按行号顺序连接。
遍历字符串s时:用 flag 来做索引标记,到达Z字形转折点时的时候 (第一行 / 最后一行),执行反向 (flag = -flag) 。更新下标索引时,用 i += flag 。

cpp 复制代码
class Solution {
public:
    string convert(string s, int numRows) 
    {
        if(numRows == 1) return s;
        // 1.将数据存入字符串数组
        vector<string> vs(numRows);
        int i = 0,flag = -1;
        for(auto& x: s)
        {
            vs[i] += x; // 刚进循环,第一个数据flag是-1
            if(i == 0 || i == numRows-1) // 第一/最后一行是转折点
                flag = -flag; // 用flag控制存储的方向,位置
            i += flag;
        }
        // 2.将字符串数组中所有字符串按行号顺序接合起来
        string str;
        for(string& rows: vs)
            str += rows;
        return str;
    }
};

方法二:找规律:

第1/numRows行,隔2*numRows-2是一个元素。

中间行,两组数据从(k,d-k)开始,每次隔2*numRows-2是一组数据,可能最后会有一个数据越界。

cpp 复制代码
class Solution {
public:
    string convert(string s, int numRows) 
    {
        // 边界情况
        if(numRows == 1) return s;

        // 找规律:第1/numRows行,隔2*numRows-2是一个元素
        // 中间行,两组数据从(k,d-k)开始,每次隔2*numRows-2是一组数据,
        // 可能最后会有一个数据越界
        string str;
        int d = 2 * numRows - 2; // 计算步长
        int n = s.size();
        
        // 处理第一行,从s[0]开始
        for(int i = 0;i < n;i += d)
            str += s[i];
        // 处理中间行的每一行,从(s[k],s[d-k])开始
        for(int k = 1;k < numRows-1;k++)
        {
            for(int i = k,j = d-k;(i < n || j < n);i += d,j += d)
            {
                if(i < n) str += s[i];
                if(j < n) str += s[j];
            }
        }
        // 处理最后一行,从s[numRows-1]开始
        for(int i = numRows-1;i < n;i += d)
            str += s[i];
        return str;
    }
};

5、外观数列

. - 力扣(LeetCode)

模拟 + 双指针,双指针用来模拟顺序读取下字符的连续次数。

cpp 复制代码
class Solution {
public:
    string countAndSay(int n) {
        // 模拟 + 双指针
        string ret = "1"; // 初始值
        for (int i = 1;i < n; i++) // 模拟n-1次,因为第一次就是1
        {
            string tmp;
            int len = ret.size(); // 限定区域是在ret内
            for(int left = 0,right = 0;right < len;)
            {
                while (right < len && ret[left] == ret[right]) right++;// 确定重复次数
                tmp += to_string(right - left); // 重复次数
                tmp += ret[left]; // 对应字符
                left = right; // 重置指针
            }
            ret = tmp; // 更换新字符串
        }
        return ret;
    }
};

6、数青蛙

. - 力扣(LeetCode)

方法:模拟 + 分情况讨论

用哈希表模拟寻找青蛙叫声的过程,开始时,青蛙开始发出叫声【c】,在哈希表中映射记录一次【c】叫声,假设紧接着发出【r】时,就要先在哈希表中找【r】的前驱字符【c】是否已经记录过叫,如果有记录就把这个记录传承下来,前驱【c】的叫声次数-1,当前【r】的叫声+1次;如果没有记录,就说明此时字符串记录的叫声是不完整的,不是有效组合。后续【o】...叫声与上面演示的【r】的处理方式类似。

但因为我们要找的是最少的青蛙数,青蛙在叫完完整一次"croak"后可以紧接着继续蛙鸣,所以,当读到【c】时还有一种情况是先查看哈希表中对应的【k】叫声个数,如果不是0,说明有青蛙可以重新开始一轮蛙鸣,对应我们的操作就是把记录到的【k】叫声个数-1,给【c】叫声个数+1。
总结:

1、r o a k ---> 查看前序字符是否在哈希表中存储个数

(1)存在:前驱个数--,当前个数++

(2)不存在:返回-1

2、c ---> 找最后一个字符 k 在哈希表中是否存储

(1)存在:k字符个数--,c字符个数++

(2)不存在:c字符个数++

cpp 复制代码
class Solution 
{
public:
    int minNumberOfFrogs(string croakOfFrogs) 
    { // 用哈希表模拟
        string str = "croak"; // 先把叫声存储起来
        int n = str.size();
        vector<int> hash(n); // 数组模拟哈希表,用下表对应存储str的每个字符('c'对应0...)

        unordered_map<char,int> index(n); 
        // 存储 [x,x对应的下标] 方便寻找前驱字符(通过下标)
        for(int i = 0;i < n; i++)
            index[str[i]] = i;
        
        for(auto ch : croakOfFrogs)
        {
            if(ch == 'c')
            {
                if(hash[n - 1] != 0) hash[n - 1]--;
                hash[0]++;
            }
            else
            {
                int prev = index[ch] - 1; // 找到前驱字符的下标
                if(hash[prev] == 0) return -1;
                hash[prev]--;
                hash[index[ch]]++;
            }
        }
        for(int i = 0;i < n - 1;i++)
            if(hash[i] != 0) return -1;
        return hash[n - 1];
    }
};
cpp 复制代码
class Solution 
{
public:
    int minNumberOfFrogs(string croakOfFrogs) 
    {
        unordered_map<char,int> vv;
        for(auto e : "croak")
        {
            vv[e] = 0;
        }
        int cur = 0,len = croakOfFrogs.size();
        while(cur < len)
        {
            if(croakOfFrogs[cur] == 'r')
            {
                if(vv['c'] > 0) 
                {
                    vv['c']--;
                    vv['r']++;
                } 
                else return -1;
            }
            else if(croakOfFrogs[cur] == 'o')
            {
                if(vv['r'] > 0) 
                {
                    vv['r']--;
                    vv['o']++;
                } 
                else return -1;
            }
            else if(croakOfFrogs[cur] == 'a')
            {
                if(vv['o'] > 0) 
                {
                    vv['o']--;
                    vv['a']++;
                } 
                else return -1;
            }
            else if(croakOfFrogs[cur] == 'k')
            {
                if(vv['a'] > 0) 
                {
                    vv['a']--;
                    vv['k']++;
                } 
                else return -1;
            }
            else if(croakOfFrogs[cur] == 'c')
            {
                if(vv['k'] > 0)
                {
                    vv['k']--;
                    vv['c']++;
                }
                else vv['c']++;
            }
            cur++;
        }
        for(auto e: "croa")
        {
            if(vv[e] > 0) return -1;
        }
        return vv['k'];
    }
};
相关推荐
怀澈12243 分钟前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
chnming19871 小时前
STL关联式容器之set
开发语言·c++
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔1 小时前
《线性代数的本质》
线性代数·算法·决策树
威桑1 小时前
MinGW 与 MSVC 的区别与联系及相关特性分析
c++·mingw·msvc
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
yigan_Eins1 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
Mr.132 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++
阿史大杯茶2 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
C++忠实粉丝2 小时前
计算机网络socket编程(3)_UDP网络编程实现简单聊天室
linux·网络·c++·网络协议·计算机网络·udp