Leetcode-双指针遍历/滑动窗口


q3无重复字符的最长子串


题目传送门


题解

使用滑动窗口求解,设置左右指针来确定窗口的边界,其中右指针就是循环变量的位置. 窗口的变化取决于当前遍历到的字符之前有没有出现过,如果出现过,那么左指针移动到字符上一个位置+1.
我们设置了一个cache数组,用于保存每个元素最后一次出现的后一位,以下面一组元素来举例:

 	i:						0 1 2 3 4 5 6
	元素:					a b c a d a e
 							cache的值变化		左边界(l)	 子串长度
	遍历到 00 0 0 0 0 0 0			0			1
	遍历到 11 0 0 0 0 0 0			0			2
	遍历到 21 2 0 0 0 0 0			0			3
	遍历到 31 2 3 0 0 0 0			1			3
	遍历到 44 2 3 4 0 0 0			1			4
	遍历到 54 2 3 4 5 0 0			4			2
	遍历到 66 2 3 6 5 6 0			4			3
	

我们可以发现,子串的长度实际就是i - l + 1.

要注意每次遍历到一个元素的时候,cache数组总是在最后才更新值,假设当前刚好遍历到一个重复的元素a,刚好能让左指针先跳转到前面那个a的后一个位置,再计算子串的长度,最后才更新cache数组的值.

func lengthOfLongestSubstring(s string) (maxLen int) {
    l := 0
    cache := make([]int, 128)
    for i := 0; i < len(s); i++ {
        // 如果当前元素之前出现过,就需要移动l指针
        l = max(l, cache[s[i]])
        // 更新最大子串长度
        maxLen = max(maxLen, i - l + 1)
        // 更新cache数组的值
        cache[s[i]] = i + 1
    }
    return
}
func max(a, b int) int {
    if a > b {
        return a
    } else {
        return b
    }
}


q11盛最多水的容器


题目传送门


题解

使用双指针从两边向中间遍历,同时更新最大值.

func maxArea(height []int) (Max int) {
	left, right := 0, len(height) - 1
	Max = (right -left) * min(height[left], height[right])
	// 左右指针从两边向中间遍历,更新最大值
	for left < right {
		// 哪边更小哪边的指针就向中间走,寻找更高的垂直线
		if height[left] < height[right] {
			left++
		} else {
			right--
		}
		// 更新最大值
		area := (right -left) * min(height[left], height[right])
		Max = max(Max, area)
	}
	return
}
func min(a, b int) int {
	if a < b {
		return a
	} else {
		return b
	}
}
func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

q15三数之和


题目传送门


题解

首先排序,然后用三指针遍历,注意需要去重。

步骤:

  1. 对数组进行排序。
  2. 开始循环遍历。
  3. 首先判断左指针指向的数是否大于0,大于0就break。
  4. 对左指针进行去重,nums[i] == nums[i - 1],就continue。
  5. 定义中间指针和右指针:j, k := i+1, len(nums)-1。
  6. 在内置循环中进行遍历,判断nums[i]+nums[j]+nums[k]是否等于0,同时对j和k进行去重。
func threeSum(nums []int) (res [][]int) {
	// 对数组进行排序
	sort.Ints(nums)
	// 循环遍历
	for i := 0; i < len(nums)-2; i++ {
		// 最左边的数大于0,结果肯定大于0
		if nums[i] > 0 {
			break
		}
		// 对最左边的数进行去重
		if i > 0 && nums[i] == nums[i-1] {
			continue
		}
		// 定义中间指针和右指针
		j, k := i+1, len(nums)-1
		for j < k {
			if nums[i]+nums[j]+nums[k] == 0 {
				res = append(res, []int{nums[i], nums[j], nums[k]})
				// 对中指针和右指针去重
				n1, n2 := nums[j], nums[k]
				for j < k && nums[j] == n1 {
					j++
				}
				for j < k && nums[k] == n2 {
					k--
				}
			} else if nums[i]+nums[j]+nums[k] < 0 {
				j++
			} else {
				k--
			}
		}
	}
	return
}

q16最接近的三数之和


题目传送门


题解
先排序,然后用双指针操作。

func threeSumClosest(nums []int, target int) int {
	// 先排序
	sort.Ints(nums)
	// 初始化最大值
	n, sumClose := len(nums), nums[0]+nums[1]+nums[2]
	// i从0到n - 3遍历
	// 剩下两个数在i+1到n - 1的范围遍历
	for i := 0; i < n-2; i++ {
		// 去重
		if i > 0 && nums[i] == nums[i-1] {
			continue
		}
		// 遍历剩下两个数
		j, k := i+1, n-1
		for j < k {
			sum := nums[i] + nums[j] + nums[k]
			// 正好相加等于target
			if sum == target {
				return target
			}
			// 更新最接近的数
			if math.Abs(float64(int64(sum-target))) < math.Abs(float64(int64(sumClose-target))) {
				sumClose = sum
			}
			// 偏大就左移右指针,偏小就左移右指针
			if sum > target {
				k--
			} else {
				j++
			}
		}
	}
	return sumClose
}

