【算法专题】模拟算法题

        模拟算法题往往不涉及复杂的数据结构或算法,而是侧重于对特定情景的代码实现,关键在于理解题目所描述的情境,并能够将其转化为代码逻辑。所以我们在处理这种类型的题目时,最好要现在演草纸上把情况理清楚,再动手编写代码。

目录

1. Z字形变换

2. 外观数列

3. 数青蛙


1. Z字形变换

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

        对这道题,最容易想到的肯定是创建一个二维数组,像题目描述的那样,以Z字形填充数组,然后再遍历一遍数组,得到结果序列。然而这种做法比较复杂,而且时空复杂度都是比较高的,所以我们便来试着优化一下,找到更优秀的解法。一般而言,模拟题的优化往往都是根据找到的规律来编写代码,这道题也不例外。

        由于题目最后仅要求我们写出经过Z字形变换后得到的序列,所以我们其实是不需要真的创建数组的,只要能找到每一行的变换规律,编写代码,把每一行都加到要返回的字符串中就行了。

        为了方便画图,我们画的是要填入的字符串的下标,通过下图我们可以发现,图形中的第0行和最后一行填入的数规律是差不多的,假设公差为d,

则第0行:0,d,2d,……

最后一行:n-1,n-1+d,n-1+2d,……

        对于第一行和最后一行,用简单的数列知识就能得出d为2*n-2,至于中间的n-2行,看起来似乎有些复杂,但我们根本就没必要理会填入的元素在二维数组中的位置,只要知道它们的值就行了,所以注意观察数值规律,不难发现每一行的元素实际上可以被划分为两个数列的元素:

那么,现在我们已经可以找到了每一行的元素的规律,代码实现也就压根不需要二维数组了,希望大家看到这里,可以尝试根据算法原理,自行编写一下代码,然后再来看答案。

class Solution {
public:
    string convert(string s, int numRows) {
        if(numRows == 1) return s;
        int d = 2 * numRows - 2;
        int r0 = 0, rn = numRows - 1;
        string res;
        while(r0 < s.size())
        {
            res += s[r0];
            r0 += d;
        }
        for(int k = 1; k < numRows - 1; k++)
        {
            for(int i = k, j = d - k; i < s.size() || j < s.size(); i += d, j += d)
            {
                if(i < s.size()) res += s[i];
                if(j < s.size()) res += s[j];
            }
        }
        while(rn < s.size())
        {
            res += s[rn];
            rn += d;
        }
        return res;
    }
};

2. 外观数列

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

        本题的题意如下:

        外观数列的第一项是“1”,而在这之后的每一项就是对上一项的外观的解释。而弄清楚情景之后,我们开始思考如何实现。事实上这种压缩操作是非常适合用双指针来实现的,定义left,right两个指针,首先固定left,然后令right向后移动,当right下标的元素不等于left下标的元素时,说明我们已经找到了能找到的最长的连续相同子串,子串长度就是right-left,把这个长度和元素添加到字符串中,然后令left=right,继续寻找下一个连续相同子串即可。

        现在我们已经知道如何求外观数列的项了,接下来只要加个循环,就能求出指定的外观数列的项了。

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

3. 数青蛙

1419. 数青蛙 - 力扣(LeetCode)

        根据题目描述和几个示例,我们需要找出能发出要求呱声的最小青蛙数量,我们可以发现:如果呱声是连续的,即连续依序输出croak这五个字母,则最少一只青蛙就能完成;如果呱声并不是连续的,但是能找到数个完整的croak,需要多只青蛙;如果呱声中存在不完整的croak,则不存在青蛙可以完成,此时返回-1.

        由于需要判断croak是否完整,并计算最少需要多少只青蛙,所以我们可以用哈希表来统计字母个数。但值得注意的是,青蛙们必须连续地输出croak,所以哈希表的更新必须能够保证连续性,比方说遍历到r时需要判断是否已经有c、到o时需要判断是否已经有r、……也就是说最后所有已经输出完的青蛙的数量将会被统计在哈希表中的k对应的项。

        考虑到可能会有多只青蛙同时发声,为了防止计数混乱,我们更新哈希表时,采取如下步骤:假设哈希表为hash,当前下标为index,判断hash[index-1]、hash[index]是否符合顺序,如果不是,返回-1;如果是,则将hash[index-1]--,hash[index]++。

        到这还没完,为了找出最少的符合要求的青蛙数,我们要把输出了完整单词的青蛙重复利用,让它接着输出,所以碰到c时,我们先去找哈希表的k下标的值,如果大于0,将hash[k]--,hash[c]++。这样一来,我们就能统计出最少的符合要求的青蛙数了,但这依然是不够的,我们还需要检查一下哈希表的其他项是否都是0,如果存在非0项,那就说明存在不完整的呱声,不符合题意,再返回-1。

        

class Solution {
public:
    int minNumberOfFrogs(string croakOfFrogs) 
    {
        string t = "croak";
        int n = t.size();
        vector<int> hash(n);
        unordered_map<char, int> ind;
        for(int i = 0; i < n; i++)
            ind[t[i]] = i;
        for(char ch : croakOfFrogs)
        {
            if(ch == 'c')
            {
                if(hash[n - 1] > 0) hash[n - 1]--;
                hash[0]++;
            }
            else
            {
                int index = ind[ch];
                if(hash[index - 1] <= 0) return -1;
                hash[index - 1]--;
                hash[index]++; 
            }
        }
        for(int i = 0; i < n - 1; i++)
        {
            if(hash[i] != 0) return -1;
        }
        return hash[n - 1];
    }
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值