算法解析——双指针算法(2)追及问题

欢迎来到博主的专栏——算法解析
博主ID:代码小豪


leetcode202——快乐数

在这里插入图片描述

在这里插入图片描述
这题的最大难度就在于理解题目了。我们来看看题目到底要求的是什么?

首先对于一个数n是否是快乐数,我们要求得该数n各个位数上的平方之和,记为s1。如果s不为1。那么求s各个位数上的平方之和,记为s2。如果s2不为1,那么继续求s2各个位数上的平方之和。以此类推,一个数n可能会推出s1,s2,……,sn。如果sn等于1,则n为快乐数

这个定义乍一看那是吓到年幼的我了。这么复杂的数学问题我咋能会啊。后来仔细看了看示例,发现情况并非如此,如果找到规律,那么这道题确实是一道简单题。

我们来看看,示例1,n为19。
在这里插入图片描述
经过计算,最后的结果出现了1,因此19是快乐数。

那么示例2,n为2。
2不是快乐数,但那是怎么得出来的呢?我们现在演算纸上计算出s1,s2……sn。算了,这还是太费劲了,既然咱会编程,那么我们就用电脑来生成s1,s2……,s20.代码如下:

void createhappynum20(int n)
{
	static int count = 0;
	int happysum = 0;
	int reminder = 0;
	while (n)
	{
		reminder = n % 10;
		happysum += reminder*reminder;
		n /= 10;
	}
	cout << happysum <<endl;
	count++;
	if (count == 20) {
		return;
	}
	createhappynum20(happysum);
}

我们将2输入至这个函数当中。输出结果为
在这里插入图片描述
有没有发现什么规律吗?如果没有那么就将数据排成横向的。
在这里插入图片描述

有没有发现,其实这个数组中的数据会在某个节点处开始循环!如果还没有发现,那么也可以将图画成这样

在这里插入图片描述
有没有发现,这个数列是循环的。那么也就是说,如果一个数不是快乐数,那么该数的数列一定会在某个节点进入循环。但是我们只有2这么一个例子,其他自然数也会进入循环吗?

首先我们要知道一点,当位数超过3位数,小于13位数时,其各个位数的平方和不会超过3位数,那么也就是说,不存在没有循环的非快乐数。原理如下:

假如现在有5个抽屉,而你又6个球,那么一定会有一个抽屉出现2个以上的球。那么反过来,现在int类型的数据不会超过11位。因此,只要在这个范围内的任何数据,其平方和都不会超过999。如果我们让数列循环求平方和1000次,那么,一定会存在2个以上平方和是相同的,一旦出现一个相同的平方和,那么该数列就相当于进入了死循环,就能证明n是非快乐数了。

那么难题就在于,如何证得该数列进入了一个死循环。

指针的追及问题

博主这里直接给出暴论:在数列的起始位置,设一个指针为慢指针,一次向后移动1位,另一个指针为快指针,一次向后移动两位。如果该数列循环,则快慢指针一定会相遇!

你们大可以在演算纸上试着画一下,你会发现这个结论是正确的,那么博主就这么草率的下了这个定义吗?当然不是了,小人不才,但是这个算法正好略懂。

对于这种需要证明数据循环的问题,快慢双指针当然是一个非常不错的选择,我们想要证明数据循环,那么就要证明快慢指针会相遇。其实这个道理可以用一个例子解决,即一个人坐飞机,一个人开船,他们都从一个地方一直往东走,如果开飞机的转了一圈,遇到了开船的,那么不就说明,地球是圆的(哈哈)。

由于快指针和慢指针都是从头部位置开始,那么,快指针一定会比慢指针先进入循环。我们假设这个环的周长为C,当慢指针进入循环入口时,快指针距离入口为N,那么,快指针与慢指针之间的距离为C-N。
在这里插入图片描述
(用这个画图工具来画图有点不顺手,还是CAD用的舒服,在后面会附上一个链接,也是双指针的追及问题,里面的图使用CAD画的,感兴趣可以看看)

已知,快指针fast一次前进2步,slow每次前进一步,那么,每经过一回合,fast距离slow的距离就会减少1。如果fast要追上slow,只需要经历C-N个回合。

这就证明,在循环数据中,快慢指针一定会在循环内部相遇。那么回到快乐数这个问题本身,我们要证明的,不就是这个非快乐数构成的数列会进入循环吗?

代码如下:

class Solution {
public:
    int calc_sum_of_bit_squre(int n)//此函数用来计算各个位数的平方和
    {
        int ret = 0;
        while (n) {
            int reminder = n % 10;
           ret += reminder * reminder;
            n /= 10;
        }
        return ret;
    }
    bool isHappy(int n) {
        vector<int> arr;
        size_t size;
        while(1)
        {
            n=calc_sum_of_bit_squre(n);
            arr.push_back(n);
            size=arr.size();
            if(arr.back()==1)
            {
                return true;
            }          
            if(size>2)
            {
                if(arr[size/2]==arr[size-1])
                {
                    return false;
                }
            }
        }
    }
};

说明:
由于数列是一个位置数量的数列,因此我们可以用一个vector来承载这些数列的数据(因为vector可以变长)。然后这里面没有使用快慢指针,这是因为vector是动态增长的,因此slow永远遇不到fast。但是证明数列循环不需要让指针相等,只需要让指针指向的数据相等即可。

而fast和slow的关系是,fast/2=slow,因此我们可以数组下标来模仿快慢指针。即arr[size]==arr[size/2]。这个关系式就是代表快慢指针指向的数据相等。

当然,还有下面这种方式,就是不好理解,但是效率比上面高得多

class Solution {
public:
    int calc_sum_of_bit_squre(int n)//此函数用来计算各个位数的平方和
    {
        int ret = 0;
        while (n) {
            int reminder = n % 10;
           ret += reminder * reminder;
            n /= 10;
        }
        return ret;
    }
    bool isHappy(int n) {
        int slow=calc_sum_of_bit_squre(n);//slow获得数列上的下一个数
        int fast=calc_sum_of_bit_squre(calc_sum_of_bit_squre(n));//fast获得数列上的下两个数
        while(fast!=slow)//当快慢指针相遇即结束
        {
            slow=calc_sum_of_bit_squre(slow);
            fast=calc_sum_of_bit_squre(calc_sum_of_bit_squre(fast));
        }
        return fast==1;//判断循环内部值是否为1
    }
};

前面提到了,这种线性动态增长的数列,如果是用普通指针的形式,slow永远追不上fast。但是,我们只需要证明slow指向的值和fast指向的值相等,也能证明这个数列循环,因此我们甚至可以抛弃常用的双指针形式(即数组下标,迭代器,指针类型)。直接使用整型数据来获取数列上的值。slow获取数列上的下一个数据,fast获得数列上的下两个数据。如果fast与slow相等了,那么就证明,这个数列进入了循环。如果循环体内的值都是1,说明这个数就是快乐数,若非1,则非快乐数。

更加典型的双指针追及问题

快乐数用到指针比较抽象,而且追及的图像画的也不够好,也不够形象。实际上在博主以前的博客当中就出现过一个快慢指针算法,来解决的追及问题的示例。博主将链接放在底下,大家感兴趣的可以看看。里面的指针可是正儿八经的指针哦。
leetcode141——环形链表

  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小豪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值