参考来自代码随想录
双指针法是什么?贴一下GPT的说明。
双指针法(Two Pointer Technique)是一种常用的算法思想,特别适用于数组和链表等线性数据结构的问题。它的核心思想是使用两个指针来遍历数据结构,通常有以下几种常见的用法:
-
相向双指针法: 这种情况下,两个指针分别从数组的两端开始移动,向中间靠拢。它通常用来解决需要从两个方向逼近的问题,例如在有序数组中寻找两个数的和等于目标值。
-
快慢双指针法: 这种情况下,两个指针以不同的速度移动。一个指针移动得快(快指针),另一个指针移动得慢(慢指针)。它通常用来解决链表中的问题,如判断链表是否有环,找到链表的中间节点等。
-
同向双指针法: 这种情况下,两个指针都从数据结构的同一端开始移动。这种方法通常用来解决需要在同一方向上寻找满足特定条件的一对元素的问题,例如在有序数组中查找满足特定条件的元素。
双指针法的优势在于它能够在不需要额外空间的情况下,实现原地修改和元素移动。它在许多数组和链表问题中都有很高的效率,因为它能够在线性时间复杂度内解决很多问题。
第一道题
难度:简单
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
class Solution {
public int removeElement(int[] nums, int val) {
// slow为新数组指针(新数组指原地修改后的数组),表示新数组的下一个可填充位置
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) { // fast表示旧数组指针,
if (nums[fast] != val) // 如果fast指向不为val
nums[slow++] = nums[fast]; // 复制到新数组,并将slow往后指
}
// slow从0开始,当循环结束,slow最后一次++指向了新数组的下一个元素位置,正好就是数组长度
return slow;
}
}
class Solution {
// 相向双指针法
public int removeElement(int[] nums, int val) {
int left = 0; // 左指针,指向当前要考虑的元素位置
int right = nums.length - 1; // 右指针,指向从右数第一个不为val的元素位置
// 将right指针移到从右数第一个不为val的位置
while (right >= 0 && nums[right] == val) {
right--;
}
// 使用相向双指针进行元素移除和覆盖
while (left <= right) {
if (nums[left] == val) { // 当前left位置的元素需要移除
// 将right位置的元素移到left(覆盖),然后right位置的元素被移除
nums[left] = nums[right];
right--; // 缩小有效数组的范围
}
left++; // 继续处理下一个位置
// 将right指针移到从右数第一个不为val的位置
while (right >= 0 && nums[right] == val) {
right--;
}
}
return left; // 返回处理后的有效数组长度
}
}
第二道题
难度:简单
给你一个 升序排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
class Solution {
// 双指针法,这道题不适用fast指针从尾部开始,因为要保持升序数组相对顺序不变,相邻处理更好
public int removeDuplicates(int[] nums) {
// 首先,已经升序了,则如果有相同,必然相邻
int slow = 0; // slow指向新数组的可存放元素的下一个位置,同时也可以表示唯一元素的个数
for (int fast = 1; fast < nums.length; fast++) { // fast为1,因快慢指针作相邻元素比较
if (nums[slow] != nums[fast]) { // 如果不同
nums[slow + 1] = nums[fast]; // 复制到新数组(slow要加一是因为slow位置的(重复项)首个值要保留)
slow++; // 指针后移
}
}
// 由于存放的元素一直都是在slow+1的位置,所以最后一次slow++后,slow其实指向的是最后一个唯一元素的位置,而数组从0开始,所以要返回slow+1才是新数组长度,也就是唯一元素的个数。
return slow + 1;
}
}
第三道题
难度:简单
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
class Solution {
// 双指针
public void moveZeroes(int[] nums) {
// 时间复杂度O(n), 空间复杂度O(1), 但是不稳定
int n = nums.length;
int slow = 0;
for (int fast = 0; fast < n; fast++) {
if (nums[fast] != 0) { // 如果i指向的是非0, 则交换
nums[slow] = nums[fast];
slow++;
}
}
for (int i = slow; i < n; i++) { // 将剩余的位置补0
nums[i] = 0; // 从j开始, 因为j之前的位置都是非0
}
}
}
第四道题
844. 比较含退格的字符串 (想不到)
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
class Solution {
public boolean backspaceCompare(String s, String t) {
int ptr_s = s.length() - 1; // s 字符串逆序遍历的指针
int ptr_t = t.length() - 1; // t 字符串逆序遍历的指针
int skip_s = 0; // 记录当前字符之后需要被跳过的退格字符数(s)
int skip_t = 0; // 记录当前字符之后需要被跳过的退格字符数(t)
while (ptr_s >= 0 || ptr_t >= 0) { // 但凡有一个还未到头,逆序遍历
// 跳过退格字符
while (ptr_s >= 0) { // 逆序遍历
if (s.charAt(ptr_s) == '#') { // 如果是
skip_s++; // 要跳过的字符数+1
ptr_s--; // 指针前移
} else if (skip_s > 0) {
skip_s--; // 跳过
ptr_s--;
} else {
break;
}
}
while (ptr_t >= 0) {
if (t.charAt(ptr_t) == '#') {
skip_t++;
ptr_t--;
} else if (skip_t > 0) {
skip_t--;
ptr_t--;
} else {
break;
}
}
// 比较有效字符
if (ptr_s >= 0 && ptr_t >= 0) {
// 跳过无效字符后,两个字符串在当前位置的有效字符不一致,则已经不对
if (s.charAt(ptr_s) != t.charAt(ptr_t)) {
return false;
}
} else {
// 一个字符串遍历结束,另一个还有有效字符
if (ptr_s >= 0 || ptr_t >= 0) {
return false;
}
}
ptr_s--;
ptr_t--;
}
return true;
}
}
第五道题
class Solution {
public int[] sortedSquares(int[] nums) {
/* 观察可得,有两种情况。
如果有负数,平方后两边大中间小 -→ 从两端向中间遍历
全为正数,平方后依然升序
*/
int n = nums.length;
int[] result = new int[n];
int left = 0, right = n - 1; // 定义左右指针,分别指向数组的起始和末尾
int index = n - 1; // 从结果数组的末尾开始填充
// 从两端向中间遍历,依次计算平方值,并将较大的平方值放在结果数组的末尾
while (left <= right) {
// 计算左右指针对应元素的平方
int leftSquare = nums[left] * nums[left];
int rightSquare = nums[right] * nums[right];
// 把较大的值放到结果数组末尾
if (leftSquare > rightSquare) {
result[index] = leftSquare;
left++;
} else {
result[index] = rightSquare;
right--;
}
index--; // 结果数组指针左移到下一个填充位置
}
return result; // 返回按非递减顺序排列的每个数字的平方组成的新数组
}
}