Talk Is Cheap Show Me The Code - 枯燥乏味的leetcode刷题

  • 刷题链接
  • 工作至今,解决过的最难的问题,莫过于实现一个bft 算法,弄懂bft 算法,但是后来发现,在leetcode面前,bft就是毛毛雨昂
  • 刷不完的题,一段时间不刷,就忘了思路,关键做不到每天刷(毕竟已经工作了 😦)
  • 头疼的刷题啊

记录

  • 2022-05-18
    • 下个月应该可以恢复刷题了,学了将近一个月的rust
  • 2022-05-05
    • 刷题暂停一段时间,大概一个月吧,先搞rust 框架了
  • 2022-04-22
    • 磨磨唧唧,总算又刷完100道了
  • 2022-04-06
    • 几天没刷,就感觉没思路了
  • 2022-03-21
    • typora? ,我的记录呢?
  • 2022-02-21
    • 刷不完的题
  • 2022-01-25
    • 枯燥的刷题
  • 2022-01-06
    • 讨厌刷题
  • 2021-12-26
    • 卷王第一步,刷不完的题
  • 2021-12-10
  • 2021-12-08
    • 先刷hot100 吧,困难的跳过
  • 2021-12-05
    • 开始刷第二遍
  • 2021-12-02
    • 总算又刷完一遍了

汇总

  • 固定不可以申请新的空间的情况下

    • 可以使用 双指针
  • 滑动窗口的临界条件是right<len(s),而不是 left<right

  • 二维数组: arr[m][n] 代表的是n行m列,所以m是列,n是行

  • 回溯算法或者说是深度优先dfs算法考虑以下问题

    • 什么时候添加到结果集
    • 什么时候终止递归
  • 排列组合问题

    • 使用递归回溯法
  • 无重复:

    • 注意临界条件: 此时通常都是 a<b 而不是 a<=b
  • 动态规划

    • 其实本质上是 子问题
      • 例如对于正则匹配题,可以将 a* 这个看做一个单独的整体
  • 连续: 代表着没有重复的数字

  • 数组:

    • 双指针(一头一尾)
    • 快慢指针
    • 滑动窗口
    • 连续:
      • 滑动窗口
    • 有序:
      • 二分
      • 快慢指针(删除重复元素)
  • 整数:

    • x&(x-1) 消除最后1个1
    • (x&(x-1))^x: 获取最后一个1的位置
    • 异或消除重复元素
  • 滑动窗口的核心:

    • 1. 2个变量,left,right
      2. 第一层循环: for
      3. for 循环内, 
      	if  xxx 满足:
      		right++
        else if xxx 满足
        	left++
        else: 满足结果集 
        	xxx,最后left++ 
      
  • 二进制:

    • x&(x-1) 获取最后一个1
    • 判断是否是2的整数次幂:
      • x&(x-1)==0
  • 数组:

    • 有序: 二分
    • 双指针
  • 链表:

    • 双指针
    • 快慢指针(求中点)
  • 在二叉树中:

    • 通过栈遍历:
      • 分两步骤:
          1. 数据入栈(所以无论是先序中序后序都会有个for循环进行处理)
          2. 根节点操作(找到根节点: 因此先序是在入栈的第一步进行操作,而后序遍历则是在 需要判断下右节点是否已经处理了)
    • 其实非递归遍历的处理,根本就是对root 根节点的处理而已
    • 先序非递归: 先代表着 根左右,所以在入栈前就可以存储值了
    • 中序非递归: 中代表着 左右根,
    • 尤其是当涉及到递归的时候,都是需要把值传入到参数中
  • 有序代表着,相连不相同,则之后的必然不相同

  • O(nlogn)肯定都需要折半,意味着都需要找中点 (链表则是快慢指针,数组则直接中间)

  • 链表需要找中间点的,注意最开始的退出条件需要为:

    • if head!=nil && head.next!=nil 才行
  • 判断是否是回文链表/字符串

    • 都需要找中点
    • 然后后半段反转,再与前半段匹配
  • 暴力解法:

    • 通常来说都是固定一个值,更改另外一个值
      • 如84 题,暴力求面积的情况, 面积=高*宽,所以可以在稳定高的情况下,求宽的最大值
  • dfs 模板:

    • func dfs(node *TreeNode) {
      	stack := make([]*TreeNode, 0)
      	stack = append(stack, node)
      	for len(stack) > 0 {
      		v := stack[len(stack)-1]
      		stack = stack[:len(stack)-1]
      		if nil != v.Right {
      			stack = append(stack, v.Right)
      		}
      		if nil != v.Left {
      			stack = append(stack, v.Left)
      		}
      		if len(stack)==0{
      			return
      		}
      	}
      }
      
  • Bfs 模板:

    • 
      func levelOrder1(root *TreeNode) [][]int {
      	if root == nil {
      		return nil
      	}
      	r := make([][]int, 0)
      	queue := make([]*TreeNode, 0)
      	queue = append(queue, root)
      	for len(queue) > 0 {
      		list := make([]int, 0)
      		l := len(queue)
      		for i := 0; i < l; i++ {
      			node := queue[0]
      			queue=queue[1:]
      			list = append(list, node.Val)
      			if nil != node.Left {
      				queue = append(queue, node.Left)
      			}
      			if nil != node.Right {
      				queue = append(queue, node.Right)
      			}
      		}
      		r=append(r,list)
      	}
      	return r
      }
      
      
  • 分治法的关键:

      1. 退出条件
      2. 分治
      3. 合并结果
  • 二进制核心:

    • 异或:

      • 2个数不同则为1,相同则为0,可以用来查找重复元素
    • 得到一个数最后一个1的位置:

      • x&(x-1)的作用在于: 消除最后一个1

      • (x&(v-1))^v

      • 原理:

        • x-1 使得 x二进制下的最右边的值变为0
          x&(x-1)使得,消除了最后一个1  
          
          x(10)  0000 1010
          x-1(9) 0000 1001
          x&x-1  0000 1000	消除最后一个1
          ^x     0000 1010
          
          (x&(x-1))^x 0000 0010  就得到了最后一个1
          
          
          
    • 2个数的交换,也可以通过异或来实现:

      • a=a^b
        b=b^a
        a=a^b 
        则最终,a,b的值互相交换了
        a=10100001,b=00000110
        a=a^b;   //a=10100111
        b=b^a;   //b=10100001
        a=a^b;   //a=00000110
        
  • 计算一个数中在二进制形式下1的个数,通过位移判断(既 x>>1 &1 )即可

    • r:=0
      for i:=0;i<64;i++{
      		if v>>i & 1 >0{
      			r++
      		}
      }
      则这个元素 v 二进制形式下1的个数为r 
      
      或者 
      while(x>0){
      count++
      x=x&(x-1)
      }
      原理:
      x-1 会将 这个数 最右边的1 变为0 
      则可以计算1的个数
      
      

模板

  • 回溯

    • 回溯算法的基本模板是: 1. 先将推出条件全都写上 2. 直接进入下一个循环 3. 开始剪枝
      直接无脑dfs
      dfs = func(left int, index int) {
      		//1. 先将推出条件全都写上
      		if index == len(candidates) {
      			return
      		}
      		if left == 0 {
      			// 表明符合条件,可以作为其中一个结果
      			ret = append(ret, append([]int(nil), single...))
      			return
      		}
      		//2. 直接进入下一个循环
      		// 然后直接跳到下一个
      		dfs(left, index+1)
      
      		// 3. 开始剪枝
      		if xxxxx {
      			single = append(single, candidates[index])
      			// 继续dfs
      			dfs(left-candidates[index], index)
      			// 剪枝
      			single = single[:len(single)-1]
      		}
      	}
      
  • 搜索:

    • 二分搜索:

      • 
        func search(nums []int, target int) int {
        	left := 0
        	right := len(nums) - 1
        	for left <= right {
        		mid := (left + right) >> 1
        		if nums[mid] == target {
        			return mid
        		} else if nums[mid] < target {
        			left = mid + 1
        		} else {
        			right = mid - 1
        		}
        	}
        	return -1
        }
        
        func search2(nums []int,target int)int{
        	left:=0
        	right:= len(nums)-1
        	for left+1<right{
        		mid:=left+(right-left)>>1
        		if nums[mid]==target{
        			right=mid
        		}else if nums[mid]<target{
        			left=mid
        		}else{
        			right=mid
        		}
        	}
        	if nums[right]==target{
        		return right
        	}
        	if nums[left]==target{
        		return left
        	}
        	return -1
        }
        
        

