[leetcode]双指针

贪心

  1. 盛水最多的容器
    在这里插入图片描述
    /*
    左右指针最开始指向最两边,此时底最大,然后往里面缩小,此时将低的淘汰,往里缩.
    */
    func maxArea(height []int) (ret int) {
        min := func(a,b int)int{
            if a<b{
                return a
            }
            return b
        }
        for i,j:=0,len(height)-1;i<j; {
            x := (j-i)*min(height[i],height[j])
            if ret < x {
                ret = x
            }
            if height[i]<height[j]{
                i++
            }else{
                j--
            }
        }
        return
    }
    

两数之和题型

从数组中找出几个数,其和满足某要求

先排序,再固定端点值(相对),减少变动的量,将题型转换为两数之和类型

  1. 两数之和
    在这里插入图片描述

    func twoSum(num []int, target int) (ret []int) {
    	type Num struct {
    		id int
    		v  int
    	}
    	nums := make([]Num, len(num))
    	for i := range num {
    		nums[i] = Num{id: i, v: num[i]}
    	}
    	sort.Slice(nums, func(i, j int) bool { //排序
    		return nums[i].v < nums[j].v
    	})
    	for l, r := 0, len(nums)-1; l < r; {//通过和来确定l和r的变化
    		if sum := nums[l].v + nums[r].v; sum == target {
    			ret = append(ret, nums[l].id, nums[r].id)
    			return
    		} else if sum < target {
    			l++
    		} else {
    			r--
    		}
    	}
    	return
    }
    
  2. 三数之和

    此题要求不能出现重复的结果!

    a,b,c三个变动的量,先相对固定a,再将b和c转换为两数之和 在这里插入图片描述

    func threeSum(nums []int) (ret [][]int) {
    	target := 0
    	n := len(nums)
    	sort.Ints(nums) //排序
    	for a := 0; a < n; a++ { //定a
    		if a > 0 && nums[a] == nums[a-1] { //确保a不重复
    			continue
    		}
    		for b, c := a+1, n-1; b < c; { //转换为求b+c
    			if sum := nums[a] + nums[b] + nums[c]; sum == target {
    				ret = append(ret, []int{nums[a], nums[b], nums[c]})
    				for b++; nums[b] == nums[b-1] && b < c; b++ { // 确保b不重复
    				}
    				for c--; nums[c] == nums[c+1] && b < c; c-- { //确保c不重复
    				}
    			} else if sum < target {
    				b++
    			} else {
    				c--
    			}
    		}
    	}
    	return
    }
    
  3. 最接近的三数之和

    和上面类似,只不过判定条件变了 在这里插入图片描述

    func threeSumClosest(nums []int, target int) (ret int) {
    	sort.Ints(nums)
    	ret = math.MaxInt32
    	n := len(nums)
    	abs := func(n int) int {
    		if n < 0 {
    			return -n
    		}
    		return n
    	}
    	for a := 0; a < n; a++ {
    		if a > 0 && nums[a] == nums[a-1] {
    			continue
    		}
    		for b, c := a+1, n-1; b < c; {
                //这个得硬遍历,因为不能准确判定两个边界怎么动
    			for b > a+1 && nums[b] == nums[b-1] && b < c {
    				b++
    			}
    			if b < c {
    				sum := nums[a] + nums[b] + nums[c]
    				if abs(sum-target) < abs(ret-target) {
    					ret = sum
    					if ret == target { //做个小小的优化
    						return
    					}
    				}
    				if sum < target {
    					b++
    				} else {
    					c--
    				}
    			}
    		}
    	}
    	return
    }
    
  4. 四数之和

    这个需要定a,d(相对),求b+c
    在这里插入图片描述

    func fourSum(nums []int, target int) (ret [][]int) {
    	sort.Ints(nums) //排序
    	n := len(nums)
    	for a := 0; a < n; a++ { //a从左向右
    		if a > 0 && nums[a] == nums[a-1] {//a不重复
    			continue
    		}
    		for d := n - 1; a+2 < d; d-- { //d从右向左
    			if d < n-1 && nums[d] == nums[d+1] {//d不重复
    				continue
    			}
    			for b, c := a+1, d-1; c > b; {
    				if sum := nums[a] + nums[b] + nums[c] + nums[d]; sum == target {
    					ret = append(ret, []int{nums[a], nums[b], nums[c], nums[d]})
    					for b++; nums[b] == nums[b-1] && b < c; b++ { //b可以往右因为不可能再有值相同了(因为不能重复)
    					}
    					for c--; nums[c] == nums[c+1] && b < c; c-- { //c可以往左因为值不能重复,所以不可能有相同的结果了
    					}
    				} else if sum < target {
    					b++
    				} else {
    					c--
    				}
    			}
    		}
    	}
    	return
    }
    

