【算法专题--双指针算法】leecode-202. 快乐数(medium)、leecode-11. 盛最多水的容器(medium)-LMLPHP



前言

  • 对撞指针从两端向中间移动。一个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼
    近。
  • 对撞指针的终止条件一般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循
    环),也就是:
    • left == right (两个指针指向同一个位置)
    • left > right (两个指针错开)

快慢指针的实现方式有很多种,最常用的⼀种就是:

  • 在一次循环中,每次让慢的指针向后移动一位,而快的指针往后移动两位,实现一快一慢。

1. 快乐数(medium)

题目描述:
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:

示例 1:

示例 2:

提示:

2. 解法

思路:
可以把题目中的两种情况当成一种情况来看,就是一直在死循环

  1. 对于情况一:⼀直在 1 中死循环
  2. 对于情况二:在历史的数据中死循环,但始终变不到 1

为什么会死循环?

解题方法:

  1. ProductSum 函数:
  • 这个函数计算一个整数的每个位上数字的平方和。
  • 通过不断地对整数取模 10 来获取其最后一位数字,然后将其平方并累加到 sum 变量中。
  • 每次迭代,整数都通过整除 10 来移除最后一位数字。
  • 当整数变为 0 时,函数返回累加的和 sum。
  1. isHappy 函数:
  • 使用“快慢指针”技术来检测循环。
  • slow 和 fast 初始时都指向 n。
  • slow 每次移动一步,即计算当前数字的平方和。
  • fast 每次移动两步,即连续计算两次平方和。
  • 如果 n 是一个快乐数,slow 和 fast 最终都会达到 1。
  • 如果 n 进入循环,slow 和 fast 会在循环中的某个点相遇(即它们的值相等)。
  • 如果 slow 和 fast 相等且等于 1,则 n 是快乐数。
  • 算法中使用了 do-while 循环而不是 while 循环,以确保至少执行一次循环体(即至少计算一次ProductSum),即使 slow 和 fast 初始时就相等。

复杂度

C++算法代码:

class Solution {
public:
    int ProductSum(int n)
    {
        int sum = 0;
        while(n)
        {
            int temp = n % 10;
            sum += temp*temp;
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        int slow = n,fast = n;
        // 快慢指针,找环的相遇位置
        do
        {
            slow = ProductSum(slow);
            fast = ProductSum(ProductSum(fast));
        }while(slow != fast);
        // 如果相遇时是 1 就是快乐数
        return slow == 1;
    }
};

【算法专题--双指针算法】leecode-202. 快乐数(medium)、leecode-11. 盛最多水的容器(medium)-LMLPHP
java算法代码:

class Solution
{
	public int bitSum(int n) // 返回 n 这个数每⼀位上的平⽅和
	{
		int sum = 0;
		while (n != 0)
		{
			int t = n % 10;
			sum += t * t;
			n /= 10;
		}
		return sum;
 	}
 	public boolean isHappy(int n)
 	{
		 int slow = n, fast = bitSum(n);
		 while (slow != fast)
		{
			 slow = bitSum(slow);
			 fast = bitSum(bitSum(fast));
		 }
	 return slow == 1;
	 }
}

【算法专题--双指针算法】leecode-202. 快乐数(medium)、leecode-11. 盛最多水的容器(medium)-LMLPHP

3. 盛水最多的容器(medium)

题目描述:

示例 1:
【算法专题--双指针算法】leecode-202. 快乐数(medium)、leecode-11. 盛最多水的容器(medium)-LMLPHP

示例 2:

提示:

4. 解法

解法一(暴力求解)(会超时):

算法思路:
枚举出能构成的所有容器,找出其中容积最大的值。

算法代码:

class Solution {
public:
	int maxArea(vector<int>& height) {
		int n = height.size();
		int ret = 0;
		// 两层 for 枚举出所有可能出现的情况
		for (int i = 0; i < n; i++) {
			for (int j = i + 1; j < n; j++) {
				// 计算容积,找出最⼤的那⼀个
				ret = max(ret, min(height[i], height[j]) * (j - i));
			}
		}
		return ret;
	}
};

解法二(对撞指针):

算法思路:
设两个指针 left , right 分别指向容器的左右两个端点,此时容器的容积 : v = (right - left) * min( height[right], height[left])
容器的左边界为height[left],右边界为 height[right]
为了方便叙述,我们假设「左边边界」小于「右边边界」. 如果此时我们固定一个边界,改变另一个边界,水的容积会有如下变化形式:

  • 容器的宽度⼀定变小
  • 由于左边界较小,决定了⽔的⾼度.如果改变左边界,新的水面高度不确定,但是⼀定不会超过右边的柱子高度,因此容器的容积可能会增大.
  • 如果改变右边界,无论右边界移动到哪里,新的水面的高度⼀定不会超过左边界,也就是不会超过现在的水面高度,但是由于容器的宽度减小,因此容器的容积⼀定会变小的.
  • 由此可见,左边界和其余边界的组合情况都可以舍去.所以我们可以 left++ 跳过这个边界,继续去判断下⼀个左右边界.

当我们不断重复上述过程,每次都可以舍去⼤量不必要的枚举过程,直到 left 与 right 相 遇.期间产生的所有的容积里面的最大值,就是最终答案.

C++ 算法代码:

class Solution
{
public:
	int maxArea(vector<int>& height)
	{
		int left = 0, right = height.size() - 1, ret = 0;
		while (left < right)
		{
			int v = min(height[left], height[right]) * (right - left);
			ret = max(ret, v);
			// 移动指针
			if (height[left] < height[right]) left++;
			else right--;
		}
		return ret;
	}
};

【算法专题--双指针算法】leecode-202. 快乐数(medium)、leecode-11. 盛最多水的容器(medium)-LMLPHP
Java 算法代码:

class Solution
{
	public int maxArea(int[] height)
	{
		int left = 0, right = height.length - 1, ret = 0;
		while (left < right)
		{
			int v = Math.min(height[left], height[right]) * (right - left);
			ret = Math.max(ret, v);
			if (height[left] < height[right]) left++;
			else right--;
		}
		return ret;
	}
}
03-20 12:22