文章目录
- 一、思想或常用方法
- 二、力扣习题
- 1.0003 Longest Substring Without Repeating Characters
- 2.0076 Minimum Window Substring
- 3.0209 Minimum Size Subarray Sum
- 4.0424 Longest Repeating Character Replacement
- 5.0567 Permutation in String
- 7.0167 Two Sum II - Input array is sorted
- 8.0016 3Sum Closest
- 9.0287 Find the Duplicate Number
- 10.0142 Linked List Cycle II
一、思想或常用方法
- 双指针滑动窗⼝的经典写法。右指针不断往右移,移动到不能往右移动为⽌(具体条件根据题⽬⽽定)。当右指 针到最右边或不能右移(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步
- f = 2s (fast每轮走2步)
- f = s + nb (双指针都走过a步,fast比slow多走环的整数倍)
- 由1,2得 f = 2nb , s = nb
- 而 走到环入口节点需要k步, k = a + nb,故想办法让slow再走a步即可
- 让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
}