[力扣03]Two Pointer


一、思想或常用方法

  • 双指针滑动窗⼝的经典写法。右指针不断往右移,移动到不能往右移动为⽌(具体条件根据题⽬⽽定)。当右指 针到最右边或不能右移(right+1的fi判断字符已存在,不满足右移条件)以后,开始挪动左指针,释放窗⼝左边界。第 3 题,第 76 题,第 209 题,第 424 题,第 438 题,第 567 题,第 713 题,第 763 题,第 845 题,第 881 题,第 904 题,第 978 题,第 992 题,第 1004 题,第 1040 题,第 1052 题

在这里插入图片描述

  • 快慢指针可以查找重复数字,时间复杂度 O(n),第 287 题。
  • 替换字⺟以后,相同字⺟能出现连续最⻓的⻓度。第 424 题。
  • SUM 问题集。第 1 题,第 15 题,第 16 题,第 18 题,第 167 题,第 923 题,第 1074 题。

二、力扣习题

  • 求最大、最长初始结果为0;最小、最短初始结果为切片、字符串长度+1且最后如果值没变需要返回0
  • 和未达到要求判断写法:如果是频次判断,可以判断本次结果,其他都判断的是上次结果
for left < len(nums) {
        if right+1 < len(nums) && sum < target{//上次和未达到要求
            // right++,扩大窗口
            // 这里的sum是上次的和,不能用sum + nums[right+1] < target 判断,会导致最后一次值加不上
            sum+=nums[right+1]
            right++
        }else{  //和达到要求 则缩小窗口
            if minW > right-left+1 && sum >= target {
                minW = right-left+1
            }
            sum-=nums[left]
            left++
        }
    }

1.0003 Longest Substring Without Repeating Characters

right增加不重复字符
left减少不重复字符
left-right之间是不重复字符
在这里插入图片描述

func lengthOfLongestSubstring(s string) int {
    if len(s) == 0{
        return 0
    }
    var freq [127]int
    res,left,right := 0,0,-1
    for left < len(s) {
        if right+1 < len(s) && freq[s[right+1]] == 0 {
            freq[s[right+1]]++
            right++
        }else{
            freq[s[left]]--
            left++
        }
        res = Max(res,right - left + 1)
    }
    return res
}

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

2.0076 Minimum Window Substring

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。

func minWindow(s string, t string) string {
    if s == "" || t == "" {
        return ""
    }
    var sfreq,tfreq [256]int
    left,right,fLeft,fRight,minW,count,res := 0,-1,-1,-1,len(s)+1,0,"" 
    for i:=0; i < len(t); i++ {
        tfreq[t[i] - 'a']++
    }
    for left < len(s) {
        if right + 1 < len(s) && count < len(t) { //右指针没到底 且 未达到最小覆盖子串
            sfreq[s[right+1] - 'a']++
            if sfreq[s[right+1] - 'a'] <= tfreq[s[right+1] - 'a'] {
                count++
            }
            right++
        }else{// 缩小最小子串范围
            if right - left + 1 < minW && count == len(t) {
                minW = right - left + 1
                fLeft = left
                fRight = right
            }
            if sfreq[s[left] - 'a'] == tfreq[s[left] - 'a'] {
                count--
            }
            sfreq[s[left] - 'a']--
            left++
        }
    }
    if fLeft != -1 {
        res = string(s[fLeft:fRight+1]) 
    }
    return res
}

3.0209 Minimum Size Subarray Sum

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

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

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

这一题的解题思路是用滑动窗口。在滑动窗口 [i,j]之间不断往后移动,如果总和小于 s,就扩大右边界 j,不断加入右边的值,直到 sum > s,之和再缩小 i 的左边界,不断缩小直到 sum < s,这时候右边界又可以往右移动。以此类推。

func minSubArrayLen(target int, nums []int) int {
    if len(nums) == 0 {
        return 0
    }
    left, right, sum, minW := 0, -1, 0, len(nums)+1
    for left < len(nums) {
        if right+1 < len(nums) && sum < target{//上次和未达到要求
            // right++,扩大窗口
            // 这里的sum是上次的和,不能用sum + nums[right+1] < target 判断,会导致最后一次值加不上
            sum+=nums[right+1]
            right++
        }else{  //和达到要求 则缩小窗口
            if minW > right-left+1 && sum >= target {
                minW = right-left+1
            }
            sum-=nums[left]
            left++
        }
    }
    if minW == len(nums)+1 {
        minW = 0
    }
    return minW
}

