目录
移动零
题解:
复写零
题解:
快乐数
题解:
盛最多水的容器
移动零
283. 移动零 - 力扣(LeetCode)https://leetcode.cn/problems/move-zeroes/description/
题解:
题目要求我们把数组中的 0 放在数组的末尾,非零数按照原来的相对顺序,可以用快排的思想来解决这道题。
定义两个指针 dest 和 cur,这两个指针对数组进行了分类,下标从 0 到 dest 的数组区间存的是非零数,下标从 dest+1 到 cur 的数组区间存的是零,下标从 cur+1 到数组的结尾部分是还没有处理过的数,需要 cur 继续往后遍历来进行处理。
如何实现上面的分类呢?
从上面的分析上,我们发现 dest 是划分非零数和 0 的界限,arr[ dest ] 是非零数,arr[ dest+1 ] 就是 0,而 cur 的作用是为了找 0,
- 如果 cur 遍历到的数是非零数,那么交换 arr[ dest+1 ] 和 arr[ cur ] ,同时 dest++,dest 就可以继续发挥划分界限的作用!
- 如果 cur 遍历到的数是 0,不必交换,直接 ++cur 。
用题目中的例1来进行分析:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for(int dest=-1,cur=0;cur<nums.size();++cur)
{
if(nums[cur]!=0)
swap(nums[++dest],nums[cur]);
}
}
};
复写零
1089. 复写零 - 力扣(LeetCode)https://leetcode.cn/problems/duplicate-zeros/description/
题解:
由于题目要求在原地修改数组,所以我们不可以重新开辟一个新的数组进行异地修改,但是可以通过异地修改来整理本题的思路。
以示例 1 为例,复写之后超出数组范围的数将会被删掉。
如果我们从前往后模拟复写过程,比如我们用 i 来遍历数组,当 arr[ i ] 为 0 时,让 arr[ i+1 ] 复写为 0,那么 arr[ i+1 ] 原本的数就被覆盖了,最终得到的答案一定是错误的。
所以采用从后往前的方式, 那么从后往前要从哪里开始遍历,才可以避免复写时发生数组越界呢?
我们可以先用两个指针来模拟一下复写过程,得出复写后的数组的最后一个数是谁。
指针 i 用来找出复写后的数组的最后一个数是谁,指针 top 用来遍历数组,
- 如果 arr[ i ] == 0,那么 top += 2 ;
- 如果 arr[ i ] != 0,那么 top++ ;
其实指针 i 是在模拟复写的过程,在找出复写后的元素是什么,而指针 top 是在模拟 arr[ i ] 复写后会怎么移动,指向 arr[ i ] 移动后的位置的下一个下标。
当 top 越界时,说明复写过程已经模拟完成了,结束模拟,此时的 arr[ i ] 就是复写后的数组的最后一个元素。
找到复写完的数组的最后一个元素后,就可以开始进行复写操作了,指针 j 从数组的最后一个元素开始遍历,
- arr[ i ] !=0 ,此时 arr[ i ] 不需要复写,则 arr[ j ] = arr[ i ];
- arr[ i ] ==0,此时 arr[ i ] 需要复写,则 arr[ j ] = arr[ i ] 之后,--j,把 0 再写一次。
完成一次复写操作之后,把 i 和 j 都减 1 即可。
上面的模拟还存在一种特殊情况,如果复写后的最后一个数是 0 呢?
比如下面的数组,arr = [ 1, 0, 2, 3, 0, 4 ],先进行复写模拟:
可以发现复写后得到的数组的最后一个元素为 0 ,但是这个 0 是不需要复写的,或者说复写后的第二个 0 超出了数组的范围,被砍掉了。这时候再按照上面的复写过程,就会得到错误的结果。
所以我们需要在复写之前,对指针进行修正:
- 如果复写后的数组的最后一个元素不是 0,则 top == n;
- 如果复写后的数组的最后一个元素为 0 ,才会导致 top == n+1。
如果 top == n+1,则先把复写后的数组的最后一个元素填写为 0,然后指针 i 和 j 都向前移动一步,这样就可以保证复写后的数组的正确性。
复写过程如下:
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
//模拟复写过程,找出复写后数组的最后一个数
int top=0,i=-1,n=arr.size();
while(top<n)
{
i++;//i一个个遍历数组
//top模拟复写后的情况
if(!arr[i]) top+=2;//是0,要复写,所以+2
else top++;//不是0,不用复写,所以+1
}
int j=n-1;
if(top==n+1)//说明复写后的数组的最后一个元素为0
{
arr[n-1]=0;
j--; i--;
}
//开始复写
while(j>=0)
{
arr[j]=arr[i]; --j;
if(arr[i]==0)//复写
{
arr[j--]=0;
}
i--;
}
}
};
快乐数
202. 快乐数 - 力扣(LeetCode)https://leetcode.cn/problems/happy-number/description/
题解:
由于 n 是 int 类型,int 类型下能表示的最大正整数为 2147483647,按照题目的要求,对于一个正整数,把正整数的每一位置的数的平方后相加,这个和的最大值和最小值是多少呢?
在 int 范围内,当正整数的每一位置的数都尽可能大时,平方和就可以达到最大值,当这个正整数为 19 9999 9999 时(29 9999 9999 已经超出 int 的范围了),平方和达到最大,为 81*9 + 1=730 。反之,当正整数的每一位置的数都尽可能小时,平方和就可以达到最小值,显然,平方和的最小值为 1。所以平方和的取值范围为 1 到 730 .
也就是说,根据抽屉原理,一个数 n 经过无数次 求每一位置的数的平方和 时,一定会存在重复的平方和,如果这个重复的平方和为 1,那么 n 就是快乐数,如果不是 1,那么 n 就不是快乐数!
那么我们怎样得到这个重复的平方和呢?
按照题目的暗示,求平方和的过程是一个循环,既然是循环,就可以按照快慢指针的思想(快指针的速度是慢指针的两倍),快指针比慢指针多求一次平方和,在一个循环中,快慢指针最终一定会相遇!当快慢指针相遇时,也就是出现了重复的平方和,如果这个重复的平方和为 1,那么 n 就是快乐数,如果不是 1,那么 n 就不是快乐数!
为什么初始化时慢指针为 n,快指针先求了一(两)次平方和,而不是让快慢指针都初始化为 n ?
因为我们用了 while 来模拟快慢指针的循环过程,当快慢指针相遇时,即 快慢指针相等时,while 循环结束,如果我们一开始就把快慢指针都初始化为 n,是没办法进入循环过程的!
class Solution {
public:
int Pow(int num)
{
int ret=0;
while(num)
{
ret+=pow(num%10,2);
num/=10;
}
return ret;
}
bool isHappy(int n) {
int fast=Pow(n),slow=n;
//int fast=Pow(Pow(n)),slow=n; 也可以
while(fast!=slow)
{
fast=Pow(Pow(fast)),slow=Pow(slow);
}
if(fast==1 && slow==1) return true;
else return false;
}
};
盛最多水的容器
11. 盛最多水的容器 - 力扣(LeetCode)https://leetcode.cn/problems/container-with-most-water/description/
class Solution {
public:
int maxArea(vector<int>& height) {
int left=0,right=height.size()-1,ret=0;
while(left<right)
{
int tmp=min(height[left],height[right])*(right-left);
ret=max(tmp,ret);
//移动指针
if(height[left]>height[right]) --right;
else ++left;
}
return ret;
}
};