快慢指针

利用快慢指针求解

  1. 删除链表倒数第n个节点

    先让快指针走出去n步,然后两个同时走就会保持n的间距

    这种一般弄个头结点比较好
    在这里插入图片描述

    func removeNthFromEnd(head *ListNode, n int) *ListNode {
    	pHead := new(ListNode)
    	pHead.Next = head
    	p := pHead
    	for i := 0; i < n; i++ {
    		p = p.Next
    	}
    	q := pHead
    	for ; p.Next != nil; q, p = q.Next, p.Next {
    	}
    	q.Next = q.Next.Next
    	return pHead.Next
    }
    
  2. 爱生气的书店老板

    维持一个矩阵从左往右,计算和即可
    在这里插入图片描述

    func maxSatisfied(customers []int, grumpy []int, minutes int) (ret int) {
    	if minutes >= len(customers) { // 如果能全部不生气就直接返回所有值
    		for _, v := range customers {
    			ret += v
    		}
    		return
    	}
    	sum := 0
    	for i := range customers { //先把所有不生气的人数加起来
    		if grumpy[i] == 0 {
    			sum += customers[i]
    		}
    	}
    	for l, r := 0, 0; r < len(customers); l,r=l+1,r+1 { // 维持一个 不生气的区间 往右走,遇到生气的就加入sum,抛弃生气的要从sum中减去
    		if grumpy[r] == 1 {
    			sum += customers[r]
    		}
    		if r-l+1 != minutes {
    			continue
    		}
    		if sum > ret {
    			ret = sum
    		}
    		if grumpy[l] == 1 {
    			sum -= customers[l]
    		}
    	}
    	return
    }
    

滑动窗口

这种一般是保持一个由双指针围成的满足条件的区间,快指针用于探索未知的元素,而慢指针则用于剔除区间内的元素,使区间满足条件

负雪明烛的题解讲的很清晰!

求满足条件的子区间
  1. A 中由 K 个不同整数组成的最长子数组的长度

    1. 替换后的最长重复字符

      翻译为 找到包含重复字母的最长区间

      即 对于每个区间(长度为length)除了最多重复数的字符以外字符的个数最多为k个
      在这里插入图片描述

      func characterReplacement(s string, k int) (ret int) {
      	max := func(a, b int) int {
      		if a < b {
      			return b
      		}
      		return a
      	}
          //这个感觉没写好,应该有更好的方法
      	count := func(nums [26]int) (m int) {
      		for _, v := range nums {
      			m = max(m, v)
      		}
      		return
      	}
      
      	cnt := [26]int{}                    // 计算数组
      	for l, r := 0, 0; r < len(s); r++ { // 定左右边界 每一轮让右边界往右走,探索新元素
      		cnt[s[r]-'A']++              // 计入新情况
      		for (r-l+1)-count(cnt) > k { // 判断是否满足条件,如果不满足就缩小左边界
      			cnt[s[l]-'A']--
      			l++
      		}
      		ret = max(ret, r-l+1) //统计结果
      	}
      	return
      }
      
    2. 最大连续1的个数III

      和上面那个差不多
      在这里插入图片描述

      func longestOnes(nums []int, k int) (ret int) {
      	cnt := 0
      	for l, r := 0, 0; r < len(nums); r++ {
      		if nums[r] == 0 {
      			cnt++
      		}
      		for cnt > k {
      			if nums[l] == 0 {
      				cnt--
      			}
      			l++
      		}
      		if cnt <= k && ret < r-l+1 {
      			ret = r-l+1
      		}
      	}
      	return
      }
      
  2. A 中由最多 K 个不同整数组成的子数组的个数

    其实只用修改一个地方即可
    在这里插入图片描述

  3. k个不同整数的子数组

    恰好由 K 个不同整数的子数组的个数 = 最多由 K 个不同整数的子数组的个数 - 最多由 K - 1 个不同整数的子数组的个数
    在这里插入图片描述
    转换问题为求最多有k个不同整数的子数组的个数

    func subarraysWithKDistinct(nums []int, k int) (ret int) {
    	return count(nums, k) - count(nums, k-1)
    }
    func count(nums []int, k int) (ret int) {
    	cntMap := make(map[int]int)
    	newWord := 0
    	for l, r := 0, 0; r < len(nums); r++ {
    		if cntMap[nums[r]] == 0 {
    			newWord++
    		}
    		cntMap[nums[r]]++
    		for newWord > k {
    			cntMap[nums[l]]--
    			if cntMap[nums[l]] == 0 {
    				newWord--
    			}
    			l++
    		}
    		ret += r - l + 1
    	}
    	return
    }
    
  4. 至少有k个重复字符的最长子串

    这个题其实没用滑动窗口做,用分治的思路做,扫描当前字符串,找到重复数量小于k的字符,然后用此字符将字符串分隔,再进行求解,直到字符串中所有字符都至少出现过k次,或者字符串长度小于k.