4.0424 Longest Repeating Character Replacement

在这里插入图片描述
题目大意 #
给一个字符串和变换次数 K,要求经过 K 次字符转换以后,输出相同字母能出现连续最长的长度。

解题思路 #
这道题笔者也提交了好几遍才通过。这一题是考察滑动窗口的题目,但是不能单纯的把左右窗口往右移动。因为有可能存在 ABBBBBA 的情况,这种情况需要从两边方向同时判断。正确的滑动窗口的做法应该是,边滑动的过程中边统计出现频次最多的字母,因为最后求得的最长长度的解,一定是在出现频次最多的字母上,再改变其他字母得到的最长连续长度。窗口滑动的过程中,用窗口的长度减去窗口中出现频次最大的长度,如果差值比 K 大,就代表需要缩小左窗口了直到差值等于 K。res 不断的取出窗口的长度的最大值就可以了。

// func characterReplacement(s string, k int) int {
//     if s == "" {
//         return 0
//     }
//     left, right, maxW, t := 0, -1, 0, k
//     for left < len(s) {//right == left || k > 0
//         //right == left 
//         if right+1 < len(s) && s[right+1] == s[left]  {
//             right++
//         }else if right+1 < len(s) && t > 0{
//             right++
//             t--
//         }else{
//             if right-left+1 > maxW {
//                 maxW = right-left+1
//             }
//             left++
//             right = left
//             t = k
//         }
//     }
//     return maxW
// }

func characterReplacement(s string, k int) int {
    res, left, count, freq := 0,0,0,make([]int,26)
    for right := 0;right < len(s); right++ {
        freq[s[right] - 'A']++
        count = max(count,freq[s[right]-'A'])// count 记录窗口内最大字母频次
        for right-left+1 -count > k {// 实际变值个数比k大,就要缩小窗口,找到满足存在k个变值的最大窗口,否则当前窗口满足,比较当前窗口找到最大值
            freq[s[left] - 'A']--
            left++
        }
        res = max(res,right-left+1)
    }
    return res
}

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

在这里插入图片描述

5.0567 Permutation in String

在这里插入图片描述
2.滑动窗口 自己写法

func checkInclusion(s1 string, s2 string) bool {
	if len(s2) == 0 || len(s2) < len(s1) {
		return false
	}
    left, right, sfreq, tfreq, count := 0, -1, make([]int,256), make([]int,256), 0
    for i:=0; i < len(s1); i++ {
        tfreq[s1[i] - 'a']++
    }
    for left < len(s2) {
        if right+1 < len(s2) && count < len(s1){//覆盖全部子串
            sfreq[s2[right+1] - 'a']++
            if sfreq[s2[right+1]- 'a'] <= tfreq[s2[right+1] - 'a'] {
                count++
            }
            right++
        }else{//找到覆盖子串
            if count == right-left+1 && count == len(s1) {//匹配字符个数 和 区间长度相等
                return true
            }
            //缩小子串范围 
            if sfreq[s2[left] - 'a'] == tfreq[s2[left] - 'a'] { //若是匹配字符,减小匹配字符
                count--
            }
            sfreq[s2[left] - 'a']--
            left++
        }
    }
    return false
} 

大神写法:

package leetcode

func checkInclusion(s1 string, s2 string) bool {
	var freq [256]int
	if len(s2) == 0 || len(s2) < len(s1) {
		return false
	}
	for i := 0; i < len(s1); i++ {
		freq[s1[i]-'a']++
	}
	left, right, count := 0, 0, len(s1)

	for right < len(s2) {
		if freq[s2[right]-'a'] >= 1 {
			count--
		}
		freq[s2[right]-'a']--
		right++
		if count == 0 {
			return true
		}
		if right-left == len(s1) {
			if freq[s2[left]-'a'] >= 0 {
				count++
			}
			freq[s2[left]-'a']++
			left++
		}
	}
	return false
}

7.0167 Two Sum II - Input array is sorted

在这里插入图片描述

