暑期算法训练.8

目录

[37.力扣. 1576替换所有的问号](#37.力扣. 1576替换所有的问号)

[37.1 题目解析:](#37.1 题目解析:)

[37.2 算法思路:](#37.2 算法思路:)

[37.3 代码演示:](#37.3 代码演示:)

​编辑

[37.4 总结反思:](#37.4 总结反思:)

[38. 力扣495 提莫攻击](#38. 力扣495 提莫攻击)

[38.1 题目解析:](#38.1 题目解析:)

[38.2 算法思路:](#38.2 算法思路:)

[38.3 代码演示:](#38.3 代码演示:)

[38.4 总结反思:](#38.4 总结反思:)

[39. 力扣6 Z字形变化](#39. 力扣6 Z字形变化)

[39.1 题目解析:](#39.1 题目解析:)

​编辑

[39.2 算法思路:](#39.2 算法思路:)

[39.3 代码演示:](#39.3 代码演示:)

​编辑

[39.4 总结反思:](#39.4 总结反思:)

[40. 力扣 38 外观数列](#40. 力扣 38 外观数列)

[41.1 题目解析:](#41.1 题目解析:)

[41.2 算法思路:](#41.2 算法思路:)

[41.3 代码演示:](#41.3 代码演示:)

[41.4 总结反思:](#41.4 总结反思:)

[42. 力扣 1419 数青蛙](#42. 力扣 1419 数青蛙)

[42.1 题目解析:](#42.1 题目解析:)

​编辑

[42.2 算法思路:](#42.2 算法思路:)

​编辑

[42.3 代码演示:](#42.3 代码演示:)

​编辑

[42.4 总结反思:](#42.4 总结反思:)


37.力扣. 1576替换所有的问号

37.1 题目解析:

今天咱们要学一个新算法,就是模拟算法。这类题的特点就是:比着葫芦画瓢(题目中已经告诉你该怎么做了)。那么考察你的就是代码能力,能不能把思路转化为代码,这个很重要。

1.先模拟一遍算法流程(一定要在演草纸上过一遍流程)

2.把流程转化为代码

37.2 算法思路:

就是,咱们需要先找到问号在哪个位置,找到之后,在寻找'a'与'z'之间的字符,确保这个字符填到这里不与问号左右的字符重复即可。(返回一种结果就可以了)

但是需要注意一个细节:

这个情况,就是如果说问号在第一个位置,只要确保问号与右边的不同即可。如果问号在最后一个位置,只要确保最后一个位置的问号代表的字符与左边的字符不同即可。

37.3 代码演示:

cpp 复制代码
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]))//如果说i等于0,那么只需要判断右面的不等于ch即可。
                {
                    s[i] = ch;
                    break;
                }
            }
        }

    }
    return s;
}
int main()
{
    string s("?zs");
    cout << modifyString(s) << endl;
    return 0;
}

37.4 总结反思:

基本的做题方法就是这样,注意:模拟的题一定要去找规律才可以。

38. 力扣495 提莫攻击

38.1 题目解析:

这道题很有意思,我个人认为就是游戏里面的掉血的计算,这个要是不会做,那我只能说,你以后要是做游戏开发,人物血量你都掉不明白哈哈哈哈,说着玩的,大家不要当真。

38.2 算法思路:

题目的意思已经很明显了,所以咱们只需要照着葫芦画瓢即可。这里基本也可以找到规律,就是如果说你的第二个元素减去第一个元素大于duration的话,说明,这个时候,是可以直接加上duration的。否则,就需要先加上第二个元素与第一个元素的差再加上duration。(这个其实是边界情况)

OK,咱们直接来看代码:

38.3 代码演示:

cpp 复制代码
int findPoisonedDuration(vector<int>& timeSeries, int duration) {
    int n = timeSeries.size();
    int ret = 0;//记录结果
    for (int i = 0; i < n - 1; i++)
    {
        if (timeSeries[i + 1] - timeSeries[i] > duration)
        {
            ret += duration;//如果说这个右边的比左边的大duration,说明不用重置时间,否则只能加一部分
        }
        else {
            ret += timeSeries[i + 1] - timeSeries[i];
        }
    }
    int i = n - 1;
    ret += duration;//单独考虑边界情况
    return ret;
}
int main()
{
    vector<int> timeSeries = { 1,4 };
    int duration = 2;
    cout << findPoisonedDuration(timeSeries, duration) << endl;
    return 0;
}

38.4 总结反思:

这道题基本就是这样的,咱们只需要掌握住题目的意思,根据这个意思去写就可以了。

39. 力扣6 Z字形变化

39.1 题目解析:

这个题目,你要看清楚,这个z是竖着的,不是横着的。

39.2 算法思路:

这道题目就是找出规律才好做,当时作者在那里苦苦的想了半天,硬是没发现规律,结果一看坐标就发现了。

咱们会发现第一行与最后一行都是有公差的,即公差d=2n-2(就是比如0与6之间的元素个数,即2n-2(n是行数))

第一行:0->0+d->0+2d->.....->0+kd

第k行:(k,d-k)->(k+d,d-k+d)->(k+2d,d-k+2d).....

第n-1行 :n-1->n-1+d->n-1+2d->....->n-1+kd

而中间的,则是2个2个的打包的往后递进。例如1+5=公差d,而1与7之间正好为d。规律也就产生了。

注:以上写的只是在原字符串中进行的移动,所以说它的第一行与最后一行还有中间的,都是要小于n才可以。

还有一种情况需要处理,就是:n=1的时候,d=0.那么这个时候,你要是不处理,就会导致死循环。所以n=1需要当作边界情况处理。

以上只是咱们以4行推出来的,所以要再验证一下其他的才具有说服力:

这个是3行,公差就是2n-2=4,第一行与最后一行均符合,中间的也是2个2个的进行打包,也是可以的。

39.3 代码演示:

cpp 复制代码
string convert(string s, int numRows) {
    string ret;//这个用来存储结果
    int n = s.size();
    int d = 2 * numRows - 2;//算出来公差
    if (numRows == 1) return s;//处理边界情况
    //1.先处理第一行
    for (int i = 0; i < n; i += d)
    {
        ret += s[i];
    }
    //2.处理中间的行数
    for (int i = 1; i < numRows - 1; i++)//先得是行数的移动
    {
        for (int j = i, k = d - j; j < n || k < n; j += d, k += d)//这里j或者k,可能j越界了,i还没有,所以要填或者
        {
            if (j < n) ret += s[j];
            if (k < n) ret += s[k];//因为进入到这里的是j越界了或者k越界了,所以还得检测一下才可以加,否则就数组越界了
        }
    }
    //3.处理最后一行
    for (int i = numRows - 1; i < n; i += d)
    {
        ret += s[i];
    }
    return ret;
}
int main()
{
    string s("PAYPALISHIRING");
    int numRows = 3;
    cout << convert(s, numRows) << endl;
    return 0;
}

39.4 总结反思:

本道题就是你要是找不出规律,那很难做出来。你要是想不出来看下标,硬做,其实还是挺难的。

40. 力扣 38 外观数列

41.1 题目解析:

题目得好好的读一下,就是你得明白什么意思:

第四个对于第三个进行翻转就是1个2,1个1,就是1211

第五个对于第四个翻转就是:1个1,1个2,2个1,即111221

...................

41.2 算法思路:

咱们这道题目需要用到模拟加上双指针算法才可以解决。

left与right一开始都是在下标为0的位置,right先开始移动,找到与left相同的元素就往右加加,直到遇到与left不同的元素的时候停下,记录一下right与left中间相同元素的数量(此时的元素就是left所指向的位置)。一般的左闭右闭,咱们使用right-left+1.但是这里是右开区间,所以要减去1,那么就是count=right-left。

一直往后移动,直到right越界了这个字符串为止。

所以,咱们应该可以知道,最外层应该是执行了多少次翻转,之后里面才是对与left,right循环的for循环,最里面才是关于寻找count的循环(只要是执行多次相同步骤的,就得写循环)。就是right++(但这个right++,只管一次left,left也得移动,所以才在外层加上一个for循环,以便于控制left的移动)。

41.3 代码演示:

cpp 复制代码
string countAndSay(int n) {
    string ret = "1";//定义一个ret去接收结果。并且这个得初始化为1,因为一开始就是从1开始的
    for (int i = 1; i < n; i++)//执行n-1次翻转
    {
        string tmp;//用来暂时存储字符串
        int len = ret.size();//这个是right必须得小于len才可以,因为ret才是那个字符串
        for (int left = 0, right = 0; right < len;)
        {
            while (right < len && ret[right] == ret[left]) right++;//还得防止right越界。注意,这里是个循环
            tmp += to_string(right - left) + ret[left];//这个是将有几个1转化为字符串,并且要加上1,即left
            left = right;
        }
        ret = tmp;
    }
    return ret;
}
int main()
{
    int n = 4;
    cout << countAndSay(n) << endl;
    return 0;
}

41.4 总结反思:

本题就是得灵活的结合前面的只是才可以解决。

42. 力扣 1419 数青蛙

42.1 题目解析:

本题是作者今天做的题目里面较复杂的,因为这个模拟算法比较难想。就是如何模拟的比较难想。这个题就是你的每一个c,r,o,a,k都必须得有对应的c,r,o,a,k与之对应,否则就是多余的,多余的就是错的。

42.2 算法思路:

1.从前往后遍历:遇到了C:则C+1,若是遇到了遇到r,看前驱字符,前驱字符若是存在,则前驱字符个数减减,当前字符个数加加。

.............

直到k,开始下一个。

所以C:找最后在一个字符是否在哈希表中存在。若存在,则说明可以把这个青蛙搬过来用(因为它让求的是最小的数目,所以肯定得让利用最大化).若不存在,当前字符(C)++。

而对于r,o,a,k->找前驱字符(r前驱为c,o前驱为r,a前驱为o,k前驱为a)。是否在哈希表中存在过,若是存在过,前驱个数减减,当前字符个数减减。若是不存在,返回-1即可(因为没有对应的前驱与之匹配,说明这个字符是多余的,多余的就是错的,返回-1)。

大家仔细的理解一下,会发现,这个算法还是挺好的,基本做到了每一个字符都是一一对应对应的croak。这样的话,就可以看出来是否croak有多余,是否有顺序。

最后还得判断一下,k之前的字符个数必须为0,否则返回-1(否则就是多余的,多余的肯定返回-1)。

最后这些都完成了,返回k,这个里面的字符的个数。即hash[n-1]

42.3 代码演示:

cpp 复制代码
int minNumberOfFrogs(string croakOfFrogs)
{
string t = "croak";
int n = t.size();
vector<int> hash(n); // ⽤数组来模拟哈希表

unordered_map<char, int> index; //[x, x这个字符对应的下标]
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[n-1]一定是0,但是第二次就好多了
            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;//保证哈希表中k这个字符前面的字符的个数必须为0,否则就是多出了字符,就是错的
    return hash[n - 1];//最后这些都完成了,返回哈希表中k这个字符的个数
}
int main()
{
    string croakOfFrogs("croakcroa");
    cout << minNumberOfFrogs(croakOfFrogs) << endl;
    return 0;
}

这个里面的数组比较多,为了防止大家搞晕,我画了几幅图,便于大家理解:

42.4 总结反思:

这道题目还是很考验大家的算法能力的,以及代码的实现能力。

相关推荐
屁股割了还要学5 分钟前
【C语言进阶】柔性数组
c语言·开发语言·数据结构·c++·学习·算法·柔性数组
草莓熊Lotso5 分钟前
【LeetCode刷题指南】--有效的括号
c语言·数据结构·其他·算法·leetcode·刷题
Alla T14 分钟前
【通识】算法案例
算法
Electrolux17 分钟前
你敢信,不会点算法没准你赛尔号都玩不明白
前端·后端·算法
天天开心(∩_∩)21 分钟前
代码随想录算法训练营第三十一天
算法
oioihoii32 分钟前
C++实战案例:从static成员到线程安全的单例模式
java·c++·单例模式
qq_513970441 小时前
力扣 hot100 Day55
算法·leetcode
charlie1145141911 小时前
快速入门Socket编程——封装一套便捷的Socket编程——导论
linux·网络·笔记·面试·网络编程·socket
zzzzz_ccc2 小时前
AVL树和红黑树的特性以及模拟实现
c语言·数据结构·c++
zl_dfq2 小时前
数据结构 之 【排序】(递归实现快速排序)
数据结构