算法通关村第三关——双指针的妙用

所谓的双指针其实就是两个变量,不一定真的是指针。双指针可以分为快慢双指针(以不同速度向同一边走)、对撞双指针(从两边向中间走)、相反双指针(从中间向两边走)

适用场景:处理数组、字符串问题,比如删除有序数组中的重复项、元素奇偶移动、数组轮转、数组区间、字符串空格替换等。

删除元素专题

1.原地移除所有数值等于val的元素

力扣27题,给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。要求:不要使用额外的数组空间,你必须仅使用O(1)额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

本题可以使用双指针方式,三种都可以来解决。

1.1 快慢双指针

思想:定义两个指针fastslow,初始值都为0,slow之前的位置都是有效部分,fast表示当前要访问的元素。

这样遍历的时候,fast不断向后移动:

  • 如果nums[fast]的值不为val,则将其移动到nums[slow++]处。
  • 如果nums[fast]的值为val,则fast继续向前移动,slow先等待。

算法通关村第三关——双指针的妙用_第1张图片
算法通关村第三关——双指针的妙用_第2张图片

过程如上图所示,这样的话slow的前面就都是不重复的数字。

// 快慢双指针
var removeElement = function(nums, val) {
	let slowPointer = 0;
	
	for (let fastPointer = 0; fastPointer < nums.length; fastPointer++) {
	 	if (nums[fastPointer] !== val]) {
	 		nums[slowPointer] = nums[fastPointer];
	 		slowPointer++;
	 	}
	 }
	 // 最后剩余元素的数量
	 return slowPointer;
};

1.2对撞双指针

对撞双指针,也叫交换移除,核心思想是从右侧找到不是val的值来顶替左侧是val的值。分别让两个指针leftright指向数组首位和末位。当nums[left] !== val时,left指针向前移动,当nums[left] === val时停止移动,right指针开始向左移动,直到nums[right] !== val时,用nums[right]交换nums[left]的值。重复上述操作,直到left === right

left === right时,left以及左侧的值就是删除掉val的所有数据元素了。

// 对撞双指针,交换左右指针位置的值
var removeElement = function(nums, val) {
	let leftPointer = 0;
	let rightPointer = nums.length - 1;

	for (leftPointer = 0; leftPointer <= rightPointer;) {
		if (nums[leftPointer] === val) {
			if (nums[rightPointer] !== nums[leftPointer]) {
				let tempNum = nums[leftPointer];
				nums[leftPointer] = nums[rightPointer];
				nums[rightPointer] = tempNum;
				leftPointer++;
			} else {
				rightPointer--;
			}
		}else {
			leftPointer++;
		}
	}
	return leftPointer;
}

1.3对撞双指针 + 覆盖

这种方法是融合了第一种和第二种方法,它与第二种方法思路是相同的,只不过这种方法是当nums[left] === val时,就用nums[right]位置的元素覆盖nums[left]的值。

// 对撞双指针+覆盖
var removeElement = function(nums, val) {
	let rightPointer = nums.length - 1;

	for (let leftPointer = 0; leftPointer <= rightPointer;) {
		if (nums[leftPointer] === val) {
			nums[leftPointer] = nums[rightPointer];
			rightPointer--;
		} else {
			leftPointer++
		}
	}
	return rightPointer + 1;
}

快慢双指针执行完操作后留下的元素顺序与原始序列中的是一致的(参考第一种方法),而在对撞型指针中元素的顺序和原来的可能不一样了(参考第三种方法)。

2.删除有序数组中的重复项

力扣26题,给你一个有序数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。

思路:这一题用双指针最佳,一个指针负责数组遍历,一个指向有效数组的最后一个位置

算法通关村第三关——双指针的妙用_第3张图片
算法通关村第三关——双指针的妙用_第4张图片

为了减少不必要的操作,令slow = 1,并且比较的对象换做nums[slow - 1]

// 快慢双指针,数组里只保留一个重复数字
var removeDuplicates = function(nums) {
	let slowPointer = 1;

	for (let fastPointer = 0; fastPointer < nums.length; fastPointer++) {
		if (nums[fastPointer] !== nums[slowPointer - 1]) {
			nums[slowPointer] = nums[fastPointer];
			slowPointer++;
		}

	}
	return slowPointer;
}

拓展:既然重复元素可以保留一个,也可以保留k个。(k >= 0)

力扣80题,给你一个有序数组 nums ,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 给你一个有序数组 nums ,请你** 原地** 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 并在使用 O(1) 额外空间的条件下完成。

思路:这个是上一题的变种题,就还是用快慢双指针,只不过这次要保留两个重复数字,我们让slow = fast = 2,不仅要比较nums[slow - 1]还要比较nums[slow - 2]是否与nums[fast]相等,

算法通关村第三关——双指针的妙用_第5张图片
算法通关村第三关——双指针的妙用_第6张图片
代码如下:

// 快慢双指针,数组里数字重复最多不超过两次
var removeDuplicates = function(nums) {
	const lengthOfNums = nums.length;
	if (lengthOfNums <= 2) {
		return lengthOfNums;
	}
	let slowPointer = fastPointer = 2;
	while (fastPointer < lengthOfNums) {
		if (nums[slowPointer - 2] !== nums[fastPointer]) {
			nums[slowPointer] = nums[fastPointer];
			++slowPointer;
		}
		++fastPointer;
	}
	return slowPointer;
}

3.小结

双指针解法在数组、字符串的去重排序问题上真的有很大应用空间,非常有效率。

你可能感兴趣的:(算法,算法,数据结构,javascript)