题目来自:Leetcode专项学习计划——LeetCode热题100,共 100 道题。
本文章只记录当前(2024.05.25)掌握得不太好的题目,完整的题目请参考上面的链接。
基础篇
最长连续序列
128. 最长连续序列
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{100, 4, 200, 1, 3, 2}
r := longestConsecutive(nums)
fmt.Println(r)
}
func longestConsecutive(nums []int) int {
// ......
}
|
分析
本题要求时间复杂度为O(n)。
自己的一些思考:
自己的思路1:开辟容量为 maxVal-minVal+1 的数组,将每个位置出现的次数记录下来,然后统计最长的连续序列。
这样写存在的问题是,num 可能是负数,就不好使用数组的下标来表示 num 了。
自己的思路2:从大到小,使用 map 记录当前数值连续的数量。
这里有一种 map 和动态规划相结合的思想。
但这样也存在问题:元素出现的顺序不是从大到小的,元素是可以交换顺序的。
参考答案的方法:既然元素之间的顺序不重要,那么我们就可以直接将所有的元素存入 set(numSet)中,
遍历数组,如果 num-1 不在 numSet 集合中,那么就可以从 num 开始往右搜索。(这里还是比较巧妙的)
如果 num+1 在 numSet 集合中,则当前连续的长度加 1(循环这个过程,获得最大的长度)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func longestConsecutive(nums []int) int {
numSet := map[int]bool{}
for _, num := range nums {
numSet[num] = true
}
maxLen := 0
for num := range numSet {
if !numSet[num-1] {
curNum := num
curLen := 1
for numSet[curNum+1] { // 一直找从num开始的所有连续数字
curNum++
curLen++
}
if maxLen < curLen {
maxLen = curLen
}
}
}
return maxLen
}
|
字母异位词分组
49. 字母异位词分组
1
2
3
4
5
6
7
8
|
func main() {
strs := []string{"eat", "tea", "tan", "ate", "nat", "bat"}
r := groupAnagrams(strs)
fmt.Println(r) // [[eat tea ate] [tan nat] [bat]]
}
func groupAnagrams(strs []string) [][]string {
// ......
}
|
分析
参考答案1:对字符串中的字母进排序,如果是字母异位词,则排序之后得到的字符序列一定是相同的。
把排序之后的字符串作为 key 存入 map 中,map 的 value 存相同 key 对应的字符串集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func groupAnagrams(strs []string) [][]string {
strMap := make(map[string][]string) // 存字母异位词
for _, str := range strs {
s := []byte(str)
sort.Slice(s, func(i, j int) bool {
return s[i] < s[j]
})
sortedS := string(s)
strMap[sortedS] = append(strMap[sortedS], str)
}
res := make([][]string, len(strMap)) // 最后的返回值
i := 0
for _, strSlice := range strMap {
res[i] = strSlice
i++
}
return res
}
|
参考答案2:开辟一个长度为 26 的数组 byteCount,将 strs 数组的每个元素 str 中每个字母出现的次数进行统计。
如果是字母异位词,那么每个字母出现次数就是相等的,也就是 byteCount 是相等的。
所以我们可以构建一个以 byteCount 为 key 的 map,如果 byteCount 相同,则将其添加到 byteCount 对应的 map 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func groupAnagrams(strs []string) [][]string {
map01 := make(map[[26]int][]string)
for _, str := range strs {
byteCount := [26]int{}
for _, byte := range str {
byteCount[byte-'a']++ // 存放每个字母出现的次数
}
map01[byteCount] = append(map01[byteCount], str)
}
res := make([][]string, len(map01))
i := 0
for _, strSlice := range map01 { // 将map中的value转移到res切片中
res[i] = strSlice
i++
}
return res
}
|
盛最多水的容器
11. 盛最多水的容器
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{1, 8, 6, 2, 5, 4, 8, 3, 7}
r := maxArea(nums)
fmt.Println(r) // 49
}
func maxArea(height []int) int {
// ......
}
|
分析
自己的解法:双重循环,第 55 个测试用例超时。
1
2
3
4
5
6
7
8
9
10
11
12
|
func maxArea(height []int) int {
// 左右双指针,取左右高度较矮的作为高度
n := len(height)
realH, largestArea := 0, 0
for l := 0; l < n; l++ {
for r := n - 1; r >= 0; r-- {
realH = min(height[l], height[r])
largestArea = max(largestArea, realH*(r-l))
}
}
return largestArea
}
|
贪心算法 + 双指针:
参考:对O(n)的算法写一下自己的理解。
最开始 l 和 r 分别指向 0 位置和 n-1 位置,现在我们需要需要移动 l 和 r,无论我们怎么移动,都会使容器的宽度减 1。
这时候我们就应该尽量让高度 h 更大,所以我们应该移动高度更低的指针,以获得更大的容量。
还有一个比较有哲学意味的回答:感觉这个移动有点博弈论的味了,感兴趣可以看看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func maxArea(height []int) int {
maxVal, realH := 0, 0
n := len(height)
l, r := 0, n-1
for l < r {
realH = min(height[l], height[r])
if (r-l)*realH > maxVal {
maxVal = (r - l) * realH
}
if height[l] <= height[r] {
l++
} else {
r--
}
}
return maxVal
}
|
找到字符串中所有字母异位词
438. 找到字符串中所有字母异位词
1
2
3
4
5
6
7
8
|
func main() {
s, p := "cbaebabacd", "abc"
r := findAnagrams(s, p)
fmt.Println(r) // [0,6]
}
func findAnagrams(s string, p string) []int {
// ......
}
|
分析
方法1:
自己写的版本,每个位置都需要去判断滑动窗口内的字符串是否合法。
滑动窗口的大小为 len(p),判断滑动窗口内每个字符出现的次数与 p 中每个字符出现的次数是否相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func findAnagrams(s string, p string) []int {
res := make([]int, 0)
lenP := len(p)
pChars := [26]byte{}
for _, pStr := range p {
pChars[pStr-'a']++
}
for i := range s {
sChars := [26]byte{}
if i+lenP > len(s) {
break
}
for _, sStr := range s[i : i+lenP] { // 判断滑动窗口中的字符串是否合法
sChars[sStr-'a']++
}
if sChars == pChars {
res = append(res, i)
}
}
return res
}
|
上面这种写法在每个滑动窗口中都需要统计每个字母出现的次数,时间复杂度较高。
方法2:
参考了答案,先一起遍历 s 和 p,判断 s[:lenP] 部分是否是和 p 是字母异位词,如果是,则将下标 0 添加到 res 中
然后就可以移动滑动窗口了,在移动滑动窗口时,移出去的元素个数应该减 1,新添加入滑动窗口的元素个数应该加 1,即:sChars[s[i]-‘a’]–,sChars[s[i+lenP]-‘a’]++。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func findAnagrams(s string, p string) []int {
if len(s) < len(p) { // 剪枝
return nil
}
res := make([]int, 0)
lenP := len(p)
pChars, sChars := [26]byte{}, [26]byte{}
for i, pStr := range p {
pChars[pStr-'a']++
sChars[s[i]-'a']++
}
if sChars == pChars {
res = append(res, 0)
}
for i := 0; i < len(s)-lenP; i++ { // 滑动窗口的移动过程,i是移除滑动窗口的位置
sChars[s[i]-'a']-- // 移出
sChars[s[i+lenP]-'a']++ // 移入
if sChars == pChars {
res = append(res, i+1) // 加入到res的应该是i+1,而不是i
}
}
return res
}
|
合并区间
56. 合并区间
1
2
3
4
5
6
7
8
9
|
func main() {
//intervals := [][]int{{1, 3}, {2, 6}, {8, 10}, {15, 18}}
intervals := [][]int{{2, 3}, {4, 5}, {6, 7}, {8, 9}, {1, 10}}
r := merge(intervals)
fmt.Println(r)
}
func merge(intervals [][]int) [][]int {
// ......
}
|
分析
计算重叠区间。
先将数组按照左区间值进行排序,然后计算当前区间的最右侧位置,如果大于等于下一个元素的左区间位置,那么就说明存在区间的重叠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func merge(intervals [][]int) [][]int {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
res := make([][]int, 1)
res[0] = intervals[0]
for i := 1; i < len(intervals); i++ {
if intervals[i][0] <= res[len(res)-1][1] { // 存在重叠。注意这里比较的右区间位置是res[len(res)-1][1],而不是intervals[i-1][0](这里很关键)
rightIndex := max(intervals[i][1], res[len(res)-1][1])
res[len(res)-1][1] = rightIndex // 更新右区间的值
} else {
res = append(res, intervals[i])
}
}
return res
}
|
轮转数组
189. 轮转数组
1
2
3
4
5
6
7
8
|
func main() {
nums, k := []int{1, 2, 3, 4, 5, 6, 7}, 3
rotate(nums, k)
fmt.Println(nums) // [5 6 7 1 2 3 4]
}
func rotate(nums []int, k int) {
// ......
}
|
分析
方法1:
自己写的方法,每轮都将元素后移一个位置,时间复杂度为 O(n*k),当k较大时,时间复杂度很高。
1
2
3
4
5
6
7
8
9
10
11
12
|
func rotate(nums []int, k int) {
n := len(nums)
k = k % n
for k > 0 {
temp := nums[n-1]
for i := n - 1; i >= 1; i-- {
nums[i] = nums[i-1]
}
nums[0] = temp
k--
}
}
|
方法2:
参考别人的代码:先将 nums 整体翻转,再将 0 到 k 之间的元素进行翻转,最后将 k 到 n 之间的元素进行翻转,时间复杂度为 O(n)。
这样处理是比较巧妙的,反正我自己想是想不到这种方法的,哈哈哈。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func rotate(nums []int, k int) {
reverse := func(nums []int) {
l, r := 0, len(nums)-1
for l < r {
nums[l], nums[r] = nums[r], nums[l]
l++
r--
}
}
reverse(nums)
reverse(nums[:k])
reverse(nums[k:])
}
|
除自身以外数组的乘积
238. 除自身以外数组的乘积
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{1, 2, 3, 4}
r := productExceptSelf(nums)
fmt.Println(r) // [24 12 8 6]
}
func productExceptSelf(nums []int) []int {
// ......
}
|
分析
参考题解:前缀积&后缀积。
先从前往后遍历,计算前缀积(不算自己);然后从后往前遍历,计算后缀积(也不算自己)。
使用一个数组 res 存放前缀积的值,最后乘上后缀积之后返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func productExceptSelf(nums []int) []int {
pre, post := 1, 1
res := make([]int, len(nums))
for i := 0; i < len(nums); i++ {
res[i] = pre
pre *= nums[i]
}
for j := len(nums) - 1; j >= 0; j-- {
res[j] *= post
post *= nums[j]
}
return res
}
|
螺旋矩阵
54. 螺旋矩阵
1
2
3
4
5
6
7
8
9
|
func main() {
matrix := [][]int{{7}, {9}, {6}}
//matrix := [][]int{{2, 5}, {8, 4}, {0, 1}}
r := spiralOrder(matrix)
fmt.Println(r) // [7,9,6]
}
func spiralOrder(matrix [][]int) []int {
// ......
}
|
分析
本题是 59. 螺旋矩阵 II 的进阶版本,行数和列数不一定相等。
我们需要使用一个变量 count 统计已经遍历过的元素数量,如果 count 等于了 m*n,那么就应该直接返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
func spiralOrder(matrix [][]int) []int {
// 遍历matrix数组的每一条边
m := len(matrix)
n := len(matrix[0])
res := make([]int, m*n)
count := 0 // 遍历过的元素数量
i, j, startX, offset := 0, 0, 0, 1
for c := 0; c < max(m, n)/2; c++ { // c表示圈数
i, j = startX, startX
for ; j < n-offset; j++ { // 每条边不包含最后一个元素
if count == m*n {
return res
}
res[count] = matrix[i][j]
count++
}
for ; i < m-offset; i++ {
if count == m*n {
return res
}
res[count] = matrix[i][j]
count++
}
for ; j > startX; j-- {
if count == m*n {
return res
}
res[count] = matrix[i][j]
count++
}
for ; i > startX; i-- {
if count == m*n {
return res
}
res[count] = matrix[i][j]
count++
}
offset++
startX++
}
if count == m*n-1 { // 最中心的位置如果没有遍历到,则最后再赋值
res[count] = matrix[m/2][n/2]
}
return res
}
|
搜索二维矩阵II
240. 搜索二维矩阵 II
本题较为简单,可以减少练习次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func main() {
matrix := [][]int{
{1, 4, 7, 11, 15},
{2, 5, 8, 12, 19},
{3, 6, 9, 16, 22},
{10, 13, 14, 17, 24},
{18, 21, 23, 26, 30}}
target := 5
r := searchMatrix(matrix, target)
fmt.Println(r) // true
}
func searchMatrix(matrix [][]int, target int) bool {
// ......
}
|
分析
先列二分,再行二分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
func searchMatrix(matrix [][]int, target int) bool {
m := len(matrix)
n := len(matrix[0])
colLeft, colRight := 0, m-1
for colLeft <= colRight {
colMid := colLeft + (colRight-colLeft)>>1
if target == matrix[colMid][0] {
return true
} else if target < matrix[colMid][0] { // 只有target小的时候才能二分
colRight = colMid - 1
} else {
break
}
}
for i := colLeft; i <= colRight; i++ {
rowLeft, rowRight := 0, n-1
for rowLeft <= rowRight {
rowMid := rowLeft + (rowRight-rowLeft)>>1
if target == matrix[i][rowMid] {
return true
} else if target > matrix[i][rowMid] {
rowLeft = rowMid + 1
} else {
rowRight = rowMid - 1
}
}
}
return false
}
|
最小栈
本题较为简单,可以减少练习的次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type MinStack struct {
}
func Constructor() MinStack {
}
func (this *MinStack) Push(val int) {
}
func (this *MinStack) Pop() {
}
func (this *MinStack) Top() int {
}
func (this *MinStack) GetMin() int {
}
|
分析
参考了答案:
维护一个正常栈和一个最小栈(二者长度是相同的),正常栈的元素正常添加,最小栈始终添加当前元素的最小值。也就是在最小栈中加入 min(num, minStack[len(minStack)-1])。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
type MinStack struct {
stack []int
minStack []int
}
func Constructor() MinStack {
return MinStack{
stack: []int{},
minStack: []int{math.MaxInt},
}
}
func (this *MinStack) Push(val int) {
this.stack = append(this.stack, val)
minTop := this.minStack[len(this.minStack)-1]
this.minStack = append(this.minStack, min(minTop, val))
}
func (this *MinStack) Pop() {
this.stack = this.stack[:len(this.stack)-1]
this.minStack = this.minStack[:len(this.minStack)-1]
}
func (this *MinStack) Top() int {
return this.stack[len(this.stack)-1]
}
func (this *MinStack) GetMin() int {
return this.minStack[len(this.minStack)-1]
}
|
在排序数组中查找元素的第一个和最后一个位置
1
2
3
4
5
6
7
8
|
func main() {
nums, target := []int{5, 7, 7, 8, 8, 10}, 8
r := searchRange(nums, target)
fmt.Println(r) // [3,4]
}
func searchRange(nums []int, target int) []int {
// ......
}
|
分析
两次二分查找,这个二分查找必须返回 target 最左侧的下标值。
先查找左边的位置 left,在查找右边的位置 right(查找 target+1 的位置,然后减 1)。
最后需要检查返回的 left 是否合法,如果不合法,则返回 []int{-1, -1}。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func searchRange(nums []int, target int) []int {
// 返回target最左侧的位置
binarySearch := func(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)>>1
if target > nums[mid] {
l = mid + 1
} else {
r = mid - 1
}
}
return l
}
left := binarySearch(nums, target)
right := binarySearch(nums, target+1) - 1
if left == len(nums) || nums[left] != target { // 可能存在数组中没有找到target的情况
return []int{-1, -1}
}
return []int{left, right}
}
|
多数元素
169. 多数元素
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{2, 2, 1, 1, 1, 2, 2}
r := majorityElement(nums)
fmt.Println(r) // 2
}
func majorityElement(nums []int) int {
// ......
}
|
分析
参考官方题解:方法五:Boyer-Moore 投票算法
Boyer-Moore投票算法,使用两个变量,一个保存候选众数candidate,一个记录它出现的次数count。
遍历数组,如果元素值与candidate不相等,则count–。
如果元素值与candidate相等,则count++。
如果count等0了,就换一个候选数组为当前元素值。
遍历完成后,candidate中保存的就是众数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func majorityElement(nums []int) int {
candidate, count := 0, 0
for _, num := range nums {
if count == 0 {
candidate = num
}
if num == candidate {
count++
} else {
count--
}
}
return candidate
}
|
颜色分类
75. 颜色分类
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{2, 0, 2, 1, 1, 0}
sortColors(nums)
fmt.Println(nums) // [0,0,1,1,2,2]
}
func sortColors(nums []int) {
// ......
}
|
分析
方法一:单指针。 参考官方题解:方法一:单指针
遍历两遍数组,第1遍将全部的0放到最前面,并使用count0统计0的个数。
再次遍历数组(从count0开始),将所有的1放到最前面。
这样所有的2自然也就在最后面了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func sortColors(nums []int) {
countTarget := 0
var swap func(nums []int, target int)
swap = func(nums []int, target int) { // 需要把target放到数组最前面
countTarget = 0
for i := 0; i < len(nums); i++ {
if nums[i] == target {
nums[countTarget], nums[i] = nums[i], nums[countTarget]
countTarget++
}
}
}
swap(nums, 0)
swap(nums[countTarget:], 1)
}
|
进阶篇
接雨水
【困难】42. 接雨水
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func main() {
height := []int{0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1}
r := trap(height)
fmt.Println(r) // 6
// 010210132121
// o
// o×××oo×o
// o×oo×oooooo
// ×位置就是可以接雨水的位置,总共可以接6个单位的雨水。
}
func trap(height []int) int {
// ......
}
|
分析
方法1:找到每个位置左侧的最小高度 lHeight 和右侧的最小高度 rHeight,每个位置能接的雨水量就等于 min(lHeight, rHeight) - height[i]。
lHeight 可以根据一个单调的数组 left 来得到,也就是从左到右遍历 height 数组,left[i]=max(left[i-1], height[i])。
同理,rHeight 也可以根据一个单调的数组 right 来得到,right 数组的计算需要从右到左遍历 height 数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func trap(height []int) int {
n := len(height)
left, right := make([]int, n), make([]int, n)
left[0], right[n-1] = height[0], height[n-1]
for i := 1; i < n; i++ {
left[i] = max(left[i-1], height[i])
}
for i := n - 2; i >= 0; i-- {
right[i] = max(right[i+1], height[i])
}
sum := 0
for i, h := range height {
sum += min(left[i], right[i]) - h
}
return sum
}
|
对上面方法在空间上的优化:使用 l 和 r 双指针,在遍历数组的同时更新 lMax 和 rMax 的值(也就是使用两个变量来代替前面的两个数组)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func trap(height []int) int {
n := len(height)
l, r := 0, n-1
lMax, rMax, sum := 0, 0, 0
for l < r {
lMax = max(lMax, height[l]) // 遍历过程中,更新lMax和rMax的值
rMax = max(rMax, height[r])
if lMax < rMax { // 如果lMax更小,则决定桶高度的就是lMax,计算lMax与height[l]的差值,加入到sum中,并右移l指针
sum += lMax - height[l]
l++
} else { // 反之,rMax更小,决定桶高度的就是rMax
sum += rMax - height[r]
r--
}
}
return sum
}
|
和为K的子数组
560. 和为 K 的子数组
1
2
3
4
5
6
7
8
|
func main() {
nums, k := []int{1, -1, 0}, 0
r := subarraySum(nums, k)
fmt.Println(r) // 3
}
func subarraySum(nums []int, k int) int {
// ......
}
|
分析
方法1:自己写的版本,利用前缀和数组就行求解,时间复杂度较大。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func subarraySum(nums []int, k int) int {
count := 0
preSum := make([]int, len(nums)+1)
preSum[0] = 0
for i, num := range nums { // 构建前缀和数组
preSum[i+1] = preSum[i] + num
}
for i := 0; i < len(preSum); i++ { // i遍历preSum的每个位置,j遍历i后面的每个位置
for j := i + 1; j < len(preSum); j++ {
if preSum[j]-preSum[i] == k {
count++
}
}
}
return count
}
|
方法2:参考了答案,使用前缀和+map。
preSum[i] = preSum[i−1] + nums[i]。
我们需要在 0 到 i-1 之间找到一个位置 j,使得:pre[i]−pre[j−1]==k,然后统计满足这样等式的 j 有多少个。
上面等式移项之后为:pre[j−1]==pre[i]−k,也就是说,我们要计算有多少个前缀和满足上面这个等式。
我们可以使用 map 来存已经存在的前缀和,map 的 key 为前缀和,map 的 value 为该前缀和出现的次数。
遍历 nums 数组,使用 pre 变量保存当前的前缀和,如果 map 中存在 key 为 pre−k 的元素,说明从某个位置到当前位置的连续子数组的和为 k,我们将对应的次数累加到结果中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func subarraySum(nums []int, k int) int {
preSum := 0
count := 0
preSumMap := make(map[int]int, 1) // key为前缀和,value为该前缀和出现的次数
preSumMap[0] = 1 // 前缀和为0至少出现了1次(这里的初始化很重要)
for _, num := range nums {
// 计算当前的前缀和,由于后一个位置的前缀和可以由前一个位置得到,而所有的前缀和都保存到了map中,
// 所以,这里只需要一个不断迭代的变量就可以了,不需要一个前缀和数组
preSum += num
if preSumCount, exist := preSumMap[preSum-k]; exist { // 找到了满足条件的前缀和
count += preSumCount
}
preSumMap[preSum]++
}
return count
}
|
滑动窗口最大值
【困难】239. 滑动窗口最大值
1
2
3
4
5
6
7
8
9
10
|
func main() {
//nums, k := []int{1, 3, -1, -3, 5, 3, 6, 7}, 3 // [3 3 5 5 6 7]
//nums, k := []int{7, 2, 4}, 2 // [7 4]
nums, k := []int{1, 3, 1, 2, 0, 5}, 3 // [3,3,2,5]
r := maxSlidingWindow(nums, k)
fmt.Println(r)
}
func maxSlidingWindow(nums []int, k int) []int {
// ......
}
|
分析
维护一个单调不增(可以相等)队列,在元素加入队列时,把前面比它小的元素全部删除,再加入队列。
想清楚维护什么样的队列很重要,不然会存在各种问题。
在元素出队时,如果出队的元素和队首元素相等,才真正地出队,否则就什么也不做。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
func maxSlidingWindow(nums []int, k int) []int {
queue := make([]int, 1)
queue[0] = nums[0]
push := func(num int) {
if len(queue) == 0 || num < queue[len(queue)-1] {
queue = append(queue, num)
} else {
for len(queue) > 0 && num > queue[len(queue)-1] { // 把前面小于num的元素全部删除
queue = queue[:len(queue)-1]
}
queue = append(queue, num)
}
}
pop := func(num int) {
if num == queue[0] {
queue = queue[1:]
}
}
res := make([]int, len(nums)-k+1)
for i := 1; i <= k-1; i++ { // 先把前k个元素加入到队列中
push(nums[i])
}
res[0] = queue[0]
for i := k; i < len(nums); i++ {
pop(nums[i-k])
push(nums[i])
res[i-k+1] = queue[0]
}
return res
}
|
最小覆盖子串
【困难】76. 最小覆盖子串
本题比较复杂,建议多加理解和练习。
1
2
3
4
5
6
7
8
|
func main() {
s, t := "ADOBECODEBANC", "ABC"
r := minWindow(s, t)
fmt.Println(r) // "BANC"
}
func minWindow(s string, t string) string {
// ......
}
|
分析
参考了答案:使用滑动窗口+哈希表。
参考题解:最小覆盖子串-超级无敌最小白式整理
两个指针 l 和 r,最初都指向 0 位置,然后移动 r 位置,直到t中所有的字符都在滑动窗口中出现,然后就可以移动l了。
然后构建两个哈希表,一个存 t 中所有出现的字符和出现的次数(tMap),另一个存现在滑动窗口中有的 t 中出现的字符的次数(sMap)。
具体如下:将 t 中所有表的字符添加到哈希 tMap 中,然后判断r位置的字符是否在 tMap 中存在,如果不存在,则直接将 r 位置右移,然后继续下一次循环
如果 sMap[rStr] 小于 tMap[rStr],则使用 count 统计 s 中出现 t 中相同元素的次数,count++。然后将sMap[rStr]++、r++。
当 count 等于 len(t) 时,说明 t 中所有的字符都在滑动窗口中出现了,就可以移动 l 了。(这是一个循环的过程,循环条件为:count==len(t))。
在移动之前,先记录最小长度 minLen 和当前的 left 位置作为起始位置 start。
判断滑动窗口最左侧位置的元素是否在 tMap 中,如果不在,则直接右移 l,并继续下一次循环。
如果sMap(l)==tMap(l),说明这个是需要的元素,则将 sMap[s[l]]–、l++、count–,这样之后前面循环条件就不成立了,退出移动 l 的过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
func minWindow(s string, t string) string {
if len(s) < len(t) {
return ""
}
tMap, sMap := make(map[byte]int), make(map[byte]int)
for i := 0; i < len(t); i++ {
tMap[t[i]]++
}
l, r, start, minLen, count := 0, 0, 0, len(s)+1, 0
for r < len(s) {
if tMap[s[r]] == 0 { // r位置的元素在tMap中不存在
r++
continue
}
if sMap[s[r]] < tMap[s[r]] {
count++
}
sMap[s[r]]++
r++
for count == len(t) { // 这是一个循环的过程
if r-l < minLen {
minLen = r - l // 记录start后面到r的距离,不用加1
start = l
}
if tMap[s[l]] == 0 { // 滑动窗口最左侧的元素不在t中
l++
continue
}
if sMap[s[l]] == tMap[s[l]] {
count--
}
sMap[s[l]]--
l++
}
}
if minLen == len(s)+1 { // 如果最小长度还为初始值,说明没有符合条件的子串
return ""
}
return s[start : start+minLen]
}
|
缺失的第一个正数
【困难】41. 缺失的第一个正数
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{3, 4, -1, 1}
r := firstMissingPositive(nums)
fmt.Println(r) // 2
}
func firstMissingPositive(nums []int) int {
// ......
}
|
分析
如果 nums 数组的长度为 n,那么缺失的第 1 个正数只可能在 [1, n+1] 的范围内。
因为如果 1 到 n 的每个数都出现了,那么缺失的就是 n+1,,否则缺失的就是 [1, n] 范围内的某个数。
我们可以遍历 nums 数组,把合法的 nums[i] 放到它应该在的位置上。
什么是应该在的位置呢?比如:1 应该放在 0 位置,2 应该放在 1 位置……
所以遇到 nums[i] 应该放在 nums[nums[i]-1] 位置。
最后再次遍历 nums 数组,如果 nums[i]和 i+1 不相等,说明 i+1 位置就是第一个缺失的正整数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func firstMissingPositive(nums []int) int {
n := len(nums)
for i := 0; i < n; i++ {
for nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i]-1] {
nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i] // 交换两个位置的值
}
}
for i := 0; i < len(nums); i++ {
if nums[i] != i+1 {
return i + 1
}
}
return n + 1
}
|
矩阵置零
73. 矩阵置零
1
2
3
4
5
6
7
8
|
func main() {
matrix := [][]int{{1, 1, 1}, {1, 0, 1}, {1, 1, 1}}
setZeroes(matrix)
fmt.Println(matrix) // [[1 0 1] [0 0 0] [1 0 1]]
}
func setZeroes(matrix [][]int) {
// ......
}
|
分析
参考:O(1)空间
使用数组的第 0 行和第 0 列标记每列和每行是否存在 0。
但是对于第 0 行和第 0 列要设置一个标志位,为了防止第 0 行和第 0 列也有 0的情况。
先遍历第 0 行和第 0 列,使用两个标识变量记录它们是否存在 0。
后面遍历其他元素时,i 和 j 从 1 开始。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
func setZeroes(matrix [][]int) {
m := len(matrix)
n := len(matrix[0])
col_zero, row_zero := false, false // 记录第0列个第0行是否存在0
for i := 0; i < m; i++ {
if matrix[i][0] == 0 {
col_zero = true
break
}
}
for j := 0; j < n; j++ {
if matrix[0][j] == 0 {
row_zero = true
break
}
}
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
if matrix[i][j] == 0 {
matrix[i][0] = 0
matrix[0][j] = 0
}
}
}
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
if matrix[i][0] == 0 || matrix[0][j] == 0 {
matrix[i][j] = 0
}
}
}
if col_zero {
for i := 0; i < m; i++ {
matrix[i][0] = 0
}
}
if row_zero {
for j := 0; j < n; j++ {
matrix[0][j] = 0
}
}
}
|
两数相加
2. 两数相加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
type ListNode struct {
Val int
Next *ListNode
}
func main() {
node13 := &ListNode{9, nil}
node12 := &ListNode{9, node13}
node11 := &ListNode{9, node12}
node23 := &ListNode{9, nil}
node22 := &ListNode{9, node23}
node21 := &ListNode{9, node22}
r := addTwoNumbers(node11, node21)
for r != nil {
fmt.Printf("%d ", r.Val)
r = r.Next
}
}
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
// ......
}
|
分析
始终以 l1 为基准,将 l2 的值加到 l1 中,如果 l1 为空了,使用 pre1 指向 l1 的最后一个位置,让 pre1 的 next 等于 l2。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
oldL1 := l1 // 指向最初l1的头结点,用于最后返回
addFlag := 0 // 进位值
var pre1 *ListNode
for l1 != nil && l2 != nil {
temp := l1.Val + l2.Val + addFlag // 注意这里要用temp临时保存计算的值,因为l1.Val的值在这里会被修改,所以后面求进位值时,不能使用l1.Val + l2.Val + addFlag(此时l1.Val的值已经发生了变化)
l1.Val = temp % 10
addFlag = temp / 10
pre1 = l1
l1 = l1.Next
l2 = l2.Next
}
if l1 == nil {
pre1.Next = l2
l1 = l2
}
for l1 != nil {
temp := l1.Val + addFlag
l1.Val = temp % 10
addFlag = temp / 10
if addFlag == 0 { // 如果没有进位值了,就可以不用再进行计算了
break
}
pre1 = l1
l1 = l1.Next
}
if addFlag == 1 {
pre1.Next = &ListNode{1, nil}
}
return oldL1
}
|
K个一组翻转链表
【困难】25. K 个一组翻转链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
type ListNode struct {
Val int
Next *ListNode
}
func main() {
node5 := &ListNode{5, nil}
node4 := &ListNode{4, node5}
node3 := &ListNode{3, node4}
node2 := &ListNode{2, node3}
node1 := &ListNode{1, node2}
k := 2
newHead := reverseKGroup(node1, k)
for newHead != nil {
fmt.Printf("%d ", newHead.Val) // 2 1 4 3 5
newHead = newHead.Next
}
}
func reverseKGroup(head *ListNode, k int) *ListNode {
// ......
}
|
分析
参考了答案:这种递归的写法很简洁,也很好理解。
定义一个辅助函数,传入需要翻转链表的头结点和k,翻转该链表的前k个结点,
该辅助函数有两个返回值,一个是翻转之后的头结点(用于链表连接),一个是该翻转的头结点(用于下一次翻转)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func reverseKGroup(head *ListNode, k int) *ListNode {
if head == nil || k == 1 {
return head
}
curLen := 0
curHead := head
for curHead != nil {
curLen++
curHead = curHead.Next
}
if curLen < k { // 判断当前链表长度是否大于等于k
return head
}
reversedHead, nextHead := reverseK(head, k)
head.Next = reverseKGroup(nextHead, k) // 递归地处理剩余的链表,head的next为翻转之后的头结点,这样就把链表连接起来了
return reversedHead // 向上层返回翻转之后的头结点
}
func reverseK(head *ListNode, k int) (*ListNode, *ListNode) {
var pre *ListNode
for i := 0; i < k; i++ {
temp := head.Next
head.Next = pre
pre = head
head = temp
}
return pre, head // 返回翻转之后的头结点和下一个需要翻转的头结点
}
|
随机链表的复制
138. 随机链表的复制
1
2
3
4
5
6
7
8
|
type Node struct {
Val int
Next *Node
Random *Node
}
func copyRandomList(head *Node) *Node {
// ......
}
|
分析
参考了答案,使用map+递归。
定义一个辅助函数deepCopy,帮我们完成结点的拷贝,该函数返回拷贝后的新结点。
由于有随机指针的存在,我们需要在拷贝结点时,将该结点的随机指针指向的结点也创建出来,不让随机结点的指向就是空了。
那么我们可以递归地调用deepCopy函数来完成随机指针指向节点的拷贝。
为了防止创建多个相同的结点,我们可以使用map来存原结点和新结点的映射关系。
map的key为原结点,value为新的结点,如果存在某结点的key,说明其对应的新结点已经创建了,可以直接存map中拿出,不用再次创建。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func copyRandomList(head *Node) *Node {
existNode := make(map[*Node]*Node) // key为原结点,value为新结点
var deepCopy func(head *Node) *Node
deepCopy = func(head *Node) *Node {
if head == nil {
return nil
}
if newNode, exist := existNode[head]; exist { // 要复制的结点已经创建
return newNode
}
newNode := &Node{Val: head.Val}
existNode[head] = newNode
newNode.Next = deepCopy(head.Next) // 递归调用
newNode.Random = deepCopy(head.Random)
return newNode
}
return deepCopy(head)
}
|
排序链表
148. 排序链表
本题较为复杂,建议多加练习。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type ListNode struct {
Val int
Next *ListNode
}
func main() {
node5 := &ListNode{0, nil}
node4 := &ListNode{4, node5}
node3 := &ListNode{3, node4}
node2 := &ListNode{5, node3}
node1 := &ListNode{-1, node2}
newHead := sortList(node1)
for newHead != nil {
fmt.Printf("%d ", newHead.Val) // -1 0 3 4 5
newHead = newHead.Next
}
}
func sortList(head *ListNode) *ListNode {
// ......
}
|
分析
使用归并排序的思想:将链表分成两半,递归地对两半进行排序,然后将已排序的两半合并成一个有序链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
func sortList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
var merge func(head1, head2 *ListNode) *ListNode
merge = func(head1, head2 *ListNode) *ListNode {
pre := &ListNode{} // 虚拟头结点
oldPre := pre
temp1, temp2 := head1, head2
for temp1 != nil && temp2 != nil {
if temp1.Val <= temp2.Val { // 链表1的值更小,选择链表1的值
pre.Next = temp1
temp1 = temp1.Next
} else {
pre.Next = temp2
temp2 = temp2.Next
}
pre = pre.Next
}
if temp1 != nil { // 将 链表1 和 链表2 中不为空的那个接在pre指针后面
pre.Next = temp1
} else if temp2 != nil {
pre.Next = temp2
}
return oldPre.Next
}
var sort func(head, tail *ListNode) *ListNode
sort = func(head, tail *ListNode) *ListNode {
if head == nil {
return nil
}
if head.Next == tail { // 链表只有两个元素,而且tail位置是不包含的,所以这里要让head.Next置空,然后返回head
head.Next = nil
return head
}
slow, fast := head, head // 使用快慢指针找到链表的中点
for fast != tail && fast.Next != tail { // 注意这里是tail,而不是nil
slow = slow.Next
fast = fast.Next.Next
}
mid := slow
return merge(sort(head, mid), sort(mid, tail))
}
return sort(head, nil)
}
|
合并K个升序链表
【困难】23. 合并 K 个升序链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
type ListNode struct {
Val int
Next *ListNode
}
func main() {
list14 := &ListNode{2, nil}
list13 := &ListNode{0, list14}
list12 := &ListNode{-1, list13}
list11 := &ListNode{-2, list12}
list23 := &ListNode{4, nil}
list22 := &ListNode{1, list23}
list21 := &ListNode{-3, list22}
list32 := &ListNode{-1, nil}
list31 := &ListNode{-1, list32}
// lists:[[-1,1],[-3,1,4],[-2,-1,0,2]]
lists := []*ListNode{list11, list21, list31}
newHead := mergeKLists(lists)
for newHead != nil {
fmt.Printf("%d ", newHead.Val) // -3 -2 -1 -1 -1 0 1 2 4
newHead = newHead.Next
}
}
func mergeKLists(lists []*ListNode) *ListNode {
// ......
}
|
分析
利用堆的自动有序性,将所有的结点都加入到小根堆中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
func mergeKLists(lists []*ListNode) *ListNode {
minHeap := make([]*ListNode, 0)
for _, list := range lists {
for list != nil {
minHeap = append(minHeap, list)
list = list.Next
}
}
n := len(minHeap)
for i := n/2 - 1; i >= 0; i-- {
heapify(minHeap, n, i)
}
pre := &ListNode{} // 虚拟头结点
oldPre := pre
for i := 0; i < n; i++ {
pre.Next = &ListNode{minHeap[0].Val, nil}
pre = pre.Next
minHeap[0], minHeap[n-i-1] = minHeap[n-i-1], minHeap[0] // 把0位置和堆中最后一个位置交换。注意:不能简单粗暴地把第0位置的元素删除。
heapify(minHeap, n-i-1, 0)
}
return oldPre.Next
}
func heapify(minHeap []*ListNode, n, i int) {
minimum := i
l := i*2 + 1
r := i*2 + 2
if l < n && minHeap[l].Val < minHeap[minimum].Val {
minimum = l
}
if r < n && minHeap[r].Val < minHeap[minimum].Val {
minimum = r
}
if minimum != i {
minHeap[minimum], minHeap[i] = minHeap[i], minHeap[minimum]
heapify(minHeap, n, minimum)
}
}
|
这种方法也可以合并k个无序链表,对于每个链表的有序性没有利用到,应该还有更好的办法来实现。
LRU缓存
146. LRU 缓存
本题暂时跳过。
二叉树展开为链表
114. 二叉树展开为链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func main() {
node4 := &TreeNode{2, nil, nil}
node3 := &TreeNode{4, nil, nil}
node2 := &TreeNode{1, nil, node4}
node1 := &TreeNode{3, node2, node3}
flatten(node1)
// 3
// 1 4
// 2
for node1 != nil {
fmt.Printf("%d ", node1.Val) // 3 1 2 4
node1 = node1.Right
}
}
func flatten(root *TreeNode) {
// ......
}
|
分析
参考:详细通俗的思路分析,多解法——解法2
通过修改每个结点的右指针来达到目的。但如果使用前序遍历,会导致遍历过的结点之前的右结点丢失,使用我们需要把前序遍历逆过来。
也就是遍历顺序为:右左中,这样遍历过的结点的右孩子结点就不会丢失了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func flatten(root *TreeNode) {
var pre *TreeNode
var traversal func(root *TreeNode)
traversal = func(root *TreeNode) {
if root == nil {
return
}
traversal(root.Right)
traversal(root.Left)
root.Right = pre
root.Left = nil // 注意:还需要把左孩子置空
pre = root // 记录前一个遍历过的结点
}
traversal(root)
}
|
从前序与中序遍历序列构造二叉树
105. 从前序与中序遍历序列构造二叉树
1
2
3
4
5
6
7
8
|
func main() {
preorder, inorder := []int{3, 9, 20, 15, 7}, []int{9, 3, 15, 20, 7}
root := buildTree(preorder, inorder)
fmt.Println(root.Val, root.Left.Val, root.Right.Val) // 3, 9, 20
}
func buildTree(preorder []int, inorder []int) *TreeNode {
// ......
}
|
分析
前序:中左右;
中序:左中右。
先根据前序数组的第0个元素找到根结点的值rootVal,然后根据rootVal去切割中序数组,分成 左中序 和 右中序 两个数组。
然后根据 左中序 和 右中序 两个数组的长度去切割前序数组,得到 左前序 和 右前序 两个数组。
最后递归调用buildTree去构造二叉树
递归的出口是前序数组为空,说明二叉树为空了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
func main() {
preorder, inorder := []int{3, 9, 20, 15, 7}, []int{9, 3, 15, 20, 7}
root := buildTree(preorder, inorder)
fmt.Println(root.Val, root.Left.Val, root.Right.Val) // 3, 9, 20
}
func buildTree(preorder []int, inorder []int) *TreeNode {
if len(preorder) == 0 {
return nil
}
rootVal := preorder[0]
root := &TreeNode{rootVal, nil, nil}
if len(preorder) == 1 { // 前缀数组只有1个结点,说明是叶子结点了,直接返回
return root
}
i := 0 // i为分割位置
for i = 0; i < len(inorder); i++ { // 对中序数组进行分割,找到分割位置
if inorder[i] == rootVal {
break
}
}
leftInorder := inorder[:i]
rightInorder := inorder[i+1:]
leftPreOrder := preorder[1 : len(leftInorder)+1] // 不包含前序数组的第0个元素
rightPreOrder := preorder[len(leftInorder)+1:]
root.Left = buildTree(leftPreOrder, leftInorder)
root.Right = buildTree(rightPreOrder, rightInorder)
return root
}
|
路径总和III
437. 路径总和 III
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func main() {
node6 := &TreeNode{3, nil, nil}
node5 := &TreeNode{5, nil, nil}
node4 := &TreeNode{4, nil, nil}
node3 := &TreeNode{3, node6, nil}
node2 := &TreeNode{2, node4, node5}
node1 := &TreeNode{1, node2, node3}
target := 6
r := pathSum(node1, target)
fmt.Println(r) // 2
// 1
// 2 3
// 4 5 3
}
func pathSum(root *TreeNode, targetSum int) int {
// ......
}
|
分析
参考了答案:使用前缀和+回溯算法。
使用map存放当前遍历过的结点的前缀和(key为前缀和,value为该前缀和出现的次数)。
然后使用前序遍历,将当前结点的值加到前缀和curPreSum中,判断map中是否存在curPreSum-target的键,
如果存在,则统计该键出现的次数,并加到res中。
将key为curPreSum前缀和哈希表的值加1,然后遍历左子树和右子树。
遍历之后,需要将key为curPreSum前缀和哈希表的值再减1(回溯),因为本子树遍历完之后,遍历父结点的另一个子树时,当前的curPreSum值是不存在的(这点很重要)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func pathSum(root *TreeNode, targetSum int) int {
resCount := 0
preSumMap := make(map[int]int, 1) // key为前缀和,value为次数
preSumMap[0] = 1 // 初始化map,0出现了1次
var traversal func(root *TreeNode, curPreSum int)
traversal = func(root *TreeNode, curPreSum int) {
if root == nil {
return
}
curPreSum += root.Val
resCount += preSumMap[curPreSum-targetSum]
preSumMap[curPreSum]++
traversal(root.Left, curPreSum)
traversal(root.Right, curPreSum)
preSumMap[curPreSum]-- // 回溯
}
traversal(root, 0)
return resCount
}
|
二叉树的最近公共祖先
236. 二叉树的最近公共祖先
1
2
3
4
5
6
7
8
|
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
// ......
}
|
分析
如果 root 等于 p 或 q,直接返回 root。
如果 root 的左孩子返回 p,右孩子返回 q,则直接返回 root。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
if root == nil || root == p || root == q {
return root
}
left := lowestCommonAncestor(root.Left, p, q)
right := lowestCommonAncestor(root.Right, p, q)
if left != nil && right != nil {
return root
} else if left != nil {
return left
} else {
return right
}
}
|
二叉树中的最大路径和
【困难】124. 二叉树中的最大路径和
1
2
3
4
5
6
7
8
|
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func maxPathSum(root *TreeNode) int {
// ......
}
|
分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func maxPathSum(root *TreeNode) int {
// 使用一个全局变量maxSum记录最大路径和,maxGain函数返回的是最大贡献值
if root == nil {
return 0
}
maxSum := root.Val
var maxGain func(root *TreeNode) int
maxGain = func(root *TreeNode) int {
if root == nil {
return 0
}
leftGain := max(maxGain(root.Left), 0)
rightGain := max(maxGain(root.Right), 0)
maxSum = max(maxSum, root.Val+leftGain+rightGain)
return root.Val + max(leftGain, rightGain)
}
maxGain(root)
return maxSum
}
|
岛屿数量
腐烂的橘子
课程表
实现Trie(前缀树)
电话号码的字母组合
17. 电话号码的字母组合
1
2
3
4
5
6
7
8
|
func main() {
digits := "23"
r := letterCombinations(digits)
fmt.Println(r) // ["ad","ae","af","bd","be","bf","cd","ce","cf"]
}
func letterCombinations(digits string) []string {
// ......
}
|
分析

