贪心
- 盛水最多的容器
/* 左右指针最开始指向最两边,此时底最大,然后往里面缩小,此时将低的淘汰,往里缩. */ 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 }
两数之和题型
从数组中找出几个数,其和满足某要求
先排序,再固定端点值(相对),减少变动的量,将题型转换为两数之和类型
-
两数之和
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 }
-
三数之和
此题要求不能出现重复的结果!
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 }
-
最接近的三数之和
和上面类似,只不过判定条件变了
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 }
-
四数之和
这个需要定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 }
快慢指针
利用快慢指针求解
-
删除链表倒数第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 }
-
爱生气的书店老板
维持一个矩阵从左往右,计算和即可
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 }
滑动窗口
这种一般是保持一个由双指针围成的满足条件的区间,快指针用于探索未知的元素,而慢指针则用于剔除区间内的元素,使区间满足条件
求满足条件的子区间
-
A 中由 K 个不同整数组成的最长子数组的长度
-
替换后的最长重复字符
翻译为 找到包含重复字母的最长区间
即 对于每个区间(长度为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 }
-
最大连续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 }
-
-
A 中由最多 K 个不同整数组成的子数组的个数
其实只用修改一个地方即可
-
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 }
-
至少有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
-
通过连接另一个数组的子数组得到一个数组
其实就是规定起始值的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题解的大佬们
跟着三叶姐的题单走的
三叶姐的双指针题单
未完待续…