HOT 100

  • two sum

    • 关键是通过一个map存储剩余的值即可,如果存在,直接返回

      • func twoSum(nums []int, target int) []int {
        	m := make(map[int]int)
        	for index, v := range nums {
        		left := target - v
        		if leftIndex, exist := m[left]; exist {
        			return []int{index, leftIndex}
        		}
        		m[v] =index
        	}
        	return nil
        }
        
  • lt_2_两链表数相加

    • 
      // 关键: 2个节点,一个作为返回值,一个作为后续末尾值,因为最后可能会大于10,所以需要手动的添加一个值
      func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
      	needMore := false
      	var ret *ListNode
      	var tail *ListNode
      	for nil != l1 || nil != l2 {
      		tmp := 0
      		if nil != l1 {
      			tmp += l1.Val
      			l1 = l1.Next
      		}
      		if nil != l2 {
      			tmp += l2.Val
      			l2 = l2.Next
      		}
      		if needMore {
      			tmp++
      		}
      		if tmp >= 10 {
      			needMore = true
      			tmp %= 10
      		} else {
      			needMore = false
      		}
      		if nil == ret {
      			ret = &ListNode{Val: tmp}
      			tail = ret
      		} else {
      			tail.Next = &ListNode{Val: tmp}
      			tail = tail.Next
      		}
      	}
      	// 如果>10 ,还需要的是,将其末尾追加一个值
      	if needMore {
      		h := &ListNode{Val: 1}
      		tail.Next = h
      	}
      
      	return ret
      }
      
  • lt_3_最长字符串子串

    • 
      // 关键: 双指针
      func lengthOfLongestSubstring(s string) int {
      	left, right := 0, 0
      	ret := 1
      	// 关键是: 1. 通过一个set保存byte出现的下标
      	// 2. 双指针,右指针不停的向右滑动,当发现对应的元素已经在set中出现过的时候,则挪动左指针到出现过的地方
      	// 双指针, 右指针不停的移动
      	m := make(map[byte]int)
      	for ;right < len(s);right++ {
      		index, exist := m[s[right]]
      		if exist {
      			// 更新左指针的最大下标
      			left = lengthOfLongestSubstringMax(left, index)
      		}
      		// 更新最大值,因为最大值是通过 右指针-左指针+1 的
      		ret = lengthOfLongestSubstringMax(ret, right-left+1)
      		// 这一步是关键,要记得,要更新右指针的下标,因为此时也是相当于更新左指针
      		m[s[right]] = right + 1
      	}
      
      	return ret
      }
      func lengthOfLongestSubstringMax(a, b int) int {
      	if a < b {
      		return b
      	}
      	return a
      }
      
  • lt_6_Z字形变换

    • func convert(s string, numRows int) string {
      	if numRows==1{
      		return s
      	}
      	// 如: PAYPALISHIRING
      	/*
      		P   A   H   N
      		A P L S I I G
      		Y   I   R
      		P,A,Y 到达三个之后 ,上移 然后继续 P ,再继续上移 => A ,发现到了临界点,变成了L
      	*/
      	ret := ""
      	sbs := make([][]byte, numRows)
      	for i := 0; i < numRows; i++ {
      		sbs[i] = make([]byte, 0)
      	}
      	down := false
      	currentRow := 0
      	for i := 0; i < len(s); i++ {
      		sbs[currentRow] = append(sbs[currentRow], s[i])
      		if currentRow == 0 || currentRow == numRows-1 {
      			down = !down
      		}
      		if down {
      			currentRow++
      		} else {
      			currentRow--
      		}
      	}
      	for _, v := range sbs {
      		ret += string(v)
      	}
      	return ret
      }
      
  • lt_7_整数反转

    • 关键是:

      • 忽略边界条件, 返回值是先自己*10 ,然后加上之前值的取余
    • func reverse(x int) int {
      	// 123 => 321
      	r := 0
      	for x != 0 {
      		if r > math.MaxInt32/10 || r < math.MinInt32/10 {
      			return 0
      		}
      		// 先取得最后一个数
      		last := x % 10 // 123 % 10 =3  => 12%10=2 => 1%10=1
      		// 然后移除最后一个数
      		x /= 10 // 123 /10 =12  12 /10 =1  => 1
      		// 再累加结果
      		r = r*10 + last // 0*10 +3  => 3*10+2=32  => 32*10 + 1=> 321
      	}
      
      	return r
      }
      
  • lt_8_atoi的实现

    • 
      // 关键是多种极端情况下的测验
      func myAtoi(s string) int {
      	ret := 0
      
      	// 1. 去除前导前缀
      	index := 0
      	for ; index < len(s); {
      		if s[index] == ' ' {
      			index++
      			continue
      		}
      		break
      	}
      	// 2. 可能是极端情况,极端情况下会出现刚好达到长度
      	if index == len(s) {
      		return 0
      	}
      	// 判断是正数还是负数
      	div := false
      	if s[index] == '-' {
      		div = true
      		index++
      	} else if s[index] == '+' {
      		index++
      	}
      	for ; index < len(s); index++ {
      		v := s[index]
      		// 注意: 这一步是主要是为了,消除不合法的数,如 asdd aaa 123,这种是不合法的
      		if v < '0' || v > '9' {
      			break
      		}
      		if ret >= math.MaxInt32/10 {
      			if div {
      				return math.MinInt32
      			}
      			return math.MaxInt32
      		} else if ret <= math.MinInt32/10 {
      			if !div {
      				return math.MaxInt32
      			}
      			return math.MinInt32
      		}
      		ret = ret*10 + int(v-'0')
      	}
      	if div {
      		ret *= -1
      	}
      	return ret
      }
      
  • lt_9_判断是否是回文整数

    • 
      // 关键: 转换为字符串,进行互相匹配即可
      // func isPalindrome(x int) bool {
      // 	str := strconv.Itoa(x)
      // 	l := len(str)
      // 	for i, j := 0, l-1; i < j; {
      // 		if str[i] != str[j] {
      // 			return false
      // 		}
      // 		i++
      // 		j--
      // 	}
      // 	return true
      // }
      
      // 第二种方法,反转这个数字即可
      func isPalindrome(x int) bool {
      	rever := 0
      	prev := x
      	for x > 0 {
      		rever = rever*10 + x%10
      		x /= 10
      	}
      	return rever == prev
      }
      
    • lt_9_盛最多水的容器

      • 
        // 关键: 计算公式: 面积=长*宽 ,在宽移动的时候必然缩小的前提下,则往高处移动,才有可能计算出更大的面积
        // 左右双指针
        func maxArea(height []int) int {
        	left, right := 0, len(height)-1
        	ret := 0
        
        	for left < right {
        		// 并且注意: 水的计算,是要根据木桶的最短板的,所有 后面的被乘的数 是一个min的值
        		ret = maxAreaMax(ret, (right-left)*maxAreaMin(height[left], height[right]))
        		// 宽度注定减少的情况下,尽量往高的移动
        		if height[left] < height[right] {
        			left++
        		} else {
        			right--
        		}
        	}
        
        	return ret
        }
        func maxAreaMin(a, b int) int {
        	if a < b {
        		return a
        	}
        	return b
        }
        func maxAreaMax(a, b int) int {
        	if a > b {
        		return a
        	}
        	return b
        }
        
        
    • lt_11_整数转罗马数字

      • 
        // func intToRoman(num int) string {
        // 	r := ""
        // 	romans := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
        // 	ints := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
        //
        // 	for i := 0; i < len(ints); i++ {
        // 		for num >= ints[i] {
        // 			r += romans[i]
        // 			num -= ints[i]
        // 		}
        // 	}
        //
        // 	return r
        // }
        
        // 关键: 先列举出所有 数字对应的罗马数字,然后遍历 ,如1000 , num有几个1000,就可以加几个1000对应的罗马数字即可
        func intToRoman(num int) string {
        	ret := ""
        	m := map[int]string{
        		1:    "I",
        		4:    "IV",
        		5:    "V",
        		9:    "IX",
        		10:   "X",
        		40:   "XL",
        		50:   "L",
        		90:   "XC",
        		100:  "C",
        		400:  "CD",
        		500:  "D",
        		900:  "CM",
        		1000: "M",
        	}
        	nums := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
        
        	for _, k := range nums {
        		if num == 0 {
        			break
        		}
        		v := m[k]
        		if num/k > 0 {
        			count := num / k
        			for i := 0; i < count; i++ {
        				ret += v
        			}
        			num %= k
        		}
        	}
        
        	return ret
        }
        
    • lt_13_罗马数字转整数

      • 
        // 关键: 如果说通过当前值获取得到罗马之后,小的在前面,则 返回值需要减去,否则的话,直接累加
        func romanToInt(s string) int {
        	ret := 0
        	for i := 0; i < len(s); i++ {
        		v := getInt(s[i])
        		if i+1 < len(s) && v < getInt(s[i+1]) {
        			// 如果说小的在前面,如 IV ,I < V  ,CD C<D
        			// 则此时是要减去的
        			ret -= v
        		} else {
        			ret += v
        		}
        	}
        
        	return ret
        }
        
        func getInt(c byte) int {
        	switch c {
        	case 'I':
        		return 1
        	case 'V':
        		return 5
        	case 'X':
        		return 10
        	case 'L':
        		return 50
        	case 'C':
        		return 100
        	case 'D':
        		return 500
        	case 'M':
        		return 1000
        	default:
        		return 0
        	}
        }
        
    • lt_14_最长公共前缀

      • // 关键: 其实类似于暴力法,直接遍历全部,然后进行一个一个匹配即可
        func longestCommonPrefix(strs []string) string {
        	if len(strs) == 0 {
        		return ""
        	}
        	ret := strs[0]
        	for i := 1; i < len(strs); i++ {
        		ret = longestCommonPrefixCompare(ret, strs[i])
        	}
        	return ret
        }
        func longestCommonPrefixCompare(str1, str2 string) string {
        	l1, l2 := len(str1), len(str2)
        	limit := l1
        	if l2 < l1 {
        		limit = l2
        	}
        	i := 0
        	for ; i < limit; i++ {
        		if str1[i] == str2[i] {
        			continue
        		}
        		break
        	}
        	return str1[:i]
        }
        
    • lt_15_三数之和

      • // 关键
        // 1. 建立手机数字和字符的映射关系
        var phoneMap map[byte]string = map[byte]string{
        	2: &amp;quot;abc&amp;quot;,
        	3: &amp;quot;def&amp;quot;,
        	4: &amp;quot;ghi&amp;quot;,
        	5: &amp;quot;jkl&amp;quot;,
        	6: &amp;quot;mno&amp;quot;,
        	7: &amp;quot;pqrs&amp;quot;,
        	8: &amp;quot;tuv&amp;quot;,
        	9: &amp;quot;wxyz&amp;quot;,
        }
        var ret []string
        
        // 关键: 回溯法
        func letterCombinations(digits string) []string {
        	if len(digits) == 0 {
        		return nil
        	}
        	ret = nil
        	// 2 回溯法
        	letterCombinationsBackTrack(digits, 0, &amp;quot;&amp;quot;)
        	return ret
        }
        func letterCombinationsBackTrack(digits string, index int, res string) {
        	if index == len(digits) {
        		ret = append(ret, res)
        		return
        	}
        	str := phoneMap[digits[index]-&amp;#39;0&amp;#39;]
        	// 第三步: 各自遍历自己的数字所对应的值:回溯法, 如 2,3 ,当当前下标为0的时候,2对应的 value为: abc,3 对应的为:def
        	// 则开始以 abc ,abd,abe 开始遍历
        	for i := 0; i &amp;lt; len(str); i++ {
        		// 最终得到的结果序列为: ad,ae,af,bd ...
        		letterCombinationsBackTrack(digits, index+1, res+string(str[i]))
        	}
        }
        
    • lt_16_最接近的三数之和

      • // 关键: 排序+双指针
        func threeSumClosest(nums []int, target int) int {
        	ret := math.MaxInt32
        	// 第一步: 排序
        	sort.Ints(nums)
        	var a, b, c int
        	update := func(cur int) {
        		r1 := cur - target
        		r2 := ret - target
        		if r1 < 0 {
        			r1 *= -1
        		}
        		if r2 < 0 {
        			r2 *= -1
        		}
        		if r1 < r2 {
        			ret = cur
        		}
        	}
        	for i := 0; i < len(nums); i++ {
        		// 第二步: 以a 为基准值
        		a = nums[i]
        		// 第三步: 去除a的重复解
        		if i > 0 && nums[i] == nums[i-1] {
        			continue
        		}
        		for j, k := i+1, len(nums)-1; j < k; {
        			// 第四步: 定b,c的基准
        			b, c = nums[j], nums[k]
        			sum := a + b + c
        			if sum == target {
        				return target
        				// 因为数组是已经排序过了的,所以可以控制移动方向
        			} else if sum > target {
        				// 如果比target大,左移,并且去除 c 重复解
        				k--
        				for ; k > j && c == nums[k]; k-- {
        				}
        			} else {
        				// 如果比target小,右移,去除b重复解
        				j++
        				for ; j < k && nums[j] == b; j++ {
        				}
        			}
        			// 第五步: 更新返回值
        			update(sum)
        		}
        	}
        	return ret
        }
        
    • lt_17电话号码的全部组合

      • 
        // 关键
        // 1. 建立手机数字和字符的映射关系
        var phoneMap map[byte]string = map[byte]string{
        	2: "abc",
        	3: "def",
        	4: "ghi",
        	5: "jkl",
        	6: "mno",
        	7: "pqrs",
        	8: "tuv",
        	9: "wxyz",
        }
        
        var ret []string
        
        // 关键: 回溯法
        func letterCombinations(digits string) []string {
        	if len(digits) == 0 {
        		return nil
        	}
        	ret = nil
        	// 2 回溯法
        	letterCombinationsBackTrack(digits, 0, "")
        	return ret
        }
        func letterCombinationsBackTrack(digits string, index int, res string) {
        	if index == len(digits) {
        		ret = append(ret, res)
        		return
        	}
        	str := phoneMap[digits[index]-'0']
        	// 第三步: 各自遍历自己的数字所对应的值:回溯法, 如 2,3 ,当当前下标为0的时候,2对应的 value为: abc,3 对应的为:def
        	// 则开始以 abc ,abd,abe 开始遍历
        	for i := 0; i < len(str); i++ {
        		// 最终得到的结果序列为: ad,ae,af,bd ...
        		letterCombinationsBackTrack(digits, index+1, res+string(str[i]))
        	}
        }
        
        
    • lt_19_删除链表的倒数k个节点

      • 
        // 关键: 快慢指针,快指针走n步
        func removeNthFromEnd(head *ListNode, n int) *ListNode {
        	dummy := &ListNode{Next: head}
        	fast, slow := head, dummy
        	// 关键:
        	// 第一步: fast从head触发,快指针先走n步,slow 从dummy出发,当走到尾的时候,代表着中点
        	for i := 0; i < n; i++ {
        		fast = fast.Next
        	}
        	// 第二步: 慢指针从dummy出发,当fast走到nil之后,代表着slow 走到了第n步
        	for nil != fast {
        		slow = slow.Next
        		fast = fast.Next
        	}
        	slow.Next = slow.Next.Next
        	return dummy.Next
        }
        
        
    • lt_20_有效的括号

      • // 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
        // 关键: 用栈处理
        func isValid(s string) bool {
        	stack := make([]byte, 0)
        	for i := 0; i < len(s); i++ {
        		v := s[i]
        		if v == '(' || v == '[' || v == '{' {
        			stack = append(stack, v)
        		} else {
        			// 如果非 左边方向,则栈弹出匹配是否是相反的值
        			if len(stack) == 0 {
        				return false
        			}
        			pop := stack[len(stack)-1]
        			stack = stack[:len(stack)-1]
        			if (v == ')' && pop != '(') || (v == '}' && pop != '{') || (v == ']' && pop != '[') {
        				return false
        			}
        		}
        	}
        	return len(stack) == 0
        }
        
    • lt_21_合并2个有序链表

      • // 关键: 直接暴力遍历即可
        func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
        	dummy := &ListNode{}
        	tmp := dummy
        	for nil != l1 && nil != l2 {
        		if l1.Val < l2.Val {
        			tmp.Next = l1
        			l1 = l1.Next
        		} else {
        			tmp.Next = l2
        			l2 = l2.Next
        		}
        		tmp = tmp.Next
        	}
        	if nil != l1 {
        		tmp.Next = l1
        	} else {
        		tmp.Next = l2
        	}
        	return dummy.Next
        }
        
    • lt_22_括号生成

      • 
        // 关键: 回溯算法,深度优先遍历(dfs)
        // 参考: https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/
        // 对于回溯算法,都可以使用二叉树来想象代入
        func generateParenthesis(n int) []string {
        	if n == 0 {
        		return nil
        	}
        	ret := make([]string, 0)
        	generateParenthesisDfs("", n, 0, 0, &ret)
        	return ret
        }
        
        func generateParenthesisDfs(str string, limit int, leftCount, rightCount int, ret *[]string) {
        	if leftCount == limit && rightCount == limit {
        		*ret = append(*ret, str)
        		return
        	}
        	// 因为 ( ) 是成对出现的, ( 必然是要比 ) 个数要多的
        	if leftCount < rightCount {
        		return
        	}
        	if leftCount < limit {
        		generateParenthesisDfs(str+"(", limit, leftCount+1, rightCount, ret)
        	}
        	if rightCount < limit {
        		generateParenthesisDfs(str+")", limit, leftCount, rightCount+1, ret)
        	}
        }
        
    • lt_23_两两交换链表中的节点

      • // 关键: 使用dummy节点,用dummy来移动整个链表
        // 每次交换都是交换dummy后的两个节点即可
        func swapPairs(head *ListNode) *ListNode {
        	dummy := &ListNode{Next: head}
        	tmp := dummy
        	// tmp->1->2 =>  tmp->2->1
        	for nil != tmp && tmp.Next != nil && tmp.Next.Next != nil {
        		node1 := tmp.Next
        		node2 := node1.Next
        		tmp.Next = node2
        		node1.Next = node2.Next
        		node2.Next = node1
        		tmp = node1
        	}
        	return dummy.Next
        }
        
        
    • lt_26_删除有序数组中的重复项

      • // 关键: 双指针:快慢指针
        func removeDuplicates(nums []int) int {
        	if len(nums) < 2 {
        		return len(nums)
        	}
        	slow, fast := 0, 1
        	for ; fast < len(nums); {
        		if nums[fast] == nums[slow] {
        			// 当重复的时候,快指针继续移动,直到遇到不相等的数
        			fast++
        		} else {
        			slow++
        			// 不重复的时候,这个元素移动到之前重复的元素位置处
        			nums[slow] = nums[fast]
        		}
        	}
        	return slow + 1
        }
        
    • lt_27_移除元素

      • 
        // 关键: 就是 重新赋值
        // func removeElement(nums []int, val int) int {
        // 	index := 0
        // 	for _, v := range nums {
        // 		if v != val {
        // 			nums[index] = v
        // 			index++
        // 		}
        // 	}
        // 	return index
        // }
        
        // 关键: 双指针,将相等的数,都放到后面去
        func removeElement(nums []int, val int) int {
        	if len(nums) == 0 {
        		return 0
        	}
        	left, right := 0, len(nums)
        	for left < right {
        		if nums[left] == val {
        			nums[left] = nums[right-1]
        			right--
        		} else {
        			left++
        		}
        	}
        	return left
        }
        
        
    • lt_28_实现strStr

      • // 只会暴力匹配,kmp什么吊玩意,对我没用,学个j
        func strStr(haystack string, needle string) int {
        	if len(needle) == 0 {
        		return 0
        	}
        	m, n := len(haystack), len(needle)
        match:
        	for i := 0; i+n < m; i++ {
        		for j := 0; j < n; j++ {
        			if haystack[i+j] != needle[j] {
        				continue match
        			}
        			return i
        		}
        	}
        	return -1
        }
        
        
    • lt_29_两数相除

      • 
        // 关键: 除法可以等同为 加法,如 7/2 = 7比2大,至少为1,2翻倍为4,7比4大,则也翻倍,4翻倍为8比7大,所以要用7-4 与2继续比对
        // 参考: https://leetcode-cn.com/problems/divide-two-integers/solution/po-su-de-xiang-fa-mei-you-wei-yun-suan-mei-you-yi-/
        // 还要注意临界条件
        func divide(dividend int, divisor int) int {
        	flag := 1
        	if dividend < 0 {
        		flag *= -1
        		dividend *= -1
        	}
        	if divisor < 0 {
        		flag *= -1
        		divisor *= -1
        	}
        	// 注意临界条件
        	if divisor == 1 {
        		ret := dividend * flag
        		if ret > math.MaxInt32 {
        			return math.MaxInt32
        		} else if ret < math.MinInt32 {
        			return math.MinInt32
        		}
        		return ret
        	}
        
        	return div(dividend, divisor) * flag
        }
        func div(a, b int) int {
        	if a < b {
        		return 0
        	}
        	ret := 1
        	tmp := b
        	// 核心是这个for循环
        	// 如 7/2 = 7比2大,至少为1,2翻倍为4,7比4大,则也翻倍,4翻倍为8比7大,所以要用7-4 与2继续比对
        	for tmp+tmp <= a {
        		ret = ret + ret
        		tmp += tmp
        	}
        	return ret + div(a-tmp, b)
        }
        
    • lt_31_下一个排列

      • 
        // 关键:
        // 1. 从后往前遍历,找到 前一个数比当前数小的值 i,代表着是可以有下一个排列的
        // 2. 就算找到之后,也是不可以直接 i+1和i 替换位置的,因为需要找到 nums[i+1:]大于nums[i]的最小值
        // 3. 在nums[i+1:] 找到大于nums[i]的最小值之后,替换,替换之后,对nums[i+1:]进行升序排列,确保是最小的
        /*
        
        nums = [1,2,7,4,3,1],
        第一步: 倒序遍历数组, 找出第一组: 前一个数比后一个数小的两个数, 即[2, 7]
        2所处的这个位置就是需要找出比它稍微大的数的位置;
        我们从[7,4,3,1]中找出比2大的数中的最小值, 也就是3, 找到后跟2交换即可;; 当然了, 如果没找到的话, 直接跳到第5步, 直接升序排列输出.
        目前nums=[1,3,7,4,2,1], 不用我说你们也看出来还不算下一个排列
        对3后面的数, 升序排列, 即最终结果: nums = [1,3,1,2,4,7]
        */
        func nextPermutation(nums []int)  {
        	if len(nums)==0{
        		return
        	}
        	firstIndex:=-1
        	// 第一步
        	for i:= len(nums)-2;i>=0;i--{
        		if nums[i]<nums[i+1]{
        			firstIndex=i
        			break
        		}
        	}
        	if firstIndex==-1{
        		lt31reverse(nums)
        		return
        	}
        	// 第二步 ,在nums[i+1:] 找大于nums[i]的最小值
        	secondIndex:=firstIndex+1
        	min:=nums[firstIndex+1]
        	for i:=firstIndex;i< len(nums);i++{
        		if nums[i]<min && nums[i]>nums[firstIndex]{
        			min=nums[i]
        			secondIndex=i
        		}
        	}
        
        	// 第三步,对nums[i+1:]进行升序排序
        	nums[firstIndex],nums[secondIndex]=nums[secondIndex],nums[firstIndex]
        	sort.Ints(nums[firstIndex+1:])
        }
        
        func lt31reverse(a []int) {
        	for i, n := 0, len(a); i < n/2; i++ {
        		a[i], a[n-1-i] = a[n-1-i], a[i]
        	}
        }
        
    • lt_33_搜索旋转排序数组

      • // 关键:
        // mid 值可能在 反转区间
        func search(nums []int, target int) int {
        	left, right := 0, len(nums)-1
        	for left+1 < right {
        		mid := left + (right-left)>>1
        		if nums[mid] == target {
        			return mid
        		}
        		if nums[mid] > nums[left] {
        			// 表明在正常区间
        			// 则开始判断target 所处的范围: 是在反转区间内,还是在正常区间
        			if target >= nums[left] && target < nums[mid] {
        				// 正常区间
        				right = mid - 1
        			} else {
        				left = mid + 1
        			}
        		} else {
        			// 表面,mid 处于 反转区间
        			if target > nums[mid] && target <= nums[right] {
        				left = mid + 1
        			} else {
        				right = mid - 1
        			}
        		}
        	}
        	if nums[left] == target {
        		return left
        	}
        	if nums[right] == target {
        		return right
        	}
        	return -1
        }
        
  • lt_34_在排序数组中查找元素的第一个和最后一个位置

    • 
      // 关键:
      // 两次查找,一次是不停的往左找,直到找到最先出现的,第二次是向右找,直到找到最后一个出现的
      // 注意,我们要保存的是当前找到的值,所以会有一个额外的变量定义返回值
      func searchRange(nums []int, target int) []int {
      	if len(nums) == 0 {
      		return []int{-1, -1}
      	}
      	f := func(leftDirection bool) int {
      		left, right := 0, len(nums)-1
      		ret := -1
      		for left <= right {
      			mid := left + (right-left)>>1
      			if target < nums[mid] {
      				right = mid - 1
      			} else if target > nums[mid] {
      				left = mid + 1
      			} else {
      				// 注意,我们要保存的是当前找到的值,所以会有一个额外的变量定义返回值
      				ret = mid
      				// 如果是找第一个,则不停的左移动
      				if leftDirection {
      					right = mid - 1
      				} else {
      					// 说明是找最后一个,则不停的右移动
      					left = mid + 1
      				}
      			}
      		}
      		return ret
      	}
      	ret := []int{-1, -1}
      	ret[0] = f(true)
      	ret[1] = f(false)
      	return ret
      }
      
  • lt_35_搜索插入位置

    • 
      // 关键: 题目需要转换为:「在一个有序数组中找第一个大于等于 target的下标 (相当于是找第一个出现的位置)
      func searchInsert(nums []int, target int) int {
      	left, right := 0, len(nums)-1
      	for left+1 < right {
      		mid := left + (right-left)>>1
      		if target < nums[mid] {
      			right = mid
      		} else if target > nums[mid] {
      			left = mid
      		} else {
      			// 一直往左边找,因为是第一个出现的位置
      			right = mid
      		}
      	}
      	// 如果是基于 for left+1<right 的模板, 最后都是需要判断left,right的
      	if nums[left] >= target {
      		return left
      	}
      	if nums[right] >= target {
      		return right
      	} else {
      		// 说明当前right达到了最大值,并且是整个数组中都没有这个数
      		return right + 1
      	}
      }
      
      
  • lt_38_外观数列

    • 
      /*
      n=5的时候:
      1
      11
      21
      1211
      111221
      一步一步来
      给一个数,这个数是1
      描述上一步的数,这个数是 1 即一个1,故写作11
      描述上一步的数,这个数是11即两个1,故写作21
      描述上一步的数,这个数是21即一个2一个1,故写作12-11
      描述上一步的数,这个数是1211即一个1一个2两个1,故写作11-12-21
      */
      // 关键是计算 n中出现相同数字的次数
      func countAndSay(n int) string {
      	// 初始为1
      	prev := "1"
      	count := 0
      	for i := 2; i <= n; i++ {
      		cur := strings.Builder{}
      		// start=j 可以使得跳到下一个不匹配的开始,如之前是 11234,则start=j 可以跳到2开始
      		for j, start := 0, 0; j < len(prev); start = j {
      			//开始计算次数
      			for j < len(prev) && prev[j] == prev[start] {
      				j++
      				count++
      			}
      			cur.WriteString(strconv.Itoa(count))
      			cur.WriteByte(prev[start])
      			count = 0
      		}
      		prev = cur.String()
      	}
      	return prev
      }
      
  • lt_36_有效的数独

    • 
      // 关键:
      // 根据题意: 一行数字不可以相同,一列数字也不可以相同,斜线也不可以相同
      // 一次遍历+ 子盒子的下标计算为 i/3,j/3
      func isValidSudoku(board [][]byte) bool {
      	var (
      		// 代表的是,9行,每行的9个元素的次数
      		rows [9][9]int
      		// 代表的是9列,每列9个元素的次数
      		cols [9][9]int
      
      		// 在每个 3*3 的子box中,9个数出现的次数
      		subboxes [3][3][9]int
      	)
      	for i := 0; i < 9; i++ {
      		for j := 0; j < 9; j++ {
      			num := board[i][j]
      			if num == '.' {
      				continue
      			}
      			// index 为 num-'0',因为数字从0开始,所以直接减去1 ,等价于 num-'0'-1
      			index := num - '1'
      			// 更新行中出现的次数
      			rows[i][index]++
      			// 更新列
      			cols[j][index]++
      
      			// 更新子盒子内的值
      			subboxes[i/3][j/3][index]++
      
      			// 最后计算结果,因为只能出现1次,所以>1 就是false
      			if rows[i][index] > 1 || cols[j][index] > 1 || subboxes[i/3][j/3][index] > 1 {
      				return false
      			}
      		}
      	}
      	return true
      }
      
  • lt_39_组合总和

    • 
      // 关键: 回溯算法,想到回溯,直接无脑 dfs
      // 当target ==0 的时候,代表是符合条件的其中一个解
      // 回溯算法的基本模板是: 1. 先将推出条件全都写上 2. 直接进入下一个循环 3. 开始剪枝
      func combinationSum(candidates []int, target int) [][]int {
      	var single []int
      	var ret [][]int
      	var dfs func(left, index int)
      
      	dfs = func(left int, index int) {
      		//1. 先将推出条件全都写上
      		if index == len(candidates) {
      			return
      		}
      		if left == 0 {
      			// 表明符合条件,可以作为其中一个结果
      			ret = append(ret, append([]int(nil), single...))
      			return
      		}
      		//2. 直接进入下一个循环
      		// 然后直接跳到下一个
      		dfs(left, index+1)
      
      		// 3. 开始剪枝
      		if left-candidates[index] >= 0 {
      			single = append(single, candidates[index])
      			dfs(left-candidates[index], index)
      			// 剪枝
      			single = single[:len(single)-1]
      		}
      	}
      	dfs(target, 0)
      	return ret
      }
      
      
  • lt_40_缺失的第一个正数

    • // 关键: 将 1放在下标为0 的地方,2放在1的地方,类推
      func firstMissingPositive(nums []int) int {
      	for i := 0; i < len(nums); i++ {
      		for nums[i] > 0 && nums[i] < len(nums) && nums[nums[i]-1] != nums[i] {
      			nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]
      		}
      	}
      
      	for i := 0; i < len(nums); i++ {
      		if nums[i] < 0 || nums[i] != i+1 {
      			return i + 1
      		}
      	}
      	return len(nums)+1
      }
      
  • lt_43_字符串相乘

    • 
      /*
      // 解题关键:
      注意点:
      	1. 边界条件: 有一个数为0,直接返回0
      	2.遍历计算乘积的时候,要注意多出来的数, 如 4*5=20 ,多出来的是2,要充分记得考虑这个值
      // 采用加法:
      // num1的数 * num2 的每个数, 然后和再累加 (这里唯一需要注意的点是,记得结果跟上0)
      // 如 num1=1234 ,num2=456
      // 1. 1234 *6
      		1234
      		   6
      		7404
      	2. 1234 *5  // 因为5 为倒数第2个数,后面还有个数,所以最后要加上一个0
      	   1234
      		 5
      	  6170+0=> 61700
      	3. 1234*4 // 因为4为倒数第3个数,后面有2个数,所以要加上2个0
      	   1234
      	    4
      	 493600
      	4. 最终将所有结果相加: 7304+61700+49360 =562704
      */
      func multiply(num1 string, num2 string) string {
      	if num1 == "0" || num2 == "0" {
      		return "0"
      	}
      	l1, l2 := len(num1), len(num2)
      
      	ret := ""
      	// 遍历num2 的每个数
      	for i := l2 - 1; i >= 0; i-- {
      		cur := ""
      		// 表明在这个位置上该追加几个0 ,
      		for j := l2 - 1; j > i; j-- {
      			cur += "0"
      		}
      		// 代表的是乘 多于的值,如 3*4=12 则 more为1
      		more := 0
      		// 遍历num1的每个数,每个数去乘 num2的每个数
      		y := int(num2[i] - '0')
      		for j := l1 - 1; j >= 0; j-- {
      			x := int(num1[j] - '0')
      			mux := x*y + more
      			// 然后取余 获取得到最后一个数
      			cur = strconv.Itoa(mux%10) + cur
      			more = mux / 10
      		}
      		// 此时more 可能不为0 ,所以还需要加上more
      		for ; more != 0; more /= 10 {
      			cur = strconv.Itoa(more%10) + cur
      		}
      		// 最后再讲结果相加,字符串相加
      		ret = addStrings(cur, ret)
      	}
      	return ret
      }
      
      func addStrings(num1, num2 string) string {
      	i1, i2 := len(num1)-1, len(num2)-1
      	add := 0
      	ret := ""
      	for ; i1 >= 0 || i2 >= 0 || add != 0; i1, i2 = i1-1, i2-1 {
      		x, y := 0, 0
      		if i1 >= 0 {
      			x = int(num1[i1] - '0')
      		}
      		if i2 >= 0 {
      			y = int(num2[i2] - '0')
      		}
      		result := x + y + add
      		// 然后计算多的值
      		ret = strconv.Itoa(result%10) + ret
      		add = result / 10
      
      	}
      
      	return ret
      }
      
      
    • lt_46_全排列

      • 
        // 关键:
        // 看到这种题,直接回溯(dfs+裁剪)
        // 需要注意点的是,需要有一个bool数组标识是否已经使用
        // 同时也需要注意的是,append 数据的时候要使用拷贝
        func permute(nums []int) [][]int {
        	ret := make([][]int, 0)
        	used := make([]bool, len(nums))
        	var dfs func(index int)
        	cur := make([]int, 0)
        	dfs = func(count int) {
        		if count == len(nums) {
        			ret = append(ret, append([]int{},cur...))
        			return
        		}
        		for i := 0; i < len(nums); i++ {
        			if !used[i] {
        				// 追加数据
        				used[i] = true
        				cur = append(cur, nums[i])
        				dfs(count + 1)
        				// 裁剪
        				used[i] = false
        				cur = cur[:len(cur)-1]
        			}
        		}
        	}
        
        	dfs(0)
        	return ret
        }
        
  • lt_47_去重的全排列

    • 
      // 关键:
      // 1. 排序: 因为涉及到去重(去重必然有排序,排序可以将相同的放在一起)
      // 2. dfs+剪枝
      func permuteUnique(nums []int) [][]int {
      	sort.Ints(nums)
      	ret := make([][]int, 0)
      	used := make([]bool, len(nums))
      	var dfs func(index int)
      	cur := make([]int, 0)
      	dfs = func(index int) {
      		if index == len(nums) {
      			ret = append(ret, append([]int{}, cur...))
      			return
      		}
      		// 因为是要构造成长度相同的值,所以需要
      		for i, v := range nums {
      			if used[i] || i > 0 && !used[i-1] && nums[i-1] == nums[i] {
      				// !used[i-1]的原因在于,当剪枝的时候, used[i-1] 会被设置为false(因为for 循环i-1 肯定是在i之前遍历到的)
      				// 而当遍历到当前下标i的时候,i-1肯定为false
      				continue
      			}
      			cur = append(cur, v)
      			used[i] = true
      			dfs(index + 1)
      			// 剪枝
      			used[i] = false
      			cur = cur[:len(cur)-1]
      		}
      	}
      	dfs(0)
      	return ret
      }
      
      
  • lt_48_旋转图像

    • // 关键: 2次翻转
      // 1. 先按水平线翻转 (既中间行)
      // 2. 按对角线翻转,此时的下标的变化: (2,1) => (1,2) (注意,当对角线翻转的时候,第二个for循环的退出条件不是全长度)
      func rotate(matrix [][]int) {
      	if len(matrix) == 0 {
      		return
      	}
      	l := len(matrix)
      	// 1. 先按水平线进行翻转
      	mid := l >> 1
      	for i := 0; i < mid; i++ {
      		matrix[i], matrix[l-i-1] = matrix[l-i-1], matrix[i]
      	}
      	// 2. 按对角线翻转
      	for i := 0; i < l; i++ {
      		for j := 0; j <i; j++ {
      			matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
      		}
      	}
      }
      
  • lt_49_字母异位词分组

    • // 题目的意思是,将同为`字母异位词`的放在一起返回
      // 字母异位词是字母出现的元素都相同,顺序不同而已
      // 如 abc 与 acb 是同一组异位词,此时可以发现,abc,acb出现的字符的个数都是相同的,因此对于这样的数,可以用map来做映射
      func groupAnagrams(strs []string) [][]string {
      	ret := make([][]string, 0)
      	m := make(map[[26]int][]string)
      	for _, str := range strs {
      		// 计算这个str 中出现的字符的次数,异位词必然都是相同的
      		count := [26]int{}
      		for _, v := range str {
      			count[v-'a']++
      		}
      		m[count] = append(m[count], str)
      	}
      	for _, v := range m {
      		ret = append(ret, v)
      	}
      	return ret
      }
      
  • lt_50_pow

    • 
      // 实现一个2的n次幂
      // 关键: 快速幂, 结果进行平方,而不是通过 原先的值进行平方
      // 2^64=2->2^2->2^4->2^8->2^16->2^32 => 2^64 
      func myPow(x float64, n int) float64 {
      	if n<0{
      		return 1/quickMyPow(x,n)
      	}
      	return quickMyPow(x,n)
      }
      func quickMyPow(x float64, n int) float64 {
      	if n == 0 {
      		return 1
      	}
      	y := quickMyPow(x, n/2)
      	if n%2 == 0 {
      		// 直接通过结果进行平方
      		return y * y
      	}
      	return y * y * x
      }
      
      
  • lt_53_最大子数组和

    • // 关键: 动态规划
      // f(i) 为到当前i的最大值(既累加)
      func maxSubArray(nums []int) int {
      	if len(nums) == 0 {
      		return 0
      	}
      	max := nums[0]
      	for i := 1; i < len(nums); i++ {
      		// 注意: 只有当 当前值更大的时候,才触发更新,因为求的是连续的最大和,而不是整个数组中的最大和
      		// 既: f(i)是单独的一个,还是作为整体中的一部分参与比较大小,而这个判断条件为 和
      		if nums[i]+nums[i-1]>nums[i]{
      			nums[i]=nums[i]+nums[i-1]
      		}
      		if nums[i]>max{
      			max=nums[i]
      		}
      	}
      
      	return max
      }
      
  • lt_54_螺旋矩阵

    • 
      // 1. 从右往左和从上往下是需要单独的if判断的(并且是与条件,而不是或)
      // 2. 注意if的边界条件,从左往右和从上往下都是<= 的边界条件,但是剩下的2个都是 <
      // 3. 还是注意边界if
      func spiralOrder(matrix [][]int) []int {
      	if len(matrix)==0{
      		return nil
      	}
      	ret:=make([]int,0)
      
      	left,right,top,bottom:=0, len(matrix[0])-1,0, len(matrix)-1
      
      	for ;left<=right && top<=bottom;{
      		// 开始遍历: 从左往右
      		for i:=left;i<=right;i++{
      			ret=append(ret,matrix[top][i])
      		}
      
      		// 继续遍历: 从上往下
      		for i:=top+1;i<=bottom;i++{
      			ret=append(ret,matrix[i][right])
      		}
      
      		// 最关键的是这一步,从右往左和从下往上是需要经过if判断
      		if right>left && bottom>top{
      			// 从右往左
      			for i:=right-1;i>left;i--{
      				ret=append(ret,matrix[bottom][i])
      			}
      
      			// 从下往上
      			for i:=bottom;i>top;i--{
      				ret=append(ret,matrix[i][left])
      			}
      		}
      		left++
      		top++
      		right--
      		bottom--
      	}
      
      
      	return  ret
      }
      
      
    • lt_55_跳跃游戏

      • 
        // 关键:
        // 1个变量,维护当前能跳到的最大值
        // for 循环,如果下标比最大值都要大,表明肯定跳不到该下标
        func canJump(nums []int) bool {
        	max := 0
        
        	for index, v := range nums {
        		if index > max {
        			return false
        		}
        
        		// 计算当处于当前index的时候,能跳到的最远距离
        		if v+index >= max {
        			max = v + index
        		}
        	}
        
        	return true
        }
        
    • lt_56_合并区间

      • 
        type arrSorts [][]int
        
        func (a arrSorts) Len() int {
        	return len(a)
        }
        
        func (a arrSorts) Less(i, j int) bool {
        	return a[i][0] < a[j][0]
        }
        
        func (a arrSorts) Swap(i, j int) {
        	a[i], a[j] = a[j], a[i]
        }
        
        // 关键:
        // 1. 排序
        // 2. for 循环,如果该index的数组的最后一个值比result中的最后一个的数组的最后一个小,则合并
        // 3. 合并的时候还有一个注意点,就是必须判断合并的最大值
        func merge(intervals [][]int) [][]int {
        	if len(intervals) == 0 || len(intervals[0]) == 0 {
        		return nil
        	}
        	sort.Sort(arrSorts(intervals))
        	ret := make([][]int, 0)
        
        	for i := 0; i < len(intervals); i++ {
        		node := intervals[i]
        		// 初始化
        		if len(ret)==0{
        			ret=append(ret,node)
        			continue
        		}
        		last:=ret[len(ret)-1]
        		// 判断初始值与last中的最后值
        		if node[0]<=last[len(last)-1]{
        			// 表明可以合并,此时还需要判断谁的值更大
        			if last[len(last)-1]<node[len(node)-1]{
        				last[len(last)-1]=node[len(node)-1]
        			}
        		}else{
        			// 直接append
        			ret=append(ret,node)
        		}
        	}
        
        	return ret
        }
        
  • lt_57_插入区间

    • // 关键: 判断什么时候需要合并: 当只有有交叉的时候需要合并
      // 1. 当interval 更小的时候,直接append即可
      // 1. 遍历原始区间, 判断是否在区间内 : 当left< val0 || right>val1 的时候 不在区间内
      func insert(intervals [][]int, newInterval []int) [][]int {
      	left, right := newInterval[0], newInterval[1]
      	ret := make([][]int, 0)
      
      	merged := false
      	for _, interval := range intervals {
      		val0, val1 := interval[0], interval[1]
      		// 先if 不在区间的情况
      		if val0 > right {
      			// 说明当前interval 在右侧 ,并且是没有交集
      			if !merged {
      				ret = append(ret, []int{left, right})
      				merged = true
      			}
      			ret = append(ret, interval)
      		} else if val1 < left {
      			// 说明当前interval在左侧,并且没有交集 (既 这个interval更小)
      			// 此时不需要对left,right 进行处理,因为还没有匹配到更大的
      			ret = append(ret, interval)
      		} else {
      			// 此时表明是有重叠空间了
      			// 则开始计算并集,并集的计算,左边选更小的,右边选更大的
      			left = insertMin(val0, left)
      			right = insertMax(val1, right)
      			// 当合并了区间之后,就可以拿新的值去继续for循环了
      		}
      	}
      
      	// 注意: 最后一步,可能这个新的interval,或者是合并区间后的left,right 是最右边(既最大),可能会在for循环中一直没有append
      	// 所以最后还需要继续if
      	if !merged {
      		ret = append(ret, []int{left, right})
      	}
      
      	return ret
      }
      func insertMin(a, b int) int {
      	if a < b {
      		return a
      	}
      	return b
      }
      func insertMax(a, b int) int {
      	if a < b {
      		return b
      	}
      	return a
      }
      
      
  • lt_59_螺旋矩阵

    • func generateMatrix(n int) [][]int {
      	ret := make([][]int, 0)
      	for i := 0; i < n; i++ {
      		ret = append(ret, make([]int, n))
      	}
      	left, right, up, down := 0, n-1, 0, n-1
      	index := 1
      	for index <= n*n {
      		// 开始从左往右赋值
      		for i := left; i <= right; i++ {
      			ret[up][i] = index
      			index++
      		}
      		up++
      		// 开始从上往下赋值
      		for i := up; i <= down; i++ {
      			ret[i][right] = index
      			index++
      		}
      		right--
      		// 开始从右往左赋值
      		for i := right; i >= left; i-- {
      			ret[down][i] = index
      			index++
      		}
      		// 开始从下往上赋值
      		down--
      		for i := down; i >= up; i-- {
      			ret[i][left] = index
      			index++
      		}
      		// 继续
      		left++
      	}
      	return ret
      }
      
  • lt_61_旋转链表

    • 
      // 关键:
      // 1. 构建成环
      // 2. 新的头节点在 n-(k%n) ,尾节点则在 n-(k%n)-1 处
      // 注意点就是找新的节点时候的边界条件而已
      func rotateRight(head *ListNode, k int) *ListNode {
      	if nil==head{
      		return nil
      	}
      	// 第一步,先构建成环
      	loopNode := head
      	l := 1
      	for nil != loopNode.Next {
      		loopNode = loopNode.Next
      		l++
      	}
      	loopNode.Next = head
      
      	// 第二步, 开始找尾节点,找到尾节点,就代表着已经找到了新的头结点
      	// 注意点: 找新的head的时候,从loop开始
      	walkNode := loopNode
      	newTailCount := l - (k % l)
      	for ;newTailCount>0;newTailCount--{
      		walkNode=walkNode.Next
      	}
      
      	tail:=walkNode
      	newHead:=tail.Next
      	tail.Next=nil
      	return newHead
      }
      
  • lt_62_不同路径

    • 
      // 关键:
      // 动态规划: f(i,j)=f(i−1,j)+f(i,j−1)
      // 当走到 i,j 位置的时候,可以是从i-1,j 过来 ,也可以是从 i,j-1 过来
      // 注意点: 对于边界的情况(既i=0,j=0),此时只有一种可能(如i=0,只能从 0,j-1过来)
      func uniquePaths(m int, n int) int {
      	paths := make([][]int, m)
      	// 初始化 边界路径都为1
      	for i := 0; i < m; i++ {
      		paths[i] = make([]int, n)
      		paths[i][0] = 1
      	}
      	for i := 0; i < n; i++ {
      		paths[0][i] = 1
      	}
      
      	// 然后下标从1开始进行累加
      	for i := 1; i < m; i++ {
      		for j := 1; j < n; j++ {
      			paths[i][j] = paths[i-1][j] + paths[i][j-1]
      		}
      	}
      	return paths[m-1][n-1]
      }
      
  • lt_63_不同路径2

    • 
      // 关键:
      func uniquePathsWithObstacles(obstacleGrid [][]int) int {
      	if len(obstacleGrid) == 0 {
      		return 0
      	}
      	// 与62类似,当处于边界的时候,只能一种走法
      	ret := make([][]int, len(obstacleGrid))
      	// 初始化元素
      	for i := 0; i < len(ret); i++ {
      		ret[i] = make([]int, len(obstacleGrid[i]))
      	}
      	// 初始化第一列
      	for i := 0; i < len(ret); i++ {
      		if obstacleGrid[i][0] == 1 {
      			// 当边界为1的时候,则再也走不下去了,所以直接break
      			break
      		}
      		ret[i][0] = 1
      	}
      	// 初始化第一行
      	for i := 0; i < len(obstacleGrid[0]); i++ {
      		if obstacleGrid[0][i] == 1 {
      			// 同上
      			break
      		}
      		ret[0][i] = 1
      	}
      	// 然后从第一个开始
      	for i := 1; i < len(obstacleGrid); i++ {
      		for j := 1; j < len(obstacleGrid[0]); j++ {
      			if obstacleGrid[i][j] == 1 {
      				ret[i][j] = 0
      			} else {
      				ret[i][j] = ret[i-1][j] + ret[i][j-1]
      			}
      		}
      	}
      	return ret[len(obstacleGrid)-1][len(obstacleGrid[0])-1]
      }
      
  • lt_64_最小路径和

    • 
      // 关键
      // 1. 动态规划
      // 2. 当处于第一行的时候,只能从左边过来
      // 3. 当处于第一列的时候,只能从上面过来
      // 4. 当处于其他地方的时候,结果值为 最小值+ 当前值
      func minPathSum(grid [][]int) int {
      	if len(grid) == 0 {
      		return 0
      	}
      	dp := make([][]int, len(grid))
      	for i := 0; i < len(grid); i++ {
      		dp[i] = make([]int, len(grid[i]))
      	}
      
      	// 初始化第一行
      	dp[0][0] = grid[0][0]
      	for i := 1; i < len(grid[0]); i++ {
      		dp[0][i] = dp[0][i-1] + grid[0][i]
      	}
      	// 初始化第一列
      	for i := 1; i < len(grid); i++ {
      		dp[i][0] = dp[i-1][0] + grid[i][0]
      	}
      
      	// 初始化其他地方
      	for i := 1; i < len(grid); i++ {
      		for j := 1; j < len(grid[i]); j++ {
      			dp[i][j] = minPathSumMin(dp[i-1][j], dp[i][j-1]) + grid[i][j]
      		}
      	}
      	return dp[len(grid)-1][len(grid[0])-1]
      }
      func minPathSumMin(a, b int) int {
      	if a < b {
      		return a
      	}
      	return b
      }
      
  • lt_67_二进制求和

    • 
      // 关键:
      // 类似于两个 数组合并
      // 问题: carry的作用
      func addBinary(a string, b string) string {
      	l1, l2 := len(a)-1, len(b)-1
      	carry := 0
      
      	ret := strings.Builder{}
      	for l1 >= 0 && l2 >= 0 {
      		sum := carry
      		sum += int(a[l1] - '0')
      		sum += int(b[l2] - '0')
      		carry = sum / 2
      		ret.WriteString(strconv.Itoa(sum % 2))
      		l1--
      		l2--
      	}
      	// 如果l1更长
      	for l1 >= 0 {
      		sum := carry + int(a[l1]-'0')
      		carry = sum / 2
      		ret.WriteString(strconv.Itoa(sum % 2))
      		l1--
      	}
      
      	// 如果l2 更长
      	for l2 >= 0 {
      		sum := carry + int(b[l2]-'0')
      		carry = sum / 2
      		ret.WriteString(strconv.Itoa(sum % 2))
      		l2--
      	}
      	// 还有个进位数没加进去,需要补充
      	if carry == 1 {
      		ret.WriteString("1")
      	}
      	return addBinaryReverse(ret.String())
      }
      
      func addBinaryReverse(str string) string {
      	ret := make([]byte, 0)
      	for i := len(str) - 1; i >= 0; i-- {
      		ret = append(ret, str[i])
      	}
      	return string(ret)
      }
      
  • lt_69_x的平方根

    • // 关键:
      // 1. 二分法
      // 当输入为8的时候,mid值为4 ,因为4*4=16 > 8 ,所以往右移动, 只有当 mid*mid< x 的时候,才代表这是一个可行的解
      // 求中间值,然后不停的平方,
      func mySqrt(x int) int {
      	l,r:=0,x
      	ret:=0
      	for l<=r{
      		mid:=l+(r-l)>>1
      		if mid*mid<=x{
      			ret=mid
      			l=mid+1
      		}else{
      			r=mid-1
      		}
      	}
      	return ret
      }
      
  • lt_70_爬楼梯

    • 
      // 关键: 动态规划
      // f(n)=f(n-1)+f(n-2)
      func climbStairs(n int) int {
      	dp := make([]int, n+1)
      	for i := 1; i <= n; i++ {
      		if i == 1 {
      			dp[i] = 1
      			continue
      		}
      		if i == 2 {
      			dp[i] = 2
      			continue
      		}
      		dp[i] = dp[i-1] + dp[i-2]
      	}
      
      	return dp[n]
      }
      
      
  • lt_71_简化路径

    • 
      // 关键:
      // 第一想法: 用栈来处理, 结合strings的api
      func simplifyPath(path string) string {
      	stack := make([]string, 0)
      	for _, v := range strings.Split(path, "/") {
      		if v == ".." {
      			// 则弹出上一个
      			if len(stack) > 0 {
      				stack = stack[:len(stack)-1]
      			}
      		} else if v != "" && v != "." {
      			stack = append(stack, v)
      		}
      	}
      	return "/" + strings.Join(stack, "/")
      }
      
  • lt_73_矩阵置0

    • 
      // 题目关键:
      // 有一个元素为0,则所在行和列都为0
      // 解题关键: 使用标记数组的方式,就是先遍历匹配,某个值为0,则标记该行为0
      func setZeroes(matrix [][]int) {
      	if len(matrix) == 0 {
      		return
      	}
      	rows := make([]bool, len(matrix))
      	cols := make([]bool, len(matrix[0]))
      	for col, v := range matrix {
      		for row, vv := range v {
      			if vv == 0 {
      				rows[col] = true
      				cols[row] = true
      			}
      		}
      	}
      	for i := 0; i < len(matrix); i++ {
      		for j := 0; j < len(matrix[i]); j++ {
      			if cols[j] || rows[i] {
      				matrix[i][j] = 0
      			}
      		}
      	}
      }
      
      
  • lt_81_搜索旋转排序数组(我之前的记录呢)

    • func search81(nums []int, target int) bool {
      	left, right := 0, len(nums)-1
      	for left+1 < right {
      		mid := left + (right-left)>>1
      		if nums[mid] == target {
      			return true
      		}
      		// 先判断正常情况的内容
      		// 正常情况为: mid 处于正常区间, mid>left
      		if nums[mid] > nums[left] {
      			// 当处于正常区间,表明 left--> mid 这区间是正常的,但是mid --> right是不能保证的
      			if target >= nums[left] && target < nums[mid] {
      				right = mid - 1
      			} else {
      				left = mid + 1
      			}
      		} else {
      			// 表明处于不正常区间,则此时,从mid-right 区间判断,因为该区间内有序
      			if target <= nums[right] && target > nums[mid] {
      				left = mid + 1
      			} else {
      				right = mid - 1
      			}
      		}
      	}
      	if nums[left] == target || nums[right] == target {
      		return true
      	}
      	return false
      }
      
      
  • lt_82_删除链表中的重复元素

  • // 关键: 链表已经排序
    // 还要注意,可能头节点被删除,所以1. 要有dummy 2. 开始的节点不能是dummy#Next
    // 因为要删除所有重复元素,而不是只保留一个,所以 必须用next 和next.next 去匹配
    func deleteDuplicates(head *ListNode) *ListNode {
       dummy := &ListNode{}
       dummy.Next = head
    
       var rmValue int
       for temp := dummy; temp.Next != nil && temp.Next.Next != nil; {
          if temp.Next.Val == temp.Next.Next.Val {
             rmValue = temp.Next.Val
             // 然后删除所有与之相同的节点
             for nil!=temp.Next && temp.Next.Val == rmValue {
                temp.Next = temp.Next.Next
             }
          } else {
             temp = temp.Next
          }
       }
    
       return dummy.Next
    }
    
  • Lt_83_删除链表中的重复元素,保留一个

    • // 关键: 根据题意,重复的元素保留1个
      // 头结点可能会被删除
      func deleteDuplicates(head *ListNode) *ListNode {
      	dummy := &ListNode{Next: head}
      	for temp := dummy.Next; nil !=temp && temp.Next != nil; temp = temp.Next {
      		for temp != nil && temp.Next != nil && temp.Val == temp.Next.Val {
      			temp.Next = temp.Next.Next
      		}
      	}
      	return dummy.Next
      }
      
      
  • Lt_86_分隔链表

    • 
      // 关键:
      // 2个链表,将大于x的节点都挪到另外一个链表中,最后再拼接
      func partition(head *ListNode, x int) *ListNode {
      	if head == nil {
      		return nil
      	}
      	bigger := &ListNode{}
      	dummy := &ListNode{Next: head}
      	head = dummy
      	// 然后开始遍历
      	biggerTemp := bigger
      	temp := head
      	// 要从 temp.next 作为判断条件,原因在于,如果用temp作为判断条件,
      	// 则当temp 当前值 >=x 的时候, 需要讲temp 从原先的head中删除,此时是做不到的
      	// 所以只能使用temp.next
      	for temp.Next != nil {
      		if temp.Next.Val < x {
      			temp = temp.Next
      		} else {
      			// 把更大的节点放到bigger中
      			biggerTemp.Next = temp.Next
      			biggerTemp = biggerTemp.Next
      			// 然后跳到下一个节点
      			temp.Next = temp.Next.Next
      		}
      	}
      
      	// 最后将两个链表连接
      	biggerTemp.Next = nil
      	temp.Next = bigger.Next
      
      	return dummy.Next
      }
      
      
  • Lt_89_格雷编码

    • // 关键
      // 无他: 死记硬背
      // 1. for 循环, 右移缩小index位
      // 2. 异或^ 缩小的值即可
      func grayCode(n int) []int {
      	ret := make([]int, 1<<n)
      	for index := range ret {
      		ret[index] = index>>1 ^ index
      	}
      	return ret
      }
      
      
  • lt_90_重复子集

    • 
      // 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
      // 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
      // 关键: dfs
      // 若当前数x之前的数y ,x==y,并且y 之前没有被选中,则当前可以直接退出
      func subsetsWithDup(nums []int) [][]int {
      	sort.Ints(nums)
      	ret := make([][]int, 0)
      	var dfs func(chosePre bool, index int)
      
      	temp := make([]int, 0)
      	dfs = func(chosePre bool, index int) {
      		if index == len(nums) {
      			ret = append(ret, append([]int{}, temp...))
      			return
      		}
      		dfs(false, index+1)
      		// 如果之前的数没有被选择
      		if !chosePre && index > 0 && nums[index-1] == nums[index] {
      			// 并且紧邻的两个数相等
      			// 则直接return
      			return
      		}
      		temp = append(temp, nums[index])
      		dfs(true, index+1)
      		// 裁剪
      		temp = temp[:len(temp)-1]
      	}
      	dfs(false, 0)
      	return ret
      }
      
      
  • lt_91_解码方法

    • 
      // 关键:
      // 一大堆边界条件判断,dp的时候的长度都是len+1,返回值都是返回len
      // 状态转移方程:
      // f(i)=f(i-1) + f(i-2)
      // 当选择一个数的时候,f(i)+=f(i-1)
      // 当选择2个数的时候,f(i)=f(i-1)+f(i-2)
      // 解码的时候,可以由1个数解码,也可以是2个数合在一起解码
      func numDecodings(s string) int {
      	dp := make([]int, len(s)+1)
      	dp[0] = 1
      	for i := 1; i <= len(s); i++ {
      		if s[i-1] != '0' {
      			dp[i] += dp[i-1]
      		}
      		if i > 1 && s[i-2] > '0' && ((s[i-2]-'0')*10+(s[i-1]-'0')) <= 26 {
      			dp[i] += dp[i-2]
      		}
      	}
      
      	return dp[len(s)]
      }
      
  • lt_92_反转链表2

    • 
      // 链表区间反转
      // 关键:
      // 1. 头节点是可能被反转的,所以需要dummy
      func reverseBetween(head *ListNode, left int, right int) *ListNode {
      	dummy:=&ListNode{Next: head}
      	var headBeforeReverse *ListNode
      	head=dummy
      	i:=0
      	for ;i<left;i++{
      		headBeforeReverse=head
      		head=head.Next
      	}
      	// 然后开始区间反转
      	// 记录下leftNode的值,因为要与rightNode.next连接
      	leftNode:=head
      
      	var prev *ListNode
      	for j:=i;j<=right;j++{
      		// 链表反转
      		tmp:=head.Next
      
      		head.Next=prev
      		prev=head
      
      		head=tmp
      	}
      	// 开始重新连接,leftNode之前的一个节点的next 需要为right节点(既区间内的最后一个节点)
      	headBeforeReverse.Next=prev
      	// 此时的head是rightNode的下一个节点
      	leftNode.Next=head
      
      	return  dummy.Next
      }
      
  • lt_93_复原ip地址

    • 
      // 关键
      // 回溯算法 dfs
      // 并且,注意 current ,当append 之后是不可以重新初始化的,因为后续的递归dfs 依赖了这个
      func restoreIpAddresses(s string) []string {
      	current:=make([]string,4)
      	ret:=make([]string,0)
      	var dfs func(index int,ipIndex int)
      
      	dfs= func(index int,ipIndex int) {
      		// dfs: 先考虑退出条件
      		// 当当前长度为4的时候,并且 当前index 到了最后,则代表是一个结果集
      		if ipIndex==4{
      			if len(current)==4 && index== len(s){
      				ret=append(ret,strings.Join(current,"."))
      			}
      			return
      		}
      
      		// 如果已经有了4元组,但是 index 还没到长度,直接return
      		if index==len(s){
      			return
      		}
      		if s[index]=='0'{
      			current[ipIndex]="0"
      			dfs(index+1,ipIndex+1)
      		}
      		// 开始给每个下标进行赋值
      		add:=0
      		for i:=index;i<len(s);i++{
      			// add *10 是因为,每次在上一次移动到下一个的时候,都需要扩大10倍
      			add=add*10+int(s[i]-'0')
      			if add>0 && add<=255{
      				current[ipIndex]=strconv.Itoa(add)
      				dfs(i+1,ipIndex+1)
      			}else{
      				// 说明这个值已经不符合要求了
      				break
      			}
      
      		}
      	}
      	dfs(0,0)
      
      	return ret
      }
      
  • lt_94_二叉树的中序遍历

    • 
      // 关键: 中序遍历: 左根右
      // 栈遍历或者是递归遍历
      func inorderTraversal(root *TreeNode) []int {
      	return inorderTraversalWithStack(root)
      }
      
      // 栈遍历
      func inorderTraversalWithStack(root *TreeNode) []int {
      	ret := make([]int, 0)
      	stack := make([]*TreeNode, 0)
      
      	for len(stack) > 0 || nil != root {
      		for nil != root {
      			// 栈的话,先要把所有的左节点都入栈
      			stack = append(stack, root)
      			root = root.Left
      		}
      		// 然后弹出
      		node := stack[len(stack)-1]
      		stack = stack[:len(stack)-1]
      		ret = append(ret, node.Val)
      		// 注意这一步,这时候要用弹出的node 的右节点去遍历
      		root = node.Right
      	}
      
      	return ret
      }
      
      // 递归遍历
      func inorderTraversalWithLoop(root *TreeNode) []int {
      	ret := make([]int, 0)
      	doInorderTraversalWithLoop(&ret, root)
      	return ret
      }
      func doInorderTraversalWithLoop(ret *[]int, root *TreeNode) {
      	if nil == root {
      		return
      	}
      	if nil != root.Left {
      		doInorderTraversalWithLoop(ret, root.Left)
      	}
      	*ret = append(*ret, root.Val)
      	if nil != root.Right {
      		doInorderTraversalWithLoop(ret, root.Right)
      	}
      }
      
      
  • lt_98_验证是否是二叉搜索树

    • 
      // 关键:
      // 树的特点:
      // 左子树<root<右子树
      // dfs: 对每棵子树判断是否是正确的二叉搜索树
      // 左边最大值 要小于node ,右边最小值要>node
      func isValidBST(root *TreeNode) bool {
      
      	type tempResult struct {
      		max *TreeNode
      		min *TreeNode
      		valid bool
      	}
      	var dfs func(node *TreeNode) tempResult
      
      	dfs = func(node *TreeNode) tempResult {
      		ret := tempResult{}
      		ret.valid = true
      		if node == nil {
      			return ret
      		}
      		left := dfs(node.Left)
      		right := dfs(node.Right)
      		if !left.valid || !right.valid {
      			ret.valid=false
      			return ret
      		}
      		// 左边最大值 要小于node ,右边最小值要>node
      		if (left.max!=nil && left.max.Val>=node.Val) || (nil!=right.min && right.min.Val<=node.Val){
      			ret.valid=false
      			return ret
      		}
      		ret.max=node
      		if right.max!=nil{
      			ret.max=right.max
      		}
      		ret.min=node
      		if left.min!=nil{
      			ret.min=left.min
      		}
      		return ret
      	}
      	return dfs(root).valid
      }
      
  • Lt_100_是否是相同的树

    • // 关键:
      // 递归判断,左右节点
      func isSameTree(p *TreeNode, q *TreeNode) bool {
      	return sameTree(p,q)
      }
      func sameTree(l1,l2 *TreeNode)bool{
      	if l1==nil && l2==nil{
      		return true
      	}
      	if (l1==nil && l2!=nil) || (l2==nil && l1!=nil)|| (l1.Val!=l2.Val){
      		return false
      	}
      	return sameTree(l1.Left,l2.Left) && sameTree(l1.Right,l2.Right)
      }
      