q26 删除有序数组中的重复项


题目传送门


题解

这道题使用快慢指针来求解,首先循环遍历数组,快指针就是循环变量,使用一个变量p来缓存上一个遍历到的变量,与本次遍历到的变量做比较,如果相等就忽略,如果大于p,就覆盖到慢指针指向的那个位置,同时向后移动慢指针。

func removeDuplicates(nums []int) int {
	// 慢指针
	slow := 0
	// 用于保存上一个变量
	p := -10001
	for i, _ := range nums {
		// 当前变量比上一个变量大,说明没有重复,直接覆盖到原数组
		if nums[i] > p {
			nums[slow] = nums[i]
			// 备份当前变量,用于下一次比较
			p = nums[slow]
			slow++
		}
	}
	return slow
}

q31 下一个排列


题目传送门


题解

该题的算法思路是:
我们的目标就是找出两个数:右边尽可能小的大数和左边尽可能大的小数,将这两个数交换,然后将交换后的位置后面的数组升序排序,即可得到答案。下面是算法步骤:

  1. 从后向前查找第一个相邻升序的元素对 (i,j),满足 A[i] < A[j]。此时 [j,end) 必然是降序。
  2. 在 [j,end) 从后向前查找第一个满足 A[i] < A[k] 的 k。A[i]、A[k] 分别就是左边的最大数、右边的最小数。
  3. 将 A[i] 与 A[k] 交换。
  4. 可以断定这时 [j,end) 必然是降序,逆置 [j,end),使其升序。
    如果在步骤 1 找不到符合的相邻元素对,说明当前 [begin,end) 为一个降序顺序,则直接跳到步骤 4。
func nextPermutation(nums []int) {
	n := len(nums)
	i := n - 2
	// 查找第一个相邻的顺序对(i<j,nums[i]<nums[j])
	// 顺序对的左边那个数就是左半边数组的最小值
	for i >= 0 && nums[i] >= nums[i + 1] {
		i--
	}
	// 查找右半边数组的较小值
	// 右半边数组一定是逆序数组
	if i >= 0 {
		// 从n - 1开始找右半边数组中找比nums[i]大的最小数
		j := n - 1
		for j >= 0 && nums[i] >= nums[j] {
			j--
		}
		nums[i], nums[j] = nums[j], nums[i]
	}
	reverse(nums[i + 1:])
}

// 翻转数组
func reverse(nums []int) {
	for i, n := 0, len(nums); i < n / 2; i++ {
		nums[i], nums[n - i - 1] = nums[n - i - 1], nums[i]
	}
}

q42 接雨水


题目传送门


题解

使用双指针求解。这道题的策略是要将两边尽量高的柱体向中间移,计算出外围高柱体与移动过程中碰见的低柱体的高度差。

func trap(height []int) (ans int) {
	// 定义两个指针
	l, r := 0, len(height)-1
	// 如果数组的大小不够,直接返回
	if r < 2 {
		return
	}
	// 循环遍历高度图
	for l < r {
		// 策略是要将两边尽量高的柱体向中间移,计算出外围高柱体与移动过程中碰见的低柱体的高度差
		// 如果左边界比右边界低,移动左指针
		if height[l] < height[r] {
			// 将外围的高柱体向中间移动
			if height[l] > height[l + 1] {
				// 统计高度
				ans += height[l] - height[l + 1]
				height[l + 1] = height[l]
			}
			l++
		} else {
			if height[r] > height[r - 1] {
				// 统计高度
				ans += height[r] - height[r - 1]
				height[r - 1] = height[r]
			}
			r--
		}
	}
	return
}

q76 最小覆盖子串


题目传送门


题解

题目要求目标子串需要覆盖t串,然后要求子串的长度最小,可以使用滑动窗口求解。如何来判断目标子串包含了t串呢,我们可以使用一个数组tArr记录下t串中的每个字母的出现次数,然后另一个数组sArr记录下s串中每个字母出现的次数。当我们遍历tArr数组的时候,如果都能满足tArr[i] <= sArr[i]的话,那么当前子串就包含了t串。
在满足了以上的情况后,我们可以尝试着缩小滑动窗口的大小了。我们的策略是向右移动左边界,如果移动了左边界以后,子串仍然能够包含t串,那么就视为滑动窗口被成功缩小了。