在这里插入图片描述

 func mMax(a, b int) int {
     	if a > b {
     		return a
     	}
     	return b
     }
     //函数定义:s字符串中每个字符出现次数均不小于k的最大字符串长度
     func longestSubstring(s string, k int) (ret int) {
     	if len(s) < k { // 当此字符串长度小于k直接返回0
     		return
     	}
     	cntMap := make([]int, 26)
     	for i := range s {
     		cntMap[s[i]-'a']++
     	}
     	var ch byte // 找到整个字符串中出现次数小于k的字符,依次字符串为分割点,将所有字符串分隔递归进行计算.
     	for i, v := range cntMap {
     		if v > 0 && v < k {
     			ch = byte(i) + 'a'
     			break
     		}
     	}
     	if ch == 0 { // 当此字符串中所有字符均满足条件则直接返回总长度
     		return len(s)
     	}
     	for _, v := range strings.Split(s, string(ch)) {//到此步说明中间存在分割点可以继续向下分隔
     		ret = mMax(ret, longestSubstring(v, k))
     	}
     	return
     }

kmp

  1. 通过连接另一个数组的子数组得到一个数组

    其实就是规定起始值的kmp
    在这里插入图片描述

func canChoose(groups [][]int, nums []int) bool {
	getNext := func(s []int) []int { //获取模式串的next数组
		next := make([]int, len(s))
		next[0] = -1
		for i, j := 0, -1; i < len(s)-1; {
			if j == -1 || s[i] == s[j] {
				i++
				j++
				if s[i] == s[j] {
					next[i] = next[j]
				} else {
					next[i] = j
				}
			} else {
				j = next[j]
			}
		}
		return next
	}
	kmp := func(s1, s2 []int) int { //在s1中找s2第一次出现的下标
		next := getNext(s2)
		for i, j := 0, 0; i < len(s1); {
			if j == -1 || s1[i] == s2[j] {
				i++
				j++
			} else {
				j = next[j]
			}
			if j == len(s2) {
				return i - j
			}
		}
		return -1
	}
	startCompare := func(r1 []int, index int, r2 []int) int { //从r1的index下标开始找r2第一次出现的下标
		offset := kmp(r1[index:], r2)
		if offset == -1 {
			return -1
		}
		return index + offset
	}
	start := 0 //每次查找的起始位置
	for i := range groups {
		nx := startCompare(nums, start, groups[i])
		if nx == -1 {
			return false
		}
		start = nx + len(groups[i])
	}
	return true
}

部分思路来自于leetcode题解的大佬们

跟着三叶姐的题单走的
三叶姐的双指针题单

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值