链表

  • 先看是否是有序的

  • 再看是否可能头结点会变化,会则使用 dummy

  • 链表中删除所有重复过的元素,只保留原先不存在重复的数字

    • 关键:

      • 链表的头结点可能会删除 (所以需要有dummy节点)
      • 因为是有序的,所以删除所有重复的元素,判断的是next和next的next
      • 真正的删除的时候,移动next即可
    • 
      func deleteDuplicates(head *ListNode) *ListNode {
      	node := head
      	for ; nil != node; node = node.Next {
      		// 拿当前节点与后面的所有节点匹配
      		for ; node.Next != nil && node.Next.Val == node.Val; {
      			node.Next = node.Next.Next
      		}
      	}
      	return head
      }
      
      
  • reverse

    • 关键:

      • 2个节点,一个是cur 一个是prev
        • ,cur 既反转前的当前节点 , 初始的时候prev 肯定指向nil
      • 移动的时候,无论是cur,还是prev 都是往前移,并且赋值的时候,先赋值再替换
    • func reverseList(head *ListNode) *ListNode {
      	var prev *ListNode
      	cur := head
      	for nil != cur {
      		tNode := cur.Next
      		cur.Next = prev
      		prev = cur
      		cur = tNode
      	}
      	return prev
      }
      
  • recerse 2

    • 反转某个区间内的链表
    • 关键:
      • 链表的头结点可能被反转,所以需要dummy
      • 3个需要保存的节点
        • 反转开始区间 节点 A 的前一个节点: 因为反转后,需要将这个节点 连接反转后的头节点
        • 原先的头 节点 head: 因为反转后,原先的头变成了尾,需要将尾节点连接到 之前的尾节点的后续节点
        • 反转后的头结点 head: 使得 之前的 prev 可以连到这个新的head 既 beforeReversePrev.Next = newHead
  • merge_sort_list 有序链表/有序数组合并成一个,并不需要O(n^2)的时间复杂度

    • 关键:
      • 需要额外的空间
      • 然后谁小,谁向前移,意味着是 if else
      • 就是用空间换时间的思路 ,所以需要额外定义空间, 链表则为指针,数组则为 数组
      • 新创建的链表或者是数组,都需要向前移动,
  • func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
    	dummy := &ListNode{}
    	head := dummy
    
    	for l1 != nil && l2 != nil {
    		if l1.Val < l2.Val {
    			head.Next = l1
    			l1 = l1.Next
    		} else {
    			head.Next = l2
    			l2 = l2.Next
    		}
    		// head 移动从而进行匹配
    		head = head.Next
    	}
    	// 为什么这列不能
    	if nil != l1 {
    		head.Next = l1
    	}
    	if nil != l2 {
    		head.Next = l2
    	}
    
    	return dummy.Next
    }
    
  • sort_list ,链表排序,并且不能额外申请内存,并且要求O(nlogn)时间复杂度

    • 关键:

      • logn ,意味着肯定需要折半,不能2层for 循环,则使用归并排序

      • 注意退出条件:

        • head的next为空的时候,就不需要找中点了,直接返回该元素就行
        • 找到中点之后,基于链表的特性,需要将 next置空:
    • func sortList(head *ListNode) *ListNode {
      	return sortListMergeSort(head)
      }
      func sortListMergeSort(head *ListNode) *ListNode {
      	if head == nil || head.Next == nil {
      		return head
      	}
      
      	mid := sortListFindMiddle(head)
      	tail := mid.Next
      	mid.Next = nil
      
      	left := sortListMergeSort(head)
      	right := sortListMergeSort(tail)
      	return sortListMergeList(left, right)
      }
      func sortListFindMiddle(head *ListNode) *ListNode {
      	fast := head.Next
      	slow := head
      	for fast.Next != nil && fast.Next.Next != nil {
      		fast = fast.Next.Next
      		slow = slow.Next
      	}
      	return slow
      }
      
      func sortListMergeList(l1 *ListNode, l2 *ListNode) *ListNode {
      	dummy := &ListNode{}
      	temp := dummy
      	for l1 != nil && l2 != nil {
      		if l1.Val < l2.Val {
      			temp.Next = l1
      			l1 = l1.Next
      		} else {
      			temp.Next = l2
      			l2 = l2.Next
      		}
      		temp = temp.Next
      	}
      
      	if nil != l1 {
      		temp.Next = l1
      	}
      	if nil != l2 {
      		temp.Next = l2
      	}
      
      	return dummy.Next
      }
      
  • reorder_list

    • 要求:

      • 给定一个单链表L:L0→L1→…→Ln-1→Ln ,
        将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
        你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
        
    • 链表重新排序

    • 分析发现:

      • 是按顺序来的, 依次交叉入队
    • 关键:

      • 前置退出条件,
        1. 找到中点
        2. 断开2个链表
        3. 后半段反转
        4. 链表合并(注意是交叉合并,所以需要一个bool变量控制)
    • 
      func reorderList(head *ListNode) {
      	if head == nil || head.Next == nil {
      		return
      	}
      	// 1. 找到中点
      	mid := reorderListFindMiddle(head)
      	// 2. 断开mid
      	next := mid.Next
      	mid.Next = nil
      	// 3. 反转后半段
      	afterReverse := reorderListReverse(next)
      	// 4. 拼接2端
      	head = reorderListMergeList(head, afterReverse)
      }
      func reorderListMergeList(l1 *ListNode, l2 *ListNode) *ListNode {
      	dummy := &ListNode{}
      	move := dummy
      	toogle := true
      	for nil != l1 && nil != l2 {
      		if toogle {
      			move.Next = l1
      			l1 = l1.Next
      		} else {
      			move.Next = l2
      			l2 = l2.Next
      		}
      		toogle = !toogle
      		move = move.Next
      	}
      
      	if nil != l1 {
      		move.Next = l1
      	}
      	if nil != l2 {
      		move.Next = l2
      	}
      
      	return dummy.Next
      }
      func reorderListFindMiddle(head *ListNode) *ListNode {
      	fast := head.Next
      	slow := head
      	for nil != fast.Next && fast.Next.Next != nil {
      		fast = fast.Next.Next
      		slow = slow.Next
      	}
      	return slow
      }
      
      func reorderListReverse(head *ListNode) *ListNode {
      	var prev *ListNode
      	cur := head
      	for nil != cur {
      		next := cur.Next
      		cur.Next = prev
      
      		prev = cur
      		cur = next
      	}
      	return prev
      }
      
      
  • linked-list-cycle

    • 关键:

      • 判断是否有环,关键在于快慢指针,有环,则必然会追到 慢指针
      • 所以有环的结果是
        • 快指针=慢指针
    • func hasCycle(head *ListNode) bool {
      	if head == nil || head.Next == nil {
      		return false
      	}
      
      	fast := head.Next
      	slow := head
      	for fast != nil && nil != fast.Next {
      		if fast == slow {
      			return true
      		}
      		fast = fast.Next.Next
      		slow = slow.Next
      	}
      	return false
      }
      
  • linked-list-cycle-ii

    • 关键:

      • 快慢指针
      • 当快慢指针相遇之后,
        • 快指针从头开始
        • 慢指针从相遇的next开始
        • 快慢指针同一个步调移动,相遇既为初始节点
    • func detectCycle(head *ListNode) *ListNode {
      	if head == nil || head.Next == nil {
      		return nil
      	}
      
      	fast := head.Next
      	slow := head
      	for nil != fast && fast.Next != nil {
      		if fast == slow {
      			fast=head
      			slow=slow.Next
      			for nil != slow {
      				if fast == slow {
      					return slow
      				}
      				slow = slow.Next
      				fast = fast.Next
      			}
      		}
      		fast = fast.Next.Next
      		slow = slow.Next
      	}
      	return nil
      }
      
  • palindrome-linked-list

    • 判断一个链表是否是回文链表

    • 关键:

      • 回文的特性是 ,2半都相同
        1. 找中点
        2. 后半段反转
        3. 2段进行匹配(前半段后半段基于回文的特性,反转之后应该是要相同的), 要注意这种的 案例: 1,0,1
    • unc isPalindrome(head *ListNode) bool {
      	if head == nil || head.Next == nil {
      		return true
      	}
      	mid := isPalindromeFindMiddle(head)
      	// 断开
      	next := mid.Next
      	mid.Next = nil
      	rev := isPalindromeReverse(next)
      	for nil != head && nil != rev {
      		if head.Val != rev.Val {
      			return false
      		}
      		head = head.Next
      		rev = rev.Next
      	}
      	// return  rev == nil && head==nil
      	return true
      }
      func isPalindromeReverse(head *ListNode) *ListNode {
      	cur := head
      	var prev *ListNode
      	for nil != cur {
      		next := cur.Next
      		cur.Next = prev
      		prev = cur
      		cur = next
      	}
      	return prev
      }
      func isPalindromeFindMiddle(head *ListNode) *ListNode {
      	fast := head.Next
      	slow := head
      	for nil != fast && nil != fast.Next {
      		fast = fast.Next.Next
      		slow = slow.Next
      	}
      	return slow
      }
      

