- 刷题链接
- 工作至今,解决过的最难的问题,莫过于实现一个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
-
数组:
- 有序: 二分
- 双指针
-
链表:
- 双指针
- 快慢指针(求中点)
-
在二叉树中:
- 通过栈遍历:
- 分两步骤:
-
- 数据入栈(所以无论是先序中序后序都会有个for循环进行处理)
- 根节点操作(找到根节点: 因此先序是在入栈的第一步进行操作,而后序遍历则是在 需要判断下右节点是否已经处理了)
-
- 分两步骤:
- 其实非递归遍历的处理,根本就是对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 }
-
-
分治法的关键:
-
- 退出条件
- 分治
- 合并结果
-
-
二进制核心:
-
异或:
- 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: &quot;abc&quot;, 3: &quot;def&quot;, 4: &quot;ghi&quot;, 5: &quot;jkl&quot;, 6: &quot;mno&quot;, 7: &quot;pqrs&quot;, 8: &quot;tuv&quot;, 9: &quot;wxyz&quot;, } var ret []string // 关键: 回溯法 func letterCombinations(digits string) []string { if len(digits) == 0 { return nil } ret = nil // 2 回溯法 letterCombinationsBackTrack(digits, 0, &quot;&quot;) return ret } func letterCombinationsBackTrack(digits string, index int, res string) { if index == len(digits) { ret = append(ret, res) return } str := phoneMap[digits[index]-&#39;0&#39;] // 第三步: 各自遍历自己的数字所对应的值:回溯法, 如 2,3 ,当当前下标为0的时候,2对应的 value为: abc,3 对应的为:def // 则开始以 abc ,abd,abe 开始遍历 for i := 0; i &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 都是往前移,并且赋值的时候,先赋值再替换
- 2个节点,一个是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→… 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
-
-
链表重新排序
-
分析发现:
- 是按顺序来的, 依次交叉入队
-
关键:
- 前置退出条件,
-
- 找到中点
- 断开2个链表
- 后半段反转
- 链表合并(注意是交叉合并,所以需要一个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半都相同
-
- 找中点
- 后半段反转
- 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中推入
- push: push的时候,将数据都打到 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_判断是否是二叉搜索树
- 解题关键
- 左子树的最大值小于根节点
- 右子树的最小值大于根节点
- 所以需要定义一个变量存储这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 }
-