双指针法

Posted newbase

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双指针法相关的知识,希望对你有一定的参考价值。

双指针技巧 1(两端指针)

在前面中,我们通过迭代数组来解决一些问题。通常,我们只使用从第一个元素开始并在最后一个元素结束的一个指针来进行迭代。 但是,有时候,我们可能需要同时使用两个指针来进行迭代。

使用双指针技巧的典型场景之一是你想要:从两端向中间迭代数组

这时你可以使用双指针技巧:

一个指针从始端开始,而另一个指针从末端开始。

技术图片

下面进行几个字符串和数组的练习:

1. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII码表中的可打印字符。

示例 1:

输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
func reverseString(s []byte) {

/*
    双指针法
*/

	i, j := 0, len(s)-1
	for i < j {
		s[i], s[j] = s[j], s[i]
		i++
		j--
	}
}

2. 数组拆分

给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。

示例 1:

输入: [1,4,3,2]

输出: 4
解释: n 等于 2, 最大总和为 4 = min(1, 2) + min(3, 4).

提示:

  1. n 是正整数,范围在 [1, 10000].
  2. 数组中的元素范围在 [-10000, 10000].
func arrayPairSum(nums []int) int {
    
/*
    题目花哨,其实就是排序后奇数位相加
*/
     
	nLen := len(nums) 
	a := 0
	sort.Ints(nums) // 排序
	for i := 0; i < nLen; i += 2 {
		a += nums[i]
	}
	return a
}

3. 两数之和:输入有序数组

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1index2,其中 index1 必须小于 index2

说明:

  • 返回的下标值(index1index2)不是从零开始的。
  • 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

解:

func twoSum(numbers []int, target int) []int {

/*
    双指针法
    left从左向右查找,right从右向左查找,每走一步,计算一下指针处的值,
    大于target右指针左移,小于target左指针右移
*/

	left := 0
	right := len(numbers) - 1

	for left < right {
		sum := numbers[left] + numbers[right]
		if sum == target {
			return []int{left + 1, right + 1}
		} else if sum > target {
			right--
		} else {
			left++
		}
	}
	return []int{}
}

双指针技巧 2(快慢指针)

你还有一种需要使用双指针技巧的非常常见的情况:同时有一个慢指针和一个快指针

解决这类问题的关键是:确定两个指针的移动策略

与前一个场景类似,你有时可能需要在使用双指针技巧之前对数组进行排序,这种技巧一般来解决窗口滑动问题。

跟上面相同,我们来进行几个字符串和数组的练习:

1. 移除元素

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组

注:元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。

解:

func removeElement(nums []int, val int) int {

/*
	双指针法,快慢指针
	快指针:i
	慢指针:count
*/

	var count int
	for i:=0;i<len(nums);i++{
		if nums[i] != val {
			nums[count] = nums[i]
			count++
		}
	}
	return count
}

2. 最大连续1的个数

给定一个二进制数组, 计算其中最大连续1的个数。

示例 1:

输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.

注意:

  • 输入的数组只包含 01
  • 输入数组的长度是正整数,且不超过 10,000。

解:

func findMaxConsecutiveOnes(nums []int) int {
	
/*
	双指针法
*/
	
	maxCount := 0
	count := 0
	for _,j := range nums {
		if j == 1 {
			count++
		}else {
			if count > maxCount {
				maxCount = count
			}
			count = 0
		}
	}
    // 考虑数组尾部可能为1的情况
	if count > maxCount {
		maxCount = count
	}
	return maxCount
}

3. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度如果不存在符合条件的连续子数组,返回 0。

示例:

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

解:

func minSubArrayLen(s int, nums []int) int {

	/*
		双指针法(滑窗法)
		窗口的大小就是子数组长度,一直维持滑窗的和>=要求参数s。
		如果大了,左边减元素,左指针右移;
		如果小了,右指针右移,右边加元素,
	*/

	// 数组长度
	nLen := len(nums)
	// 传入数组长度为0
	if nLen == 0 {
		return 0
	}
	// 子数组最小长度,这里的 nLen+1 是为不存在符合条件的连续子数组的情况准备的
	minLen := nLen + 1

	// 左指针,右指针,子数组和
	i, j, sum := 0, 0, nums[0]

	// 由 左指针,右指针=0 开始遍历,右指针达到右边界结束遍历
	for j < nLen {

		// 和 > 指定值s,左指针右移,左边减去元素
		if sum >= s {
			if j-i+1 < minLen { // 子数组长度
				minLen = j - i + 1
			}
			sum -= nums[i]
			i++
		} else {
			// 和 < 指定值s,右指针右移,右边加上元素
			j++
			if j >= nLen { // 右指针遍历到数组尾部则跳出循环
				break
			}
			sum += nums[j]
		}
	}

	// 上面没有出现 和 > 指定值s 的情况,即不存在符合条件的连续子数组
	if minLen == nLen+1 {
		return 0
	}
	return minLen
}

4. 反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例 1:

输入: "Let‘s take LeetCode contest"
输出: "s‘teL ekat edoCteeL tsetnoc" 

注意:在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。

解:

func reverseWords(s string) string {
    
/*
    双指针法
    i快指针,j慢指针,count计数器
    最后一个字符没有空格,需要额外处理
*/
    
	a := []rune(s)
	i := 0
	j := 0

	for count := 0; count < len(s); count++ {
		if s[count] == ‘ ‘ || count == len(s)-1 {
			if count == len(s)-1 {
				i = count
			} else {
				i = count - 1
			}
			for i > j {
				a[i], a[j] = a[j], a[i]
				i--
				j++
			}
			count++
			j = count
		}
	}
	return string(a)
}

以上是关于双指针法的主要内容,如果未能解决你的问题,请参考以下文章

双指针法及题目

双指针法及题目

双指针法

2021/5/20 刷题笔记相交链表以及双指针法

双指针法将时间复杂度从 O(n^2) 优化到 O(n)

LeetCode刷题总结之双指针法