栈和队列

汇总

  • 栈的特点:
    • 先入后出, 可以保存一些数据,使用的时候再依次弹出来
  • 栈D: 栈用于DFS ,队列用于BFS

min-stack

  • 关键:
    • 2个栈,一个栈存储push时候的每个值,一个栈存储的都是每次push的时候最小的值(如果该值为最小,则push该值)

eval表达式

  • 关键:

    • 栈保存数字,碰到运算符弹出2个,再将结果继续入栈
    • 注意点就是最先入栈的是除数,既 1,2,3,4 ,/ 既在这个入参中,3是除数
  • 
    func evalRPN(tokens []string) int {
    	if len(tokens)==0{
    		return 0
    	}
    	// 1. 遍历,然后发现如果不是运算符则入栈,是的话,则全部出栈,然后运算再入栈
    	stack := make([]int, 0)
    	for _, v := range tokens {
    		switch v {
    		case "+", "-", "*", "/":
    			if len(stack)<2{
    				return -1
    			}
    			first:=stack[len(stack)-2]
    			second:=stack[len(stack)-1]
    			stack=stack[:len(stack)-2]
    			res:=0
    			if v == "+" {
    				res = first + second
    			} else if v == "-" {
    				res = first - second
    			} else if v == "*" {
    				res = first * second
    			} else {
    				res = first / second
    			}
    			stack = append(stack, res)
    		default:
    			intV, _ := strconv.Atoi(v)
    			stack = append(stack, intV)
    		}
    	}
    	return stack[0]
    }
    

