哈希
1.两数之和:求数组中两数的和为target,返回下标。用hash,key存数,value存下标,一次遍历,每次判断hash[taget-num]是否存在,存在就返回两个下标。
https://blog.csdn.net/midi666/article/details/127170342
func twoSum(nums []int, target int) []int {
res := make(map[int]int)
for index,value := range nums {
if preIndex, ok := res[target - value]; ok {
return []int{preIndex, index}
} else {
res[value] = index
}
}
return []int{}
}
49.字母异位词分组:给定一个数组,数组中每个元素是字符串如abc,然后按照abc、acb为异位词的原则放到一个数组中,结果是个二维数组。用hash,设置一个map[[26]int][]string,每遍历到一个字符串,再遍历他的每个字符,根据数组中每个字符出现的次数的数组来当作map的key。
https://blog.csdn.net/midi666/article/details/140032365
func groupAnagrams(strs []string) [][]string {
tmpMap := make(map[[26]int][]string)
for _, str := range strs {
tmp := [26]int{}
for _, c := range str {
tmp[c-'a']++
}
tmpMap[tmp] = append(tmpMap[tmp], str)
}
ans := [][]string{}
for _, v := range tmpMap {
ans = append(ans, v)
}
return ans
}
128.最长连续序列:注意不是子序列,相当于要原数组排序后的子数组,求长度。先构造哈希表map[int]bool类型,然后遍历哈希表,如果map的[k-1]不存在,则意味着这是一个新的开始,再二层for循环判断k+1在不在,在就遍历并计数(长度++且k++),比较后取最大值。
https://blog.csdn.net/midi666/article/details/127170342
func longestConsecutive(nums []int) int {
tmpMap := make(map[int]bool)
for _, v := range nums {
tmpMap[v] = true
}
ans := 0
for key, _ := range tmpMap {
if tmpMap[key-1] == false {
long := 1
for tmpMap[key+1] {
long++
key++
}
ans = max(ans, long)
}
}
return ans
}
双指针
283.移动零:将所有的0移动到最后面。双指针,left和right都初始化为0,right小于N的循环中,只要nums[right] != 0, left和right交换,left++;然后不管怎样都right++,这样0都跑到最后去了。双指针的含义就如下:左边的指针是非0数组部分最右边的边界,右边的指针是为0数组最左边的边界,注意是不为0则进行交换。(好神奇的思路,不好理解,背吧)
https://blog.csdn.net/midi666/article/details/139945762
func moveZeroes(nums []int) {
n := len(nums)
left := 0
right := 0
for right < n {
if nums[right] != 0 {
nums[left], nums[right] = nums[right], nums[left]
left++
}
right++
}
}
11.盛水最多的容器:用线围成的边,求最大面积。双指针,从两边开始,每次取较短的那个边乘以宽度,然后比较更新最大面积,然后移动短的边界。
https://blog.csdn.net/midi666/article/details/133173678
func maxArea(height []int) int {
left := 0
right := len(height) - 1
res := 0
for left < right {
tmp := (right - left) * min(height[left], height[right])
res = max(res, tmp)
if height[left] < height[right] {
left++
} else {
right--
}
}
return res
}
15.三数之和:返回所有满足三数相加等于0的元素,搞成一个二维数组。先排序然后遍历i,在一些剪枝(包括去除i维度的重复元素)的操作后,用双指针进行处理,注意在匹配到合适的数据后,还需要处理这部分的重复元素
四题汇总
func threeSum(nums []int) [][]int {
res := [][]int{}
n := len(nums)
if n < 3 {
return res
}
sort.Ints(nums)
for i := 0; i < n-2; i++ {
// 几个异常条件
// 第一个就大于0,已经排序了,后面的都会大于0
if nums[i] > 0 {
break
}
// 去重,题目中要求了不能有重复的组合
if i > 0 && nums[i] == nums[i-1] {
continue
}
// 前三个加起来大于0了,后面的也会大于0
if nums[i]+nums[i+1]+nums[i+2] > 0 {
break
}
// 第一个数和最后的俩数加起来还小于0,直接结束这次的循环,让第一个数加点,可能还有救
if nums[i]+nums[n-1]+nums[n-2] < 0 {
continue
}
// 前面的都可以后,在这里采用左右双指针
j := i + 1
k := n - 1
for j < k {
sum := nums[i] + nums[j] + nums[k]
if sum > 0 {
k--
} else if sum < 0 {
j++
} else {
// 符合预期,先追加进结果
res = append(res, []int{nums[i], nums[j], nums[k]})
// 再分别对jk去重
j++
for j < k && nums[j] == nums[j-1] {
j++
}
k--
for j < k && nums[k] == nums[k+1] {
k--
}
}
}
}
return res
}
42.接雨水:用木板算范围,求最大面积。第一种先开辟俩数组,分别代表每个位置的前缀最大值和后缀,再遍历一遍,较小的那个减去木桶高度;第二种是双指针,left和right是遍历,pre和suf变量是前后缀最大值,用较小的那个减去木桶高度。注意这道题是前缀最大值,而不是前缀和最大值
https://blog.csdn.net/midi666/article/details/133675415
func trap(height []int) int {
n := len(height)
ans := 0
left := 0
right := n-1
preMax := 0
sufMax := 0
for left <= right {
preMax = max(preMax, height[left])
sufMax = max(sufMax, height[right])
if preMax < sufMax {
ans += preMax - height[left]
left++
} else {
ans += sufMax - height[right]
right--
}
}
return ans
}
滑动窗口
3.无重复字符的最长子串:求长度。滑动窗口+双指针,双指针求长度,定义一个固定大小的数组26[bool]当作固定大小的滑动窗口,遍历字符串,然后每次判断之前有数在数组中时,先将就窗口的left指针对应的数在窗口中改成false,然后left++来缩小左边界直到符合条件为止,然后用ans更新最大长度right-left+1
https://blog.csdn.net/midi666/article/details/138990452
func find(s string) int {
window := [26]bool{}
left := 0
ans := 0
for right, c := range s {
for window[c-'a'] {
window[s[left]-'a'] = false
left++
}
window[c-'a'] = true
ans = max(ans, right-left+1)
}
return ans
}
438.找到字符串中所有字母的异位词:s长,p短,返回这些子串的起始索引。用两个数组[26]int,来分别存两个字符串的滑动窗口,存的是字符出现的次数,先初始化p长度的,然后就比较一下;再从头开始遍历s,p窗口就永远不变了,s一进来先左边界–,再右边界++,看看是不是和p相等(数组维度相等)
https://blog.csdn.net/midi666/article/details/139282460
func findAnagrams1(s, p string) (ans []int) {
sLen := len(s)
pLen := len(p)
if sLen < pLen {
return
}
var sWindow, pWindow [26]int
for i, c := range p { // 先把p长度的数据都存进各自的滑动窗口中
sWindow[s[i]-'a']++
pWindow[c-'a']++
}
if sWindow == pWindow { // 前置位置的判断,只需要这一次判断
ans = append(ans, 0)
}
// 在这之后pWindow只是用来比较的,没有更新的场景了
for i, c := range s[:sLen-pLen] { // 从s字符串的首位开始遍历(这里减去P长度的意思是,每个窗口的大小是pLen,不减的话最后一位就越界了)
// 在这里的移动窗口,不是根据left、right来处理的,而是将当前窗口的第一个字符在数组中的位置减1
sWindow[c-'a']-- // 将窗口内对应字符的值-1
sWindow[s[i+pLen]-'a']++ // 将最后一个字符的下一个字符加1(相当于窗口整体滑动了一位)
if sWindow == pWindow {
ans = append(ans, i+1) // 如果滑动后的新窗口满足条件,将起始下标返回,此时在这个的循环中,因为已经滑动了,起始下标就是i+1
}
}
return
}
前缀和
560.和为k的子数组:给一个数组和K,求数组中和为K的子数组个数。使用前缀和+hash的思想,单独的前缀和会超时,前缀和的思路就是用一个数组来存每个位置之前的前缀和,注意一般是pre[i+1] = pre[i]+nums[i],然后默认pre[0]=0(看题目灵活变化,也可能是pre[i] = pre[i-1] + nums[i-1]),本题中用一个变量记也可以;然后就是用hash存前缀和出现的次数,如果hash[pre-k]存在,则代表有这么多个子数组和为k。最好的办法是一次遍历,用一个变量来代表前缀和,且代码很短,注意初始化map的时候要初始{0,1},因为在遍历的时候是从s[1]开始计算,相当于s[0] = 0。这道题二次遍历和一次遍历的方法都可以记下(直接背代码把)
另外注意前缀和一般都是下面的模板
https://blog.csdn.net/midi666/article/details/131565463
func subarraySum(nums []int, k int) (ans int) {
s := 0 // 相当于s是前缀和
cnt := map[int]int{0: 1} // s[0]=0 单独统计
for _, x := range nums {
s += x
ans += cnt[s-k] // 这里的含义是s[i] - s[j] = k 代表有一段和为k
cnt[s]++
}
return
}
栈和单调栈
239.滑动窗口最大值:给个数组,给个K,求长度为k的窗口滑动,每次滑动的最大值取出来,成数组返回。这题叫滑动窗口,用的方法却是单调栈(递减),遍历数组,先看queue数组的队尾是否小于num,否的话删队尾,直到可以入队列(入下标)。然后判断长度是不是超了,超了就出对头。然后就是每次循环只要i>=k-1就将队首加进结果数组。
https://blog.csdn.net/midi666/article/details/127185344
func maxSlidingWindow(nums []int, k int) []int {
var ans []int
var queue []int
for i, num := range nums {
// 1.入队列(元素入队尾,同时要满足队列的单调性)
for len(queue) > 0 && nums[queue[len(queue)-1]] <= num {
queue = queue[:len(queue)-1]
}
queue = append(queue, i) // 入的是下标
// 2.出队列(元素离开队首),出队列的条件是此时的下标比减去窗口起始位置的下标,大于K
if i-queue[0] >= k {
queue = queue[1:]
}
// 3.记录答案
if i >= k-1 { // 这一步主要是用于拦截最开始长度不够K的时候,比如K=3,i是下标要大于等于2的时候,才够第一个窗口
// 由于队首到队尾是单调递减,所以窗口最大值就是队首
ans = append(ans, nums[queue[0]])
}
}
return ans
}
53.最大子数组和:顾名思义。动态规划或贪心,相对简单,用变量存就行,不必要非得dp数组,先判断下之前的和加上此时的数,和单独此时数的比较,然后在维护一个最大值就行
https://blog.csdn.net/midi666/article/details/122774531
func maxSubArray(nums []int) int {
n := len(nums)
preMax := nums[0]
res := nums[0]
for i := 1; i < n; i++ {
preMax = max(preMax+nums[i], nums[i])
res = max(preMax, res)
}
return res
}
56.合并区间:给个二维数组,输出合并区间后的二维数组。先排序,一维数组的左端点排序,完事后遍历,要是已处理的右端点大于未处理的左端点,就可能可以合并,取两个的右端点较大的那个。注意排序用slices.SortFunc
https://blog.csdn.net/midi666/article/details/139760334
func merge(intervals [][]int) (ans [][]int) {
slices.SortFunc(intervals, func(p, q []int) int {
return p[0] - q[0]
})
for _, p := range intervals {
// 遍历到的每一个数组
m := len(ans)
if m > 0 && p[0] <= ans[m-1][1] { // 遍历到的左端点小于已经遍历过的右端点,可以合并
ans[m-1][1] = max(ans[m-1][1], p[1]) // 比如[[1,4], [2,3]]其实就不需要变化
} else {
ans = append(ans, p)
}
}
return
}
189.轮转数组:给个nums,给个K,1234567在k=3时变成5671234。反转三次,整体一次,左边一次,右边一次
https://blog.csdn.net/midi666/article/details/139815796
func rotate(nums []int, k int) {
k = k % len(nums)
// slices.Reverse(nums)
// slices.Reverse(nums[:k])
// slices.Reverse(nums[k:])
reverse(nums)
reverse(nums[:k])
reverse(nums[k:])
}
func reverse(nums []int) {
left := 0
right := len(nums) - 1
for left < right {
nums[left], nums[right] = nums[right], nums[left]
left++
right--
}
}
238.除自身以外数组的乘积:给定nums,返回新数组。前缀和(这个叫前缀积吧),从头到尾遍历一次,从尾到头遍历一次,每个位置的前缀积是这个位置之前的数的乘机,不要写错了,然后就是再遍历一遍,前后缀相乘
https://blog.csdn.net/midi666/article/details/139816050
func productExceptSelf(nums []int) []int {
n := len(nums)
pre := make([]int, n)
pre[0] = 1
for i := 1; i < n; i++ {
pre[i] = pre[i-1] * nums[i-1]
}
suf := make([]int, n)
suf[n-1] = 1
for i := n - 2; i >= 0; i-- {
suf[i] = suf[i+1] * nums[i+1]
}
ans := make([]int, n)
for i := 0; i < n; i++ {
ans[i] = pre[i] * suf[i]
}
return ans
}
41.缺失的第一个正整数:给一个数组,返回一个数,如果没有的话,就返回数组的下一个元素。考虑原地哈希的办法,具体思路是,在经过一系列的调整后,使得每个位置存对应这个位置的数,即nums[i] == i+1, 在i=0的时候代表第一个位置存1,若不相等,则返回i+1;为了满足这个条件,需要做的就是判断nums[i] != nums[nums[i]-1]时且0<nums[i]<n,交换(注意是for循环交换,直到满足才i++)(不好记,背把)
https://blog.csdn.net/midi666/article/details/140136149
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 < n; i++ {
// 如果当前元素不等于它的索引+1,那索引+1就是第一个缺失的正整数
if nums[i] != i+1 {
return i + 1
}
}
// 没有的话,缺失的正整数就是数组长度+1
return n + 1
}
73.矩阵置零:给定二维数组,返回原数组。开两个bool类型的row和col数组,遍历原数组,只要一个元素为0,两个数组的这一行(列)都为0。重新遍历原数组,只要有对应的行列为0,遍历到的这个元素就为0
https://blog.csdn.net/midi666/article/details/139668417
func setZeroes(matrix [][]int) {
row := make([]bool, len(matrix))
col := make([]bool, len(matrix[0]))
for i, r := range matrix {
for j, v := range r {
if v == 0 {
row[i] = true
col[j] = true
}
}
}
for i, r := range matrix {
for j := range r {
if row[i] || col[j] {
r[j] = 0
}
}
}
}
54.螺旋矩阵:给定二维矩阵m*n,输出一维矩阵,要顺时针输出。
三题合一
func spiralOrder(matrix [][]int) []int {
rows := len(matrix)
columns := len(matrix[0])
top, bottom := 0, rows -1
left, right := 0, columns -1
res := make([]int, 0)
nums := rows*columns
for nums >= 1 {
for i := left; i <= right && nums >= 1; i++ {
res = append(res, matrix[top][i])
nums--
}
top++
for i := top; i <= bottom && nums >= 1; i++ {
res = append(res, matrix[i][right])
nums--
}
right--
for i := right; i >= left && nums >= 1; i-- {
res = append(res, matrix[bottom][i])
nums--
}
bottom--
for i := bottom; i >= top && nums >= 1; i-- {
res = append(res, matrix[i][left])
nums--
}
left++
}
return res
}
48.旋转图像:给定二维数组,顺时针旋转90度。先主对角线反转,再水平反转
三题和一
func rotate(matrix [][]int) {
//图片一般都是n*n的
n := len(matrix)
//水平翻转
for i := 0; i < n/2; i++ {
matrix[i], matrix[n-1-i] = matrix[n-1-i], matrix[i]
}
//主对角线翻转
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
}
}
}
74.搜索二维矩阵I:每行递增,每列递增,且下一列的开头元素还大于上一列的结尾,判断target是否存在。这道题用上面的解法也能实现,但实际上是用二分法,left := 0, right := m*n, 然后算出来mid,x := matrix[mid/n][mid%n],再比较大小。n其实是一维矩阵中元素的个数,除以n就是第几行,取余n就是第几列
https://blog.csdn.net/midi666/article/details/139376973
func searchMatrix(matrix [][]int, target int) bool {
m := len(matrix)
n := len(matrix[0])
left := 0
right := m * n
for left < right {
mid := left + (right-left)/2
x := matrix[mid/n][mid%n]
if x == target {
return true
} else if x < target {
left = mid + 1
} else {
right = mid
}
}
return false
}
240.搜索二维矩阵II:每行递增,每列递增,给target,判断是否存在。从右上角开始,若右上角小于target,则排除整行;小于则排除整列。
https://blog.csdn.net/midi666/article/details/139668235
func searchMatrix(matrix [][]int, target int) bool {
m := len(matrix)
n := len(matrix[0])
i, j := 0, n-1 // 从右上角开始
for i <= m-1 && j >= 0 { // 还有剩余元素
if matrix[i][j] == target {
return true
}
if matrix[i][j] < target {
i++
} else {
j--
}
}
return false
}
160.相交链表:求两条链表的交点。
https://blog.csdn.net/midi666/article/details/125612723
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func getIntersectionNode(headA, headB *ListNode) *ListNode {
a := headA
b := headB
for (a != b) {
if a != nil {
a = a.Next
} else {
a = headB
}
if b != nil {
b = b.Next
} else {
b = headA
}
}
return a
}
206.反转链表
https://blog.csdn.net/midi666/article/details/124164028
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {
var pre *ListNode
//dummy := &ListNode{} //之前初始化哑节点是用这种方式,在这里用的话,返回结果会多一个0
//不用dummy的原因是这里不是头节点,其实相当于一个临时的tmp节点
cur := head
for cur != nil {
next := cur.Next
cur.Next = pre
pre = cur
cur = next
}
return pre
}
234.回文链表:先快慢指针到中间节点,再反转后半部分,再一起比较两个链表
https://blog.csdn.net/midi666/article/details/133035919
func isPalindrome(head *ListNode) bool {
if head == nil || head.Next == nil {
return true
}
slow := head
fast := head
prev := head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
new := reverseList(slow)
for prev != nil && new != nil {
if prev.Val != new.Val {
return false
}
prev = prev.Next
new = new.Next
}
return true
}
func reverseList(head *ListNode) *ListNode {
var pre *ListNode
cur := head
for cur != nil {
next := cur.Next
cur.Next = pre
pre = cur
cur = next
}
return pre
}
141.环形链表:判断是否有环
12都有
func hasCycle(head *ListNode) bool {
if head == nil || head.Next == nil {
return false
}
slow := head
fast := head
for fast != nil && fast.Next != nil { // 这里必须要先判一个fast != nil,不然会报panic,具体的示例可以用[1,2]且无环来测
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
return true
}
}
return false
}
142.环形链表II:有环,求入口位置
func detectCycle(head *ListNode) *ListNode {
slow := head
fast := head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
for slow != head {
slow = slow.Next
head = head.Next
}
return slow
}
}
return nil
}
21.合并两个有序链表。
https://blog.csdn.net/midi666/article/details/122774412
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
prehead := &ListNode{}
result := prehead
for l1 != nil && l2 != nil {
if l1.Val < l2.Val {
prehead.Next = l1
l1 = l1.Next
}else{
prehead.Next = l2
l2 = l2.Next
}
prehead = prehead.Next
}
if l1 != nil {
prehead.Next = l1
}
if l2 != nil {
prehead.Next = l2
}
return result.Next
}
2.两数相加:这道题其实是链表,给俩链表按照数序相加,有进位的话存一下,代码其实还不太好写,计算进位的时候注意先求进位再更新和,构造新链表需要一个head节点代表头,然后更新的时候都用tail。最后再判断一下进位。
https://blog.csdn.net/midi666/article/details/133030927
func addTwoNumbers(l1, l2 *ListNode) *ListNode {
var head *ListNode
var tail *ListNode
carry := 0 // 进位
for l1 != nil || l2 != nil { // 或的关系
n1 := 0
n2 := 0
if l1 != nil {
n1 = l1.Val
l1 = l1.Next
}
if l2 != nil {
n2 = l2.Val
l2 = l2.Next
}
sum := n1 + n2 + carry
sum, carry = sum%10, sum/10
if head == nil { // 第一次构造头节点
head = &ListNode{Val: sum}
tail = head
} else {
tail.Next = &ListNode{Val: sum}
tail = tail.Next
}
}
// 到了最后还有进位
if carry > 0 {
tail.Next = &ListNode{Val: carry}
}
return head
}
19.删除链表的倒数第 N 个结点: 快慢指针
https://blog.csdn.net/midi666/article/details/125593558
func removeNthFromEnd(head *ListNode, n int) *ListNode {
dummy := &ListNode{}
dummy.Next = head
fast := dummy
slow := dummy
for i := 0; i < n; i++ {
fast = fast.Next
}
for fast.Next != nil {
fast = fast.Next
slow = slow.Next
}
slow.Next = slow.Next.Next
return dummy.Next
}
24.两两交换链表中的节点: 相当于每两个一反转
https://blog.csdn.net/midi666/article/details/125403378
func swapPairs(head *ListNode) *ListNode {
//先定义一个哑节点
dummy := &ListNode{
Next : head,
}
pre := dummy
for head != nil && head.Next != nil { // 这里不需要定义cur = head,因为本身这个链表是要被改变的,有dummy.Next作为开头的定位就行,当然定义一个cur = head也能实现
pre.Next = head.Next
next := head.Next.Next
head.Next.Next = head
head.Next = next
pre = head
head = next
}
return dummy.Next
}
25.K个一组翻转链表:给定链表和K,返回反转后的头节点。可以用反转后递归的方式,反转套反转链表的代码。注意第一次反转返回的节点就是新的头节点。这道题直接从head开始,不需要dummy。先反转,再递归。整体思路是先移动K个位置的cur,然后反转newHead = reverser(head, cur),然后再递归head.Next = xxx(cur, k)
https://blog.csdn.net/midi666/article/details/133042307
func reverseKGroup(head *ListNode, k int) *ListNode {
cur := head
for i := 0; i < k; i++ {
if cur == nil {
return head // 不够K个,就不反转了
}
cur = cur.Next
}
newHead := reverse(head, cur) // 注意这里cur是从和head开始的,所以会多走了一位。第一次返回的newHead就是结果的头结点。在反转后,head所在的位置不变(还是开头,但此时已经变成了反转后的结尾,看上图,其next正好连接下一段链表)
head.Next = reverseKGroup(cur, k)
return newHead
}
func reverse(start, end *ListNode) *ListNode {
var pre *ListNode
cur := start
for cur != end {
next := cur.Next
cur.Next = pre
pre = cur
cur = next
}
return pre
}
92.反转链表II:给定一个链表,给定left和right两个整数,反转中间这一段的。因为left给的是整数,不能当节点用,先找到left的前一个节点pre,然后用反转链表的代码,返回后面一段的,此时pre到了right的位置,cur到了right后面那个位置,再让leftBefore.Next.Next = cur, leftBefore.Next = pre即可。
https://blog.csdn.net/midi666/article/details/133064184
func reverseBetween(head *ListNode, left, right int) *ListNode {
dummy := &ListNode{}
dummy.Next = head
pre := dummy
for i := 0; i < left-1; i++ {
pre = pre.Next
}
leftBefore := pre
cur := pre.Next
for i := 0; i < right-left+1; i++ {
next := cur.Next
cur.Next = pre
pre = cur
cur = next
}
leftBefore.Next.Next = cur
leftBefore.Next = pre
return dummy.Next
}
138.随机链表的复制:给定一个node头节点,返回新链表的头节点,node结构体中多加一个random指针。第一种方法是先遍历一遍,用一个map来存数,再把每个节点的指向搭建起来;第二种的是在原有链表的基础上每个节点向后复制一个节点,再遍历一遍,把random指向给加上,再遍历一遍,改动next的指向为自己的那条链表,并断开头节点的链接。
https://blog.csdn.net/midi666/article/details/139047472
func copyRandomList2(head *Node) *Node {
if head == nil {
return nil
}
cur := head
// 复制各节点,并构建拼接链表
for cur != nil {
tmp := &Node{Val: cur.Val}
tmp.Next = cur.Next
cur.Next = tmp
cur = tmp.Next
}
// 构建各新节点的random指向
cur = head
for cur != nil {
if cur.Random != nil {
cur.Next.Random = cur.Random.Next
}
cur = cur.Next.Next
}
// 拆分链表
cur = head.Next
res := head.Next
pre := head
for cur.Next != nil {
pre.Next = pre.Next.Next
cur.Next = cur.Next.Next
pre = pre.Next
cur = cur.Next
}
// 单独处理原链表尾节点
pre.Next = nil
return res
}
148.排序链表:给定一个链表,返回排序后的链表。先快慢指针找到链表的中间节点,还需要一个指针记慢节点的前一个节点(用于每次断开),找到中间节点后,断开两个链表,然后递归左右链表,最后调用合并两个有序链表的代码(快慢指针找位置的,是不是slow和fast开始定义在head的居多?而不是dummy或pre)
https://blog.csdn.net/midi666/article/details/133026359
func sortList(head *ListNode) *ListNode {
if head == nil || head.Next == nil { // 递归的出口,不用排序 直接返回
return head
}
slow, fast := head, head // 快慢指针
var preSlow *ListNode // 保存slow的前一个结点
for fast != nil && fast.Next != nil {
preSlow = slow
slow = slow.Next // 慢指针走一步
fast = fast.Next.Next // 快指针走两步
}
preSlow.Next = nil // 断开,分成两链
left := sortList(head) // 已排序的左链
right := sortList(slow) // 已排序的右链
return merge(left, right)
}
func merge(head1, head2 *ListNode) *ListNode {
dummy := &ListNode{}
pre := dummy
for head1 != nil && head2 != nil {
if head1.Val <= head2.Val {
pre.Next = head1
head1 = head1.Next
} else {
pre.Next = head2
head2 = head2.Next
}
pre = pre.Next
}
if head1 != nil {
pre.Next = head1
}
if head2 != nil {
pre.Next = head2
}
return dummy.Next
}
23.合并K个升序链表:给的是链表数组,返回一个排序后的链表。可以直接取中点,递归+调用合并两个链表的代码
https://blog.csdn.net/midi666/article/details/133031079
func mergeTwoLists1(list1 *ListNode, list2 *ListNode) *ListNode {
dummy := &ListNode{}
pre := dummy
for list1 != nil && list2 != nil {
if list1.Val <= list2.Val {
pre.Next = list1
list1 = list1.Next
} else {
pre.Next = list2
list2 = list2.Next
}
pre = pre.Next
}
if list1 != nil {
pre.Next = list1
}
if list2 != nil {
pre.Next = list2
}
return dummy.Next
}
func mergeKLists(lists []*ListNode) *ListNode {
n := len(lists)
if n == 0 { // 递归终止条件
return nil
}
if n == 1 {
return lists[0]
}
left := mergeKLists(lists[:n/2])
right := mergeKLists(lists[n/2:])
res := mergeTwoLists1(left, right)
return res
}
146.LRU缓存:这道题主要使用的结构就是哈希表map和双向链表。可以使用go的list包来用双向链表,有Element元素;结构体中有list 和 cache(value是*list.Element),还需要有一个额外的entry的kv结构体。如果想自己实现双向链表的话,就需要一个dlinknode的结构体,每次加入节点的时候都要init一下这个结构体
https://blog.csdn.net/midi666/article/details/130028247
type LRUCache struct {
capacity int
list *list.List
keyToNode map[int]*list.Element
}
type entry struct {
key int
value int
}
func Constructor(capacity int) LRUCache {
return LRUCache{
capacity: capacity,
list: list.New(),
keyToNode: map[int]*list.Element{},
}
}
func (c *LRUCache) Get(key int) int {
node := c.keyToNode[key]
if node == nil {
return -1
}
c.list.MoveToFront(node)
return node.Value.(entry).value
}
func (c *LRUCache) Put(key, value int) {
if node, ok := c.keyToNode[key]; ok {
node.Value = entry{key: key, value: value}
c.list.MoveToFront(node)
return
}
c.keyToNode[key] = c.list.PushFront(entry{key: key, value: value}) // 新的放在最上面
if len(c.keyToNode) > c.capacity {
delete(c.keyToNode, c.list.Remove(c.list.Back()).(entry).key)
}
}
94 二叉树的中序遍历
https://blog.csdn.net/midi666/article/details/127188520
func inorderTraversal(root *TreeNode) []int {
//中序遍历是左根右,所以要先找到最左的那个节点
ans := []int{}
if root == nil {
return ans
}
st := list.New()
cur := root //定义一个指针
for cur != nil || st.Len() > 0 {
if cur != nil {
st.PushBack(cur)
cur = cur.Left //先把左节点压入栈,直到最左边的
} else {
//弹出cur,放入结果数组中;有右节点的在将其压入栈,没有的话cur为nil,则会再从栈中弹出一个数据赋值给cur
cur = st.Remove(st.Back()).(*TreeNode)
ans = append(ans, cur.Val)
cur = cur.Right
}
}
return ans
}
104.二叉树最大深度:递归的话后序,max(leftDepth, rightDepth)+1。注意深度的题可以都记递归法
https://blog.csdn.net/midi666/article/details/127380753
func maxDepth(root *TreeNode) int {
//其实算是后序遍历
if root == nil {
return 0
}
leftDepth := maxDepth(root.Left) //左
rightDepth := maxDepth(root.Right) //右
res := max(leftDepth, rightDepth) + 1 //中,+1是指加上当前层
return res
}
226.翻转二叉树:核心是node.left 和right交换;不用判断直接换,递归和迭代的哪种方法都行
https://blog.csdn.net/midi666/article/details/127356476
func invertTree(root *TreeNode) *TreeNode {
if root == nil {
return nil
}
invertTree(root.Left)
invertTree(root.Right)
root.Left, root.Right = root.Right, root.Left
return root
}
101.对称二叉树:得拿到两个节点,判断为nil的情况和val相等的情况,迭代的话需要push left.left+right.right 和 left.right+right.left
https://blog.csdn.net/midi666/article/details/127357147
func isSymmetric(root *TreeNode) bool {
queue := list.New()
if root != nil {
queue.PushBack(root.Left)
queue.PushBack(root.Right)
}
for queue.Len() > 0 {
left := queue.Remove(queue.Front()).(*TreeNode)
right := queue.Remove(queue.Front()).(*TreeNode)
if left == nil && right == nil {
continue
}
if left == nil || right == nil || left.Val != right.Val {
return false
}
queue.PushBack(left.Left)
queue.PushBack(right.Right)
queue.PushBack(left.Right)
queue.PushBack(right.Left)
}
return true
}
543.二叉树的直径:求的是树中任意两个节点的最长路径,可以转换为每个节点的左右子树最大深度之和,再求最大值,用递归,注意递归的返回结果还是求最大深度的结果(1 + max(leftDepth, rightDepth))
https://blog.csdn.net/midi666/article/details/133156910
func diameterOfBinaryTree(root *TreeNode) int {
ans := 0
var traversal func(node *TreeNode) int
traversal = func(node *TreeNode) int {
if node == nil {
return 0
}
leftDepth := traversal(node.Left)
rightDepth := traversal(node.Right)
ans = max(ans, leftDepth+rightDepth)
return max(leftDepth, rightDepth) + 1
}
traversal(root)
return ans
}
102.二叉树的层序遍历
https://blog.csdn.net/midi666/article/details/127313205
func levelOrder(root *TreeNode) [][]int {
ans := [][]int{}
if root == nil {
return ans
}
queue := list.New() //这个list,又当stack又当queue的。。。
queue.PushBack(root)
tmpArr := []int{}
for queue.Len() > 0 {
length := queue.Len() //这里必须要有这一步,因为下面这个循环中的终止条件,会在内部有压入队列的操作,有影响
for i:=0; i < length; i++ {
node := queue.Remove(queue.Front()).(*TreeNode)
tmpArr = append(tmpArr, node.Val)
if node.Left != nil {
queue.PushBack(node.Left)
}
if node.Right != nil {
queue.PushBack(node.Right)
}
}
ans = append(ans, tmpArr)
tmpArr = []int{}
}
return ans
}
108.将有序升序数组转化为高度平衡的二叉搜索树:递归法,数组的中间节点作为根节点,左右分别递归构造左右子树(其实代码很少)
https://blog.csdn.net/midi666/article/details/127859242
func sortedArrayToBST(nums []int) *TreeNode {
//递归终止条件
if len(nums) == 0 {
return nil
}
root := &TreeNode{Val:nums[len(nums)/2]} //将数组的中间节点作为二叉树的根节点
root.Left = sortedArrayToBST(nums[:len(nums)/2])
root.Right = sortedArrayToBST(nums[len(nums)/2+1:])
return root
}
98.验证二叉搜索树:中序遍历,符合二叉搜索树的性质,就需要一个pre遍历来存上一个节点,用于比较是否有序
https://blog.csdn.net/midi666/article/details/127709019
func isValidBST(root *TreeNode) bool {
if root == nil {
return true
}
stack := list.New()
cur := root
var pre *TreeNode //保存上一个指针
for cur != nil || stack.Len() > 0 {
if cur != nil {
stack.PushBack(cur)
cur = cur.Left
} else {
cur = stack.Remove(stack.Back()).(*TreeNode)
if pre != nil && cur.Val <= pre.Val {
return false
}
pre = cur
cur = cur.Right
}
}
return true
}
230.二叉搜索树中第K小的元素:迭代中序遍历,每弹出一个就K–,直到为0
https://blog.csdn.net/midi666/article/details/139079301
func kthSmallest(root *TreeNode, k int) int {
ans := 0
if root == nil || k <= 0 {
return ans
}
cur := root
stack := list.New()
for cur != nil || stack.Len() > 0 {
if cur != nil {
stack.PushBack(cur)
cur = cur.Left
} else {
cur = stack.Remove(stack.Back()).(*TreeNode)
k--
if k == 0 {
return cur.Val
}
cur = cur.Right // 大意了,这一行记错了,记成pushback了
}
}
return ans
}
199.二叉树的右视图:迭代法就正常层序遍历,到每一层最右边节点的时候记下来;递归法就在递归函数中传入本层的深度(根节点的时候是0),然后根右左,当depth == len(ans)的时候append
https://blog.csdn.net/midi666/article/details/137806602
func xxx(root *TreeNode) []int {
ans := []int{}
var traversal func(node *TreeNode, depth int)
traversal = func(node *TreeNode, depth int) {
if node == nil {
return
}
if depth == len(ans) {
ans = append(ans, node.Val)
}
traversal(node.Right, depth+1)
traversal(node.Left, depth+1)
}
traversal(root, 0)
return ans
}
114.二叉树展开为链表:入参是根节点,无返回结果,意味着要改原始结构,题目说了和先序遍历相同,那就用迭代法将每个节点放到一个数组中结构是[]*TreeNode,然后遍历这个数组,使用prev和cur,每个的left指向nil,right指向下一个
https://blog.csdn.net/midi666/article/details/133139313
func flatten(root *TreeNode) {
if root == nil {
return
}
listNew := []*TreeNode{}
stack := list.New()
stack.PushBack(root)
for stack.Len() > 0 {
node := stack.Remove(stack.Back()).(*TreeNode)
listNew = append(listNew, node)
if node.Right != nil {
stack.PushBack(node.Right)
}
if node.Left != nil {
stack.PushBack(node.Left)
}
}
for i := 1; i < len(listNew); i++ {
prev, cur := listNew[i-1], listNew[i]
prev.Left = nil
prev.Right = cur
}
}
105.从前序与中序遍历序列构造二叉树:先使用中序找到中间节点,然后递归生成
两道题
func buildTree(preorder []int, inorder []int) *TreeNode {
if len(preorder) < 1 || len(inorder) < 1 {
return nil
}
nodeInorder := findRootIndex(inorder, preorder[0])
root := &TreeNode{
Val : preorder[0],
Left : buildTree(preorder[1:nodeInorder+1], inorder[:nodeInorder]),
Right : buildTree(preorder[nodeInorder+1:], inorder[nodeInorder+1:]),
}
return root
}
func findRootIndex(inorder []int, target int) int {
for i := 0; i < len(inorder); i++ {
if target == inorder[i] {
return i
}
}
return -1
}
112.路径总和I:给二叉树的根节点和target,判断是否存在从根节点到叶子节点的路径。简单方法递归,每次进行target-cur.Val,完事判断左右字节是否为空以及target是否减到0,否则左右节点递归
https://blog.csdn.net/midi666/article/details/127603799
func hasPathSum(root *TreeNode, targetSum int) bool {
if root == nil {
return false
}
targetSum -= root.Val
if root.Left == nil && root.Right == nil && targetSum == 0 {
return true
}
return hasPathSum(root.Left, targetSum) || hasPathSum(root.Right, targetSum)
}
113.路径总和II: 和上题一样,但要返回满足的所有路径。用回溯法,每次target-,path加数据。但和别的回溯的区别是进入回溯后,先处理target和path,再判是否添加到结果集
https://blog.csdn.net/midi666/article/details/127605476
func pathSum(root *TreeNode, targetSum int) [][]int {
var res [][]int
if root == nil {
return res
}
var path []int
var dfs func(node *TreeNode)
dfs = func(node *TreeNode) {
if node == nil {
return
}
targetSum -= node.Val
path = append(path, node.Val)
if node.Left == nil && node.Right == nil && targetSum == 0 {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
}
dfs(node.Left)
dfs(node.Right)
targetSum += node.Val
path = path[:len(path)-1]
}
dfs(root)
return res
}
437.路径总和III: 这道题不要求根节点和叶子结点,返回数目。用回溯的思想+前缀和+哈希,其中核心逻辑是,先累加到前缀和s,再ans+=cnt[s-target],再cnt[s]++,再回溯,再将cnt[s]–;也可以用递归的写法,两个递归
https://blog.csdn.net/midi666/article/details/139079702
func pathSum(root *TreeNode, targetSum int) (ans int) {
cnt := map[int]int{0: 1} // 01是防止包含根节点的时候找不到
var dfs func(*TreeNode, int)
dfs = func(node *TreeNode, s int) {
if node == nil {
return
}
// 判断是否存在符合条件的前缀和
s += node.Val
ans += cnt[s-targetSum]
// 将当前前缀和记录下来
cnt[s]++
// 继续向下递归
dfs(node.Left, s)
dfs(node.Right, s)
// 回溯,恢复状态
cnt[s]--
s -= node.Val // 这一行加不加都能过
return
}
dfs(root, 0)
return
}
236.二叉树的最近公共祖先:给根节点和两个节点,返回一个节点。后序遍历,先写终止条件,再左右递归,再处理返回的left和right
https://blog.csdn.net/midi666/article/details/127799396
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
if root == nil {
return root
}
if 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
}
if left != nil {
return left
}
if right != nil {
return right
}
return nil
}
124.二叉树中的最大路径和:返回一个整数。用普通的dfs,不回溯,类似于后序遍历,先求出左右链的最大链和,在把两链加起来+节点,更新整体最大值,dfs返回的是当前子树(包括该节点)的最大链和。注意由一点是dfs的返回结果里也加了节点的值,这个需要自己用数据想一下
https://blog.csdn.net/midi666/article/details/139134415
func maxPathSum(root *TreeNode) int {
ans := math.MinInt
var dfs func(*TreeNode) int
dfs = func(node *TreeNode) int {
if node == nil {
return 0 // 没有节点,和为0
}
leftMaxVal := dfs(node.Left) // 左子树最大链和
rightMaxVal := dfs(node.Right) // 右子树最大链和
ans = max(ans, leftMaxVal+rightMaxVal+node.Val) // 两条链拼成路径
return max(max(leftMaxVal, rightMaxVal)+node.Val, 0) // 当前子树最大链和
}
dfs(root)
return ans
}
110.平衡二叉树:要判断的是高度平衡的二叉树(子树高度绝对值相差小于1)只有递归法!二叉树的递归基本上都是后序遍历,主要判断条件是判断返回的左右子树的高度abs相差是否大于1,是的话直接一层层返回,否则递归返回当前节点的最大深度
https://blog.csdn.net/midi666/article/details/127525920
func isBalanced(root *TreeNode) bool {
if getHeight(root) == -1 {
return false
}
return true
}
func getHeight(node *TreeNode) int {
if node == nil {
return 0
}
leftHeight := getHeight(node.Left)//左
if leftHeight == -1 {
return -1
}
rightHeight := getHeight(node.Right)//右
if rightHeight == -1 {
return -1
}
if abs(leftHeight - rightHeight) > 1 {//中
return -1
}
return 1 + max(leftHeight, rightHeight) // 以当前节点为根节点的树的最大高度
}
257.二叉树的所有路径:返回从根节点到叶子节点的所有路径。递归(不是回溯),因为这道题path是字符串,将字符串每次传参到递归中,是一个新的字符串,就不用回溯了;之前的数组是改动后会被影响的
https://blog.csdn.net/midi666/article/details/127600892
func binaryTreePaths(root *TreeNode) []string {
var res []string
var dfs func(node *TreeNode, path string)
dfs = func(node *TreeNode, path string) {
if node.Left == nil && node.Right == nil {
val := path + strconv.Itoa(node.Val)
res = append(res, val)
return
}
path = path + strconv.Itoa(node.Val) + "->"
if node.Left != nil {
dfs(node.Left, path)
}
if node.Right != nil {
dfs(node.Right, path)
}
}
dfs(root, "")
return res
}
96.不同的二叉搜索树:给一个N,判断能组成多少个不同的二叉搜索树。这题其实是动态规划,核心观点是每一个节点可以组成的二叉树数量dp[i] += dp[j-1] + dp[i-j],i的范围是1-n, j的范围是1-i。dp[i]的含义是,有i个节点时的组合数,所以最后返回dp[n]就行(代码相当短,实在不行就背代码)
https://blog.csdn.net/midi666/article/details/128198547
func numTrees(n int) int {
dp := make([]int, n+1)
dp[0] = 1
for i := 1; i <= n; i++ { //这里这两个小于等于的判断条件再捋捋
for j := 1; j <= i; j++ {
dp[i] += dp[j-1] * dp[i-j]
}
}
return dp[n]
}
200.岛屿数量:给定一个grid [][]byte,求由1组成的岛屿的数量。dfs的方法,先二层循环遍历每一个元素,如果这个元素=1,则进入dfs,然后count+1;dfs里面的逻辑是直到遇到ij的边界才return,然后将遍历到的这个位置改成0防止再次遍历到,然后上下左右分别进行dfs,注意这个dfs不需要回溯,因为每个位置一旦用了,后面就不会再用了,只需要dfs(i,j)就行
https://blog.csdn.net/midi666/article/details/139668711
func numIslands(grid [][]byte) int {
m := len(grid)
n := len(grid[0])
var dfs func(i, j int)
dfs = func(i, j int) {
if i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0' {
return
}
grid[i][j] = '0'
dfs(i-1, j)
dfs(i+1, j)
dfs(i, j-1)
dfs(i, j+1)
}
var count int
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == '1' {
dfs(i, j)
count++
}
}
}
return count
}
695.岛屿的最大面积:和上一道题一样,不过上一道题是grid [][]byte, 这道题给的是grid [][]int。这道题与上面不一样的地方主要是首先dfs有返回结果了,是面积;其次默认进了dfs后满足条件,面积为1;最后维护一个最大面积
https://blog.csdn.net/midi666/article/details/139669097
func maxAreaOfIsland(grid [][]int) int {
var dfs func(i, j int) int
dfs = func(i, j int) int {
if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) || grid[i][j] == 0 {
return 0
}
area := 1
grid[i][j] = 0
area += dfs(i+1, j)
area += dfs(i-1, j)
area += dfs(i, j+1)
area += dfs(i, j-1)
return area
}
maxArea := 0
for i := 0; i < len(grid); i++ {
for j := 0; j < len(grid[0]); j++ {
if grid[i][j] == 1 {
maxArea = max(maxArea, dfs(i, j))
}
}
}
return maxArea
}
994.腐烂的橘子:给定二维数组[][]int,其中数据0代表没有放橘子,1是新鲜的橘子,2是腐烂的橘子,问几分钟后橘子能全腐烂。图BFS,这道题由于要四个方向遍历,设置一个Pair结构体比较好,先遍历一遍二维数组,新鲜的橘子数量++,腐烂横纵坐标入队列;先默认ans=-1, 然后遍历队列中的元素,清空队列,时间+1,遍历队列中的元素,分别对四个方向的橘子进行判断,边界内的新鲜橘子添加到下一次的队列中,fresh–,同时将橘子改成腐烂
https://blog.csdn.net/midi666/article/details/140234263
type Pair struct {
x int
y int
}
var direction = []Pair{{-1, 0},{1, 0},{0, -1},{0, 1},}
func orangesRotting(grid [][]int) int {
m := len(grid)
n := len(grid[0])
fresh := 0
queue := []Pair{}
for i, row := range grid {
for j, col := range row {
if col == 1 {
fresh++ // 如果是新鲜的橘子就增加个数
} else if col == 2 {
queue = append(queue, Pair{i, j}) // 如果是腐烂的橘子就追加进队列中
}
}
}
ans := -1 // 因为下面的循环条件中,一进来就会加一分钟,比如腐烂的橘子到了边界,就会多加一分钟
for len(queue) > 0 {
ans++ // 一进来就代表着过了一分钟
tmp := queue
// 千万注意下面这行,不要加冒号,不是重新定义局部变量!!!!
queue = []Pair{} // 相当于在一次操作中,弹出了之前队列中的所有数据,这次再把新数据放进去
for _, p := range tmp { // 处理队列中的元素(已经腐烂的橘子)
for _, d := range direction { // 处理四个方向
i, j := p.x+d.x, p.y+d.y // 四个方向分别遍历,然后看遍历到的位置橘子是否符合预期
if i >= 0 && i < m && j >= 0 && j < n && grid[i][j] == 1 { // 有效范围内的新鲜橘子
fresh--
grid[i][j] = 2 // 处理后变成腐烂的橘子
queue = append(queue, Pair{i, j}) // 腐烂的橘子入队列
}
}
}
}
if fresh > 0 {
return -1 // 还有新鲜的橘子不会被影响,返回-1
}
return max(ans, 0) // 这个是为了假如全是0(没有橘子),要返回0而不是-1
}
207.课程表:给一个数,代表要上几门课,再给个二维数组,里面的一维数组表示要上下标为0的课就得先上下标为1的课,问能不能上完课。图!!!这题老复杂了,先初始化edges图,map[int][]int,key是下标为1的课,value数组是表示哪几门课依赖这门课;初始化indeg入度数组,存的是下标为0的课出现的次数,在0的位置上出现一次,就代表入度+1,必须要减到0才可以处理,追加到queue中
https://blog.csdn.net/midi666/article/details/140235567
func canFinish(numCourses int, prerequisites [][]int) bool {
var (
edges = make(map[int][]int, numCourses) // 边,也叫邻接表,存的是每个位置,可以指向后面的哪些位置
indeg = make([]int, numCourses) // 入度数组
result []int
)
for _, info := range prerequisites {
edges[info[1]] = append(edges[info[1]], info[0]) // 边中存的是每门课程构成的一个图
indeg[info[0]]++ // 先计算出每门课程的初始入度值,即在内层数组的下标0位置上出现一次,就代表依赖1位置的课要先上,入度就要+1
}
queue := []int{}
for i := 0; i < numCourses; i++ {
if indeg[i] == 0 {
queue = append(queue, i) // 所有入度为0的入队列
}
}
for len(queue) > 0 {
u := queue[0]
queue = queue[1:]
result = append(result, u) // 表示上了一门入度为0的课,看最后课的总数是否相等
for _, v := range edges[u] { // 对于入度为0的数据指向的数据进行遍历
indeg[v]-- // 入度为0的数据消费了,则对应的依赖的这些节点的入度就--
if indeg[v] == 0 {
queue = append(queue, v)
}
}
}
return len(result) == numCourses
}
208.实现Trie(前缀树): 主要就是前缀树的原理,每个位置有是[26]*Trie
https://blog.csdn.net/midi666/article/details/140415328
type Trie struct {
children [26]*Trie
isEnd bool
}
func Constructor() Trie {
return Trie{}
}
func (t *Trie) Insert(word string) {
for _, ch := range word {
ch -= 'a' // 减去a的ascii码,转换到26长度中去
if t.children[ch] == nil {
t.children[ch] = &Trie{}
}
t = t.children[ch]
}
// 整个都插入完成后,改变end
t.isEnd = true
}
func (t *Trie) SearchPrefix(prefix string) *Trie {
for _, ch := range prefix {
ch -= 'a'
if t.children[ch] == nil {
return nil
}
t = t.children[ch]
}
return t
}
// 下面这个函数要求的是整个单词在前缀树中,意味着要到end
func (t *Trie) Search(word string) bool {
node := t.SearchPrefix(word)
return node != nil && node.isEnd
}
// 这个只要判断前缀在树中就可以了
func (t *Trie) StartsWith(prefix string) bool {
node := t.SearchPrefix(prefix)
return node != nil
}
46.全排列I:给一个数组,返回数组的全排列。用回溯,因为要全排列了,那么dfs里的for循环,就要每次从0开始了;除了path需要回溯外,还需要定义一个usedMap来存每个树枝上的元素是否被用过,用过的话就continue,注意dfs后也需要把这个usedMap对应的值给改回去。注意:排列类型的题目都是dfs(i+1),其余的可能是dfs(j+1)居多
https://blog.csdn.net/midi666/article/details/128107346
func permute(nums []int) [][]int {
var res [][]int
var path []int
n := len(nums)
if n <= 0 {
return res
}
usedMap := make(map[int]bool)
var dfs func(int)
dfs = func(i int) {
if i == n {
tmp := make([]int, n)
copy(tmp, path)
res = append(res, tmp)
return
}
for j := 0; j < n; j++ { // 每次都是从0开始
if usedMap[j] {
continue
}
usedMap[j] = true
path = append(path, nums[j])
dfs(i + 1) // 注意这里是i,不是j
path = path[:len(path)-1]
usedMap[j] = false
}
}
dfs(0) // 这里的传参在全排列这道题中,用途就是判断是否到n,函数里面的循环j里,每次都是从0开始了
return res
}
47.全排列II:给定的数组中有重复的元素,然后返回全排列。要去重,先排序,让相同的元素挨着。然后正常回溯,在期间for循环遍历宽度的时候,判断nums[j] == nums[j-1] 且必须是在宽度的维度,这个维度是用usedMap[j-1] == false来确定的,因为只有在前一个位置都深度走完了,才会去将map改成false(本题用树枝去重还是树层去重都可以)
https://blog.csdn.net/midi666/article/details/128107673
func permuteUnique (nums []int) [][]int {
var res [][]int
var path []int
n := len(nums)
if n <= 0 {
return res
}
sort.Ints(nums) // 去重一定要排序才有用
usedMap := make(map[int]bool)
var dfs func(int)
dfs = func(i int) { // 还是要注意,下面的i只有在dfs是有用,其余的都需要用j来判断,不要写混了
if i == n {
tmp := make([]int, n)
copy(tmp, path)
res = append(res, tmp)
return
}
for j := 0; j < n; j++ {
// 同一树层用过则跳过,这里要记住的是:树层去重用usedMap[j-1] == false。树枝去重用usedMap[j-1] == true
// 如果usedMap[j-1] == false,说明以nums[i-1]为某一层元素的选择已穷尽,以至于在回溯的时候将map数据置为false,于是后续会根据这个条件跳过同层相等元素
if j > 0 && nums[j] == nums[j-1] && usedMap[j-1] == false {
continue
}
if usedMap[j] {
continue
}
usedMap[j] = true
path = append(path, nums[j])
dfs(i + 1)
path = path[:len(path)-1]
usedMap[j] = false
}
}
dfs(0)
return res
}
78.子集:给定一个数组(元素不相同),返回所有的子集,比如[1,2,3]返回[]、[1]等。就是正常的回溯,每一步都需要将结果存起来,然后注意到了I==n的时候就return把。
https://blog.csdn.net/midi666/article/details/128105138
func subsets(nums []int) [][]int {
var path []int
var res [][]int
n := len(nums)
var dfs func(i int)
dfs = func(i int) {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp) // 因为path是全局变量,这里要固定答案
if i == n {
return
}
for j := i; j < n; j++ { // 枚举选择的数字
path = append(path, nums[j])
dfs(j + 1) // 注意这里是j+1,不是i+1
path = path[:len(path)-1] // 恢复现场
}
}
dfs(0)
return res
}
90.子集II:有重复元素。也是先排序,和上面的判断条件一样
https://blog.csdn.net/midi666/article/details/128106186
func subsetsWithDup(nums []int) [][]int {
n := len(nums)
var res [][]int
var path []int
sort.Ints(nums)
if len(nums) <= 0 {
return res
}
usedMap := make(map[int]bool)
var dfs func(i int)
dfs = func(i int) {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
if i == n {
return
}
for j := i; j < n; j++ {
if j > 0 && nums[j] == nums[j-1] && usedMap[j-1] == false { // usedMap[j-1] == false 树层去重
continue
}
usedMap[j] = true
path = append(path, nums[j])
dfs(j + 1)
usedMap[j] = false
path = path[:len(path)-1]
}
}
dfs(0)
return res
}
39.组合总和I:给定数组,无重复元素,但可以多次使用每个元素,返回和等于target的组合。回溯,剪枝(先排序)的条件是:j < n && sum + candidates[j]<= target,可重复选的条件是dfs(j),里面没有+1
https://blog.csdn.net/midi666/article/details/128071575
func combinationSum(candidates []int, target int) (ans [][]int) {
n := len(candidates)
if n <= 0 {
return
}
sort.Ints(candidates) // 剪枝的话需要先排序
var path []int
var sum int
var dfs func(int)
dfs = func(i int) {
if sum == target {
tmp := make([]int, len(path))
copy(tmp, path)
ans = append(ans, tmp)
return
}
if sum > target {
return
}
for j := i; j < n && sum + candidates[j] <= target; j++ { // 在这里剪枝
path = append(path, candidates[j])
sum += candidates[j]
dfs(j)
path = path[:len(path)-1]
sum -= candidates[j]
}
}
dfs(0)
return
}
40.组合总和II:有重复元素,树枝上可以重复,树层上需要去重。回溯,剪枝(需要排序),树层去重两种方式:1.j >0&& candidates[j]== candidates[j-1]&& usedMap[j-1]false; 2. j > i && candidates[j] candidates[j-1]
https://blog.csdn.net/midi666/article/details/128071880
func combinationSum2(candidates []int, target int) [][]int {
var res [][]int
var path []int
var sum int
usedMap := make(map[int]bool)
n := len(candidates)
if len(candidates) <= 0 {
return res
}
sort.Ints(candidates)
var dfs func(i int)
dfs = func(i int) {
if sum == target {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
return
}
if sum > target {
return
}
for j := i; j < n && sum+candidates[j] <= target; j++ {
if j > 0 && candidates[j] == candidates[j-1] && usedMap[j-1] == false {
continue
}
path = append(path, candidates[j])
sum += candidates[j]
usedMap[j] = true
dfs(j + 1)
path = path[:len(path)-1]
sum -= candidates[j]
usedMap[j] = false
}
}
dfs(0)
return res
}
216.组合总和III:给一个K代表可以使用1-9中的K个数,给个N代表要求的和,返回可以组成的二维数组。正常的递归+回溯,有终止条件
https://blog.csdn.net/midi666/article/details/128012650
func combinationSum3(k int, n int) [][]int {
//要找K个数的和为n
var res [][]int
var path []int
sum := 0
var backTrack func(int)
backTrack = func(startIndex int) {
//剪枝
if sum > n || len(path) > k {
return
}
//递归终止条件
if len(path) == k && sum == n {
tmp := make([]int, k) //一样的原因
copy(tmp, path)
res = append(res, tmp)
return
}
//单层递归循环+剪枝
for i := startIndex; i <= 9-(k-len(path))+1; i++ {
sum += i
path = append(path, i)
backTrack(i+1)
//回溯
sum -= i
path = path[:len(path)-1]
}
}
backTrack(1)
return res
}
377.组合总和IV:给一个数组,给个target,返回能求出组合的个数。这道题只能用动态规划的背包问题来解,而且是排列的思路:求组合数:外层for循环遍历物品,内层for遍历背包。求排列数:外层for遍历背包,内层for循环遍历物品。dp[i]: 凑成目标正整数为i的排列个数为dp[i]
https://blog.csdn.net/midi666/article/details/128343884
func combinationSum4(nums []int, target int) int {
// 首先题目写的是组合,但根据示例,其实是排列
// 如果是排列,那么遍历背包的顺序就是先背包,再物品了
dp := make([]int, target+1) // 背包容量为target+1
dp[0] = 1
for i := 0; i <= target; i++ { // i是背包,可以等于
for j := 0; j < len(nums); j++ { // j是物品
if i - nums[j] >= 0 { // 这一行表示此次循环中这个背包还能装下
dp[i] += dp[i-nums[j]] //注意谁是i,谁是j
}
}
}
return dp[target]
}
17.电话号码的数字组合:给个字符串如23,返回23对应的拨号盘按键的组合。回溯,先定义一个全局的数组,存每个位置对应的字母字符串,然后用dfs进行回溯,对于每个位置的字母找到匹配位置对应的字符串进行遍历,终止为i到了给定字符串的长度即可(path是字符串,不是切片,不用copy)
https://blog.csdn.net/midi666/article/details/128071002
var mapping = [...]string{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
func letterCombinations(digits string) (ans []string) {
var res []string
var path string
n := len(digits)
if n <= 0 {
return res
}
var dfs func(int)
dfs = func(i int) {
if i == n { // 到达边界,可以append
res = append(res, path)
return
}
// 没有到达边界
for _, c := range mapping[digits[i]-'0'] {
path = path + string(c)
dfs(i+1)
path = path[:len(path)-1]
}
}
dfs(0)
return res
}
22.括号生成**(不好想)**:给一个数n,输出n对括号,不同的排列方式。回溯,这个有点难,属于一种组合型回溯,首先要定义m:=2*n,然后在dfs中需要传两个值,一个是常规的i,且终止到i==m;另一个是left,代表左括号的个数,如果左括号个数小于n,则还能仅左括号的回溯dfs(i+1, left+1),右括号的个数其实为i-left,i-left小于left则代表右括号也能加入,则dfs(i+1, left)
https://blog.csdn.net/midi666/article/details/138824354
func generateParenthesis(n int) []string {
m := 2 * n
ans := []string{}
var path []byte
var dfs func(i, left int)
dfs = func(i, left int) {
if i == m {
ans = append(ans, string(path))
}
if left < n {
path = append(path, '(')
dfs(i+1, left+1)
path = path[:len(path)-1]
}
if i-left < left {
path = append(path, ')')
dfs(i+1, left)
path = path[:len(path)-1]
}
}
dfs(0, 0)
return ans
}
79.单词搜索:给定一个二维数组[][]byte和一个word,判断是否在二维数组中。二维回溯,这道题不需要记录path,因为题目判断找到就算(用一个变量来表示),找到后直接一层层结束递归就行。递归和回溯可以改变原二维数组,用于避免重复元素。dfs中的终止条件有:已找到直接退出、越过边界、字母不相等、被使用过和长度相等刚刚找到,然后就上下左右四个方向搜索+回溯(和岛屿数量那道题有点像,但这道题要dfs(i, j, k), k用来判断index)
https://blog.csdn.net/midi666/article/details/140312853
func exist(board [][]byte, word string) bool {
// 这道题的结果是判断是否存在,用一个遍历来存,满足则直接返回
found := false
m, n := len(board), len(board[0])
var dfs func(i, j, k int)
dfs = func(i, j, k int) {
// 已经找到,直接返回
if found {
return
}
// 超出索引范围
if i < 0 || j < 0 || i >= m || j >= n {
return
}
// 之前走过这个位置了,不能再走
if board[i][j] == '*' { // 也可以新起一个二维数组来存这个改动
return
}
// 元素不相等
if board[i][j] != word[k] { // 比较的递归中的某个字符
return
}
// 走到这代表元素相等,再判断长度
if k == len(word)-1 {
found = true
return
}
// 到这里表示还没找到,但是目前还是符合预期的,dfs四个方向继续找
tmp := board[i][j]
board[i][j] = '*'
dfs(i-1, j, k+1)
dfs(i+1, j, k+1)
dfs(i, j-1, k+1)
dfs(i, j+1, k+1)
board[i][j] = tmp
}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
dfs(i, j, 0)
}
}
return found
}
131.分割回文子串:给定一个字符串,返回二维数组,里面的都是回文子串,如给定abc,返回[[‘a’,‘b’,‘c’]]。回溯法,框架逻辑不变,其余的第一点是判断追加到结果中的条件是if i == n表示遍历完了,第二点是这个在for循环的横向遍历中,要先截取字符串t := s[i : j+1],然后判断这个子串是不是回文,回文才走回溯,path中追加的也是t
https://blog.csdn.net/midi666/article/details/128074125
func partition(s string) [][]string {
var res [][]string
var path []string
n := len(s)
var dfs func(i int)
dfs = func(i int) {
if i == n {
temp := make([]string, len(path))
copy(temp, path)
res = append(res, temp)
return
}
for j := i; j < n; j++ {
t := s[i : j+1] // 切片的截法,比如aab,要截取a,就是s[0,1]
if isHuiWen(t) {
path = append(path, t)
dfs(j + 1)
path = path[:len(path)-1]
}
}
}
dfs(0)
return res
}
func isHuiWen(t string) bool {
left := 0
right := len(t) - 1
for left < right {
if t[left] != t[right] {
return false
}
left++
right--
}
return true
}
51.N皇后:给定一个n,返回的是[][]string,里面存的是由Q和.组成的字符串。回溯,二维回溯,dfs中的i代表的是深度,for循环中的j是宽度,这样二维数组就对齐了。然后就是要先初始化一个二维数组,都赋值成功".“,然后dfs中,for循环里面判断下以i, j为起点,这个位置的列、左上角和右上角的三个方向是否有"Q”,没有的话才进行dfs。注意二维的dfs(i+1), 不是j+1,同时二维path要回溯
https://blog.csdn.net/midi666/article/details/128122365
func solveNQueens (n int) [][]string {
var res [][]string
path := make([][]string, n)
for i := 0; i < n; i++ {
path[i] = make([]string, n)
}
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
path[i][j] = "."
}
}
var dfs func(int)
dfs = func(i int) {
if i == n {
tmp := make([]string, n)
for j := 0; j < n; j++ {
tmp[j] = strings.Join(path[j], "")
}
res = append(res, tmp)
return
}
for j := 0; j < n; j++ {
if isValid(n, i, j, path) { // i相当于行数,纵向深度;j是列数,宽度
path[i][j] = "Q"
dfs(i + 1)
path[i][j] = "."
}
}
}
dfs(0)
return res
}
func isValid(n, row, col int, path [][]string) bool {
for i := 0; i < row; i++ {
if path[i][col] == "Q" { // 判断列
return false
}
}
for i, j := row-1, col-1; i >= 0 && j >= 0; i, j = i-1, j-1 { // 左上角
if path[i][j] == "Q" {
return false
}
}
for i, j := row-1, col+1; i >= 0 && j < n; i, j = i-1, j+1 { // 右上角
if path[i][j] == "Q" {
return false
}
}
return true
}
35.搜索插入位置:给定一个数组和一个target,能找到的话就返回下标,找不到的话就返回应该插入位置的下标。完全就是左闭右开二分查找的代码。注意一般写法是先判断nums[mid] < target
https://blog.csdn.net/midi666/article/details/139283789
func searchInsert(nums []int, target int) int {
left := 0
right := len(nums)
for left < right {
mid := left + (right-left)/2
if nums[mid] == target {
return mid
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid
}
}
return left
}
34.在排序数组中找到元素的第一个和最后一个位置:返回下标。二分查找,先用target找一次返回左边界,再用target+1找一下,-1就是右边界;注意找到左边界后,要判断下找到的值是否==target以及找到的下标是否越界,不然部分用例过不去
https://blog.csdn.net/midi666/article/details/137327349
func searchRange(nums []int, target int) []int {
start := search(nums, target)
if start == len(nums) || nums[start] != target {
return []int{-1, -1}
}
end := search(nums, (target+1)) - 1
return []int{start, end}
}
func search(nums []int, target int) int {
n := len(nums)
left := 0
right := n
for left < right {
mid := left + (right-left)/2
if nums[mid] < target { // 这里没有等于
left = mid + 1
} else {
right = mid
}
}
return left
}
33.搜索旋转排序数组:如5601234,target=0,返回0的下标。二分查找的方法,先通过nums[0]和target的大小来判断target是在左半个数组还是右半个数组中,这个是不会随着二分查找的进度而改变的;然后使用二分查找到中点的下标,根据nums[mid]和nums[0]的大小比较来判断是否先找到了在对应的那半个数组中,再用二分不断逼近
https://blog.csdn.net/midi666/article/details/139378424
func search(nums []int, target int) int {
// 左闭右开写法
left := 0
right := len(nums)
// 先提前判断target在左子数组中还是右子数组
flag := false // 在左子数组
if nums[0] > target { // 目标在右子数组(前半段的递增数组中,第一个元素就大于target,说明和target相等的下标在右子数组中)
flag = true
}
for left < right {
mid := left + (right-left)/2
if nums[mid] == target { // 直接找到了,就返回完事
return mid
}
if flag { // 目标在右子数组
if nums[mid] > nums[0] { // 目标都在右子数组了,中点还在左子数组呢,赶紧处理
left = mid + 1
} else { // 中点在右子数组
if nums[mid] < target {
left = mid + 1
} else {
right = mid
}
}
} else { // 目标在左子数组(和上方同理)
if nums[mid] < nums[0] { // 中点在右子数组
right = mid
} else { // 中点在左子数组
if nums[mid] < target {
left = mid + 1
} else {
right = mid
}
}
}
}
return -1
}
153.寻找旋转排序数组中的最小值:二分查找,这个最小值左边的值,都应该大于nums[right], 最小值包括其右边的值,都小于等于nums[right], 其实是以nums[right]为基准
https://blog.csdn.net/midi666/article/details/139379117
func findMin(nums []int) int {
left := 0
right := len(nums) - 1 // 注意右边界不能用右开的写法,会导致下面的判断方法越界
// 要找的这个最小值,其位置右边的数据(包括它自己)应该都小于右边界的值,其位置左边的数据都大于右边界的值
for left < right { // 这里不能有等于,会死循环(同时退出循环的条件就是left==right)
mid := left + (right-left)/2
if nums[mid] > nums[right] { // 中点的值大于右边界的值,,说明最小值在此时的中点和右边界之间,将left更新到mid+1
left = mid + 1
} else { // 中点的值小于右边界,说明从这个中点开始,到右边界都是递增的了,那最小值要么在前半段,要不就是这个mid
right = mid
}
}
return nums[right]
}
20.有效的括号:给一堆括号『()、[]、{}』判断是否有效。用数组实现栈的思想,定义一个map[byte]byte,先存好每种右括号对应的左括号,左括号就加进栈中,右括号就判断数组的最后一个元素是不是等于此时右括号在map中匹配的左括号,最后数组为空才对
https://blog.csdn.net/midi666/article/details/127180254
func isValid(s string) bool {
hash := map[byte]byte{')': '(', ']': '[', '}': '{'}
stack := make([]byte, 0)
if s == "" {
return true
}
for i := 0; i < len(s); i++ {
if s[i] == '(' || s[i] == '[' || s[i] == '{' { // 左括号入栈
stack = append(stack, s[i])
} else if len(stack) > 0 && stack[len(stack)-1] == hash[s[i]] {
stack = stack[:len(stack)-1]
} else {
return false
}
}
return len(stack) == 0
}
155.最小栈:实现最小栈功能的基础push、pop、top、getmin之类的功能。定义的结构体中用两个数组,一个存正常的数据,来啥存啥,另一个存每次的进来的数据和该栈的栈顶(最小的)相比较的最小数据;pop的时候都一起操作,top的时候返回第一个栈的栈顶,getmin的时候返回第二个栈的栈顶
https://blog.csdn.net/midi666/article/details/139429144
type MinStack struct {
stack []int
minStack []int
}
func Constructor() MinStack {
return MinStack{
stack: []int{},
minStack: []int{math.MaxInt64},
}
}
func (this *MinStack) Push(x int) {
this.stack = append(this.stack, x)
top := this.minStack[len(this.minStack)-1]
this.minStack = append(this.minStack, min(x, top))
}
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]
}
394.字符串解码:给定个字符串如:“3[a]2[bc]”,返回个字符串如:“aaabcbc”,这种题其实比较明显的用栈来实现,细节比较多,主要思路是用一个[]string来模拟栈,遇到数字,就将这部分挨着的数字都转成字符串加进栈中;遇到字母和左括号也加,遇到右括号弹出来左括号之前的那部分字母,拿出来后先反转下复原,然后在repeat几次重新加进栈中,大概这么个思路,还有些细节
https://blog.csdn.net/midi666/article/details/139429930
func decodeString(s string) string {
stk := []string{}
ptr := 0
for ptr < len(s) {
cur := s[ptr]
if cur >= '0' && cur <= '9' { // 此时遍历到了数字,将该位置及后面挨着的数字进行处理
digits := ""
for ; s[ptr] >= '0' && s[ptr] <= '9'; ptr++ {
digits += string(s[ptr])
}
stk = append(stk, digits)
} else if (cur >= 'a' && cur <= 'z' || cur >= 'A' && cur <= 'Z') || cur == '[' {
stk = append(stk, string(cur)) // 字母和左括号入栈
ptr++
} else { // 其余的就是匹配到了右括号
sub := []string{}
for stk[len(stk)-1] != "[" { // 注意这里,上面追加进数组的时候都字符串化了
sub = append(sub, stk[len(stk)-1])
stk = stk[:len(stk)-1]
}
for i := 0; i < len(sub)/2; i++ { // 这部分的含义是,比如是[bc]入栈的,出栈的时候成了cb了,所以要反转下
sub[i], sub[len(sub)-1-i] = sub[len(sub)-1-i], sub[i]
}
stk = stk[:len(stk)-1] // 把左括号弹出
repTime, _ := strconv.Atoi(stk[len(stk)-1]) // 数字,代表重复的次数
stk = stk[:len(stk)-1] // 把数字也弹出
t := strings.Repeat(strings.Join(sub, ""), repTime) // 将给定字符串重复多少次
stk = append(stk, t)
ptr++
}
}
return strings.Join(stk, "")
}
739.每日温度:给一个数组,返回一个数组,代表几天后到达第一个比当前位置高的温度。用单调递减栈,从右向左遍历比较好记,每次遍历到当前的温度大于等于单调栈顶的温度时,先弹出,再计算符合的下标差(栈顶下标-此时下标),再此时下标进栈
https://blog.csdn.net/midi666/article/details/139455463
func dailyTemperatures(temperatures []int) []int {
n := len(temperature)
// 两个额外数组,一个存结果ans,这个默认已经初始化为0了,后面更新哪个位置,就将对应的下标间隔更新进去
ans := make([]int, n)
// 另一个就是栈,栈中存的是下标
stack := []int{}
for i := n - 1; i >= 0; i-- {
value := temperature[i]
for len(stack) > 0 && value >= temperature[stack[len(stack)-1]] { // 从右到左遍历,当前的高度大于栈中的数据,就弹出栈中的小的数据
stack = stack[:len(stack)-1]
}
if len(stack) > 0 {
ans[i] = stack[len(stack)-1] - i // 将栈中存的比当前位置大的下标与当前位置下标相减
}
stack = append(stack, i) // stack中存的是下标
}
return ans
}
84.柱状图中的最大矩形:给一个数组,代表每个位置的高度,返回算出来的最大面积。用单调栈,这道题不好想的地方在于,遍历的某个位置的时候,计算面积的方式为:该位置的高度 * (该位置右边第一个小于它的位置 - 该位置左边第一个小于它的位置 - 1);然后计算left和right的方式就是单调栈(这种找某某条件下第一个什么什么的,一般是单调栈的思路);这里需要注意一点的是,整个left和right的都要初始化为固定长度的数组,不能使用append的方式,要直接在对应索引的位置上进行更新。所以要遍历三次,一次构造left单调栈,一次构造right单调栈,一次算面积;另外注意下,构造单调栈的时候,如果栈中没有了元素,就是left中存-1,right中存n。
https://blog.csdn.net/midi666/article/details/140168626
func largestRectangleArea(heights []int) int {
n := len(heights)
left := make([]int, n)
stack := []int{}
for i := 0; i < n; i++ {
for len(stack) > 0 && heights[i] <= heights[stack[len(stack)-1]] {
stack = stack[:len(stack)-1]
}
if len(stack) > 0 {
left[i] = stack[len(stack)-1]
} else {
left[i] = -1
}
stack = append(stack, i)
}
stack = []int{}
right := make([]int, n)
for i := n - 1; i >= 0; i-- {
for len(stack) > 0 && heights[i] <= heights[stack[len(stack)-1]] {
stack = stack[:len(stack)-1]
}
if len(stack) > 0 {
right[i] = stack[len(stack)-1]
} else {
right[i] = n
}
stack = append(stack, i)
}
var ans int
for i, val := range heights {
ans = max(ans, val*(right[i]-left[i]-1))
}
return ans
}
32.最长有效括号(hard):求长度。用栈和dp都可以,由于dp比较抽象,就说栈的方法,还是用数组实现栈,具体思路就是始终保持栈底元素为当前已经遍历过元素中『最后一个没有被匹配的右括号的下标』(开始时先把-1压进去);遍历的时候,左括号进栈,右括号的话,先匹配左括号,直接出栈,然后在此基础上,判断栈的长度是否为0,为0则表示当前的这个右括号为没有匹配到的右括号,将其入栈;若不为空,则用当前的下标i-此时栈中最后一个元素的下标,比较后取最大值
https://blog.csdn.net/midi666/article/details/139886547
func longestValidParentheses(s string) int {
maxAns := 0
stack := []int{}
stack = append(stack, -1)
for i := 0; i < len(s); i++ {
if s[i] == '(' {
stack = append(stack, i) // 下标入栈
} else {
stack = stack[:len(stack)-1] // 注意,右括号的时候已经先匹配左括号弹出了,下面的所有逻辑就是在弹出的基础上
if len(stack) == 0 {
stack = append(stack, i)
} else {
maxAns = max(maxAns, i-stack[len(stack)-1]) // 当前遍历到的右括号的下标 - 栈的最后一个元素(栈顶元素)
}
}
}
return maxAns
}
55.跳跃游戏I:给定数组,能不能跳到最后。贪心,每次遍历计算出最大覆盖的位置,最后能到n-1就行(不关心怎么到的,能到就行)
https://blog.csdn.net/midi666/article/details/128181550
func canJump(nums []int) bool {
n := len(nums)
maxPosition := 0
for i := 0; i < n; i++ {
if i > maxPosition {
return false
}
maxPosition = max(maxPosition, i + nums[i])
if maxPosition >= n-1 { // 提前退出
return true
}
}
return true
}
45.跳跃游戏II:一定能到最后,最少要几步。贪心,求最大覆盖位置是一样的,还需要关注的是要遍历到<n-1,不然可能会多算一步;还有需要一个end变量用边界来赋值,每走到一次end就是步数+1
https://blog.csdn.net/midi666/article/details/139843760
func jump(nums []int) int {
n := len(nums)
maxPosition := 0 // 最大可跳步数
end := 0 // 边界
step := 0
for i := 0; i < n-1; i++ { // 这里要小于n-1
maxPosition = max(maxPosition, i+nums[i])
if i == end { // 每次到边界,就将边界更新成最大可跳步数,并将步数+1
end = maxPosition
step++
}
}
return step
}
763.划分字符区间:给定一个字符串,要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中,返回每一段的长度。贪心法,先遍历一遍,将每种字符最后出现的下标记下来,用[26]int来记,然后遍历,用start和end来标记位置,end = max(end, endPos[c-‘a’]),直到i == end时代表一组到了结尾,算出来长度添加进结果中,再将start=end+1
https://blog.csdn.net/midi666/article/details/139848301
func partitionLabels(s string) []int {
lastPos := [26]int{}
for i, c := range s {
lastPos[c-'a'] = i // 每个字母存的是下标位置
}
start, end := 0, 0
res := []int{}
for i, c := range s {
end = max(end, lastPos[c-'a'])
if i == end {
res = append(res, end-start+1)
start = end + 1
}
}
return res
}
121.买卖股票的最佳时机I:只能买卖一次。动态规划的话定义二维数组,第二维只有01;注意这个数组定义的一般是第i天持有(不持有)啥啥啥,注意用动态规划的买入和卖出都是max,贪心的话买入是min表示花费的最少钱
股票+打家劫舍汇总
func maxProfit(prices []int) int {
// 动态规划解法
dp := make([][]int, len(prices))
for i := 0; i < len(prices); i++ {
dp[i] = make([]int, 2)
}
// 所以在这里初始化的是一个二维数组,且第二维度只有0和1两个值
// 0代表持有该股票,1代表不持有该股票
// 初始化DP数组
dp[0][0] = -prices[0] // 若第一天就持有的话,只能是当天买入
dp[0][1] = 0 // 若第一天不持有,很合理
for i := 1; i < len(prices); i++ {
dp[i][0] = max(dp[i-1][0], -prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
}
return dp[len(prices)-1][1]
}
198.打家劫舍:给定数组,返回能偷的最大价值,不能连着偷。最最普通的动态规划
func rob(nums []int) int {
if len(nums) == 1 { // 兼容测试用例
return nums[0]
}
dp := make([]int, len(nums)+1)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i := 2; i < len(nums); i++ {
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
}
return dp[len(nums)-1]
}
118.杨辉三角:给一个整数n代表有n行,返回一个二维数组符合杨辉三角。模拟法,可以将每一行的左边都对齐,这样就能看出来matrix[i][j]= matrix[i-1][j-1]+ matrix[i-1][j]
https://blog.csdn.net/midi666/article/details/139843995
func test(n int) [][]int {
matrix := make([][]int, n)
for i := 0; i < n; i++ {
matrix[i] = make([]int, i+1) // 内层数组大小
matrix[i][0], matrix[i][i] = 1, 1
for j := 1; j < i; j++ {
matrix[i][j] = matrix[i-1][j-1] + matrix[i-1][j]
}
}
return matrix
}
279.完全平方数:给定n,返回和为n的完全平方数的数量。完全背包问题,先遍历背包在遍历物品。dp[i]:和为i的完全平方数的最少数量为dp[i];初始化除了0外,都默认最大值
https://blog.csdn.net/midi666/article/details/128347624
func numSquares(n int) int {
dp := make([]int, n+1) // 其实n就是背包
dp[0] = 0
for i := 1; i < n+1; i++ {
dp[i] = math.MaxInt32
}
for i := 1; i <= n; i++ { // 先遍历背包,0没有遍历的必要了
for j := 1; j <= i; j++ { // 遍历物品,注意终止条件,如果直接写小于n的话会超时
if i >= j*j {
dp[i] = min(dp[i], dp[i-j*j]+1)
}
}
}
return dp[n]
}
322.零钱兑换:给定数组,里面的数代表硬币的面额,可以认为是无限个数的,amount代表金额,求凑成金额的最少硬币数量。完全背包,先遍历物品再遍历背包。dp[j]:凑足总额为j所需钱币的最少个数为dp[j];初始化除了0外,都默认最大值
https://blog.csdn.net/midi666/article/details/128346731
func coinChange(coins []int, amount int) int {
dp := make([]int, amount+1)
dp[0] = 0 // dp数组表示凑成总金额为0需要的最小硬币数量一定是0
for i := 1; i < amount+1; i++ {
dp[i] = math.MaxInt32 //因为要求最小数量,所以全部初始化为最大值
}
for i := 1; i <= amount; i++ { //遍历背包,0 没啥用不需要遍历了
for j := 0; j < len(coins); j++ { //遍历物品
if i - coins[j] >= 0 { // 注意是谁和谁比较大小,是背包容量大于本次物品的价值
dp[i] = min(dp[i], dp[i-coins[j]]+1)
}
}
}
if dp[amount] == math.MaxInt32 {
return -1
}
return dp[amount]
}
139.单词拆分:给定字符串,给一个字符串数组,问数组中的字符串能否拼出来给定字符串,可以重复使用。完全背包问题,求排列,先背包再物品;dp[i]: 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词;同时还需要将数组先转化成map[string]bool来进行判断
https://blog.csdn.net/midi666/article/details/128370766
func wordBreak(s string, wordDict []string) bool {
// 单词可以重复使用,完全背包问题
// 首先明确,s是背包,后面的那个数组是物品
wordDictMap := make(map[string]bool)
for _, val := range wordDict { // 注意这里遍历的是数组,每个元素可能是一个单词
wordDictMap[val] = true // map先全部初始化为true
}
dp := make([]bool, len(s)+1)
dp[0] = true //默认一定是true
// 求排列,外层遍历背包,内层物品
for i := 1; i <= len(s); i++ { // 遍历背包,0没有什么遍历的必要了
for j := 0; j < i; j++ { // 遍历物品(直接遍历小于当前背包容量的情况),想想有没有等于,虽然加上等也过了,但下面的判断条件里,后半个就没有意义了(注意下这个物品的定义方式可能和之前的不太一样)
if dp[j] && wordDictMap[s[j:i]] {
dp[i] = true
}
}
}
return dp[len(s)]
}
300.最长递增子序列:给数组,求长度。动态规划,求长度,都初始化为1;不要求连续;dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度;dp[i]=max(dp[i], dp[j]+1)
https://blog.csdn.net/midi666/article/details/128542016
func lengthOfLIS(nums []int) int {
n := len(nums)
dp := make([]int, n)
for i := 0; i < n; i++ {
dp[i] = 1 // 因为求的是长度,所以都初始化成1
}
res := dp[0]
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
if nums[i] > nums[j] {
dp[i] = max(dp[i], dp[j]+1)
}
}
res = max(res, dp[i])
}
return res
}
152.乘积最大子数组:返回乘积。动态规划,用两个变量来存,每次更新变量最大值和最小值,取三者最大值
https://blog.csdn.net/midi666/article/details/122774531
func maxProduct(nums []int) int {
n := len(nums)
preMin := nums[0]
preMax := nums[0]
res := nums[0]
for i := 1; i < n; i++ {
tmp1 := preMin * nums[i]
tmp2 := preMax * nums[i]
preMin = min(min(tmp1, tmp2), nums[i]) // 维护三者中的min
preMax = max(max(tmp1, tmp2), nums[i]) // 维护三者中的max
res = max(res, preMax)
}
return res
}
416.分割等和子集:给一个数组,判断能否可以分成两个和相同的子集。普通01背包,外层遍历物品,内层遍历背包且倒序;dp[j]表示背包所能装的总重量是j,放进物品后,背包最大重量为dp[j]
https://blog.csdn.net/midi666/article/details/128228824
func canPartition(nums []int) bool {
// 先求出来这个数组的总和,然后除以2用于算出target
sum := 0
for _, num := range nums {
sum += num
}
if sum % 2 == 1 {
return false
}
target := sum / 2
dp := make([]int, 20001)
for i := 0; i < len(nums); i++ {
for j := target; j >= nums[i]; j-- { // j的容量要大于nums[i]的重量,不然这个物品都装不进去,就没有遍历的必要了
dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])
}
}
return dp[target] == target
}
62.不同路径:给定m和n来代表矩阵,问从开始走到右下角有多少条不同的路径。普通的二维动态规划,主要初始化第一行第一列都为1
https://blog.csdn.net/midi666/article/details/128192080
func uniquePaths(m int, n int) int {
dp := make([][]int, m)
for i := 0; i < m; i++ {
dp[i] = make([]int, n)
dp[i][0] = 1
}
for i := 0; i < n; i++ {
dp[0][i] = 1
}
for i := 1; i <= m-1; i++ {
for j := 1; j <= n-1; j++ {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
return dp[m-1][n-1]//注意下标,下标要-1
}
63.不同路径II:给定二维数组,数组中的1表示障碍物,问有多少条路径。整体思路不变,初始化的时候遇到障碍物,后面的就不是1了
https://blog.csdn.net/midi666/article/details/128194747
func uniquePathsWithObstacles(obstacleGrid [][]int) int {
m, n := len(obstacleGrid), len(obstacleGrid[0])
dp := make([][]int, m)
//里面的一维数组初始化得提前到这了
for i, _ := range dp {
dp[i] = make([]int, n)
}
//初始化, 如果是障碍物, 后面的就都是0, 不用循环了
for i := 0; i < m && obstacleGrid[i][0] == 0; i++ {
dp[i][0] = 1
}
for i := 0; i < n && obstacleGrid[0][i] == 0; i++ {
dp[0][i] = 1
}
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
if obstacleGrid[i][j] != 1 { //如果obstacleGrid[i][j]这个点是障碍物, 那么dp[i][j]保持为0
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
}
return dp[m-1][n-1]
}
64.最小路径和:给定二维数组,数值代表这个位置的权重,找到右下角的路径和最小的和。不能盲目初始化为1了,而是第一行和第一列初始化为权重,递推公式也适当跟着改一下
https://blog.csdn.net/midi666/article/details/133655571
func minPathSum(grid [][]int) int {
if len(grid) == 0 || len(grid[0]) == 0 {
return 0
}
m := len(grid)
n := len(grid[0])
dp := make([][]int, m+1)
for i := 0; i <= m; i++ {
dp[i] = make([]int, n+1)
}
dp[0][0] = grid[0][0]
for i := 1; i < m; i++ { // 第一行初始化
dp[i][0] = dp[i-1][0] + grid[i][0]
}
for j := 1; j < n; j++ { // 第一列初始化
dp[0][j] = dp[0][j-1] + grid[0][j]
}
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] // 递推公式
}
}
return dp[m-1][n-1]
}
647.回文子串:求给定字符串中回文子串的个数。用中心扩展法好记点
https://blog.csdn.net/midi666/article/details/128761782
func countSubstrings(s string) int {
n := len(s)
result := 0
for i := 0; i <= n-1; i++ {
result += extend(s, i, i, n) // 一个点为中心
result += extend(s, i, i+1, n) // 两个点为中心
}
return result
}
func extend(s string, i,j,n int) int{
res := 0
for i >= 0 && j < n && s[i] == s[j] {
i-- //因为要中心扩展,所以符合条件的要在此基础上,分别向左右扩展,所以i是--,j是++,和别的是相反的
j++
res++
}
return res
}
5.最长回文子串:顾名思义。动态规划法,求出下标:二维数组,从左下到右上,在s[i] == s[j]的前提下,j-i<=1或dp[i+1][j-1]为true,另外需要记录leftIndex和maxLen;中心扩展法:expend(s, i, i, n) 和expend(s, i , i+1, n),得出左右下标,取最大的范围
https://blog.csdn.net/midi666/article/details/131276576
func longestPalindrome(s string) string {
n := len(s)
start, end := 0, 0
for i := 0; i < n; i++ {
left1, right1 := expend(s, i, i, n)
left2, right2 := expend(s, i, i+1, n)
if right1 - left1 > end - start{
start, end = left1, right1
}
if right2 - left2 > end - start{
start, end = left2, right2
}
}
return s[start:end+1]
}
func expend(s string, i, j, n int) (int, int) {
for i >= 0 && j < n && s[i] == s[j] {
i-- //中心扩展
j++
}
return i+1, j-1 // 这里的返回结果可以根据例子想一个
}
1143.最长公共子序列:给定两个字符串,求两个的公共子序列的长度。不要求连续,二维动态规划,表示长度为[0,i-1]的字符串text1和长度为[0,j-1]的字符串text2,最长公共子序列长度为dp[i][j];递推公式相等的时候dp[i-1][j-1] + 1,不等的时候dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
https://blog.csdn.net/midi666/article/details/128753801
func longestCommonSubsequence(text1 string, text2 string) int {
m := len(text1)
n := len(text2)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
// 上面这里已经初始化为0了
for i := 1; i <= m; i++ { // 一定要从1开始,因为下面用的是i-1,且小于等于m或者小于m-1
for j := 1; j <= n; j++ {
if text1[i-1] == text2[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
}
}
}
return dp[m][n]
}
72.编辑距离:给两个字符串,求一个转成另一个的最小步数。二维动态规划,dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。初始化是指一个字符串删到空串的步数dp[i][0]和dp[0][j],递推公式也是相等时候:dp[i][j]= dp[i-1][j-1],不等的时候:dp[i][j]=min(min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1])+1
https://blog.csdn.net/midi666/article/details/128760625
func minDistance(word1 string, word2 string) int {
m := len(word1)
n := len(word2)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
// 初始化
for i := range dp {
dp[i][0] = i
}
for j := range dp[0] {
dp[0][j] = j
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if word1[i-1] == word2[j-1] {
dp[i][j] = dp[i-1][j-1]
} else {
dp[i][j] = min(min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1])+1
}
}
}
return dp[m][n]
}
136.只出现一次的数字:1个元素出现一次,其余的都出现两次。用异或来解
func singleNumber(nums []int) int {
res := 0
for _, num := range nums {
res ^= num
}
return res
}
169.多数元素:返回数组中某个元素的数量超过一半以上的。用一个map,循环的时候++,大于n/2就返回
func majorityElement(nums []int) int {
tmp := make(map[int]int)
n := len(nums)
for i := 0; i < n; i++ {
tmp[nums[i]]++
if tmp[nums[i]] > n/2 {
return nums[i]
}
}
return 0
}
75.颜色分类:012三个颜色排序后返回原数组。用zero和two两个指针开始先指向开头和末尾,遍历到two(注意for循环的时候不能i++),=1的时候i++;2的时候交换i和two,two–;0的时候交换i和zero,i++,zero++
https://blog.csdn.net/midi666/article/details/139900112
func sortColors(nums []int) {
n := len(nums)
zero, two := 0, n-1
for i := 0; i < two; { // two初始化在末尾,然后不断被更新,遍历到two的时候其实后面都是确认过的2了
if nums[i] == 1 { // 1什么都不用做
i++
} else if nums[i] == 2 {
nums[i], nums[two] = nums[two], nums[i]
two -= 1
} else {
nums[i], nums[zero] = nums[zero], nums[i]
zero += 1
i++
}
}
}
31.下一个排列:求下一个更大的排列。用双指针从后向前遍历到第一个升序对,再从后半部分中找到第一个大于升序左边界的值,交换;然后再将后半部分升序排序
https://blog.csdn.net/midi666/article/details/139900898
func nextPermutation(nums []int) {
n := len(nums)
if n <= 1 {
return
}
i, j, k := n-2, n-1, n-1
// 先从后向前找第一个升序的对
for i >= 0 && nums[i] >= nums[j] {
i--
j--
}
// 不是最后一个排列
if i >= 0 {
// 找到后半段中比i大的元素
for nums[i] >= nums[k] {
k--
}
nums[i], nums[k] = nums[k], nums[i]
}
// 反转从j到末尾的后半部分,用双指针(是最后一个排列的话就直接翻转数组)
for i, j := j, n-1; i < j; i, j = i+1, j-1 {
nums[i], nums[j] = nums[j], nums[i]
}
}
287.寻找重复数:只有一个元素出现了多次,其余出现了一次。用i->nums[i]构造成环,用环形链表II的思路来解,注意初始化的时候得用nums[0]和nums[nums[0]]来初始化;注意第一次相等的时候,fast置为0,而不是nums[0]; 返回结果是slow,而不是nums[slow]
https://blog.csdn.net/midi666/article/details/139903764
func findDuplicate(nums []int) int {
slow, fast := 0, 0
for {
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast {
fast = 0
for {
slow = nums[slow]
fast = nums[fast]
if slow == fast {
return slow
}
}
}
}
}