hot100

哈希

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
				}
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值