目录


🎬 云泽Q :个人主页
🔥 专栏传送入口 : 《C语言》《数据结构》《C++》《Linux》《蓝桥杯系列》《笔试算法》
⛺️遇见安然遇见你,不负代码不负卿~
前言
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
一、移动零
解法(快排的思想:数组划分区间 - 数组分两块) :
算法思路:在本题中,我们可以用一个 cur 指针来扫描整个数组,另一个 dest 指针用来记录非零序列的最后一个位置。根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。在 cur 遍历期间,使 [0, dest] 的元素全部都是非零元素,[dest + 1, cur - 1] 的元素全是零。
算法流程 :
a. 初始化 cur = 0(用来遍历数组),dest = -1(指向非零元素序列的最后一个位置。因为刚开始我们不知道最后一个非零元素在什么位置,因此初始化为 -1)
b. cur 依次往后遍历每个元素,遍历到的元素会有下面两种情况:
- i. 遇到的元素是 0,cur 直接 ++。因为我们的目标是让 [dest + 1, cur - 1] 内的元素全都是零,因此当 cur 遇到 0 的时候,直接 ++,就可以让 0 在 cur - 1 的位置上,从而在 [dest + 1, cur - 1] 内;
- ii. 遇到的元素不是 0,dest++,并且交换 cur 位置和 dest 位置的元素,之后让 cur++,扫描下一个元素。
-
- 因为 dest 指向的位置是非零元素区间的最后一个位置,如果扫描到一个新的非零元素,那么它的位置应该在 dest + 1 的位置上,因此 dest 先自增 1;
-
- dest++ 之后,指向的元素就是 0 元素(因为非零元素区间末尾的后一个元素就是 0),因此可以交换到 cur 所处的位置上,实现 [0, dest] 的元素全部都是非零元素,[dest + 1, cur - 1] 的元素全是零。
cpp
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for(int cur = 0, dest = -1; cur < nums.size(); cur++)
{
if(nums[cur])
swap(nums[++dest], nums[cur]);
}
}
};
二、复写零
解法(原地复写 - 双指针) :
算法思路 :如果「从前向后」进行原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数「被覆盖掉」。因此我们选择「从后往前」的复写策略。
但是「从后向前」复写的时候,我们需要找到「最后一个复写的数」,因此我们的大体流程分两步:
i. 先找到最后一个复写的数;ii. 然后从后向前进行复写操作。
算法流程 :
a. 初始化两个指针 cur = 0,dest = -1;
b. 找到最后一个复写的数:
- i. 当 cur < n 的时候,一直执行下面循环:
-
- 判断 cur 位置的元素:
-
-
- 如果是 0 的话,dest 往后移动两位;
-
-
-
- 否则,dest 往后移动一位。
-
-
- 判断 dest 是否已经到结束位置(数组中最后一个数的位置),如果结束就终止循环(最后终止的时候cur指的位置就是需要复写的最后一个数,dest所指的位置正好是要填的位置);
-
- 如果没有结束,cur++,继续判断。
c. 判断 dest 是否越界到 n 的位置:
- i. 如果越界,执行下面三步:
-
- n - 1 位置的值修改成 0;
-
- cur 向前移动一步;
-
- dest 向前移动两步。
d. 从 cur 位置开始往前遍历原数组,依次还原出复写后的结果数组:
-
i. 判断 cur 位置的值:
-
- 如果是 0:dest 以及 dest - 1 位置修改成 0,dest -= 2;
-
- 如果非零:dest 位置修改成 cur 位置的值,dest -= 1;
-
ii. cur减减,复写下一个位置。
cpp
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
//找到最后要复写的元素
int n = arr.size();
int cur = 0, dest = -1;
while(cur < n)
{
if(arr[cur]) ++dest;
else dest += 2;
if(dest >= n - 1) break;
cur++;
}
//处理越界情况
if(dest == n)
{
arr[n - 1] = 0;
cur--; dest -= 2;
}
//从后向前进行复写
while(cur >= 0)
{
if(arr[cur]) arr[dest--] = arr[cur--];
else{
arr[dest--] = 0;
arr[dest--] = 0;
cur--;
}
}
}
};
三、快乐数
202. 快乐数