好久没写回溯的题了,思路有点混乱,参考了答案。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
func letterCombinations(digits string) []string {
// 先将映射关系存入map中,然后获得数字对应的字母的全排列
arr := make([]byte, 0)
res := make([]string, 0)
mapping := map[byte]string{
'2': "abc",
'3': "def",
'4': "ghi",
'5': "jkl",
'6': "mno",
'7': "pqrs",
'8': "tuv",
'9': "wxyz",
}
var backtracking func(digits string, start int)
backtracking = func(digits string, start int) {
if len(arr) == len(digits) {
res = append(res, string(arr))
return
}
word := mapping[digits[start]]
for i := 0; i < len(word); i++ { // 遍历这个数字对于的字符组合
arr = append(arr, word[i])
backtracking(digits, start+1) // 每层递归选择一个数字
arr = arr[:len(arr)-1]
}
}
backtracking(digits, 0)
return res
}
|
括号生成
22. 括号生成
1
2
3
4
5
6
7
8
|
func main() {
n := 3
r := generateParenthesis(n)
fmt.Println(r) // ["((()))","(()())","(())()","()(())","()()()"]
}
func generateParenthesis(n int) []string {
// ......
}
|
分析
参考了题解:递归——剩余左括号总数要小于等于右括号
编写一个辅助函数,用来生成目标结果,它有3个参数:
- str:当前已经生成的括号组合字符串。
- left:还可以添加的左括号数量。
- right:还可以添加的右括号数量。
如果left==right==0,则说明已经添加完所有的括号了,直接将结果添加到res数组中。
如果left和right相等,说明剩余的左右括号数量相同,下一个只能添加左括号(因为不能先添加右括号,否则它将没有匹配的左括号)。
如果left小于right,下一个可以添加左括号或右括号,但添加左括号的前提是还有剩余的左括号可用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func generateParenthesis(n int) []string {
res := make([]string, 0)
var genParenthesis func(str string, left, right int)
genParenthesis = func(str string, left, right int) {
if left == 0 && right == 0 {
res = append(res, str)
return
} else if left == right { // 只能先添加左括号
genParenthesis(str+"(", left-1, right) // 其实这里也隐含了回溯的过程,因为str值本身没有变化,就相当与回溯了
} else if left < right { // 即可以添加左括号,也可以添加右括号
if left > 0 {
genParenthesis(str+"(", left-1, right)
}
genParenthesis(str+")", left, right-1)
}
}
genParenthesis("", n, n) // 初始值,left和right都为n
return res
}
|
单词搜索
79. 单词搜索
1
2
3
4
5
6
7
8
9
|
func main() {
board := [][]byte{{'A', 'B', 'C', 'E'}, {'S', 'F', 'C', 'S'}, {'A', 'D', 'E', 'E'}}
word := "ABCCED"
r := exist(board, word)
fmt.Println(r)
}
func exist(board [][]byte, word string) bool {
// ......
}
|
分析
参考题解:深度优先搜索(DFS)+ 剪枝
对每个位置进行递归的搜索,在每个位置可以向上、下、左、右四个方向进行搜索,在搜索时需要判断该位置是否合法
使用过的位置我们要进行标记,下次不能再使用
定义一个辅助函数来实现每个位置开始的搜索过程,它有3个参数:i、j、k,分别表示开始搜索的横坐标、纵坐标、下一个应该匹配的word下标。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
func exist(board [][]byte, word string) bool {
m := len(board)
n := len(board[0])
var backtracking func(i, j, k int) bool
backtracking = func(i, j, k int) bool {
if i < 0 || i >= m || j < 0 || j >= n || board[i][j] != word[k] { // 非法,直接返回false
return false
}
if k == len(word)-1 { // k和len(word)-1相等(k位置已经匹配才会执行到这里),说明已经找到完全匹配的了,直接返回true
return true
}
board[i][j] = ' ' // 标记(i,j)位置为不可使用
res := backtracking(i-1, j, k+1) || // 可以往4个方向搜索
backtracking(i+1, j, k+1) ||
backtracking(i, j-1, k+1) ||
backtracking(i, j+1, k+1)
board[i][j] = word[k] // 回溯,让该位置在下一次递归中可用
return res
}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if backtracking(i, j, 0) { // 只要找到一个满足条件的,直接返回true
return true
}
}
}
return false
}
|
分割回文串
131. 分割回文串
1
2
3
4
5
6
7
8
|
func main() {
s := "aab"
r := partition(s)
fmt.Println(r)
}
func partition(s string) [][]string {
// ......
}
|
分析
上次练习是 2024.05.01,这次练习是 2024.05.25。本题写得有点磕绊,还是要多加练习。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
func partition(s string) [][]string {
// start表示分割的位置
arr := make([]string, 0)
res := make([][]string, 0)
var backtracking func(s string, start int)
backtracking = func(s string, start int) {
if start == len(s) {
temp := make([]string, len(arr))
copy(temp, arr)
res = append(res, temp)
return
}
for i := start; i < len(s); i++ {
if isPalindrome(s[start : i+1]) {
arr = append(arr, s[start:i+1])
backtracking(s, i+1)
arr = arr[:len(arr)-1]
}
}
}
backtracking(s, 0)
return res
}
func isPalindrome(s string) bool {
l, r := 0, len(s)-1
for l < r {
if s[l] != s[r] {
return false
}
l++
r--
}
return true
}
|
N皇后
【困难】51. N 皇后
本题过于复杂,先暂时跳过。
搜索旋转排序数组
33. 搜索旋转排序数组
1
2
3
4
5
6
7
8
9
|
func main() {
nums, target := []int{4, 5, 6, 7, 0, 1, 2}, 0 // 4
//nums, target := []int{1, 2, 3}, 3 // 2
r := search(nums, target)
fmt.Println(r) // 4
}
func search(nums []int, target int) int {
// ......
}
|
分析
二分查找,使用mid将数组分为两个部分,其中一个数组必然是升序数组,另一个数字是循环数组。
判断左右数组的属性,是升序数组还是循环数组。
然后判断target在升序数组里面,还是循环数组里面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func search(nums []int, target int) int {
n := len(nums)
l, r := 0, n-1
for l <= r {
mid := l + (r-l)>>1
if nums[mid] == target {
return mid
}
// 注意这里区分升序数组和循环数组的条件,如果mid值大于r的值,说明r的左侧是升序数组,那么右侧就是循环数组了
// 反之,r的右侧就是升序数组,左侧是循环数组
if nums[mid] > nums[r] { // 左侧升序数组,右侧循环数组
// 判断target在升序数组里面,还是循环数组里面
if nums[l] <= target && target < nums[mid] { // 在升序数组里面
r = mid - 1
} else { // 在循环数组里面
l = mid + 1
}
} else { // 左侧循环数组,右侧升序数组
if nums[mid] < target && target <= nums[r] { // 在升序数组里面
l = mid + 1
} else { // 在循环数组里面
r = mid - 1
}
}
}
return -1
}
|
寻找旋转排序数组中的最小值
153. 寻找旋转排序数组中的最小值
寻找两个正序数组的中位数
【困难】4. 寻找两个正序数组的中位数
1
2
3
4
5
6
7
8
9
|
func main() {
nums1 := []int{1, 2, 3}
nums2 := []int{4, 5, 6}
r := findMedianSortedArrays(nums1, nums2)
fmt.Println(r) // 3.5
}
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
// ......
}
|
分析
二分查找+递归。 本题比较复杂,建议多加练习和理解。
把本问题转换为求两个数组第k小的元素。
比较两个数组最中间元素的大小关系:
- 如果nums1[mid]更小,说明nums1[mid]前面的元素都不可能是第k小的元素,则前面的元素就都可以删除。
- 如果nums2[mid]更小,则nums2[mid]前面的元素都可以删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
var getKth func(i, j, k int) int // i为nums1的开始位置,j为nums2的开始位置,k为第k小的元素(k从1开始)。
getKth = func(i, j, k int) int {
if i == len(nums1) { // nums1数组已经遍历完了,那么就从nums2数组中找到j后面第k大的元素。
return nums2[j+k-1]
}
if j == len(nums2) { // 反之,从nums1数组中找到第k大的元素。
return nums1[i+k-1]
}
if k == 1 { // 收获结果的时候
return min(nums1[i], nums2[j])
}
n := min(i+k/2, len(nums1)) - 1 // nums1剩余数组的中间位置
m := min(j+k/2, len(nums2)) - 1 // nums2剩余数组的中间位置
if nums1[n] > nums2[m] { // nums1中间位置的元素更大,则删除nums2[m]前面的元素(也不是真的删除,就是开始搜索位置变成了m+1)
return getKth(i, m+1, k-(m-j+1))
} else { // 反之,i的搜索位置变成n+1
return getKth(n+1, j, k-(n-i+1))
}
}
l := len(nums1) + len(nums2) // 计算两个数组的长度
// 由于k从1开始,所以这里要找第(l+1)/2小和第(l+1)/2小元素的平均值
return float64(getKth(0, 0, (l+1)/2)+getKth(0, 0, (l+2)/2)) * 0.5
}
|
字符串解码
394. 字符串解码
1
2
3
4
5
6
7
8
9
|
func main() {
//s := "3[a2[c]]"
s := "100[leetcode]"
r := decodeString(s)
fmt.Println(r) // accaccacc
}
func decodeString(s string) string {
// ......
}
|
分析
参考:双栈法
本题的关键是处理括号嵌套的问题。
我们使用 curStr 来保存当前括号中的字符串内容,方便向上层返回。
我们使用两个栈,一个栈(kStack)记录重复次数 k,另一个栈(strStack)记录字符串
遍历s:
- 如果我们遇到的是
数字
,则将其添加到kStack中;
- 如果遇到的是
[
,则将curStr添加到strStack中,并将curStr置空
- 如果遇到的是
]
,说明我们该解码了,将curStr重复k次,并存入到curStr中。
- 如果遇到的是
字母
,则将其加到curStr即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func decodeString(s string) string {
k, kStack, strStack := 0, make([]int, 0), make([]string, 0)
curStr := ""
for _, str := range s {
if str >= '0' && str <= '9' {
k = k*10 + int(str-'0') // 处理多位数字的情况
} else if str == '[' { // 遇到的是[,将k和curStr压入到各自的栈中,并重置k和curStr
kStack = append(kStack, k)
strStack = append(strStack, curStr)
curStr = ""
k = 0
} else if str == ']' { // 遇到的是[,进行解码
tempStr := make([]string, 1)
tempStr[0] = strStack[len(strStack)-1]
strStack = strStack[:len(strStack)-1]
tempK := kStack[len(kStack)-1]
kStack = kStack[:len(kStack)-1]
for i := 0; i < tempK; i++ {
tempStr = append(tempStr, curStr) // 将curStr重复tempK次
}
curStr = strings.Join(tempStr, "")
} else { // 遇到的是字母,直接加到curStr中
curStr += string(str)
}
}
return curStr
}
|
每日温度
739. 每日温度
1
2
3
4
5
6
7
8
|
func main() {
temperatures := []int{73, 74, 75, 71, 69, 72, 76, 73}
r := dailyTemperatures(temperatures)
fmt.Println(r) // [1,1,4,2,1,1,0,0]
}
func dailyTemperatures(temperatures []int) []int {
// ......
}
|
分析
参考了官解:方法二:单调栈
维护一个温度单调递减的栈(使用队列模拟),栈中不直接存温度,而是温度的下标 i(这样方便计算两个温度之间的天数间隔)
遍历数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func dailyTemperatures(temperatures []int) []int {
n := len(temperatures)
res := make([]int, n)
minStack := make([]int, 0)
for i, temper := range temperatures {
for len(minStack) > 0 && temper > temperatures[minStack[len(minStack)-1]] { // 新加入元素比栈顶元素大
outIndex := minStack[len(minStack)-1] // 需要出栈的下标
minStack = minStack[:len(minStack)-1] // 将栈顶元素出栈
res[outIndex] = i - outIndex // i位置就是outIndex位置的后一个更高温度的下标
}
minStack = append(minStack, i) // 栈大小为空 或者 添加的元素小于栈顶元素,直接加入到栈中
}
return res
}
|
柱状图中最大的矩形
【困难】84. 柱状图中最大的矩形
1
2
3
4
5
6
7
8
|
func main() {
heights := []int{2, 1, 5, 6, 2, 3}
r := largestRectangleArea(heights)
fmt.Println(r) // 10
}
func largestRectangleArea(heights []int) int {
// ......
}
|
分析
使用单调栈来实现。
在i位置,我们需要找到左边第1个比它小的位置leftIndex和右边第1个比它小的位置rightIndex,i位置的最大宽度就是rightIndex-leftIndex-1。
我们需要维护一个最大栈,也就是栈顶元素最大,栈顶到栈底依次递减(可以相等)。具体如下:
- 如果遇到的元素比栈顶元素大(或者等),则直接将其添加到栈中(添加的是元素的下标);
- 如果遇到的元素比栈顶元素小(右边就不能继续往后延展了),就是我们需要收获结果的时候。
我们计算以栈顶元素stackTop的值为高度,能形成的最大矩形。
矩形的左侧位置就是栈顶元素stackTop在栈中的前一个位置(比栈顶位置值大);
矩形的右侧位置就是我们遇到的元素位置。
这样计算一个面积值之后,更新maxArea的值。
然后将栈中比添加元素大的元素全部弹出,将遇到的元素加入到栈中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
func largestRectangleArea(heights []int) int {
newHeights := make([]int, len(heights)+2) // 为了能够正常收获结果,在原数组的首尾加上0
newHeights[0], newHeights[len(newHeights)-1] = 0, 0
for i := 1; i < len(newHeights)-1; i++ {
newHeights[i] = heights[i-1]
}
maxStack := make([]int, 0)
curArea, maxArea := 0, 0
for i, h := range newHeights {
for len(maxStack) > 0 && h < newHeights[maxStack[len(maxStack)-1]] { // h比栈顶元素小
stackTop := maxStack[len(maxStack)-1]
maxStack = maxStack[:len(maxStack)-1] // 将栈顶元素弹出
lIndex := maxStack[len(maxStack)-1] // 左侧的位置就是弹出之后的栈顶值
curArea = (i - lIndex - 1) * newHeights[stackTop]
maxArea = max(maxArea, curArea)
}
maxStack = append(maxStack, i)
}
return maxArea
}
|
数组中的第K个最大元素
215. 数组中的第K个最大元素
分析
方法1: 使用堆排序。
维护一个容量为k的小顶堆,当全部元素都加入到堆中后,堆顶元素就是第k大的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
func findKthLargest(nums []int, k int) int {
heap := make([]int, k)
for i, num := range nums {
if i < k {
heap[i] = num
if i == k-1 {
for i := k/2 - 1; i >= 0; i-- {
heapify(heap, k, i)
}
}
} else {
if num > heap[0] {
heap[0] = num
heapify(heap, k, 0)
}
}
}
return heap[0]
}
func heapify(nums []int, n, i int) {
l := i*2 + 1
r := i*2 + 2
min := i
if l < n && nums[l] < nums[min] {
min = l
}
if r < n && nums[r] < nums[min] {
min = r
}
if min != i {
nums[i], nums[min] = nums[min], nums[i]
heapify(nums, n, min)
}
}
|
上面这种方法在极端情况下,时间复杂度为$O(nlogn)$,而本题要求的时间复杂度为$O(n)$,所以应该还有更好的方法。
前K个高频元素
347. 前 K 个高频元素
1
2
3
4
5
6
7
8
|
func main() {
nums, k := []int{2, 2, 3, 1, 1, 1}, 2
r := topKFrequent(nums, k)
fmt.Println(r) // 1,2
}
func topKFrequent(nums []int, k int) []int {
// ......
}
|
分析
上次做这道题是 2024.04.30,这次做这道题是 2024.05.25。
好久没写了,中间有一点小问题,参考了答案,当堆中元素个数为 k 时,堆化的过程是循环的,而不是只执行一次,这点需要多加注意。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
func topKFrequent(nums []int, k int) []int {
// 构建能够容纳k个元素的小根堆,根据元素出现的次数进行排序
numCount := make(map[int]int) // 统计每个元素出现的次数
for _, num := range nums {
numCount[num]++
}
var minHeap []heapItem
heapCount := 0
for num, count := range numCount {
if heapCount < k {
minHeap = append(minHeap, heapItem{num, count})
heapCount++
if heapCount == k {
for i := k / 2; i >= 0; i-- { // 注意:这里的堆化是一个循环的过程
heapify(minHeap, k, i)
}
}
} else {
if count > minHeap[0].count { // 新加入的元素次数超过了堆顶元素的次数,则将对推替换为新加入的元素
minHeap[0] = heapItem{num, count}
heapify(minHeap, k, 0)
}
}
}
res := make([]int, k)
for i := k - 1; i >= 0; i-- {
res[i] = minHeap[0].num
minHeap = minHeap[1:]
heapify(minHeap, i, 0)
}
return res
}
type heapItem struct {
num int
count int
}
func heapify(minHeap []heapItem, n, i int) {
left := i*2 + 1
right := i*2 + 2
min := i
if left < n && minHeap[left].count < minHeap[min].count {
min = left
}
if right < n && minHeap[right].count < minHeap[min].count {
min = right
}
if min != i {
minHeap[i], minHeap[min] = minHeap[min], minHeap[i]
heapify(minHeap, n, min)
}
}
|
数据流的中位数
【困难】295. 数据流的中位数
本题暂时跳过。
跳跃游戏II
45. 跳跃游戏 II
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{2, 3, 1, 1, 4}
r := jump(nums)
fmt.Println(r) // 2
}
func jump(nums []int) int {
// ......
}
|
分析
我们只关心当前覆盖的最大范围。
我们还要在当前的覆盖范围内,找到下一步能够覆盖的最大范围,因为如果这一步不能到达末尾,我们需要再跳一步,所以在遍历当前覆盖范围时,就要求出下一步能够覆盖的最大范围。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func jump(nums []int) int {
cur, next, stepCount := 0, 0, 0
for i := 0; i < len(nums); i++ {
next = max(next, i+nums[i]) // 下一步能覆盖的范围
if cur >= len(nums)-1 { // 覆盖了最后位置,返回最少步数
return stepCount
}
if i == cur { // 到达了当前最大覆盖范围,需要增加步数了
stepCount++
cur = next
}
}
return stepCount
}
|
划分字母区间
763. 划分字母区间
1
2
3
4
5
6
7
8
|
func main() {
s := "ababcbacadefegdehijhklij"
r := partitionLabels(s)
fmt.Println(r) // [9,7,8]
}
func partitionLabels(s string) []int {
// ......
}
|
分析
先遍历s,使用map记录每个元素最后出现的位置。
遍历当前范围内所有元素到其最后出现的位置,然后就可以划分下一个区间了。
使用left和right记录当前区间的左侧位置和右侧位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func partitionLabels(s string) []int {
lastIndex := make(map[byte]int)
for i := 0; i < len(s); i++ {
lastIndex[s[i]] = i
}
res := make([]int, 0)
left, right := 0, 0 // 区间的左右侧位置
for i := 0; i < len(s); i++ {
right = max(right, lastIndex[s[i]]) // 更新当前区间的右侧位置
if i == right { // 当前区间结束
res = append(res, right-left+1)
left = right + 1
}
}
return res
}
|
乘积最大子数组
152. 乘积最大子数组
1
2
3
4
5
6
7
8
9
|
func main() {
nums := []int{2, 3, -2, 4}
//nums := []int{-2, 3, -4} // 24
r := maxProduct(nums)
fmt.Println(r) // 6
}
func maxProduct(nums []int) int {
// ......
}
|
分析
参考了官方题解:方法一:动态规划——考虑优化空间
由于在i位置,获得最大乘积的方式有三种:
-
之前的最大乘积 * 当前元素
-
之前的最小乘积 * 当前元素
-
当前元素值
所以我们需要维护最大乘积和最小乘积两个dp变量(不用数组,压缩空间)。
每遍历一个元素,就更新这两个变量的值。
1
2
3
4
5
6
7
8
9
10
11
12
|
func maxProduct(nums []int) int {
maxProd, minProd, res := nums[0], nums[0], nums[0]
for i := 1; i < len(nums); i++ {
// 记录上一个位置的最大乘积和最小乘积。注意:这里要用两个临时变量来存之前的值,
// 因为在循环中,maxProd的值会先被改变,如果直接使用maxProd会导致计算错误。
maxP, minP := maxProd, minProd
maxProd = max(maxP*nums[i], minP*nums[i], nums[i])
minProd = min(maxP*nums[i], minP*nums[i], nums[i])
res = max(res, maxProd)
}
return res
}
|
最长有效括号
【困难】32. 最长有效括号
1
2
3
4
5
6
7
8
|
func main() {
s := ")()())"
r := longestValidParentheses(s)
fmt.Println(r) // 4
}
func longestValidParentheses(s string) int {
// ......
}
|
分析
方法1:动态规划
dp[i]表示以s[i]结尾的字符串,最长有效括号的长度。
遍历s:
递推表达式解释:dp[i-1]是i的前一个位置有效括号的长度,用i减去中间有效的长度,比较i位置和它应该比较的位置是否匹配。
为什么还要加上dp[i-dp[i-1]-2]?因为匹配位置的前面还有可能存在匹配的括号,所以需要加上前面的dp数组值。
初始化dp数组,dp数组的初始值都是0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func longestValidParentheses(s string) int {
maxLen := 0
dp := make([]int, len(s))
for i := 1; i < len(s); i++ { // i从1开始
if s[i] == '(' { // 左括号,不可能构成有效括号,直接跳过
continue
} else { // 右括号,比较i位置和它应该比较的位置
if i-dp[i-1]-1 < 0 { // 应该匹配的位置不合法,dp[i]为0
dp[i] = 0
} else if s[i-dp[i-1]-1] == '(' { // i位置和前面应该比较的位置匹配上了
if i-dp[i-1]-2 < 0 { // 匹配位置前面没有元素了
dp[i] = dp[i-1] + 2
} else {
dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2] // 这里记得加上 dp[i-dp[i-1]-2]
}
if dp[i] > maxLen { // 更新最大长度
maxLen = dp[i]
}
}
}
}
return maxLen
}
|
下一个排列
31. 下一个排列
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{3, 2, 1}
nextPermutation(nums)
fmt.Println(nums) // 1,2,3
}
func nextPermutation(nums []int) {
// ......
}
|
分析
参考官方题解:下一个排列
使用两个指针,先从右往左遍历,找到第1个nums[i]小于nums[i+1]的数(左边的较小值)。
然后再次从右往左遍历,找到第1个大于nums[i]的数(右边的较大值)。
然后让两个元素值进行交换。
交换之后,让之前左边较小值位置后面的元素全部逆序,这样就能得到下一个排列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func nextPermutation(nums []int) {
i := len(nums) - 2
for i >= 0 && nums[i] >= nums[i+1] { //找到第1个右到左降序的值
i--
}
if i >= 0 { // 如果i小于0,说明数组从右到左始终是升序的,那么交换元素的过程就不需要了,直接将元素值逆序即可。
j := len(nums) - 1
for j >= 0 && nums[j] <= nums[i] {
j--
}
nums[i], nums[j] = nums[j], nums[i]
}
reverse := func(nums []int) {
for i := 0; i < len(nums)/2; i++ {
nums[i], nums[len(nums)-i-1] = nums[len(nums)-i-1], nums[i]
}
}
reverse(nums[i+1:])
}
|
寻找重复数
287. 寻找重复数
1
2
3
4
5
6
7
8
|
func main() {
nums := []int{1, 3, 4, 2, 2}
r := findDuplicate(nums)
fmt.Println(r) // 2
}
func findDuplicate(nums []int) int {
// ......
}
|
分析
使用快慢指针来解决本题。让i位置的值作为索引i指向的下一个元素。
在上面的例子中,0—>1—>3—>2—>4—>2,当4指向2时,这个链表就形成了一个环,也就是有两个位置的元素值都是2,那么2就是那个重复值。
寻找环入口的方式和 142. 环形链表 II 相同。
快指针每次移动2步,慢指针每次移动1步。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func findDuplicate(nums []int) int {
slow, fast := nums[0], nums[0]
for {
slow = nums[slow] // slow移1步
fast = nums[nums[fast]] // fast移2步
if slow == fast { // 找到相遇点
break
}
}
temp := nums[0] // temp从头开始移动
for temp != slow { // temp和slow同时移动
temp = nums[temp]
slow = nums[slow]
}
return temp
}
|
做题记录
20240523
#1 两数之和
#283 移动零
#3 无重复字符的最长子串
#53 最大子数组和
#160 相交链表
#206 反转链表
#234 回文链表
#141 环形链表
#142 环形链表 II
#21 合并两个有序链表
#2 两数相加
#19 删除链表的倒数第 N 个结点
#24 两两交换链表中的节点
#94 二叉树的中序遍历
#104 二叉树的最大深度
#226 翻转二叉树
#101 对称二叉树
#543 二叉树的直径
#102 二叉树的层序遍历
#108 将有序数组转换为二叉搜索树
#98 验证二叉搜索树
#199 二叉树的右视图
#236 二叉树的最近公共祖先
#124 二叉树中的最大路径和
#121 买卖股票的最佳时机
#55 跳跃游戏
#70 爬楼梯
20240524
#15 三数之和
#239 滑动窗口最大值
#56 合并区间
#189 轮转数组
#46 全排列
#78 子集
#17 电话号码的字母组合
#131 分割回文串
#39 组合总和
#35 搜索插入位置
#34 在排序数组中查找元素的第一个和最后一个位置
#153 寻找旋转排序数组中的最小值
#20 有效的括号
#118 杨辉三角
#198 打家劫舍
#279 完全平方数
#322 零钱兑换
#139 单词拆分
#300 最长递增子序列
20240525
#416 分割等和子集
#62 不同路径
#64 最小路径和
#5 最长回文子串
#1143 最长公共子序列
#72 编辑距离
#48 旋转图像
#215 数组中的第K个最大元素
#347 前 K 个高频元素
从后面的题目开始就基本上是之前没有做过的新题了。
#49 字母异位词分组
#128 最长连续序列
#11 盛最多水的容器
#42 接雨水
#438 找到字符串中所有字母异位词
20240526
#560 和为 K 的子数组
#76 最小覆盖子串
#238 除自身以外数组的乘积
#41 缺失的第一个正数
#73 矩阵置零
#54 螺旋矩阵
#240 搜索二维矩阵 II
20240627
#74 搜索二维矩阵
20240528
#155 最小栈
20240529
#394 字符串解码
#739 每日温度
#84 柱状图中最大的矩形
#25 K 个一组翻转链表
#138 随机链表的复制
#148 排序链表
#23 合并 K 个升序链表
20240530
#230 二叉搜索树中第K小的元素
#114 二叉树展开为链表
#105 从前序与中序遍历序列构造二叉树
#437 路径总和 III
20240531
#22 括号生成
#79 单词搜索
#51 N 皇后
#45 跳跃游戏 II
#763 划分字母区间
#152 乘积最大子数组
#32 最长有效括号
#33 搜索旋转排序数组
#4 寻找两个正序数组的中位数
#136 只出现一次的数字
#169 多数元素
#75 颜色分类
#31 下一个排列
20240601
#287 寻找重复数
#146 LRU 缓存
#295 数据流的中位数