func minWindow(s string, t string) string {
	// tArr统计t串中字符的个数, sArr统计s串中字符的个数
	sArr, tArr := make([]int, 128), make([]int, 128)
	slen, tlen := len(s), len(t)
	// 首先用tArr数组统计t串的字符个数
	for i := 0; i < tlen; i++ {
		tArr[t[i]-'A']++
	}
	// left,right分别表示滑动窗口左边界和右边界的下标
	// length表示滑动窗口的大小
	left, right, length := 0, -1, slen+1
	// 遍历s数组,同时统计s串的字符个数,看看有没有将t串覆盖
	// 如果s串覆盖了t串,就尝试着减小窗口的大小
	for i, j := 0, 0; j < slen; j++ {
		sArr[s[j]-'A']++
		// 在s串覆盖t串的条件下,减小窗口的大小
		for equal(sArr, tArr) {
			// 更新窗口大小
			if length > j-i+1 {
				length = j - i + 1
				left = i
				right = j
			}
			// 尝试着剔除窗口左边界的元素
			sArr[s[i]-'A']--
			i++
		}
	}
	return s[left : right+1]
}

// 判断s串有没有覆盖t串
func equal(arr1 []int, arr2 []int) bool {
	for i := 0; i < 128; i++ {
		if arr1[i] < arr2[i] && arr2[i] != 0 {
			return false
		}
	}
	return true
}

q121 买卖股票的最佳时机


题目传送门


题解

一次性遍历数组即可求解,因为最低价格必须在前面,最高价格必须在后面,所以遍历的时候,使用minValue存储前面出现过的股票的最低价格,然后与当前遍历到的股票价格相减,得到利润,使用maxValue存储最大利润即可。

func maxProfit(prices []int) int {
	minValue := math.MaxInt64
	maxValue := 0
	for i := 0; i < len(prices); i++ {
		// minValue记录股票的最低价格
		if prices[i] < minValue {
			minValue = prices[i]
		// maxValue记录股票的最大价格差,即最大利润
		} else if prices[i] - minValue > maxValue {
			maxValue = prices[i] - minValue
		}
	}
	return maxValue
}

q209 长度最小的子数组


题目传送门


题解

使用双指针来求解,右指针不断向后遍历,同时记录中间元素的和,当和加起来大于等于目标值,就将左指针右移来尝试得到更短的子数组。

func minSubArrayLen(target int, nums []int) (ans int) {
	n := len(nums)
	// 初始化最小值
	ans = math.MaxInt32
	slow, fast := 0, 0
	// 用于累加和
	sum := 0
	for fast < n {
		sum += nums[fast]
		// 如果sum大于等于target,开始向右移动慢指针,得到更短的子数组
		for sum >= target {
			// 更新最小长度
			ans = min(ans, fast-slow+1)
			// 减去最左边的值
			sum -= nums[slow]
			// 右移慢指针
			slow++
		}
		// 后移快指针
		fast++
	}
	// 如果没有满足的子数组,就返回0
	if ans == math.MaxInt32 {
		return 0
	}
	return
}

func min(a, b int) int {
	if a < b {
		return a
	} else {
		return b
	}
}


q283 移动零


题目传送门


题解

使用双指针解法,准备left和right指针,如果right指针遍历到的数不是0,就与left指针指向的数做交换,然后同时往后移动两个指针:

func moveZeroes(nums []int)  {
	left, right, n := 0, 0, len(nums)
	for right < n {
		if nums[right] != 0 {
			nums[left], nums[right] = nums[right], nums[left]
			left++
		}
		right++
	}
}

q438 找到字符串中所有字母异位词


题目传送门


题解

这道题可以使用滑动窗口+哈希表的方法来解决,既然是异位词,那就不用考虑单词的顺序,只需要每种单词的数量满足就可以了。所以我们可以开两个hash表,一个用来储存模式串中每种单词的数量,另一个用来维护滑动窗口中每种单词的数量,这个滑动窗口的大小刚好是模式串的长度,最后一边遍历目标串,一边比较两个hash表即可:

golang中的数组是可以直接比较的,相同长度,相同类型的数组在比较的时候是相同的,所以我们可以使用数组来替换哈希表。

步骤:

  1. 比较模式串和匹配串的长度。
  2. 定义两个用于记录字母个数的数组:cntP, cntS := [26]int{}, [26]int{}
  3. len(p)位置开始循环初始化匹配串中各位字母的个数。
  4. 遍历模式串,累加上新遍历到的字符,然后剔除滑动窗口左边的字符,再与匹配串数组做比较。