思想:夹逼滑动窗口,
func twoSum(numbers []int, target int) []int {
    left, right := 0, len(numbers)-1
        if len(numbers) <= 1 {
        return nil
    }

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

//map 
func twoSum(numbers []int, target int) []int {
    m := make(map[int]int)
    for i := 0;i < len(numbers); i++{
        another := target - numbers[i]
        if _,ok := m[another]; ok{
            return []int{m[another]+1,i+1}
        }
        m[numbers[i]] = i
    }
    return nil
}

8.0016 3Sum Closest

在这里插入图片描述
在这里插入图片描述

//循环n躺,每趟夹逼O(n),共O(n^2)
func threeSumClosest(nums []int, target int) int {
	sort.Ints(nums)
	diff, res := math.MaxInt32, 0
	for i := 0; i < len(nums); i++ {
		if i > 0 && nums[i] == nums[i-1] {
			continue
		}
		for j, k := i+1, len(nums)-1; j < k; {
			sum := nums[i] + nums[j] + nums[k]
			if abs(sum - target) < diff {// 差值小于原来差值,更新差值
				diff = abs(sum - target)
				res = sum
			}
			if sum - target < 0 {// 和小了,要增大和,即增加左边界j
				j++
			}else{// 和大了,要减小和,即缩小右边界
				k--
			}
		}
	}
	return res
}

func abs(a int) int {
	if a > 0 {
		return a
	}
	return -a
}

9.0287 Find the Duplicate Number

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//map法(自己写的 时空复杂度较高 ,不推荐)
// func findDuplicate(nums []int) int {
//     m := make(map[int]int)
//     for i := 0 ;i <len(nums); i++{
//         if _,ok := m[nums[i]]; ok {
//             return nums[i]
//         }
//         m[nums[i]] = i
//     }
//     return 0
// }   

//快慢指针 数组就是映射关系:对于3 1 3 4 2,f(0)=3 f(1)=1 f(2)=3 f(3)=4 f(4)=2 
func findDuplicate(nums []int) int {
    if len(nums) <= 1 {
        return 0
    }
    slow, fast := nums[0],nums[0]
    for {
        fast = nums[nums[fast]]
        slow = nums[slow]
        //一定有环,不用无环判断
        if slow == fast {
            break
        }
    }
    fast = nums[0]
    for fast != slow {
        fast = nums[fast]
        slow = nums[slow]
    }
    return slow

}

//二分法
// func findDuplicate(nums []int) int {
//     low, high := 0,len(nums)-1
//     for low < high {
//         mid, count  := (low+high)/2, 0
//         for _,num := range nums {
//             if num <= mid {
//                 count++//统计小于mid个数
//             }
//         }
//         if count > mid {
//             //重复数在[low,mid]间
//             high = mid
//         }else{
//             //重复数在(mid,high]间
//             low = mid + 1
//         }
//     }
//     return low
// }   

//有序数组,值与下标差值只会越来越大
func findDuplicate(nums []int) int {
    sort.Ints(nums)
    diff := -1
    for i := 0; i < len(nums); i++ {
        if nums[i]-i  < diff {
            return nums[i]
        }
        diff = nums[i] - i
    }
    return 0
}   

10.0142 Linked List Cycle II

在这里插入图片描述
自己总结:
设fast每次走两步,slow每次走1步,fast,slow两指针分别走f,s步

  1. f = 2s (fast每轮走2步)
  2. f = s + nb (双指针都走过a步,fast比slow多走环的整数倍)
  3. 由1,2得 f = 2nb , s = nb
  4. 而 走到环入口节点需要k步, k = a + nb,故想办法让slow再走a步即可
  5. 让fast从头开始,直至两指针重合
    力扣详细解题过程
/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func detectCycle(head *ListNode) *ListNode {
    if head == nil || head.Next == nil {
        return nil
    }
    slow, fast := head, head
    for {
        fast = fast.Next.Next
        slow = slow.Next
        if fast == nil || slow == nil || fast.Next == nil { //指针为空 或者 为节点下一跳为空 都不是环
            return nil
        }
        if slow == fast {  //否则就是环,找到slow = nb 位置
            break
        }
    }
    // 从头开始slow 再走a步 就到环入口节点
    fast = head
    for slow != fast {
        fast = fast.Next
        slow = slow.Next
    }
    return slow

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值