decode_string

  • 关键
    • 2个栈
    • 遍历遇到 ‘]’ 的时候,栈弹出 数据到另外一个,直到遇到’['才不弹
    • 然后获取得到count ,就是 重复的次数,注意这一步 是可能出现 1000 这种情况,所以也是要遍历,弹出直到 元素 >'9’才停止

dfs 搜索模板

  • 栈d (便携记忆),所以dfs 是使用栈的

  • func dfs(node *TreeNode) {
    	stack := make([]*TreeNode, 0)
    	stack = append(stack, node)
    	for len(stack) > 0 {
    		v := stack[len(stack)-1]
    		stack = stack[:len(stack)-1]
    		if nil != v.Right {
    			stack = append(stack, v.Right)
    		}
    		if nil != v.Left {
    			stack = append(stack, v.Left)
    		}
    		if len(stack)==0{
    			return
    		}
    	}
    }
    

中序遍历树

  • 关键:
    • 树的中序遍历: 左根右 , 所以需要先将左节点一直如栈
    • 左节点全部入栈之后,就可以弹出取数据,然后移动到右节点了

深拷贝图

  • 关键:
    • 递归
    • map作为中断退出条件, key为原先的node,value 为拷贝后的node

岛屿的数量

  • 关键:

    • dfs : 如果该index为1 ,则还需要判断4周的元素的值,是否也是1(所以需要dfs 递归下去) ,同时还需要将访问过的设置为0
    • 以及在byte 中 ‘1’ 是49 ,而不是0
  • 
    func numIslands(grid [][]byte) int {
    	if grid == nil {
    		return 0
    	}
    	count := 0
    	for i := 0; i < len(grid); i++ {
    		for j := 0; j < len(grid[i]); j++ {
    			if grid[i][j] == '1' && dfs(grid, i, j) >= 1 {
    				count++
    			}
    		}
    	}
    	return count
    }
    func dfs(grid [][]byte, i, j int) int {
    	if i < 0 || i >= len(grid) || j >= len(grid[i]) || j < 0 {
    		return 0
    	}
    	if grid[i][j] == '1' {
    		grid[i][j] = '0'
    		return dfs(grid, i, j-1) + dfs(grid, i, j+1) + dfs(grid, i-1, j) + dfs(grid, i+1, j) + 1
    	}
    	return 0
    }
    

计算最大矩形面积

  • 关键:

    • 面积的计算公式为 高*宽
    • 暴力法:
      • 找到最近的大于他的值作为 宽即可
    • 单调栈法:
      • 关键:
        • 计算一个下标的最大面积 ,可以计算的条件是: 下一个下标的高度H2 ,当比当前高度 H1 小的时候,就是可以计算了,所以,当我们遍历的时候,保存 当前下标, 然后当发现比上一个下标的高度 <=的时候,我们就可以计算累加上一个下标的最大面积 ,最终不断的累加
  • 
    // 关键是计算公式
    // v=长*宽
    // 然后因为只能横向移动,所以宽是必然会减少的,那么在宽减少的情况下,怎么尽量大: 通过往长的进行移动
    func maxArea(height []int) int {
    	r := 0
    	left, right := 0, len(height)-1
    	for left < right {
    		// 因为水的计算,是通过短板来计算的,
    		r = maxAreaMax(r, (right-left)*(maxAreaMin(height[right], height[left])))
    		// 每次都往更高的板移动
    		if height[left] < height[right] {
    			left++
    		} else {
    			right--
    		}
    	}
    	return r
    }
    func maxAreaMin(a, b int) int {
    	if a < b {
    		return a
    	}
    	return b
    }
    func maxAreaMax(a, b int) int {
    	if a < b {
    		return b
    	}
    	return a
    }
    

栈实现队列

  • 关键:

    • 栈: 先进后出
    • 队列: 先进先出
    • 2个栈:
      • push: push的时候,将数据都打到 push 栈中(既有一个栈是固定的栈)
      • pop: pop的时候, 如果 原先的pop 栈有数据,则先继续弹出,没数据了之后,从push中推入
  • 
    type MyQueue struct {
    	Stack1 []int
    	Stack2 []int
    }
    
    /** Initialize your data structure here. */
    func Constructor() MyQueue {
    	s := MyQueue{
    		Stack1: make([]int, 0),
    		Stack2: make([]int, 0),
    	}
    	return s
    }
    
    /** Push element x onto stack. */
    func (this *MyQueue) Push(x int) {
    	// push的时候往固定的push
    	for len(this.Stack2) > 0 {
    		val := this.Stack2[len(this.Stack2)-1]
    		this.Stack2 = this.Stack2[:len(this.Stack2)-1]
    		this.Stack1 = append(this.Stack1, val)
    	}
    	this.Stack1 = append(this.Stack1, x)
    }
    
    /** Removes the element on top of the stack and returns that element. */
    // pop的时候也是同理,通过一个固定的pop
    func (this *MyQueue) Pop() int {
    	for len(this.Stack1) > 0 {
    		val := this.Stack1[len(this.Stack1)-1]
    		this.Stack1 = this.Stack1[:len(this.Stack1)-1]
    		this.Stack2 = append(this.Stack2, val)
    	}
    	if len(this.Stack2) == 0 {
    		return 0
    	}
    	val := this.Stack2[len(this.Stack2)-1]
    	this.Stack2 = this.Stack2[:len(this.Stack2)-1]
    	return val
    }
    
    /** Get the top element. */
    func (this *MyQueue) Peek() int {
    	for len(this.Stack1) > 0 {
    		val := this.Stack1[len(this.Stack1)-1]
    		this.Stack1 = this.Stack1[:len(this.Stack1)-1]
    		this.Stack2 = append(this.Stack2, val)
    	}
    	if len(this.Stack2) == 0 {
    		return 0
    	}
    	val := this.Stack2[len(this.Stack2)-1]
    	return val
    }
    
    /** Returns whether the stack is empty. */
    func (this *MyQueue) Empty() bool {
    	return len(this.Stack2) == 0 && len(this.Stack1) == 0
    }
    

0和1的矩阵,找到1到0的最小距离

  • 关键:

    • 多源BFS (因为BFS ,基于链表的特性,是否访问过我们是能得知的)
    • 标识已经访问过了
    • 找到1到0的最小距离,则需要先找到 离0 最近的1的距离,然后开始往外扩散(这样最外层的1的时候,边上的1的最小距离也能拿到 )
      • 其实也是个动态规划题, 局部最小
  • func updateMatrix(mat [][]int) [][]int {
    	if len(mat) == 0 {
    		return nil
    	}
    	// 多源 BFS ,0 先入队
    	queue := make([][]int, 0)
    	for i := 0; i < len(mat); i++ {
    		for j := 0; j < len(mat[i]); j++ {
    			if mat[i][j] == 0 {
    				queue = append(queue, []int{i, j})
    			} else {
    				mat[i][j] = -1
    			}
    		}
    	}
    
    	dx := []int{-1, 1, 0, 0}
    	dy := []int{0, 0, 1, -1}
    	for len(queue) > 0 {
    		q := queue[0]
    		queue = queue[1:]
    		x, y := q[0], q[1]
    		for i := 0; i < 4; i++ {
    			newX := q[0] + dx[i]
    			newY := q[1] + dy[i]
    			// 如果这个元素没有访问过,则标识为访问过
    			// 因为取出来的元素,要么是之前为0 的元素,要么为 1然后被标识了最小距离的
    			// 就是逐渐向外扩散,先把外层的1 的最小距离算出来
    			if newX >= 0 && newX < len(mat) && newY >= 0 && newY < len(mat[q[0]]) && mat[newX][newY] == -1 {
    				mat[newX][newY] = mat[x][y] + 1
    				queue = append(queue, []int{newX, newY})
    			}
    		}
    	}
    
    	return mat
    }
    

二叉树

记忆点

  • 二叉平衡树的特点:

基本遍历

  • 记忆方式: 前中后是以 根节点为基本顺序的,前 代表着根节点先,中代表中根节点在第二顺序,后序遍历代表着根节点最后一个遍历

  • 先序遍历

    • 根左右 : 先根节点,再左节点,再右节点
  • 中序遍历

    • 左根右: 先左节点,再根节点,再右节点
  • 后序遍历

    • 左右根: 先左节点,再右节点,再根节点
  • 关键:

    • 非递归形式中,根节点的处理:
      • 尤其是后序遍历:
        • 我们需要等待 该节点 node 的右节点已经处理过了,才可以pop,因此需要lastVisit记录 上一次访问的节点
  • 各种遍历

    • 
      // 递归先序遍历
      // 根左右
      func preorderTraversal(root *TreeNode) {
      	if root == nil {
      		return
      	}
      	fmt.Println(root.Val)
      	preorderTraversal(root.Left)
      	preorderTraversal(root.Right)
      }
      
      // 递归中序遍历
      // 左根右
      func inorderLoopTraversal(root *TreeNode) {
      	if root == nil {
      		return
      	}
      	inorderLoopTraversal(root.Left)
      	fmt.Println(root.Val)
      	inorderLoopTraversal(root.Right)
      }
      
      // 递归后序遍历
      func afterOrderLoopTraversal(root *TreeNode) {
      	if nil == root {
      		return
      	}
      	afterOrderLoopTraversal(root.Left)
      	afterOrderLoopTraversal(root.Right)
      	fmt.Println(root.Val)
      }
      
      // 非递归先序遍历
      func preOrderStackTree(root *TreeNode) []int {
      	if nil == root {
      		return nil
      	}
      	r := make([]int, 0)
      	stack := make([]*TreeNode, 0)
      	for root != nil || len(stack) > 0 {
      		for nil != root {
      			r = append(r, root.Val)
      			stack = append(stack, root)
      			root = root.Left
      		}
      		node := stack[len(stack)-1]
      		stack = stack[:len(stack)-1]
      		root = node.Right
      	}
      	return r
      }
      
      // 非递归中序遍历
      func inorderStackTree(root *TreeNode) []int {
      	if nil == root {
      		return nil
      	}
      	stack := make([]*TreeNode, 0)
      	r := make([]int, 0)
      	for nil != root || len(stack) > 0 {
      		for nil != root {
      			// 因为中序遍历是 左根右,所以要先把所有的左节点入栈
      			stack = append(stack, root)
      			root = root.Left
      		}
      		node := stack[len(stack)-1]
      		stack = stack[:len(stack)-1]
      		r = append(r, node.Val)
      		root = node.Right
      	}
      
      	return r
      }
      
      // 后序非递归遍历
      func afterStackTree(root *TreeNode) []int {
      	if nil != root {
      		return nil
      	}
      
      	stack := make([]*TreeNode, 0)
      	r := make([]int, 0)
      
      	var lastVisit *TreeNode
      	for nil != root || len(stack) > 0 {
      		for nil != root {
      			stack = append(stack, root)
      			root = root.Left
      		}
      		node := stack[len(stack)-1]
      		// 中序遍历导致根节点必须在右节点之后,所以必须要等待这个节点的右节点已经访问过了才可以继续
      		if node.Right == nil || node.Right == lastVisit {
      			stack = stack[:len(stack)-1]
      			r = append(r, node.Val)
      			lastVisit = node
      		} else {
      			node = node.Right
      		}
      		stack = stack[:len(stack)-1]
      		r = append(r, node.Val)
      	}
      
      	return r
      }
      
      // bfs层次遍历
      // 层次遍历都是用queue
      func bfsTree(root *TreeNode) [][]int {
      	r := make([][]int, 0)
      	queue := make([]*TreeNode, 0)
      	queue = append(queue, root)
      	for len(queue) > 0 {
      		list := make([]int, 0)
      		l := len(queue)
      		for i := 0; i < l; i++ {
      			node := queue[0]
      			list = append(list, node.Val)
      			queue = queue[1:]
      			if node.Left != nil {
      				queue = append(queue, node.Left)
      			}
      			if node.Right != nil {
      				queue = append(queue, node.Right)
      			}
      		}
      		r = append(r, list)
      	}
      
      	return r
      }
      
      // 递归版dfs,从上到下
      func dfsLoopTopDown(root *TreeNode, result *[]int) {
      	if root == nil {
      		return
      	}
      	*result = append(*result, root.Val)
      	dfsLoopTopDown(root.Left, result)
      	dfsLoopTopDown(root.Right, result)
      }
      
      // 递归版dfs,从下到上
      func dfsLoopDownTop(root *TreeNode) []int {
      	if root == nil {
      		return nil
      	}
      	left := dfsLoopDownTop(root.Left)
      	right := dfsLoopDownTop(root.Right)
      	r := make([]int, 0)
      	r = append(r, root.Val)
      	r = append(r, left...)
      	r = append(r, right...)
      	return r
      }
      
      // 非递归版dfs
      // 关键: 右节点先入栈
      func dfsStack(root *TreeNode) []int {
      	if root == nil {
      		return nil
      	}
      	r := make([]int, 0)
      	stack := make([]*TreeNode, 0)
      	stack = append(stack, root)
      	for len(stack) > 0 {
      		node := stack[len(stack)-1]
      		stack = stack[:len(stack)-1]
      		if node.Right != nil {
      			stack = append(stack, node.Right)
      		}
      		if node.Left != nil {
      			stack = append(stack, node.Left)
      		}
      		r = append(r, node.Val)
      	}
      	return r
      }
      
二叉树的最大深度
  • 关键:
    • 分治法
    • 左边查找最大值
    • 右边查找最大值
    • 左右两个值找最大值
判断是否是平衡二叉树
  • 关键:
    • 分治法
    • 平衡二叉树的条件为: 左节点平衡 && 右节点平衡 && 高度差不超过1
      • 主要是通过高度差判断:
        • left-right>1 || right-left>1 则认为非平衡
二叉树中的最大路径和
  • 关键:

    • 分治法

    • 左节点的最大路径和 ,右节点的最大路径和 ,左+右节点的最大路径和

    • 最终的返回值是 左+右节点的最大路径和

  • 
    func maxPathSum(root *TreeNode) int {
    	max := root.Val
    
    	maxPath(root, &max)
    	return max
    }
    
    func maxPath(node *TreeNode, mm *int) int {
    	if node == nil {
    		return -1 * math.MaxInt32
    	}
    	left := maxPath(node.Left, mm)
    	right := maxPath(node.Right, mm)
    
    	// 计算左右子树的最大值
    	leftRightMax := max(left, right)
    	// 计算加上了根节点的最大值
    	withRootMax := max(node.Val, leftRightMax+node.Val)
    	// 计算横跨时候的最大值
    	withThroughMax := max(withRootMax, left+right+node.Val)
    	// 与最后的结果比较
    	*mm = max(withThroughMax, *mm)
    
    	return withRootMax
    }
    
    func max(i, j int) int {
    	if i > j {
    		return i
    	}
    	return j
    }
    
公共祖先
  • 关键:

    • 分治法
    • 当左右都不为空时,则祖先为根节点
  • 
    func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
    	if root == nil {
    		return nil
    	}
    	if root == p || root == q {
    		return root
    	}
    	leftRoot := lowestCommonAncestor(root.Left, p, q)
    	rightRoot := lowestCommonAncestor(root.Right, p, q)
    
    	if leftRoot != nil && rightRoot != nil {
    		return root
    	}
    	if nil != leftRoot {
    		return leftRoot
    	}
    	return rightRoot
    }
    
    
层次二叉树的逆序遍历
  • 关键:
    • 顺序遍历
    • 翻转即可
Z形打印
  • 关键:
    • 依旧是bfs 遍历
    • 关键在于 遍历完一层之后,会有一次反转
判断是否是二叉搜索树
  • 关键:
    • 中序遍历: 中序遍历出来的树,在二叉搜索树上如果是 ,则会是一个递增数组

    • 分治法:

      • 左孩子的最大值小于根节点 && 右孩子的最小值 > 根节点
      • 所以判断是否是完全二叉树: left 的最大值 < root < right的最小值
    • 
      func isValidBST(root *TreeNode) bool {
      	result := validBst(root)
      	return result.IsValid
      }
      
      type resultType struct {
      	IsValid bool
      	Max     *TreeNode
      	Min     *TreeNode
      }
      
      func validBst(root *TreeNode) resultType {
      	var result resultType
      	if root == nil {
      		result.IsValid = true
      		return result
      	}
      
      	left := validBst(root.Left)
      	right := validBst(root.Right)
      
      	if !left.IsValid || !right.IsValid {
      		result.IsValid = false
      		return result
      	}
      	if (nil != left.Max && left.Max.Val >= root.Val) || (nil != right.Min && right.Min.Val <= root.Val) {
      		result.IsValid = false
      		return result
      	}
      	result.IsValid = true
      	result.Min = root
      	if left.Min != nil {
      		result.Min = left.Min
      	}
      	result.Max = root
      	if right.Max != nil {
      		result.Max = right.Max
      	}
      
      	return result
      }
      
      
二叉搜索树中插入值
  • 关键:
    • dfs
    • 根据二叉搜索树的性质: 左孩子小,右孩子大,找到对应的值插入即可
    • 并不需要重新调整树

二进制

一串数组找只出现一次的数
  • 关键
    • 异或运算,不同的时候值为1,相同的时候值为0
    • 但是 0 异或 一个值的时候为值本身
一串数组中其他数都出现3次,只有1个数出现1次
  • 关键:
    • 二进制中求1的个数,最终每个二进制的1的个数,要么为0要么为3的倍数+1
一串数字中,2个数字只出现了一次
  • 关键:
    • 异或消除所有重复元素
    • 最终剩下的元素是a和b的异或,则最后的1 要么是a的要么是b的
    • 重新遍历数组, 消除重复的元素 ,和消除 a 则剩下的就是b,消除b剩下的就是a
