目录
🎢1.移动零
题解
代码
⭕2.复写零
题解
代码
⭕3.快乐数
题解
代码
🎢1.移动零
题目链接:283. 移动零
题目描述:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?
题解
这类题可以归类到一类提里面【数组划分、数组分块】
- 这类题的特点就是:给一个数组然后制定一个规则,在这个规则下把这个数组划分称若干区间
解决这类题首选:双指针算法 ,数组中用双指针利用数组下标充当指针。
两个指针的作用:
- cur:从左往右扫描数组,遍历数组
- dest:已处理区间内,最后一个元素(本题是最后一个非0元素)
两个指针分三个区间:
- 【0,dest】已处理区间都是非0元素
- 【dest+1,cur-1】已处理区间都是0元素
- 【cur,n-1】待处理元素
- n 个元素 ,下标为 n-1
初始时,cur在下标 0 的位置,dest位-1,可能初始时没有非0元素。
cur 从前往后遍历的过程中:
- 遇到 0 元素:cur++
- 遇到 非零元素:
swap(++dest,cur)//将非 0 元素和 ++dest 位置交换,dest 所在区间就为 已处理 非零元素了
cur++
代码
//如果 cur 不是 0,就和++dest 目标位置 进行交换,实现非 0 元素,都移到前面
⭕2.复写零
题目链接:1089. 复写零
题目描述:
给你一个长度固定的整数数组 arr
,请你将该数组中出现的 每个零都复写一遍,并将其余的元素向右平移。
注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。
示例 1:
输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]
示例 2:
输入:arr = [1,2,3]
输出:[1,2,3]
解释:调用函数后,输入的数组将被修改为:[1,2,3]
提示:
1 <= arr.length <= 104
00 <= arr[i] <= 9
题解
一个数组,不开辟另一个数组,在原数组内解决问题。
- 解决方法:双指针算法
先根据 “异地” 操作,然后优化成双指针下的 “就地” 操作。
双指针 从左往右,我们发现dest跑到cur前面去了,对于本道题这是不行的。
那试一试从右到左,dest肯定是最后一个数组下标位置,但是cur不好弄,如果初始cur==dest,然后同样会覆盖。
因此我们想办法让cur初始时指向数组最后一个复写的数。如果这样复写
1. 先找到最后一个 “复写” 的数
这里还可以再用一次双指针算法,(双指针还能套双指针)
(查找 就可以 去想 双指针~)
- 先判断 cur 位置的值
- 决定 dest 向后走一步或者两步
- 判断一下 dest 是否已经到结束 为止
- cur++
但这里有个问题,dest越界的问题,所以要处理一下边界情况。
cur位置的值是0,dest越界,处理越界问题:下标【n-1】复写0,dest-=2,cur-=1;
2.“从后向前” 完成复写操作
代码
//cur 当前未处理 //实现 判断移动
//dest 目标
class Solution {
public://双指针
//先到最后预判
//从右往左void duplicateZeros(vector<int>& arr) {int cur=0,dest=-1;int n=arr.size();//找到 最后一个复写位置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--;}}}
};
⭕3.快乐数
题目链接:202.快乐数
题目描述:
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入:n = 2
输出:false
题解
解释:(这里省去计算过程,只列出转换后的数)
2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 -> 16
往后就不必再计算了,因为 出现了重复的数字,所以最后结果肯定不会是 1
分析:
为了方便叙述,将【对于一个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和】这一个操作记为 x 操作;
题目告诉我们,当我们不断重复 x 操作的时候,计算⼀定会【死循环】,死的方式有两种:
- 情况一:⼀直在 1 中死循环,即 1 -> 1 -> 1 -> 1…
(!重点就是 对于 111 也是一个死循环的理解~)(所以都是循环,只是循环结果不同)
- 情况二:在历史的数据中死循环,但始终变不到 1
由于上述两种情况只会出现⼀种,因此
只要我们能确定循环是在【情况⼀】中进行,还是在【情况二】中进行,就能得到结果。
简单证明:
- 经过一次变化之后的最大值 9^2 * 10 = 810 ( 2^31-1=2147483647 。选一个更大的最大 9999999999 ),也就是变化的区间在 [1, 810] 之间;
- 根据【鸽巢原理】,一个数变化 811 次之后,必然会形成一个循环;(*详可见代码后批注)
- 因此,变化的过程最终会走到一个圈里,因此可以用【快慢指针】来解决
算法原理:
以前做过一种题,判断链表是否有环,我们用的是快慢双指针,
- 定义快慢指针
- 慢指针每次向后移动一步,快指针每次向后移动两步
- 判断相遇时候的值即可
这里我们不能固定思维,快慢指针是一个思想,可以让不同东西代替指针。
这道题我们可以让某个数代替双指针。
代码
class Solution {
public:
// 1.先对 每个位置上的 数字拆分 进行实现
//细分// sum// tmp// nint BitSum(int n) {int sum = 0, a = n;while (a) {int tmp = a % 10;sum += tmp * tmp;a = a / 10;}return sum;}bool isHappy(int n) {// 可以通过bitSum来实现 对每一步的数字和 进行计算了// 2.int slow = n, fast = BitSum(n);while (slow != fast) {slow = BitSum(slow);fast = BitSum(BitSum(fast)); // 移 两步}// 直到 快慢指针 判完环之后return slow == 1;}
};
注:变化的最大值和鸽巢原理
- 变化的最大值:考虑到一个数字每一位最大可能是9,因此最大的一位数的平方是81。对于一个十位数而言(尽管实际上由于2^31-1的限制,我们不会遇到这么大的数),其经过一次x操作后的最大可能结果是9^2×10=810。这意味着,无论原始数字多大,经过一次x操作之后,得到的结果都不会超过810。
- 鸽巢原理的应用:鸽巢原理(又称抽屉原理)简单地说就是如果有更多的鸽子而鸽巢数量有限,则至少有一个鸽巢里会有多于一只的鸽子。在这个问题中,由于知道经过x操作后数值范围被限制在[1, 810]之间,如果连续进行811次变换,根据鸽巢原理,(只要 变换的次数足够多)至少有一次变换的结果会与之前的某次变换结果相同,从而形成循环。
- 快慢指针方法:一旦确定了循环的存在,就可以 使用快慢指针的方法来检测循环。这种方法通常用于链表中的环检测,但在这里也可以用来识别数字序列何时开始重复。快指针每次走两步,慢指针每次走一步,如果存在循环,两个指针最终会在环内相遇。