题目分析 :
为了方便叙述,将「对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和」这一个操作记为 x 操作;
题目告诉我们,当我们不断重复 x 操作 的时候,计算一定会「死循环」,死的方式有两种:
- 情况一:一直在 1 中死循环,即 1→1→1→1......
- 情况二:在历史的数据中死循环,但始终变不到 1
由于上述两种情况只会出现一种,因此,只要我们能确定循环是在「情况一」中进行,还是在「情况二」中进行,就能得到结果。
解法(快慢指针) :
算法思路 :
根据上述的题目分析,我们可以知道,当重复执行 x 的时候,数据会陷入到一个「循环」之中。而「快慢指针」有一个特性,就是在一个圆圈中,快指针总是会追上慢指针的,也就是说他们总会相遇在一个位置上。如果相遇位置的值是 1,那么这个数一定是快乐数;如果相遇位置不是 1 的话,那么就不是快乐数。
在链表的那道判断链表是否有环的题目中(数据结构之单链表和环形链表的应用(二)),是定义了Node*来充当快慢指针,上一道题是将数组的下标充当双指针,由于这道题是是一个个的数字,就拿环中的数字来充当指针,千万不要被双指针的名字限制了自己的思维,认为名字是双指针就要定义指针来操作

这个题目告诉我们的两种情况就是要么变为1,要么无限循环始终变不到1。大概在5,6年前,这个题目是没有直接限制在这两种情况之内的,此时题目就变得很恶心了,此时在我们的视角就有可能出现第三种情况:有不成环的可能,一直变下去,无限衍生。接下来就证明一下,为什么在变化的过程中是一定会存在环的,这涉及一个鸽巢原理,或者说抽屉原理,还是挺有意思的,如果对这种数学证明不感兴趣也可以直接写代码看题解。

简单证明:
- a. 经过一次变化之后的最大值 92 ∗ 10=810(231−1=2147483647。选一个更大的最大值 9999999999),也就是变化的区间在 [1, 810] 之间;
- b. 根据「鸽巢原理」,一个数变化 811 次之后,必然会形成一个循环;c. 因此,变化的过程最终会走到一个圈里面,因此可以用「快慢指针」来解决。
补充知识 :如何求一个数 n 每个位置上的数字的平方和。
a. 把数 n 每一位的数据提取出来:循环迭代下面步骤:
- i.
int t = n % 10提取个位; - ii.
n /= 10干掉个位;
直到 n 的值变为 0;
b. 提取每一位的时候,用一个变量 tmp 记录这一位的平方与之前提取位数的平方和
tmp = tmp + t * t
cpp
class Solution {
public:
int yunzeSum(int n)
{
int sum = 0;
while(n)
{
int t = n % 10;
sum += t * t;
n /= 10;
}
return sum;
}
bool isHappy(int n) {
//为了能够进入循环,初始slow和fast不能相等,初始的时候fast指向第二个位置
int slow = n, fast = yunzeSum(n);
//当两个指针不相等的时候,一直循环
while(slow != fast)
{
//慢指针走一步,快指针走两步
slow = yunzeSum(slow);
fast = yunzeSum(yunzeSum(fast));
}
//相遇位置的值不为1,不是快乐数
return slow == 1;
}
};
尤其注意这里的循环截至条件while(slow != fast),
循环继续的条件:还没相遇,说明还没找到环
循环终止的条件:已经相遇,说明已经进环了
快慢指针在环里的运动过程
环里是一个圈,快指针速度是慢指针 2 倍:慢指针走 1 格 → 快指针走 2 格。快指针只会不断缩短和慢指针的距离 ,最终从后方追上慢指针,两者重合。它永远不会 "超过、跑到前面",圈里追上 = 相遇相等。
四、盛最多水的容器
11. 盛最多水的容器


解法一(暴力求解)
算法思路
枚举出能构成的所有容器,找出其中容积最大的值
- 设两指针i, j,分别指向水槽板的最左端以及最右端,此时容器的宽度为
j - i。由于容器的高度由两板中的短板决定,因此可得容积公式:v = (j - i) * min(height[i], height[j])
解法二(对撞指针,如图所示)
cpp
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int left = 0, right = n - 1, ret = 0;
while(left != right)
{
int v = (right - left) * min(height[left], height[right]);
ret = max(ret, v);
if(height[left] <= height[right])
{
left++;
}else{
right--;
}
}
return ret;
}
};
结语