计算一个数字二进制下1的个数
  • 关键:
    • 二进制1的个数,通过 x&(x-1) 即可
计算一个 范围内的每个数字的二进制和
  • 关键:
    • 最关键的就是 计算1的个数 x&(x-1)
数字范围内按位与
  • 关键(强记)
    • 取公共前缀

查找算法

二分查找:
  • 关键:

    • 模板:

      • 使用这个模板吧:

        • 
          func search2(nums []int,target int)int{
          	left:=0
          	right:= len(nums)-1
          	for left+1<right{
          		mid:=left+(right-left)>>1
          		if nums[mid]==target{
          			right=mid
          		}else if nums[mid]<target{
          			left=mid
          		}else{
          			right=mid
          		}
          	}
          	if nums[right]==target{
          		return right
          	}
          	if nums[left]==target{
          		return left
          	}
          	return -1
          }
          
二分查找,范围查询相同的
  • 关键:
    • 模板:
      • 依旧是使用第三种模板
    • 2次二分查找,不同于在于, 当相等的时候移动时机不同,
      • 第一次,因为是要找首位,所以 相等的时候 end=mid (向左找)
      • 第二次,因为要找最后一个,所以相等的时候start=mid (向右找)
二分查找-搜索插入位置
  • 关键:
    • 二分查找
    • 找到大于=target 的最小值
二维矩阵-判断值是否存在
  • 关键:
    • 从左下或者右上入手即可
      • 左下:
        • <target ,则上移, > target 则右移动
旋转数组找最小值
  • 关键:
    • 二分查找
    • 因为前提是升序,所以把end当做最大值,来找二分查找即可
    • 但是注意,不同的是,常见的二分查找, 是 nums[mid]<target 的时候,start=mid ,但是在这,我们是需要不停的缩小范围, 所以 nums[mid]<target end=mid

