每日算法-6 外观数列

问题

给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。

注意:整数序列中的每一项将表示为一个字符串。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:

第n项 外观数列 说明
1 1 第一项是数字 1
2 11 描述前一项,这个数是 1 即 “一个 1 ”,记作 11
3 21 描述前一项,这个数是 11 即 “两个 1 ” ,记作 21
4 1211 描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211
5 111221 描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221

示例 1:

输入: 1
输出: “1”
解释:这是一个基本样例。

示例 2:

输入: 4
输出: “1211”
解释:当 n = 3 时,序列是 “21”,其中我们有 “2” 和 “1” 两组,“2” 可以读作 “12”,也就是出现频次 = 1 而 值 = 2;类似 “1” 可以读作 “11”。所以答案是 “12” 和 “11” 组合在一起,也就是 “1211”。


来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-and-say


解法

一、循环计数法

  • 当n为1时,直接返回1;

  • 当n为2时,

    • 记录到上一个字符串是1,直接记录第一个字母的单词为上一个值,数量为1
    • 当第i个字母出现时,跟第i-1个字母比较。如果不一致,则保存记录到字符串中 数量.上一个字母 ,数量改为1,并保存当前值为上一个字母;如果一致,则数量+1
    • 当不存在第i个字母时,代表字符串已获取结束,将当前的字母和其数量记录到字符长中,再将该字符串改为上个字符串
class Solution
{
    
    

    /**
     * @param Integer $n
     * @return String
     */
    function countAndSay($n)
    {
    
    
        if ($n === 1) {
    
    
            return '1';
        }

        //从$n=2开始,默认初始时'1'
        $perStr = '1';
        for ($i = 1; $i < $n; $i++) {
    
    
            $perVal = $perStr[0];   //初始化每一轮外观数列的第1个值
            $returnStr = '';    //当前一轮返回的外观数列,每轮开始时清空重来

            $ind = 1;       //每一轮的计算从第二个字符开始进行比较
            $valCount = 1;
            while (true) {
    
    
                if (!isset($perStr[$ind])) {
    
    
                    //当字母不存在,则会跳出循环,来到这里
                    $perStr = $returnStr . $valCount . $perVal;
                    break;
                }
                //第$ind个字母$perStr[$ind]跟上一个字母$perVal比较
                if ($perStr[$ind] !== $perVal) {
    
    
                    $returnStr .= $valCount . $perVal;
                    $perVal = $perStr[$ind];
                    $valCount = 1;
                } else {
    
    
                    $valCount++;
                }
                $ind++;
            }
        }
        return $perStr;
    }
}

在这里插入图片描述

二、正则表达式法

/(\d)\1*/g (反向引用)可以捕获相同数字。使用preg_match函数,如果抓取到数字的话将会返回数组,如:

$perStr = '11111';
preg_match('/(\d)\1*/', $perStr, $result);
print_r($result);

输出结果

Array
(
    [0] => 11111
    [1] => 1
)

通过数组下标0的字符串长度,我们就能知道连续数字的个数,而下标1的字符就是连续的数字了。


套用下上面的循环计数法上去:

class Solution
{
    
    

    /**
     * @param Integer $n
     * @return String
     */
    function countAndSay($n)
    {
    
    
        if ($n === 1) {
    
    
            return '1';
        }
        $perStr = '1';
        for ($i = 1; $i < $n; $i++) {
    
    
            $result_str = '';
            while (true) {
    
    
                preg_match('/(\d)\1*/', $perStr, $result);
                $result_str .= strlen($result[0]) . $result[1];
                $perStr = substr($perStr, strlen($result[0]));
                if (empty($perStr)) {
    
    
                    break;
                }
            }
            $perStr = $result_str;
        }
        return $perStr;
    }
}

在这里插入图片描述

三、递归法

当n=1时,返回结果一定是1,这也就是递归的终止出口。为方便理解递归,将循环体部分提取出来,只做返回结果字符串

class Solution
{
    
    

    /**
     * @param Integer $n
     * @return String
     */
    function countAndSay($n)
    {
    
    
        //当$n=1时,返回的结果就是1,递归循环体的唯一返回路径
        if ($n === 1) {
    
    
            return '1';
        }
        //循环体,当$n-1=1时,就一定会得到上面的1,然后调用say方法拿到1个1,变成21,在调用本方法,将调用say方法对21进行处理……
        return $this->say($this->countAndSay($n - 1));
    }


    /**
     * @title 获取某字符串的外观数列
     * @param $perStr
     * @return string
     */
    function say($perStr)
    {
    
    
        $result_str = '';
        while (true) {
    
    
            preg_match('/(\d)\1*/', $perStr, $result);
            $result_str .= strlen($result[0]) . $result[1];
            $perStr = substr($perStr, strlen($result[0]));
            if (empty($perStr)) {
    
    
                break;
            }
        }
        return $result_str;
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_38125045/article/details/107087494