双指针
适用于数组划分的情况
数组划分:给一个标准或者制定一定规则,把数组划分为若干区间
283. 移动零
dest与cur作用
dest: 零与非零元素的分割点,dest所指向的是最后一个非零的下标cur: 用来遍历数组- 先分为三个区间
[0, dest]表示非零元素,[dest + 1, cur - 1]表示零元素,[cur, n - 1]表示未处理元素- 执行步骤:
- 如果
nums[cur]获取的变量是0,那么执行cur++- 如果
nums[cur]获取的变量不是0,那么先dest++,然后交换cur与dest的下标,最后cur++
步骤: 0 / 0
[0, dest]
已处理的非零元素
已处理的非零元素
[dest + 1, cur - 1]
已处理的零元素
已处理的零元素
[cur, n - 1]
待处理未知元素
待处理未知元素
当前动作:等待输入...
Java
public void moveZeroes(int[] nums) {
// [0, dest] 非零元素, [dest + 1, cur - 1] 零元素, [cur, n - 1] 未处理元素
int dest = -1, cur = 0;
while (cur < nums.length) {
if (nums[cur] != 0) {
dest++;
int tmp = nums[cur];
nums[cur] = nums[dest];
nums[dest] = tmp;
}
cur++;
}
}1089. 复写零
思路:针对于数组排序先以异地的方式经行,如果成功了,那么就用就地的方式试试看
那么就用就地的方式不行,发现会覆盖其他元素,那么就以相反的方向试试看
异地
创建一个新的数组,按照题目要求, 如果是0,那么就复写两次,如果不是,那么就写一次即可(不提供代码了,就提供流程)
步骤: 0 / 0
原数组 (Source)只读,使用指针 i 进行遍历扫描
↓ 读写分离 ↓
新数组 (Destination)只写,使用指针 j 写入,长度必须与原数组一致
当前动作:等待输入...
就地
- 通过
virtualLength来获取到虚拟长度(就是arr更新之后的长度),通过它来判断是否最后一位是0
- 如果是非零元素,那么
virtualLength加1- 如果是元素为零,那么
virtualLength加2- 通过
virtualLength == arr.length + 1判断是否过长- 最后利用双指针从后往前遍历覆盖
步骤: 0 / 0
当前动作:等待输入...
Java
public void duplicateZeros(int[] arr) {
// 先计算出最后有效数据的最后一位
int i = 0;
int size = arr.length;
int virtualLength = 0; // 虚拟长度(就是 arr 更新之后的长度)
for (; virtualLength < size; i++) {
if (arr[i] == 0) {
// 元素是0的情况下,加两次
virtualLength += 2;
} else {
virtualLength += 1;
}
}
// 判断一下,最后一位的 0 有没有复写
if (virtualLength == size + 1) {
// 最后一位一定是 0
arr[size - 1] = 0;
size--;
i--;
}
// 然后利用双指针从后往前遍历覆盖
int right = size - 1, left = i - 1;
while(left >= 0 && right > left) {
if (arr[left] == 0) {
// 先复写一次
arr[right--] = arr[left];
}
// 正常覆盖
arr[right--] = arr[left--];
}
}202. 快乐数
根据题意:它的结果要么是为 1,要么成环
下面的 执行一次 这个操作表示的是 将该数替换为它每个位置上的数字的平方和 这一个步骤
思路:
- 把成为 1 的这个结果看为成为一个环,只不过这个环上的内容全是 1
- 此时发现它们的结果都是成环,那么就可以想到 给定一个链表-判断链表中是否有环 这个题目
- 用
slow表示 执行一次的值,用fast表示 执行二次的值,终止条件为slow与fast是否相等,最后判断slow是否为1即可为什么呢一定成环呢?可以通过 鸽巢原理 来解释
- 查看输入参数的范围
- 那么它执行了一次后最大值为
(把 看为最大的 ,执行一次后一定小于这个) - 获取到 “巢” 后,由于
所以区间 里面的数执行一次后一定在这个区间里面 - 由此可以得出,最多执行
次后,里面的至少有一个数字会出现两次,即满足成环条件
步骤: 0 / 0
当前动作:等待输入...
Java
public boolean isHappy(int n) {
int slow = func(n), fast = func(func(n));
while(slow != fast) {
slow = func(slow);
fast = func(func(fast));
}
return slow == 1;
}
private int func(int n) {
int ret = 0;
while(n != 0) {
int t = n % 10;
ret = ret + t * t;
n = n / 10;
}
return ret;
}11. 盛最多水的容器
思路:
先从两侧开始遍历,计算出体积
算出面积后,把对应值较小的坐标往另一侧移动
- 面积计算公式:
其中 , - 此时把值较小(高度较小)的一侧固定,只移动较大的一侧,发现
- 又因为高度比较小,所以此时高度一定小于等于原来的高度,即:
- 所以内部面积一定不会大于最外围的面积,那么就不需要管较小的一侧了,也就可以移动较小的一侧了
反复循环,直到结束即可
步骤: 0 / 0
当前动作:等待输入...
Java
public int maxArea(int[] height) {
// 从两边开始
int right = height.length - 1, left = 0;
int max = -1;
int tmp = 0;
while (left < right) {
max = (tmp = getMaxV(left, right, height)) > max ? tmp : max;
// 只需要除去最小的一侧即可
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return max;
}
private int getMaxV(int left, int right, int[] array) {
int minH = array[left] < array[right] ? array[left] : array[right];
return (right - left) * minH;
}