func findAnagrams(s string, p string) (ans []int) {
	// 如果模式串比匹配串短,直接返回
	if len(s) < len(p) {
		return nil
	}
	// 定义用于记录字母个数的数组
	cntP, cntS := [26]int{}, [26]int{}
	// 首先记录匹配串中各位的字母个数
	for i := 0; i < len(p); i++ {
		cntP[p[i]-'a']++
		cntS[s[i]-'a']++
	}
	if cntP == cntS {
		ans = append(ans, 0)
	}
	// 遍历模式串
	for i := len(p); i < len(s); i++ {
		// 统计上当前遍历到的字符
		cntS[s[i]-'a']++
		// 减去滑动串口左边的字符
		cntS[s[i-len(p)]-'a']--
		if cntS == cntP {
			ans = append(ans, i-len(p)+1)
		}
	}
	return
}

q6230 长度为 K 子数组中的最大和


题目传送门


题解

这道题需要借助于哈希表,哈希表用于判断窗口中的值是否重复。

步骤:

  1. 构建长度为k-1的窗口,维护哈希表+累加sum值。
  2. 从k-1下标开始遍历数组,首先维护哈希表+累加sum值,然后更新最大值,最后剔除窗口最左边的值。
func maximumSubarraySum(nums []int, k int) int64 {
	// 定义用于临时累加的变量和最大值变量
	ans, sum := 0, 0
	// 定义哈希表
	hashTable := make(map[int]int)
	// 初始化哈希表
	for _, v := range nums[:k-1] {
		hashTable[v]++
		sum += v
	}
	// 从k - 1开始遍历数组
	for i := k - 1; i < len(nums); i++ {
		// 累加sum 和 维护哈希表
		sum += nums[i]
		hashTable[nums[i]]++
		// 更新最大值
		if len(hashTable) == k && sum > ans {
			ans = sum
		}
		// 剔除最左边的值
		x := nums[i-k+1]
		hashTable[x]--
		// 及时清楚0
		if hashTable[x] == 0 {
			delete(hashTable, x)
		}
		sum -= x
	}
	return int64(ans)
}

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面


题目传送门


题解

这道题使用双指针遍历,定义两个指针分别指向数组的两端,左指针向右遍历,右指针向左遍历,将数组左边的偶数与数组右边的奇数做交换。

func exchange(nums []int) []int {
	// 定义双指针
	left, right := 0, len(nums)-1
	for left < right {
		// 遍历左半边数组,奇数就向后移动
		for left < right && nums[left]%2 != 0 {
			left++
		}
		// 遍历右半边数组,偶数就前后移动
		for left < right && nums[right]%2 == 0 {
			right--
		}
		nums[left], nums[right] = nums[right], nums[left]
	}
	return nums
}

剑指 Offer 48. 最长不含重复字符的子字符串


题目传送门


题解

这道题与 q3无重复字符的最长子串 相同。

func lengthOfLongestSubstring(s string) (maxLength int) {
	l := 0
	cache := make([]int, 128)
	for i, _ := range s {
		l = max(l, cache[s[i]])
		maxLength = max(maxLength, i-l+1)
		cache[s[i]] = i + 1
	}
	return
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

剑指 Offer 57 - II. 和为s的连续正数序列


题目传送门


题解

使用双指针解法,定义两个指针,每次循环分别统计两个指针直接的元素和,如果和大于目标值就将左指针向后移动,表示减少一位数字,小于目标值就将右指针向后移动一位,表示增加一位数字,如果等于目标值就循环遍历指针之间的数构造结果集。

其中两个指针之间的元素和我们采用等差数列之和的求法:(首项+尾项)* 项数 / 2.

func findContinuousSequence(target int) (res [][]int) {
	l, r := 1, 2
	for l < r {
		// 等差数列求和
		sum := (l + r) * (r - l + 1) / 2
		if sum == target {
			var row []int
			for i := l; i <= r; i++ {
				row = append(row, i)
			}
			res = append(res, row)
			l++
		} else if sum > target {
			// 减少一位数字
			l++
		} else {
			// 增加一位数字
			r++
		}
	}
	return
}

剑指 Offer 63. 股票的最大利润


题目传送门


题解

这道题的解法与买卖股票的最佳时机一样

func maxProfit(prices []int) int {
	minValue, maxValue := math.MaxInt32, math.MinInt32
	for _, v := range prices {
		if v < minValue {
			minValue = v
		} else if v-minValue > maxValue {
			maxValue = v - minValue
		}
	}
	return maxValue
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值