工作做多了有时候需要回归本心,认真刷题记忆一下算法。那就用我这练习时长两年半的代码农民工来尝试着快速解析LeetCode 100吧
快速解析
哈希
1. 两数之和 - 力扣(LeetCode)
这题很简单啊,思路也很多
1. 暴力搜索,复杂度 N^2
2. 排序后双指针 复杂度 NlogN
3. 用Hash表,已经遍历过的放入Hash表内记录,看看跟未遍历的求和是不是等于目标,复杂度N
func twoSum(nums []int, target int) []int {hashTable := map[int]int{}for i, x := range nums {if p, ok := hashTable[target-x]; ok {return []int{p, i}}hashTable[x] = i}return nil
}
49. 字母异位词分组 - 力扣(LeetCode)
异位词,比如 "abc"和"cba",或者 "acb",反正出现过同样次数的都算是异位词,那么就得想办法,设置一个hash映射,让他们得到的值相等。
1. 先对这个按顺序排序,再拼接起来作为key
2. 整一个26长度数组,统计每个英文字母出现的次数。整个数组作为key用来记录。
128. 最长连续序列 - 力扣(LeetCode)
用哈希直接记录一下整个数组,这样可以去重,还可以以O1的代价来访问值是否存在
然后比如对于值val,如果val-1也在哈希表里,就不计算了(剪枝,降低复杂度)。如果val - 1 不再哈希表里,那么证明val是起点,不断记录val + 1,看看最长能够到多少
func longestConsecutive(nums []int) int {if len(nums) == 0{return 0}cnt := map[int]bool{}for _, v := range nums{cnt[v] = true }ans := 1for k := range cnt{if cnt[k - 1]{continue}next := k + 1for cnt[next]{next ++ans = max(ans, next - k)}}return ans
}
双指针
283. 移动零 - 力扣(LeetCode)
这题我觉得不应该是easy,高低得是个Middle,我们可以使用双指针,一个指针用来按顺序,另一个用来遍历非零的元素,如果遍历到非零元素,就和第一个指针互换位置。相当于就是把非零的元素全部按顺序填上,那么剩下的就都是0了,因为0会一直被置换到后面,一直往后推。
func moveZeroes(nums []int) {for i, j := 0, 0; i < len(nums); i++{if nums[i] != 0{nums[i], nums[j] = nums[j], nums[i]j++}}
}
11. 盛最多水的容器 - 力扣(LeetCode)
传统的双指针,面积是宽度×高度,宽度可以直接用减法,高度就是两边柱子里面最低的。
那么怎么双指针呢,一左一右。那怎么移动指针呢?那就得尝试往更高地方向走,我们先从最矮的一遍移动,每次求max面积,这样就可以求出最多水的容器了。
func maxArea(height []int) int {ans := 0for i, j := 0, len(height) - 1; i < j; {area := (j - i) * min(height[i], height[j])ans = max(area, ans)if height[i] < height[j]{i ++}else{j --}} return ans
}
15. 三数之和 - 力扣(LeetCode)
正常情况之下,我们是不是想要用3个for,这样就是N^3。一般这时候就会想着省时间了,排序,或者二分。因为这里是找3个数,所以先要排序,排序完后就是一个有顺序的数组。
我们用一层循环来遍历第一个数,剩下的复杂度实际上就是两数之和的复杂度了。
这个两数之和,我们可以用双指针,不断地往中间夹缩小范围。这样时间复杂度就小于On了。
func threeSum(nums []int) (ans [][]int) {sort.Ints(nums)n := len(nums)for i := 0; i < n - 2; i ++{if i > 0 && nums[i] == nums[i-1]{continue}j, k := i + 1, n - 1 target := -nums[i]for j < k{ sum := nums[j] + nums[k]if sum == target{ans = append(ans, []int{nums[i], nums[j], nums[k]})for j < k && nums[j] == nums[j+1]{j++}for j < k && nums[k] == nums[k-1]{k--}j++k--}else if sum < target{j++}else{k--}}}return
}
42. 接雨水 - 力扣(LeetCode)
这题是比较简单的2D接雨水,跟11题非常像,但是又不一样,因为第11题的柱子不占空间,这一题的方块占空间。
我们可以很明显地看到,对于第i个位置可以储藏的雨水,肯定是 min(left, right) - val[i]的,所以我们需要计算左边和右边的高度的最大值,然后再往中间逼近,每一列储存的雨水需要每一步一步慢慢计算累加。
func trap(height []int) int {i, j := 0, len(height) - 1leftMax, rightMax := 0, 0ans := 0for i < j{leftMax = max(leftMax, height[i])rightMax = max(rightMax, height[j])if leftMax < rightMax{ ans += leftMax - height[i] i ++}else{ans += rightMax - height[j]j--}}return ans
}
滑动窗口
3. 无重复字符的最长子串 - 力扣(LeetCode)
维护一个哈希表,如果元素没有在哈希表里,就加进去。如果元素已经在哈希表里了,那么证明此时已经是最大的了,记录一下,左边窗口收缩到符合条件,右边窗口再扩张,不断地调整滑动窗口的大小。
func lengthOfLongestSubstring(s string) int {hash := map[byte]int{}ans := 0startIndex := 0for i := 0; i < len(s); i ++{for hash[s[i]] > 0{x := s[startIndex]startIndex++delete(hash, x)} hash[s[i]]++ans = max(ans, len(hash))}return ans
}
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
其实有点类似第49题的。甚至更简单,这边快速写了个go版本,可以参考下
func findAnagrams(s string, p string) []int {hash := map[[26]int]bool{}arr := [26]int{}for i := range p{arr[p[i] - 'a'] ++}hash[arr] = truenewArr := [26]int{}pLen := len(p)ans := []int{}for i := 0; i < len(s); i++{newArr[s[i] - 'a'] ++if i >= pLen{newArr[s[i - pLen] - 'a']--}if hash[newArr]{ans = append(ans, i - pLen + 1)}}return ans
}
子串
560. 和为 K 的子数组 - 力扣(LeetCode)
这边的子数组是要连续的,最基础的就是暴力啊,确定一个起点,一个终点。
其实可以认为是双指针,也可以认为是滑动数组。具体的做法也是这样的。如果这个题目的数组值都是正数就很好优化。不过因为可能存在负数,所以我们只能想办法通过空间换时间。就用前缀和吧。具体怎么做呢,可以参考two-sum,把前缀和放到hash里面,然后如果差值存在的话,就有一种情况了,示例代码如下:
func subarraySum(nums []int, k int) int {cnt, pre := 0, 0m := map[int]int{}m[0] = 1for _, v := range nums{pre += vif m[pre - k] > 0{cnt += m[pre - k]}m[pre] ++}return cnt
}
239. 滑动窗口最大值 - 力扣(LeetCode)
先看一眼,优先队列。但是除了python以外,每个语言的优先队列好像都不太好写。最近在写go,写起来还要自己构造struct,实现interface,因此再看一眼,其实可以按照递减顺序来记录的,但是这里面最好记录的是nums数组的index,这样子才号删除掉。
func maxSlidingWindow(nums []int, k int) []int {arr := []int{}push := func(i int){for len(arr) > 0 && nums[i] >= nums[arr[len(arr) - 1]]{arr = arr[:len(arr) - 1]}arr = append(arr, i)}for i := 0; i < k; i++{push(i)}n := len(nums)ans := make([]int, 1, n - k + 1)ans[0] = nums[arr[0]]for i := k; i < n; i++{push(i);for arr[0] <= i-k{arr = arr[1:]}ans = append(ans, nums[arr[0]])}return ans
}
76. 最小覆盖子串 - 力扣(LeetCode)
滑动窗口把,或者双指针,实际上都是一个东西。
func minWindow(s string, t string) (ans string) {cnt := make([]int, 128)// less 记录还有几个没有满足的字符less := 0for _, ch := range t {if cnt[ch] == 0 {less++}cnt[ch]++}l := 0for r, ch := range s {cnt[ch]--if cnt[ch] == 0 {less--}for less == 0 {if len(ans) == 0 || r-l < len(ans) {ans = s[l : r+1]}if cnt[s[l]] == 0 {less++}cnt[s[l]]++l++}}return
}
普通数组
53. 最大子数组和 - 力扣(LeetCode)
这题直接贪心,从左到右遍历累加,,如果累加起来小于0,那就干脆前面的直接丢弃掉。重新累加。
func maxSubArray(nums []int) int {ans := nums[0]sum := 0for _, v := range nums{sum += vans = max(ans, sum)if sum < 0{sum = 0}}return ans
}