剑指offer

  • 03: 数组中重复的元素:

    • 关键:
      • 既然数字是 0到n-1,则 数组 value为5的数,存放到下标为5的地址 既 nums[nums[5]]=nums[5] 两两交换,如果交换之前发现,相等,则为重复元素
  • 04: 二维数组中找一个元素:

    • 关键:
      • 从左下或者右上入手进行处理
  • 05: 两个栈实现队列:

    • 关键:
      • 一个栈用于push ,一个栈用于pop ,pop时判断pop的栈是否为空,不为空直接pop,否则从push中拿
  • 07: 重建二叉树

    • 关键:
      • 先序+中序 构建二叉树的关键:
        • 找到根节点之后,左边的是左子树,右边的是右子树
  • 08: 青蛙跳台阶问题 以及 09: 青蛙跳台阶扩展问题

    • 关键:
      • 找出规律,08题的数学公式为: f(n)=f(n-1)+f(n-2)
      • 09题的规律为: f(n)=2*f(n-1)
  • 10: 矩形覆盖:

    • 关键:
      • 记公式: f[n] = f[n-1] + f[n-2] ,这题的公式和初级的青蛙跳台阶公式一样
  • 11: 二进制1的个数:

    • 关键:
      • n&(n-1) 可以计算1的个数
  • 14: 链表倒数第n个数

    • 关键:
      • 这种题目: 无法获得size的长度,然后求倒数k个值,都使用双指针解决,一个先走k步,另外一个从头开始,当前者到nil的时候,后者就是倒数k个对应的值
  • 15: 链表反转

    • 关键:

      • 1. 保存next值
        2. 当前值next指向prev
        3. prev往前移动
        4. 当前值也往前移动
        
  • 16: 有序链表合并:

    • 关键:
      • 2个链表都是有序的
      • 穷举法: 2个都一起遍历,最后再合一下就ok
  • 17: 判断是否是子树

    • 关键:
      • 递归
      • 根节点要一直 ,然后左孩子要一直,右孩子也要有一致
  • 18: 获取数的反转数

    • 关键:
      • BFS
      • 在bfs途中进行左右孩子交换即可
  • 19: 顺时针打印矩阵

    • 关键:
      • 4个方向,上下左右控制即可
      • 注意退出条件,每轮for 循环都需要判断下
  • 20: 是否是出栈顺序

    • 关键:
      • 定义一个额外的栈 ,如果发现入栈的和之前的相同,则遍历进行判断
  • 22: 二叉树的打印(从上到下,从左到右)

    • 关键:
      • bfs: 则为queue
  • 23: 判断是否是二叉树的后序遍历

    • 关键:
      • 最后一个元素是根元素
      • 向左找到小于他的第一个值是左子树的根 , 然后左子树 < 根<右子树即可
  • 24: 二叉树中求值的路径

    • 关键:
      • dfs
  • 25: 深拷贝链表

    • 关键:
      • map存储 老的值和新的值的映射
  • 26: 二叉搜索树转为双向链表

    • 关键:
      • 中序遍历 然后左右指针赋值
      • 注意退出条件: 遍历的那个节点, 要注意设置空值
  • 28: 数组中出现次数超过一半的数字

    • 关键:
      • 排序,然后中间的数字就是超过一半的数字
  • 29: topk个最小的值

    • 关键:
      • K个数,堆排序构建大顶堆
  • 30: 连续子数组最大和

    • 关键:
      • 动态规划
      • 找出状态转移方程: dp[i]=max(arr[i],dp[i-1]+arr[i])
  • 32: 把数组排成最小的数

    • 关键:
      • 贪心算法
      • 整体最小,则把最小的放前面即可 a+b<b+a 则把b放前面
  • 33: 找到最小的丑数:

    • 看不懂
  • 34: 在字符串中找到只出现一次的字符

    • 关键:
      • 数组: 128个字符,则定义buf为128的数组
      • 然后遍历判断即可
  • 35: 不会

  • 36: 两个链表的公共节点

    • 关键:
      • 双指针
      • 双指针的前提是 2个链表长度要相等,所以: p1+p2==p2+p1
  • 37: 数字在升序数组中出现的次数

    • 关键:
      • 有序
      • 二叉查找,找左右边界
        • 找左边界的时候,相等的时候 right 移动
        • 找有边界的时候,相等的时候,left移动
  • 40: 数组中只出现1次的2个数

    • 关键:
      • 异或可以消除重复元素
      • (x&(x-1))^x 可以得到最后一个1的位置(此时的1要么是a的,要么是b的)
  • 41:和为S的连续正数序列

    • 关键:
      • 滑动窗口
  • 42:

  • 43: 左旋转字符串:

    • 关键:
      • 空间换时间的思路,计算 当前下标移动后的偏移量即可
  • 44: 翻转单词序列(这道题挺傻的)

    • 关键:
      • 空格拆分成数组,然后两两交换
  • 45: 判断是否是连续的数字(扑克牌)

    • 关键:
      • 排序
      • 连续代表着没有重复 ,并且首位相差固定的长度
  • 46,47 直接忽略,不是好题目

  • 49 字符串转为整数:

    • 关键:
      • r=r*10+(str[i]-‘0’)
      • 以及判断是否是正数还是负数
  • lt: 两个链表相加

    • 关键:
      • 时间复杂度O(max(n,m)) 既一个for循环就能搞定
      • 主要是注意一下, % 和 / 即可 , 因为 a+b >10 之后,后面肯定是需要再加1 的
  • lt_3: 最长不重复子串

    • 关键:
      • 滑动窗口
      • 辅助map定义 字符出现的下标,用于左指针移动
      • 右指针不停的右移,当发现重复之后,左指针移动到出现的重复
  • lt:4: 寻找2个有序数组的中位数

    • 关键
      • 边界条件: 需要注意不能 奇数和偶数的情况
      • 遍历找中间2个数
  • lt_5: 最长回文字符串

    • 关键

      • 动态规划

      • dp[i][j]代表的是下标i到下标j 是否为回文字符串 ,因为如果 i-j 是回文字符串,则去掉 首尾也肯定是回文字符串
        
  • lt_11 计算最大面积

    • 关键:
      • 双指针
      • 面积公式: 面积=长*宽 ,宽注定会减少,所以需要移动高度低的
  • lt_13: 罗马数字转整数

    • 关键
      • 根据条件: 如果前面一个数比后面这个数小,则需要减去
  • lt_12: 整数转罗马数字

    • 关键:
      • 贪心算法
      • 先列举出罗马数字所有可能的情况
      • 认为这个数肯定比最大值大
  • lt_14: 最长公共前缀

    • 关键
      • 暴力遍历
      • 两两匹配拿到最长公共前缀之后,与剩下的进行匹配(既a,b,c ,a,b 先得到最长公共前缀r1 ,然后r1与c 获得最长公共前缀即可)
  • lt_15_3sum: 求三个数的和,既 a+b+c=0的组合

    • 关键
      • 排序+固定a之后双指针+去重
      • 注意要去重的话, 当a+b+c==0 的时候,需要移动b,c 从而去除重复元素
  • lt_17: 电话的字母组合 (排列组合问题)

    • 关键:
      • 递归回溯法 backtrack
  • lt_19: 删除链表的倒数第n个

    • 关键
      • 快慢指针
      • 快指针先走n步之后,慢指针出发,当快指针到末尾的时候,慢指针刚好到倒数第n+1个
  • lt_20:有效的括号

    • 关键
      • 这题考察的是栈
      • 遇到(,[,{ 入栈即可,否则出栈进行匹配
  • lt_21:合并2个有序链表

    • 关键
      • dummy节点
      • 暴力匹配大小
  • lt_22: 生成括号

    • 关键
      • 回溯算法(深度优先算法:dfs)
      • 然后从题目中发现规律: 括号都是成对出现的
        • 什么时候添加到结果集
          • 当左括号数=右括号数时
        • 什么时候终止
          • 当左括号数<右括号数时(这时候肯定不会成对出现)
        • 然后继续递归
  • lt_23_合并k个链表

    • 关键
      • 分治法+mergeTwo(合并2个有序链表)
  • lt_24_有序数组删除重复的元素

    • 题目关键:
      • 有序+重复
    • 解题关键:
      • 快慢指针,慢指针充当不重复的元素的个数(既结果集),因为是有序的缘故
  • lt_29_两数相除

    • 题目关键:
    • 解题关键:
      • 从数学角度看就行, 32/3= 3+3<32 => 6+6<32 =>12+12<24 => 3+3<(32-24=8) 3<(8-6)
      • 可以简单概括为: 60/8 = (60-32)/8 + 4 = (60-32-16)/8 + 2 + 4 = 1 + 2 + 4 = 7
  • lt_48_旋转图像

    • 题目关键
    • 解题关键:
      • 死记硬背: 先以中轴线翻转,然后以每行的中间进行翻转
  • ll_33_旋转排序数组中搜搜

    • 题目关键
      • 分为2段
      • 2段都是排序的
    • 解题关键
      • 排序=二分
      • 将 旋转排序的变为整个排序:
        • 如果target 小于 target[0],说明target在右段(既小的段),此时,只需要将左段大于mid的全部设置为最小值,使得mid不停的右移动
        • 如果target 大于 target[0],说明在左段,则此时对于mid<target的值,都设置为最大值,使得mid 不停的左移
        • 参考: https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/duo-si-lu-wan-quan-gong-lue-bi-xu-miao-dong-by-swe/
  • lt_34_在排序数组中寻找这个数的第一次出现和最后一次出现的地方

    • 题目关键

      • 排序数组
      • 重复出现
    • 解题关键:

      • 二分查找
      • 找到target之后,往左找到第一次出现的index,然后往右找直到最后一次出现的下标
      • 参考: https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/0msbi-guan-fang-ti-jie-geng-hao-li-jie-1-ft3q/
  • lt_36_有效的数独

    • 题目关键
      • 行不能出现重复
      • 列不能出现重复
      • 格子内不能出现重复
    • 解题关键
      • 3个二维数组,分别充当上面的3个条件
      • 注意点是计算byte的时候,要 bytes[i][j]-'1'才行,因为9的下标是8
  • lt_41_缺失的第一个正数

    • 题目关键

      • 结果为:最小,正整数
      • 参数中存在小于0的情况
    • 解题关键:

      • 原地置换

      • 将1放在0下标,2放在1下标,依次类推,这是

      • 
        // 参考: https://leetcode-cn.com/problems/first-missing-positive/solution/tong-pai-xu-python-dai-ma-by-liweiwei1419/
        // 关键:
        // 将1放在下标0的位置,2放在下标1的位置 依次类推
        // 然后最终结果,遍历的时候,如果发现当前的下标与i+1不匹配,则返回这个值
        func firstMissingPositive(nums []int) int {
        	l := len(nums)
        	for i := 0; i < l; i++ {
        		// 因为是正整数,所以要nums[i]>0,并且可能为 7,8,远超过长度的,所以对应的值也要小于长度值
        		// FIXME:为什么这里要多出一重判断,判断是否交换后的相等 : 是为了防止死循环
        		for nums[i] > 0 && nums[i] <= l && nums[nums[i]-1] != nums[i] {
        			nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]
        		}
        	}
        	for index,_ := range nums {
        		if nums[index] > 0 && nums[index] == index+1 {
        			return index + 1
        		}
        	}
        	return l + 1
        }
        
        
  • lt_42_接雨水

    • 题目关键
      • 水的计算是通过 最小的来决定的
    • 解题关键
      • 双指针
  • lt_49_字母异位词分组

    • 题目关键

      • 异位词代表着 字符出现的次数肯定相同
    • 解题关键

      • 一个map 记录字符出现的情况所对应的字符串

        • 
          // 参考:https://leetcode-cn.com/problems/group-anagrams/solution/zi-mu-yi-wei-ci-fen-zu-by-leetcode-solut-gyoc/
          // 解题关键: 异位代表着字符串出现的个数肯定相同
          func groupAnagrams(strs []string) [][]string {
          	m := make(map[[26]int][]string)
          	for _, str := range strs {
          		node := [26]int{}
          		for _, v := range str {
          			node[v-'a']++
          		}
          		m[node] = append(m[node], str)
          	}
          	ret := make([][]string, 0)
          	for _, v := range m {
          		ret = append(ret, v)
          	}
          	return ret
          }
          
  • lt_53_最大连续子序列和

    • 题目关键

      • 最大
    • 解题关键

      • 求出每个位置的最大值即可

        • 
          func maxSubArray(nums []int) int {
          	if len(nums) == 0 {
          		return 0
          	}
          	max := nums[0]
          	for index := 1; index < len(nums); index++ {
          		if nums[index]+nums[index-1] > nums[index] {
          			nums[index] = nums[index] + nums[index-1]
          		}
          		if nums[index] > max {
          			max = nums[index]
          		}
          	}
          	return max
          }
          
  • lt_54_螺旋矩阵

    • 解题关键

      • 从左到右,从上到下,从下到左,从左到上

      • 
        func spiralOrder(matrix [][]int) []int {
        	if len(matrix) == 0 {
        		return nil
        	}
        	left, right, top, bottom := 0, len(matrix[0])-1, 0, len(matrix)-1
        	result := make([]int, 0)
        	for ; left <= right && top <= bottom; {
        		// 因为起始是要获取到left 首位的,所以只需要获取首位即可
        		for i := left; i <= right; i++ {
        			result = append(result, matrix[top][i])
        		}
        		for i := top + 1; i <= bottom; i++ {
        			result = append(result, matrix[i][right])
        		}
        		if left < right && top < bottom {
        			for i := right - 1; i > left; i-- {
        				result = append(result, matrix[bottom][i])
        			}
        			for i := bottom; i > top; i-- {
        				result = append(result, matrix[i][left])
        			}
        		}
        		left++
        		top++
        		right--
        		bottom--
        	}
        	return result
        }
        
  • lt_55_跳跃游戏

    • 参考: https://leetcode-cn.com/problems/jump-game/solution/55-by-ikaruga/

    • 解题关键

      • 如果这个位置能够到达,那么这个位置之前的位置都能到达

      • // 解题关键: 看能跳到最远的格子能否大于当前长度
        func canJump(nums []int) bool {
        	max := 0
        	for index := range nums {
        		if index > max {
        			return false
        		}
        		// 之所以是nums[index]+index 而不是nums[index]+max 是因为,计算的是当前格子的长度,而不是总的
        		if nums[index]+index > max {
        			max = nums[index] + index
        		}
        	}
        	return true
        }
        
        
  • lt_56_合并区间

    • 参考:https://leetcode-cn.com/problems/merge-intervals/solution/merge-intervals-by-ikaruga/

    • 解题关键

      • 先排序,拍完序之后,双指针,右指针移动找到当前区间的最大值(同时那个区间的值又要 >当前区间的0下标值)

      • r := make([][]int, 0)
        
        	for i := 0; i < len(intervals); {
        		j := i + 1
        		max := intervals[i][1]
        		for ; j < len(intervals); j++ {
        			if intervals[j][0] > intervals[i][0] {
        				if intervals[j][1] > max {
        					max = intervals[j][1]
        				}
        			}
        		}
        		r = append(r, []int{intervals[i][0], max})
        		i = j + 1
        	}
        
        	return r
        
  • lt_62_不同路径

    • 参考: https://leetcode-cn.com/problems/unique-paths/solution/bu-tong-lu-jing-by-leetcode-solution-hzjf/

    • 题目关键

      • 只能向下或者向右走
    • 解题关键

      • 因为只能向下或者向右走,所以 f(m,n)要么从m-1,要么从n-1过来,所以(m,n)=f(m-1,n)+f(m,n-1)
      • 同时,当处于m列或者n行时候,只会有1种路径
    • 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; i++ {
      		for j := 1; j < n; j++ {
      			dp[i][j] = dp[i-1][j] + dp[i][j-1]
      		}
      	}
      	return dp[m-1][n-1]
      }
      
  • lt_66_加一

    • 解题关键

      • 注意一下,特殊输入就行,比如说99999 这种

      • 如果中途不为0,则直接返回就行

      • func plusOne(digits []int) []int {
        	l := len(digits) - 1
        	for i := l; i >= 0; i-- {
        		digits[i]++
        		digits[i] = digits[i] % 10
        		if digits[i] != 0 {
        			return digits
        		}
        	}
        	ret := make([]int, len(digits)+1)
        	ret[0] = 1
        
        	return ret
        }
        
  • lt_69_x的平方根

    • 就是 求平方根

    • 解题关键

      • 二分法,判断是否相等是通过 mid*mid<=x 来判断的

      • 
        func mySqrt(x int) int {
        	l, r := 0, x
        	ret := 0
        	for l <= r {
        		mid := l + (r-l)>>1
        		if mid*mid <= x {
        			ret = mid
        			l = mid + 1
        		} else {
        			r = mid - 1
        		}
        	}
        
        	return ret
        }
        
        
  • lt_73_矩阵置零

    • 题目关键

      • 所在行,所在列,都为0
    • 解题关键:

      • 用数组标记状态即可

        • // 题目关键:
          // 有一个元素为0,则所在行和列都为0
          // 解题关键: 使用标记数组的方式,就是先遍历匹配,某个值为0,则标记该行为0
          func setZeroes(matrix [][]int) {
          	if len(matrix) == 0 {
          		return
          	}
          	rows := make([]bool, len(matrix))
          	cols := make([]bool, len(matrix[0]))
          	for row, v := range matrix {
          		for col, vv := range v {
          			if vv == 0 {
          				rows[row] = true
          				cols[col] = true
          			}
          		}
          	}
          	for i := 0; i < len(matrix); i++ {
          		for j := 0; j < len(matrix[i]); j++ {
          			if cols[j] || rows[i] {
          				matrix[i][j] = 0
          			}
          		}
          	}
          }
          
  • lt_75_颜色分类

    • 题目关键

      • 排序显示
    • 解题关键

      • 双指针

      • 遍历的时候,发现为2的话,则移动到右指针的地方(注意,这时候可能右指针也是2,导致换过去的也是2,所以也会是for循环一直移动)

      • // 题目关键: 最终结果是最后的值是从小到大
        // 解题关键: 双指针: 右指针将后面的2的可能情况全部移到前面
        func sortColors(nums []int) {
        	left, right := 0, len(nums)-1
        	for i := 0; i <= right; i++ {
        		// 使得如果右指针也是2,则后面的2全部往前移动
        		for ; i <= right && nums[i] == 2; right-- {
        			nums[i], nums[right] = nums[right], nums[i]
        		}
        		if nums[i] == 0 {
        			nums[i], nums[left] = nums[left], nums[i]
        			left++
        		}
        	}
        }
        
  • lt_76_最小覆盖子串

    • 参考: https://github.com/greyireland/algorithm-pattern/blob/master/advanced_algorithm/slide_window.md

    • 解题关键

      • 滑动窗口

      • 2个map,一个为need,一个为have,一个match匹配长度

      • 右指针不停的向右滑动,达到match=len(need)的时候,开始收缩左窗口

      • 
        func minWindow(s string, t string) string {
        	left, right := 0, 0
        
        	match := 0
        	start := 0
        	end := 0
        	min := math.MaxInt32
        	need := make(map[byte]int)
        	have := make(map[byte]int)
        	for i := 0; i < len(t); i++ {
        		need[t[i]]++
        	}
        
        	for right < len(s) {
        		c := s[right]
        		right++
                
        		if need[c] != 0 {
        		    have[c]++
        			if have[c] == need[c] {
        				match++
        			}
        		}
        		for match == len(need) {
        			if right-left < min {
        				min = right - left
        				start = left
        				end = right
        			}
        			c := s[left]
        			// 左指针右移
        			left++
        			if need[c] != 0 {
        				if have[c] == need[c] {
        					match--
        				}
        				have[c]--
        			}
        		}
        	}
        	if min == math.MaxInt32 {
        		return ""
        	}
        
        	return s[start:end]
        }
        
        
    • lt_78_子集

      • 题目关键

        • 子集
      • 解题关键

        • 回溯法(dfs)

        • 
          func subsets(nums []int) [][]int {
          	ret := make([][]int, 0)
          	subsetsBacktrack(nums, 0, &ret)
          	return ret
          }
          
          var current []int
          
          func subsetsBacktrack(nums []int, index int, ret *[][]int) {
          	if index == len(nums) {
          		*ret = append(*ret, append([]int(nil), current...))
          		return
          	}
          	current = append(current, nums[index])
          	subsetsBacktrack(nums, index+1, ret)
          	current = current[:len(current)-1]
          	subsetsBacktrack(nums, index+1, ret)
          }
          
    • lt_79_单词搜索

      • 题目关键

        • 可以从四面八方进行寻路
      • 解题关键

        • 回溯法,设置一个状态变量,对于访问过的,不再访问
      • 
        var directions = [][2]int{
        	{-1, 0},
        	{1, 0},
        	{0, -1},
        	{0, 1},
        }
        
        func exist(board [][]byte, word string) bool {
        	flags := make([][]bool, len(board))
        	for i := 0; i < len(board); i++ {
        		flags[i] = make([]bool, len(board[i]))
        	}
        	var backTack func(row, col int, index int) bool
        	backTack = func(row, col int, index int) bool {
        		if board[row][col] != word[index] {
        			return false
        		}
        		if index == len(word)-1 {
        			return true
        		}
        		flags[row][col] = true
        		defer func() { flags[row][col] = false }()
        		for _, dir := range directions {
        			newRow := row + int(dir[0])
        			newCol := col + int(dir[1])
        			if newRow > 0 && newRow < len(board) && newCol > 9 && newCol < len(board[0]) && !flags[newRow][newCol] {
        				if backTack(newRow, newCol, index+1) {
        					return true
        				}
        			}
        		}
        		return false
        	}
        	for i := 0; i < len(board); i++ {
        		for j := 0; j < len(board[i]); j++ {
        			if backTack(i, j, 0) {
        				return true
        			}
        		}
        	}
        	return false
        }
        
    • lt_88_合并2个有序数组

      • 题目关键

        • 2个数组都是升序的
        • 都将数据写入到nums1中
      • 解题关键

        • 每个数组各一个指针,与合并有序链表相似,但是是从尾到前赋值
        func merge(nums1 []int, m int, nums2 []int, n int) {
        	for mp, np, cur := m-1, n-1, m+n-1; mp >= 0 || np >= 0; cur-- {
        		var v int
        		if mp == -1 {
        			v = nums2[np]
        			np--
        		} else if np == -1 {
        			v = nums1[mp]
        			mp--
        		} else if nums1[mp] > nums2[np] {
        			v = nums1[mp]
        			mp--
        		} else {
        			v = nums2[np]
        			np--
        		}
        		nums1[cur] = v
        	}
        }
        
    • lt_91_编码方法

      • 解题关键

        • 可以取一个数,也可以取2个数
        • 动态规划
          • f(n)=f(n-1) / f(n)=f(n-2)
      • // 状态转移方程:
        // f(i)=f(i-1) + f(i-2)
        func numDecodings(s string) int {
        	dp := make([]int, len(s)+1)
        	dp[0] = 1
        	for i := 1; i <= len(s); i++ {
        		if s[i-1] != '0' {
        			dp[i] += dp[i-1]
        		}
        		// 代表着当取两位的时候,如果第一位为0 ,是无效的,然后得确保和<26才行
        		if i > 1 && s[i-2] != '0' && ((s[i-2]-'0')*10+s[i-1]-'0' <= 26) {
        			dp[i] += dp[i-2]
        		}
        	}
        	return dp[len(s)]
        }
        
    • lt_二叉树的中序遍历

      • 解题关键

        • 递归法和栈法

          • 
            // 非递归法: 既层次遍历: BFS:使用栈
            func inorderTraversal(root *TreeNode) []int {
            	if nil == root {
            		return nil
            	}
            	ret := make([]int, 0)
            	stack := make([]*TreeNode, 0)
            	for len(stack) > 0 || root != nil {
            		for nil != root {
            			stack = append(stack, root)
            			root = root.Left
            		}
            		node := stack[len(stack)-1]
            		stack = stack[:len(stack)-1]
            		ret = append(ret, node.Val)
            		root = node.Right
            	}
            	return ret
            }
            
            // 中序遍历方式:  左根右
            func inorderTraversalWithLoop(root *TreeNode) []int {
            	ret := make([]int, 0)
            	if nil == root {
            		return nil
            	}
            	loopInorderTraversal(root, &ret)
            	return ret
            }
            
            // 递归法
            func loopInorderTraversal(node *TreeNode, ret *[]int) {
            	if nil != node.Left {
            		loopInorderTraversal(node.Left, ret)
            	}
            	*ret = append(*ret, node.Val)
            	if nil != node.Right {
            		loopInorderTraversal(node.Right, ret)
            	}
            }
            
  • lt_二叉树的z字行打印

    • 解题关键
      • 还是bfs,但是,最后判断一下当前层级是否需要reverse
  • lt_98_判断是否是二叉搜索树

    • 解题关键
      1. 左子树的最大值小于根节点
      2. 右子树的最小值大于根节点
      3. 所以需要定义一个变量存储这2个值
  • lt_101_判断是否是对称二叉树

    • 解题关键

      • /*
           判断是否是对称二叉树
           则判断子树是否是对称二叉树(递归)
           判断子树是否是对称二叉树=>左边的和右边的对称(想到双指针)
        */
        
    • 
      func isSymmetric(root *TreeNode) bool {
      	return checkIsSymmetric(root, root)
      }
      func checkIsSymmetric(r1, r2 *TreeNode) bool {
      	if nil == r1 && nil == r2 {
      		return true
      	}
      	if nil == r1 || nil == r2 {
      		return false
      	}
      	return r1.Val == r2.Val && checkIsSymmetric(r1.Left, r2.Right) && checkIsSymmetric(r1.Right, r2.Left)
      }
      
      
  • lt_104_树的最大深度

    • 解题关键

      • 计算树的最大深度
        则需要比较左子树的深度和右子树的深度,也就意味着需要递归处理
        
    • func maxDepth(root *TreeNode) int {
      	if  root==nil{
      		return 0
      	}
      	left:=maxDepth(root.Left)
      	right:=maxDepth(root.Right)
      	if left>right{
      		return left+1
      	}
      	return right+1
      }
      
  • lt_108_有序数组构建二叉平衡树

    • 解题关键

      • 中间点作为根节点
      • 递归构建
    • // 解题关键
      // 中间节点作为根节点
      func sortedArrayToBST(nums []int) *TreeNode {
      	return sortedArrayToBSTHelper(nums, 0, len(nums)-1)
      }
      func sortedArrayToBSTHelper(nums []int, left, right int) *TreeNode {
      	if left > right {
      		return nil
      	}
      	mid := (right + left) >> 1
      	ret := &TreeNode{Val: nums[mid]}
      	ret.Left = sortedArrayToBSTHelper(nums, left, mid-1)
      	ret.Right = sortedArrayToBSTHelper(nums, mid+1, right)
      	return ret
      }
      
  • lt_116_填充每个节点的next节点为右节点

    • 解题关键

      • 本质上层次遍历: 每一层的形成链表

      • 所以关键是层次遍历

        • // 解题关键:
          // 题目的意思是: 层序遍历,然后每一层的元素,指向这层的下一个元素
          // 所以关键是层序遍历 BFS: queue
          func connect(root *Node) *Node {
          	if nil == root {
          		return nil
          	}
          	queue := make([]*Node, 0)
          	queue = append(queue, root)
          	for len(queue) > 0 {
          		tmp := queue
          		queue = nil
          		// 然后遍历这层,建立连接关系
          		for i, node := range tmp {
          			if i+1 < len(tmp) {
          				node.Next = tmp[i+1]
          			}
          			if nil != node.Left {
          				queue = append(queue, node.Left)
          			}
          			if nil != node.Right {
          				queue = append(queue, node.Right)
          			}
          		}
          	}
          	return root
          }
          
  • lt_118_杨辉三角

    • 解题关键:

      • 其实就是找规律: 是上一行左右元素的和
    • 
      func generate(numRows int) [][]int {
      	if numRows == 0 {
      		return nil
      	}
      	ret := make([][]int, numRows)
      	for index := range ret {
      		ret[index] = make([]int, index+1)
      		ret[index][0] = 1
      		ret[index][index] = 1
      		for j := 1; j < index; j++ {
      			ret[index][j] = ret[index-1][j] + ret[index-1][j-1]
      		}
      	}
      	return ret
      }
      
  • lt_121_买卖股票的最佳时机

    • 解题关键

      • 遍历找到价格最低的,然后计算最大值即可
    • func maxProfit(prices []int) int {
      	if len(prices) == 0 {
      		return 0
      	}
      	minPrice := math.MaxInt32
      	ret := 0
      	for _, v := range prices {
      		if v < minPrice {
      			minPrice = v
      		} else if v-minPrice > ret {
      			ret = v - minPrice
      		}
      	}
      
      	return ret
      }
      
  • lt_122_买卖股票的最佳时机2

    • 解题关键

      • 动态规划,二维数组
      • 列0 代表的是,第i天卖出股票的收益,列1 代表的是,买入股票后剩余的收益
    • 
      // 二维数组
      // 动态规划:行代表的是第n天,列 0代表的是股票卖了后的收益,1代表的是买入股票的收益
      func maxProfit(prices []int) int {
      	ret := 0
      	dp := make([][]int, len(prices))
      	for i := 0; i < len(prices); i++ {
      		dp[i] = make([]int, 2)
      	}
      	// 初始状态,代表的是第0天,买入股票的收益,此时为 负
      	dp[0][1] = -prices[0]
      	for i := 1; i < len(prices); i++ {
      		// 此时卖出去股票,收益取最大值
      		dp[i][0] = maxProfit2Max(dp[i-1][0], dp[i-1][1]+prices[i])
      		// 此时买入股票,更新今天的收益
      		dp[i][1] = maxProfit2Max(dp[i-1][1], dp[i-1][0]-prices[i])
      	}
      	ret = dp[len(prices)-1][0]
      	return ret
      }
      func maxProfit2Max(a, b int) int {
      	if a > b {
      		return a
      	}
      	return b
      }
      
  • lt_124_二叉树的最大路径和

    • 解题关键:

      • dfs
    • 
      // 解题关键
      // 左节点的最大路径和,右节点的最大路径和, 左+根+右的最大值
      // dfs
      func maxPathSum(root *TreeNode) int {
      	_, ret := dfsMaxPathSum(root)
      	return ret
      }
      func dfsMaxPathSum(root *TreeNode) (int, int) {
      	if root == nil {
      		return 0, -(1 << 31)
      	}
      	left, leftAll := dfsMaxPathSum(root.Left)
      	right, rightAll := dfsMaxPathSum(root.Right)
      
      	ret := maxPathSumMax(left, right)
      	ret = maxPathSumMax(0, ret+root.Val)
      
      	retAll := maxPathSumMax(leftAll, rightAll)
      	retAll = maxPathSumMax(retAll, left+right+root.Val)
      	return ret, retAll
      }
      
      func maxPathSumMax(a, b int) int {
      	if a < b {
      		return b
      	}
      	return a
      }
      
      
  • lt_125_判断是否是回文字符串

    • 解题关键:

      • 首尾双指针
    • // 解题关键: 首尾双指针遍历即可
      func isPalindrome(s string) bool {
      	ret := ""
      	for i := range s {
      		if isalnum(s[i]) {
      			ret += string(s[i])
      		}
      	}
      	s = strings.ToLower(ret)
      	for i, j := 0, len(s)-1; i < j; {
      		if s[i] != s[j] {
      			return false
      		}
      		i++
      		j--
      	}
      	return true
      }
      func isalnum(ch byte) bool {
      	return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')
      }
      
      
  • lt_128_最长连续序列

    • 解题关键

      • 用一个set存储所有的值,然后遍历set 的key,不断的用key+1 来判断是否存在即可
    • 
      // 解题关键: 用一个hashSet来处理,
      func longestConsecutive(nums []int) int {
      	set := make(map[int]bool)
      	for _, v := range nums {
      		set[v] = true
      	}
      	ret := 0
      	for k := range set {
      		// 如果之前的数不存在,则可以开始统计了
      		// 当前的数是必然存在的
      		if !set[k-1] {
      			currentN := k
      			currentL := 0
      			for set[currentN] {
      				currentL++
      				currentN++
      			}
      			if currentL > ret {
      				ret = currentL
      			}
      		}
      	}
      	return ret
      }
      
  • lt_130_被围绕的区域

    • 解题关键

      • 怎么判断这个值是否需要被替换成X
      • 根据题目: 在边界的O 不能替换,所以跟边界的O关联的 O 都不会被替换,因此从边界开始dfs,设定一个假的值 A ,用于最后消除
    • 
      // 解题关键
      // 边界上的点不会被填充为x ,则 从正方形4个边界,进行dfs
      var (
      	n, m int
      )
      
      func solve(board [][]byte) {
      	if len(board) == 0 || len(board[0]) == 0 {
      		return
      	}
      	n, m = len(board), len(board[0])
      
      	for i := 0; i < n; i++ {
      		solveDfs(board, i, 0)
      		solveDfs(board, i, m-1)
      	}
      	for i := 1; i < m-1; i++ {
      		solveDfs(board, 0, i)
      		solveDfs(board, n-1, i)
      	}
      	for i := 0; i < n; i++ {
      		for j := 0; j < m; j++ {
      			if board[i][j] == 'A' {
      				board[i][j] = 'O'
      			} else if board[i][j] == 'O' {
      				board[i][j] = 'X'
      			}
      		}
      	}
      
      }
      func solveDfs(board [][]byte, x, y int) {
      	if x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O' {
      		return
      	}
      	board[x][y] = 'A'
      	solveDfs(board, x+1, y)
      	solveDfs(board, x-1, y)
      	solveDfs(board, x, y+1)
      	solveDfs(board, x, y-1)
      }
      
  • lt_130_加油站

    • 解题关键

      • 遍历判断是否能到达
    • 
      // 解题关键:
      // 遍历所有的加油站,判断是否都能够到达
      func canCompleteCircuit(gas []int, cost []int) int {
      	n := len(cost)
      	for i := 0; i < n; {
      		count := 0     // 当前走过的加油站
      		sumOfCas := 0  // 总共加的油
      		sumOfCost := 0 // 总共要使用的油
      		for count < n {
      			index := (i + count) % n // 因为是环形,所以要取余
      			sumOfCas += gas[index]
      			sumOfCost += cost[index]
      			if sumOfCost > sumOfCas {
      				break // 代表的是油不够,所以直接break即可
      			}
      			count++ // 满足,则行驶到下一个站点
      		}
      		if count == n {
      			// 代表着,这个加油站出发,能够回到原来位置
      			return i
      		}
      		// 则从下一个未探测的站点出发,这一步就是可以避免重复计算的
      		i = i + count + 1
      	}
      	// 都不满足
      	return -1
      }
      
  • lt_136_只出现一次的数

    • 解题关键:

      • 用异或就能解决
    • func singleNumber(nums []int) int {
      	ret := 0
      	for _, v := range nums {
      		ret ^= v
      	}
      	return ret
      }
      
  • lt_148_排序链表

    • 解题关键:

      • 归并排序算法
    • 
      // 关键:
      // 归并排序法 1. 先找到中点,然后找到左边和右边 2 最后再进行归并
      func sortList(head *ListNode) *ListNode {
      	return sortListMerge1(head)
      }
      
      func sortListMerge1(head *ListNode) *ListNode {
      	if head == nil || head.Next == nil {
      		return head
      	}
      
      	mid := sortListMerge2FindMid(head)	 // 先快慢指正找中点
      	tail := mid.Next
      	mid.Next = nil
      
      	left := sortListMerge1(head) // 然后递归找中点
      	right := sortListMerge1(tail)
      
      	return sortListMerge3(left, right) // 最后对两个链表进行排序
      }
      func sortListMerge2FindMid(node *ListNode) *ListNode {
      	fast := node.Next
      	slow := node
      	for nil != fast.Next && fast.Next.Next != nil {
      		fast = fast.Next.Next
      		slow = slow.Next
      	}
      	return slow
      }
      
      func sortListMerge3(l1, l2 *ListNode) *ListNode {
      	dumy := &ListNode{}
      	tmp := dumy
      	for nil != l1 && nil != l2 {
      		if l1.Val < l2.Val {
      			tmp.Next = l1
      			l1 = l1.Next
      		} else {
      			tmp.Next = l2
      			l2 = l2.Next
      		}
      		tmp = tmp.Next
      	}
      	if nil != l1 {
      		tmp.Next = l1
      	}
      	if nil != l2 {
      		tmp.Next = l2
      	}
      	return dumy.Next
      }
      
  • lt_227_基本计算器2

    • 关键:

      • // 1. 栈进行对结果进行保存,对于 * 或者 / 则直接取出进行计算
    • func calculate(s string) int {
      	retStack := []int{}
      	num := 0
      	preSign := '+'
      	for i, v := range s {
      		isDigital := v >= '0' && v <= '9'
      		if isDigital {
      			num = num*10 + int(v-'0')
      		}
      		if !isDigital && v != ' ' || i == len(s)-1 {
      			switch preSign {
      			case '+':
      				retStack = append(retStack, num)
      			case '-':
      				retStack = append(retStack, -num)
      			case '*':
      				retStack[len(retStack)-1] *= num
      			default:
      				retStack[len(retStack)-1] /= num
      			}
      			preSign = v
      			num = 0
      		}
      	}
      
      	ret := 0
      	for _, v := range retStack {
      		ret += v
      	}
      	return ret
      }
      
      
  • lt_162_寻找峰值

    • 关键

      • 二分法
      • 将类比与爬坡, 哪边大,往哪边走(至于两边都大的情况下,任选一边即可)
    • // 关键:  想象为爬坡 ,
      // 1. 题目关键: 峰值为 大于相邻左边和右边的值
      // 二分法: 如果中间的元素 M  :  l<m<r ,表明应该往右移动
      // 如果中间的元素 : l>m>r,则表明应该往左移动
      func findPeakElement(nums []int) int {
      	n := len(nums)
      	getV := func(index int) int {
      		if index == -1 || index == n {
      			return math.MinInt64
      		}
      		return nums[index]
      	}
      	left, right := 0, n-1
      	for {
      		mid := (left + right) >> 1
      		if getV(mid) > getV(mid-1) && getV(mid) > getV(mid+1) {
      			return mid
      		}
      		if getV(mid+1) > getV(mid) {
      			left = mid + 1
      		} else {
      			right = mid - 1
      		}
      	}
      }
      
  • lt_166_分数到小数

    • 关键:

      • 与人为计算是一样的,当不能整除时,自动*10 然后取剩下的继续
      • 整除 + 取模
    • // 参考:  https://leetcode-cn.com/problems/fraction-to-recurring-decimal/solution/gong-shui-san-xie-mo-ni-shu-shi-ji-suan-kq8c4/
      // 关键: 用草稿比比划下草稿纸上运算的过程
      // 1. 先直接整除,能除下来的,则是实数部分,剩余的则是小数部分
      // 2. 然后取余对这个数整除的时候,肯定是都需要*10的
      // 3. 额外关注无限小数的这种,如 10/3 =3.33333 ,并且会是一直都是以小数结尾,这时候,对于这种是通过缓存来判断的
      // 以 10/3 为例 10/3 不整除,最后会余一个1 去除3 ,1/3 的时候,会不停的*10 然后继续除
      func fractionToDecimal(numerator int, denominator int) string {
      	if numerator%denominator == 0 {
      		return strconv.Itoa(numerator / denominator)
      	}
      	ret := make([]byte, 0)
      	if numerator*denominator < 0 {
      		ret = append(ret, '-')
      	}
      	if numerator < 0 {
      		numerator *= -1
      	}
      	if denominator < 0 {
      		denominator *= -1
      	}
      
      	first := numerator / denominator
      	left := numerator % denominator
      	ret = append(ret, byte(first))
      	ret = append(ret, '.')
      	lastCache := make(map[int]int)
      	for left != 0 {
      		lastCache[left] = len(ret)
      		left *= 10
      		// 计算结果 : 1*10 /3
      		ret = append(ret, byte(left/denominator))
      		left %= denominator
      		if v, exist := lastCache[left]; exist {
      			// 表明是重复的,如  1/3 之后  10/3 依旧有了(1.3) ,
      			// 则 抽离非重复的+ 重复的
      			nonRepeat := ret[:v]
      			repeated := append([]byte{'{'}, ret[v:]...)
      			repeated = append(repeated, '}')
      			return fmt.Sprintf("%s(%s)", string(nonRepeat), string(repeated))
      		}
      	}
      
      	return string(ret)
      }
      
      
  • lt_169_多数元素

    • // 关键:
      // 排序,找中间值
      func majorityElement(nums []int) int {
      	majorityElementQSort(nums, 0, len(nums)-1)
      	return nums[len(nums)>>1]
      }
      func majorityElementQSort(nums []int, start, end int) {
      	if start < end {
      		paration := majorityElementQSortParation(nums, start, end)
      		majorityElementQSort(nums, start, paration)
      		majorityElementQSort(nums, paration+1, end)
      	}
      }
      
      func majorityElementQSortParation(nums []int, start, end int) int {
      	standard := nums[start]
      	for start < end {
      		for ; end > start && nums[end] >= standard; end-- {
      		}
      		nums[start] = nums[end]
      		for ; start < end && nums[end] <= standard; start++ {
      		}
      		nums[end] = nums[start]
      	}
      	nums[start] = standard
      	return start
      }
      
  • lt_150_逆波兰表达式

    • 关键:

      • 用栈即可
    • func evalRPN150(tokens []string) int {
      	stack := make([]int, 0)
      	for _, v := range tokens {
      		switch v {
      		case "+", "-", "*", "/":
      			if len(stack) < 2 {
      				return -1
      			}
      			v1, v2 := stack[len(stack)-2], stack[len(stack)-1]
      			stack = stack[:len(stack)-2]
      			switch v {
      			case "+":
      				stack = append(stack, v1+v2)
      			case "-":
      				stack = append(stack, v1-v2)
      			case "*":
      				stack = append(stack, v1*v2)
      			case "/":
      				stack = append(stack, v1/v2)
      			}
      		default:
      			intV, _ := strconv.Atoi(v)
      			stack = append(stack, intV)
      		}
      	}
      	return stack[0]
      }
      
  • lt_160_相交链表

    • // 找到两个单链表相交的节点
      // 关键:
      // 双指针: pa,pb ,如果pa为空了,则 pa 移动到headB重新开始,pb同理
      func getIntersectionNode(headA, headB *ListNode) *ListNode {
      	if headA == nil || headB == nil {
      		return nil
      	}
      	pa, pb := headA, headB
      	for pa != pb {
      		if pa == nil {
      			pa = headB
      		} else {
      			pa = pa.Next
      		}
      		if pb == nil {
      			pb = headA
      		} else {
      			pb = pb.Next
      		}
      	}
      	return pa
      }
      
      
  • lt_172_阶乘后的0

    • // 关键
      // 数学规律题,背题大法好: 统计5出现的次数即可,因为为0 只有 2*5 的情况,有5必有2 ,所以统计5的次数
      func trailingZeroes(n int) int {
      	ret := 0
      	for n > 0 {
      		n /= 5
      		ret += n
      	}
      	return ret
      }
      
  • lt_179_最大数

    • // 关键: 排序: 将2个数间更大的数拍前面,但是需要注意的是 [4,45]: 454 > 445 ,所以排序算法需要定制化
      func largestNumber(nums []int) string {
      	sort.Slice(nums, func(i, j int) bool {
      		a, b := nums[i], nums[j]
      		x1, x2 := 10, 10
      		for x1 <= a {
      			x1 *= 10
      		}
      		for x2 <= b {
      			x2 *= 10
      		}
      		return x1*b+a < x2*a+b
      	})
      	if nums[0] == 0 {
      		return "0"
      	}
      	ans := []byte{}
      	for _, x := range nums {
      		ans = append(ans, strconv.Itoa(x)...)
      	}
      	return string(ans)
      }
      
  • lt_189_轮转数组

    • // 关键:
      // 3次翻转
      // 第一次: 翻转全部
      // 第二次: 翻转 0-k
      // 第三次: 翻转k-剩下的
      func rotate(nums []int, k int) {
      	k %= len(nums)
      	rotateReverse(nums)
      	rotateReverse(nums[:k])
      	rotateReverse(nums[k:])
      }
      func rotateReverse(nums []int) {
      	limit := len(nums) >> 1
      	l := len(nums) - 1
      	for i := 0; i < limit; i++ {
      		nums[i], nums[l-i] = nums[l-i], nums[i]
      	}
      }
      
  • lt_190_颠倒二进制位

    • // 关键: 计算 1的个数
      func reverseBits(num uint32) uint32 {
      	var ret uint32
      	bitOneCount := 31
      	for bitOneCount >= 0 {
      		// x&1 判断bitOneCount 这个位置是否为1,然后左移,这样的话,如果最后一个为1 ,则可以得到二进制下的部分值
      		ret += (num & 1) << bitOneCount
      		bitOneCount--
      		// 然后将num 消除最后一位置空 ,就可以不断的统计得到1的个数了(以及对应的位置)
      		num >>= 1
      	}
      	return ret
      }
      
  • lt_191_位1的个数

    • 关键: num&(num-1): 消除最后一个1
      func hammingWeight(num uint32) int {
      	ret := 0
      	for num > 0 {
      		num = num & (num - 1)
      		ret++
      	}
      	return ret
      }
      
  • lt_198_打家劫舍

    • 关键:

      • 动态规划: 方程式: dp[0]=nums[0] , dp[n]=max(dp[n-2]+nums[n],dp[n-1])
    • // 动态规划:
      //  dp[0]=nums[0] , dp[n]=max(dp[n-2]+nums[n],dp[n-1])
      func rob(nums []int) int {
      	if len(nums) == 0 {
      		return 0
      	}
      	if len(nums) == 1 {
      		return nums[0]
      	}
      	dp := make([]int, len(nums))
      	dp[0] = nums[0]
      	dp[1] = robMax(dp[0], nums[1])
      	for i := 2; i < len(nums); i++ {
      		dp[i] = robMax(dp[i-2]+nums[i], dp[i-1])
      	}
      	return dp[len(dp)-1]
      }
      
      func robMax(a, b int) int {
      	if a < b {
      		return b
      	}
      	return a
      }
      
    • lt_200_岛屿数量

      • 
        // 关键:
        // dfs
        // 找到一个1 之后(既这是一个island), 此时要将其周围所有的1 都置为0 ,因为1连接成片的时候,是只算一个岛屿的
        func numIslands2(grid [][]byte) int {
        	ret := 0
        
        	for i := 0; i < len(grid); i++ {
        		for j := 0; j < len(grid[i]); j++ {
        			if grid[i][j] == '1' {
        				ret++
        				numIslands2Dfs(grid, i, j)
        			}
        		}
        	}
        
        	return ret
        }
        func numIslands2Dfs(grid [][]byte, i, j int) {
        	if i < 0 || i >= len(grid) || j < 0 || j >= len(grid[i]) || grid[i][j] == '0' {
        		return
        	}
        	if grid[i][j] == '1' {
        		grid[i][j] = '0'
        	}
        // 此时要将其周围所有的1 都置为0 ,因为1连接成片的时候,是只算一个岛屿的
        	numIslands2Dfs(grid, i-1, j)
        	numIslands2Dfs(grid, i+1, j)
        	numIslands2Dfs(grid, i, j-1)
        	numIslands2Dfs(grid, i, j+1)
        }
        
    • lt_202_快乐数

      • 关键:

        • 判断是否有环
        • 以及,注意计算 快乐数的方式,是自己与自己平方
      • 
        // 关键: 判断算出来的数,是否在map中存在,存在则代表着有环,必然无法结果为1
        func isHappy(n int) bool {
        	m := make(map[int]struct{})
        	for n != 1 {
        		n = isHappyStep(n)
        		if _, exist := m[n]; exist {
        			return false
        		}
        		m[n] = struct{}{}
        	}
        	return n == 1
        }
        func isHappyStep(v int) int {
        	ret := 0
        	for v > 0 {
        		ret += (v % 10) * (v % 10)
        		v /= 10
        	}
        	return ret
        }
        
        
  • lt_204_计算质数

    • // 质数的定义: 乘法 只有 1和 它自己本身,1 不是质数
      // 如果一个数v是质数,那么2v,3v,4v,5v ....nv 必然不是质数
      func countPrimes(n int) int {
      	if n <= 1 {
      		return 0
      	}
      	ret := 0
      	isPrime := make([]bool, n)
      	for i := range isPrime {
      		isPrime[i] = true
      	}
      
      	for i := 2; i < n; i++ {
      		if isPrime[i] {
      			ret++
      			// 则 2v,3v等必然不是质数
      			for j := 2 * i; j < n; j += i {
      				isPrime[j] = false
      			}
      		}
      	}
      	return ret
      }
      
  • lt_206_反转链表

    • // 关键: 初始化一个prev 节点, 以及很关键的,链表的操作都是,先连后断
      func reverseList2(head *ListNode) *ListNode {
      	var prev *ListNode
      	for nil != head {
      		next := head.Next
      		head.Next = prev
      
      		prev = head
      		head = next
      	}
      
      	return prev
      }
      
  • lt_208_实现前缀树

    • 
      // 关键:
      // 1. 字典树的定义: 是根据字典序所排列的数据结构
      // 2. 数据结构定义: 必须要有children
      type Trie struct {
      	children [26]*Trie
      	isEnd    bool
      }
      
      func TrieConstructor() Trie {
      	ret := Trie{}
      	return ret
      }
      
      func (this *Trie) Insert(word string) {
      	temp := this
      	for _, v := range word {
      		index := v - 'a'
      		if temp.children[index] == nil {
      			temp.children[index] = &Trie{}
      		}
      		temp = temp.children[index]
      	}
      	temp.isEnd = true
      }
      
      func (this *Trie) Search(word string) bool {
      	node := this.searchByPrefix(word)
      	return node != nil && node.isEnd
      }
      
      func (this *Trie) searchByPrefix(prefix string) *Trie {
      	node := this
      	for _, v := range prefix {
      		index := v - 'a'
      		if node.children[index] == nil {
      			return nil
      		}
      		node = node.children[index]
      	}
      	return node
      }
      
      func (this *Trie) StartsWith(prefix string) bool {
      	return this.searchByPrefix(prefix) != nil
      }
      
  • lt_836_二叉树中所有节点距离为k的值

    • 
      // 关键: dfs
      // 还有一个关键点就是 除了向下搜,还得能向上搜,就是直接通过parent搜,并且,很关键的一点是,防止走回头路
      // 由于是二叉链表,所以,无法直接由当前结点走向其父节点,所以用了一个map加一次dfs遍历来保存所有结点的父节点,这样就可以查表直接跳到父节点了。
      // 为了防止走回头路,所以设计了一个from标志,等效于设置一个set(将路过的结点加入set,若待访问的结点不在set中,则访问它,否则跳过)。
      // 用set防止走回头路的解法,相较于设置from标志来说时间复杂度高一点,因为需要在set中进行插入和查找...
      func distanceK(root *TreeNode, target *TreeNode, k int) []int {
      	parentsMap := make(map[int]*TreeNode)
      	var fillParents func(node *TreeNode)
      	fillParents = func(node *TreeNode) {
      		if nil != node.Left {
      			parentsMap[node.Left.Val] = node
      			fillParents(node.Left)
      		}
      		if nil != node.Right {
      			parentsMap[node.Right.Val] = node
      			fillParents(node.Right)
      		}
      	}
      	fillParents(root)
      	ret := make([]int, 0)
      	// 然后从target 开始dfs
      	var dfsFillRet func(node *TreeNode, from *TreeNode, depth int)
      	dfsFillRet = func(node *TreeNode, from *TreeNode, depth int) {
      		if nil == node {
      			return
      		}
      		if depth == k {
      			ret = append(ret, node.Val)
      			return
      		}
      		// 为什么下面的限制条件全是: 节点不能等于from呢,而不是直接判断为空即可,
      		// 原因: 为了防止走回头路
      
      		if node.Left != from {
      			dfsFillRet(node.Left, node, depth+1)
      		}
      		if node.Right != from {
      			dfsFillRet(node.Right, node, depth+1)
      		}
      		// 向上搜
      		if pNode := parentsMap[node.Val]; pNode != from {
      			dfsFillRet(pNode, node, depth+1)
      		}
      	}
      	dfsFillRet(target, nil, 0)
      
      	return ret
      }
      
  • lt_230_二叉搜索树中第k小的数

    • 
      // 关键:
      // 1. 题目关键: 限定了为二叉搜索树: 左<根<右
      // 2. 中序遍历: 中序遍历二叉搜索树: 使得是从小到大排序
      func kthSmallest(root *TreeNode, k int) int {
      	ret := make([]int, 0)
      	kthSmallestInOrderer(root, &ret)
      	return ret[k-1]
      }
      func kthSmallestInOrderer(root *TreeNode, ret *[]int) {
      	if nil != root {
      		kthSmallestInOrderer(root.Left, ret)
      		*ret = append(*ret, root.Val)
      		kthSmallestInOrderer(root.Right, ret)
      	}
      }
      
  • lt_234_判断是否是回文链表

    • // 关键:
      // 1. 找中点
      // 2. 中点之后进行反转
      // 3. 匹配判断
      func isPalindrome2(head *ListNode) bool {
      	mid := isPalindrome2FindMid(head)
      	midNext := mid.Next
      	mid.Next = nil
      
      	midNext = isPalindrome2Reverse(midNext)
      
      	for nil != head && nil != midNext {
      		if head.Val != midNext.Val {
      			return false
      		}
      		midNext = midNext.Next
      		head = head.Next
      	}
      	return true
      }
      
      func isPalindrome2FindMid(node *ListNode) *ListNode {
      	if nil == node {
      		return nil
      	}
      	fast := node.Next
      	slow := node
      	for nil != fast && fast.Next != nil {
      		fast = fast.Next.Next
      		slow = slow.Next
      	}
      	return slow
      }
      func isPalindrome2Reverse(node *ListNode) *ListNode {
      	var prev *ListNode
      	cur := node
      	for nil != cur {
      		next := cur.Next
      		cur.Next = prev
      
      		prev = cur
      		cur = next
      	}
      
      	return prev
      }
      
  • lt_236_二叉树的最近公共祖先

    • 
      // 关键: 死记硬背吧,没整理清楚
      // 第二种方法是: 记录父节点的方法, 然后从p,q 往上遍历即可
      func lowestCommonAncestor2(root, p, q *TreeNode) *TreeNode {
      	// 临界条件判断
      	if nil == root {
      		return nil
      	}
      	if root == p || root == q {
      		return root
      	}
      
      	left := lowestCommonAncestor2(root.Left, p, q)
      	right := lowestCommonAncestor2(root.Right, p, q)
      
      	if nil != left && nil != right {
      		return root
      	}
      	if nil != right {
      		return right
      	}
      	return left
      }
      
  • lt_237_删除链表中的节点

    • // 关键: 直接赋值即可
      func deleteNode(node *ListNode) {
      	node.Val = node.Next.Val
      	node.Next = node.Next.Next
      }
      
  • lt_238_除自身数组以外的乘积

    • 
      // 关键: 当前数 = 左边的乘积 * 右边的乘积
      // 如: 2,4,6,8  对于下标 2的值:  左边的乘积=2*4 右边的乘积=8  => 2*4*8
      // 但是还有很关键的一点是,左边的起始为1 ,右边末尾结尾也是为1
      func productExceptSelf(nums []int) []int {
      	l, r, ret := make([]int, len(nums)), make([]int, len(nums)), make([]int, len(nums))
      	// 左边的乘积,起始为0
      	l[0] = 1
      	for i := 1; i < len(nums); i++ {
      		l[i] = nums[i-1] * l[i-1]
      	}
      	// 右边的乘积,末尾为0
      	r[len(nums)-1] = 1
      	for i := len(nums) - 2; i >= 0; i-- {
      		r[i] = nums[i+1] * r[i+1]
      	}
      
      	// 最后计算最终值
      	for i := 0; i < len(nums); i++ {
      		// 当前结果的值为 左边的值* 右边的值,但是不要计算当前的值
      		ret[i] = l[i] * r[i]
      	}
      
      	return ret
      }
      
  • lt_240_搜索二维矩阵

    • 
      // 关键,从左下或者右上开始查询
      func searchMatrix(matrix [][]int, target int) bool {
      	for i, j := 0, len(matrix[0])-1; i < len(matrix) && j >= 0; {
      		if matrix[i][j] == target {
      			return true
      		} else if matrix[i][j] < target {
      			i++
      		} else {
      			j--
      		}
      	}
      	return false
      }
      
  • lt_242_判断是否是有效的字母异位词

    • 
      // 关键: 出现的次数都要相等,代表着 排序之后,结果要一致
      // 或者是通过hash表的方式,因为字母只有26个
      func isAnagram(s string, t string) bool {
      	var c1 [26]byte
      	var c2 [26]byte
      	for _, v := range s {
      		c1[v-'a']++
      	}
      	for _, v := range t {
      		c2[v-'a']++
      	}
      	return c1 == c2
      }
      
  • lt_268_丢失的数字

    • 
      // 关键: 数学公式: n*(n+1)/2
      // 或者是排序,或者是hash表
      func missingNumber(nums []int) int {
      	n := len(nums)
      	total := n * (n + 1) / 2
      	for _, v := range nums {
      		total -= v
      	}
      	return total
      }
      
  • lt_279_完全平方数

    • // 关键:
      // 动态规划: 状态转移方程: f(n)=1+f(i-j*j)
      func numSquares(n int) int {
      	dp := make([]int, n+1)
      	for i := 1; i <= n; i++ {
      		min := math.MaxInt32
      		// 注意: 这里是 <= i ,因为我们需要统计的是 [0,n]的值
      		for j := 1; j*j <= i; j++ {
      			min = numSquaresMin(dp[i-j*j], min)
      		}
      		dp[i] = min + 1
      	}
      	return dp[n]
      }
      
      func numSquaresMin(a, b int) int {
      	if a < b {
      		return a
      	}
      	return b
      }
      
  • lt_66_加一

    • 
      // 关键:
      // 判断是否需要额外加一即可
      func plusOne(digits []int) []int {
      	if len(digits) == 0 {
      		return nil
      	}
      	for i := len(digits) - 1; i >= 0; i-- {
      		digits[i]++
      		digits[i] %= 10
      		if digits[i]!=0{
      			return digits
      		}
      	}
      	// 当运行到这里的时候,表明之前的数字都是 99999
      	ret:=make([]int, len(digits)+1)
      	ret[0]=1
      	return ret
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值