目录
[1. 移动零(283. Move Zeroes)](#1. 移动零(283. Move Zeroes))
[2. 复写零(1089. Duplicate Zeros)](#2. 复写零(1089. Duplicate Zeros))
[3. 快乐数(202. Happy Number)](#3. 快乐数(202. Happy Number))
[4. 盛水最多的容器(11. Container With Most Water)](#4. 盛水最多的容器(11. Container With Most Water))
1. 移动零(283. Move Zeroes)
题目链接 :LeetCode 283
详细解题思路:
核心思想:采用双指针法,一个指针用于遍历数组,另一个指针用于记录非零元素的最终位置。
具体步骤:
初始化
dest = -1,表示当前已处理的非零元素序列的末尾(从-1开始表示还未处理任何非零元素)遍历数组,使用
cur指针从0开始扫描每个元素当
nums[cur] != 0时:
dest指针先右移一位(dest++)交换
nums[dest]和nums[cur]的值这样保证
[0, dest]区间内都是非零元素当
nums[cur] == 0时,不做任何操作,继续遍历时间复杂度:O(n),只需一次遍历
空间复杂度:O(1),原地修改
java
class Solution {
public void moveZeroes(int[] nums) {
// dest指向已处理的非零元素序列的末尾
int dest = -1;
for (int cur = 0; cur < nums.length; cur++) {
if (nums[cur] != 0) {
// 发现非零元素,dest先右移
dest++;
// 交换dest和cur位置的元素
int tmp = nums[dest];
nums[dest] = nums[cur];
nums[cur] = tmp;
}
}
}
}
2. 复写零(1089. Duplicate Zeros)
题目链接 :LeetCode 1089
详细解题思路:
核心思想:双指针从后向前复写,避免覆盖问题
具体步骤:
第一步:找到最后一个需要保留的元素
使用两个指针
cur和dest,cur遍历原数组,dest跟踪复写后的位置遇到0时
dest += 2,遇到非0时dest += 1当
dest >= n-1时停止,cur就是最后一个需要保留的元素第二步:处理边界情况
- 如果
dest == n,说明最后一个0复写后刚好超出数组,需要特殊处理第三步:从后向前复写
从
cur位置开始向前遍历遇到0时,复写两次(注意边界)
遇到非0时,复写一次
关键点:从后向前复写避免元素被覆盖,先找到"终点"再倒推
时间复杂度:O(n),两次遍历
java
class Solution {
public void duplicateZeros(int[] arr) {
int n = arr.length;
int cur = 0, dest = -1;
// 1. 找到最后一个需要保留的元素
while (cur < n) {
if (arr[cur] == 0) dest += 2;
else dest++;
if (dest >= n - 1) break;
cur++;
}
// 2. 处理边界情况:最后一个0复写后超出数组
if (dest == n) {
arr[n - 1] = 0;
cur--;
dest -= 2;
}
// 3. 从后向前复写
while (cur >= 0) {
if (arr[cur] == 0) {
// 复写0两次
if (dest >= 0) arr[dest--] = 0;
if (dest >= 0) arr[dest--] = 0;
} else {
// 复写非0一次
if (dest >= 0) arr[dest--] = arr[cur];
}
cur--;
}
}
}
3. 快乐数(202. Happy Number)
题目链接 :LeetCode 202
详细解题思路:
核心思想:快慢指针检测循环,类似链表检测环
数学原理:
对于任何正整数,经过"各位平方和"变换,最终必然进入循环
鸽巢原理:变换值在[1, 810]范围内,最多变换811次必出现重复
具体步骤:
初始化慢指针
slow = n,快指针fast = bitSum(n)当
slow != fast时循环:
slow计算一次变换
fast计算两次变换如果最终相遇在1,则是快乐数;否则不是
辅助函数 :
bitSum计算各位平方和时间复杂度:O(log n),空间复杂度:O(1)
java
class Solution {
public boolean isHappy(int n) {
int slow = n, fast = bitSum(n);
// 快慢指针,快指针速度是慢指针的两倍
while (slow != fast) {
slow = bitSum(slow); // 慢指针走一步
fast = bitSum(bitSum(fast)); // 快指针走两步
}
// 相遇点是否为1
return slow == 1;
}
// 计算数字n的各位平方和
private int bitSum(int n) {
int sum = 0;
while (n != 0) {
int t = n % 10; // 取个位
sum += t * t;
n /= 10; // 去掉个位
}
return sum;
}
}
4. 盛水最多的容器(11. Container With Most Water)
题目链接 :LeetCode 11
详细解题思路:
核心思想:对撞指针,每次移动较短的那一边
容积公式 :
v = min(height[left], height[right]) * (right - left)移动策略证明:
假设左边界高度 < 右边界高度
如果移动较高的右边,容积一定减少(宽度减少,高度不会增加)
如果移动较低的左边,容积可能增加(高度可能增加)
因此每次都移动较短的那一边
具体步骤:
初始化
left=0, right=n-1当
left < right时循环:
计算当前容积并更新最大值
移动高度较小的指针
时间复杂度:O(n),空间复杂度:O(1)
java
class Solution {
public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int maxWater = 0;
while (left < right) {
// 计算当前容器的水量
int h = Math.min(height[left], height[right]);
int w = right - left;
int water = h * w;
// 更新最大水量
maxWater = Math.max(maxWater, water);
// 移动较短的那一边
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}
}