This commit is contained in:
2026-07-23 16:56:50 +08:00
parent 124ad9afdf
commit 92784ffb0f
36 changed files with 2015 additions and 608 deletions
+6
View File
@@ -1,2 +1,8 @@
这是 leetcode 题练习 repo。
第一层目录为题集名称,第二层目录为题号。
题号是题集内的顺序编号(如 top-100-liked 为 1-100),不是力扣题号。
判断某目录对应哪道题看文件里的 leetcode.cn URL。
文件只写函数框架 + 思路注释 + 测试用例,用户自己填逻辑。
讲实现逻辑时避免拆成过多 helper,优先一个连贯的主流程。
+54
View File
@@ -0,0 +1,54 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/find-the-duplicate-number/?envType=study-plan-v2&envId=top-100-liked
// 287. 寻找重复数
//
// 给定一个包含 `n + 1` 个整数的数组 `nums` ,其数字都在 `[1, n]` 范围内(包括 `1` 和 `n`),可知至少存在一个重复的整数。
// 假设 `nums` 只有 一个重复的整数 ,返回 这个重复的数 。
// 你设计的解决方案必须 不修改 数组 `nums` 且只用常量级 `O(1)` 的额外空间。
// 示例 1
// 输入:nums = [1,3,4,2,2]
// 输出:2
// 示例 2
// 输入:nums = [3,1,3,4,2]
// 输出:3
// 示例 3 :
// 输入:nums = [3,3,3,3,3]
// 输出:3
//
// 提示:
// - `1 <= n <= 10^5`
// - `nums.length == n + 1`
// - `1 <= nums[i] <= n`
// - `nums` 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
// 进阶:
// - 如何证明 `nums` 中至少存在一个重复的数字?
// - 你可以设计一个线性级时间复杂度 `O(n)` 的解决方案吗?
//
func findDuplicate(nums []int) int {
}
func TestFindDuplicate(t *testing.T) {
cases := []struct {
name string
nums []int
want int
}{
{"example1", []int{1, 3, 4, 2, 2}, 2},
{"example2", []int{3, 1, 3, 4, 2}, 3},
{"example3", []int{3, 3, 3, 3, 3}, 3},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := findDuplicate(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+72 -31
View File
@@ -1,43 +1,84 @@
package top100liked
import (
"fmt"
"testing"
)
import "testing"
// https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/search-in-rotated-sorted-array/?envType=study-plan-v2&envId=top-100-liked
// 33. 搜索旋转排序数组
//
// 整数数组 `nums` 按升序排列,数组中的值 互不相同 。
// 在传递给函数之前,`nums` 在预先未知的某个下标 `k``0 <= k < nums.length`)上进行了 向左旋转,使数组变为 `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]`(下标 从 0 开始 计数)。例如, `[0,1,2,4,5,6,7]` 下标 `3` 上向左旋转后可能变为 `[4,5,6,7,0,1,2]` 。
// 给你 旋转后 的数组 `nums` 和一个整数 `target` ,如果 `nums` 中存在这个目标值 `target` ,则返回它的下标,否则返回 `-1` 。
// 你必须设计一个时间复杂度为 `O(log n)` 的算法解决此问题。
// 示例 1
// 输入:nums = [4,5,6,7,0,1,2], target = 0
// 输出:4
// 示例 2
// 输入:nums = [4,5,6,7,0,1,2], target = 3
// 输出:-1
// 示例 3
// 输入:nums = [1], target = 0
// 输出:-1
//
// 提示:
// - `1 <= nums.length <= 5000`
// - `-10^4 <= nums[i] <= 10^4`
// - `nums` 中的每个值都 独一无二
// - 题目数据保证 `nums` 在预先未知的某个下标上进行了旋转
// - `-10^4 <= target <= 10^4`
func findMin(nums []int) int {
if len(nums) == 1 {
return nums[0]
// search 在旋转升序数组中查找 target,返回下标或 -1。
//
// 思路:一次二分,不需要先找旋转点。
// 取 mid 后,[lo, mid] 和 [mid, hi] 必有一半是有序的:
// - 若 nums[lo] <= nums[mid],左半有序;判断 target 是否落在 [nums[lo], nums[mid]]
// 是则搜左半,否则搜右半。
// - 否则右半有序;判断 target 是否落在 [nums[mid], nums[hi]]
// 是则搜右半,否则搜左半。
//
// 时间 O(log n),空间 O(1)。
func search(nums []int, target int) int {
lo, hi := 0, len(nums)-1
for lo <= hi {
mid := lo + (hi-lo)/2
if nums[mid] == target {
return mid
}
if nums[0] < nums[len(nums)-1] {
return nums[0]
}
start := 0
end := len(nums) - 1
for start <= end {
mid := start + (end-start)/2
if mid >= 1 && nums[mid-1] > nums[mid] && mid < len(nums)-1 && nums[mid] < nums[mid+1] {
return nums[mid]
}
if nums[mid] > nums[end] {
// 最小值在右侧
start = mid + 1
// 左半 [lo, mid] 有序
if nums[lo] <= nums[mid] {
if nums[lo] <= target && target < nums[mid] {
hi = mid - 1
} else {
// 最小值在左侧
end = mid - 1
lo = mid + 1
}
} else { // 右半 [mid, hi] 有序
if nums[mid] < target && target <= nums[hi] {
lo = mid + 1
} else {
hi = mid - 1
}
}
if nums[start] > nums[end] {
return nums[end]
}
return nums[start]
return -1
}
func Test(t *testing.T) {
// fmt.Println(findMin([]int{3, 4, 5, 1, 2}))
// fmt.Println(findMin([]int{3, 1, 2}))
fmt.Println(findMin([]int{2, 1}))
func TestSearch(t *testing.T) {
cases := []struct {
name string
nums []int
target int
want int
}{
{"example1", []int{4, 5, 6, 7, 0, 1, 2}, 0, 4},
{"example2", []int{4, 5, 6, 7, 0, 1, 2}, 3, -1},
{"example3", []int{1}, 0, -1},
{"example4", []int{1}, 1, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := search(c.nums, c.target)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+27 -24
View File
@@ -5,36 +5,39 @@ import (
"testing"
)
// https://leetcode.cn/problems/median-of-two-sorted-arrays/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/?envType=study-plan-v2&envId=top-100-liked
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
total := len(nums1) + len(nums2)
mid := total / 2
needAvg := total%2 == 0
c1, c2 := 0, 0
prev, cur := 0, 0
for i := 0; i <= mid; i++ {
prev = cur
if c1 < len(nums1) && (c2 >= len(nums2) || nums1[c1] <= nums2[c2]) {
cur = nums1[c1]
c1++
func findMin(nums []int) int {
if len(nums) == 1 {
return nums[0]
}
if nums[0] < nums[len(nums)-1] {
return nums[0]
}
start := 0
end := len(nums) - 1
for start <= end {
mid := start + (end-start)/2
if mid >= 1 && nums[mid-1] > nums[mid] && mid < len(nums)-1 && nums[mid] < nums[mid+1] {
return nums[mid]
}
if nums[mid] > nums[end] {
// 最小值在右侧
start = mid + 1
} else {
cur = nums2[c2]
c2++
// 最小值在左侧
end = mid - 1
}
}
if needAvg {
return float64(prev+cur) / 2
if nums[start] > nums[end] {
return nums[end]
}
return float64(cur)
return nums[start]
}
func Test(t *testing.T) {
// fmt.Println(findMedianSortedArrays([]int{1, 3}, []int{2}))
// fmt.Println(findMedianSortedArrays([]int{1, 2}, []int{3, 4}))
// fmt.Println(findMedianSortedArrays([]int{}, []int{1}))
// fmt.Println(findMedianSortedArrays([]int{}, []int{2, 3}))
// fmt.Println(findMedianSortedArrays([]int{1, 2, 3, 4, 5}, []int{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}))
// fmt.Println(findMedianSortedArrays([]int{1, 2}, []int{-1, 3}))
fmt.Println(findMedianSortedArrays([]int{2, 2, 2, 2}, []int{2, 2, 2}))
// fmt.Println(findMin([]int{3, 4, 5, 1, 2}))
// fmt.Println(findMin([]int{3, 1, 2}))
fmt.Println(findMin([]int{2, 1}))
}
+24 -27
View File
@@ -5,39 +5,36 @@ import (
"testing"
)
// https://leetcode.cn/problems/valid-parentheses/description/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/median-of-two-sorted-arrays/?envType=study-plan-v2&envId=top-100-liked
func isValid(s string) bool {
stack := []rune{}
for _, c := range s {
if c == '(' || c == '[' || c == '{' {
stack = append(stack, c)
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
total := len(nums1) + len(nums2)
mid := total / 2
needAvg := total%2 == 0
c1, c2 := 0, 0
prev, cur := 0, 0
for i := 0; i <= mid; i++ {
prev = cur
if c1 < len(nums1) && (c2 >= len(nums2) || nums1[c1] <= nums2[c2]) {
cur = nums1[c1]
c1++
} else {
if len(stack) == 0 {
return false
}
pre := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if c == ')' && pre != '(' {
return false
}
if c == ']' && pre != '[' {
return false
}
if c == '}' && pre != '{' {
return false
cur = nums2[c2]
c2++
}
}
if needAvg {
return float64(prev+cur) / 2
}
return len(stack) == 0
return float64(cur)
}
func Test(t *testing.T) {
fmt.Println(isValid("()"))
fmt.Println(isValid("()[]{}"))
fmt.Println(isValid("(]"))
fmt.Println(isValid("([])"))
fmt.Println(isValid("([)]"))
fmt.Println(isValid("("))
fmt.Println(isValid("]"))
// fmt.Println(findMedianSortedArrays([]int{1, 3}, []int{2}))
// fmt.Println(findMedianSortedArrays([]int{1, 2}, []int{3, 4}))
// fmt.Println(findMedianSortedArrays([]int{}, []int{1}))
// fmt.Println(findMedianSortedArrays([]int{}, []int{2, 3}))
// fmt.Println(findMedianSortedArrays([]int{1, 2, 3, 4, 5}, []int{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}))
// fmt.Println(findMedianSortedArrays([]int{1, 2}, []int{-1, 3}))
fmt.Println(findMedianSortedArrays([]int{2, 2, 2, 2}, []int{2, 2, 2}))
}
+30 -48
View File
@@ -5,57 +5,39 @@ import (
"testing"
)
// https://leetcode.cn/problems/min-stack/description/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/valid-parentheses/description/?envType=study-plan-v2&envId=top-100-liked
type MinStack struct {
slice []int
minSlice []int // 记录每个层级的最小值
}
func Constructor() MinStack {
return MinStack{}
}
func (this *MinStack) Push(value int) {
if len(this.slice) == 0 {
this.minSlice = append(this.minSlice, value)
func isValid(s string) bool {
stack := []rune{}
for _, c := range s {
if c == '(' || c == '[' || c == '{' {
stack = append(stack, c)
} else {
this.minSlice = append(this.minSlice, min(this.minSlice[len(this.minSlice)-1], value))
if len(stack) == 0 {
return false
}
this.slice = append(this.slice, value)
pre := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if c == ')' && pre != '(' {
return false
}
if c == ']' && pre != '[' {
return false
}
if c == '}' && pre != '{' {
return false
}
}
}
return len(stack) == 0
}
func (this *MinStack) Pop() {
this.slice = this.slice[:len(this.slice)-1]
this.minSlice = this.minSlice[:len(this.minSlice)-1]
}
func (this *MinStack) Top() int {
return this.slice[len(this.slice)-1]
}
func (this *MinStack) GetMin() int {
return this.minSlice[len(this.minSlice)-1]
}
func Test1(t *testing.T) {
minStack := Constructor()
minStack.Push(-2)
minStack.Push(0)
minStack.Push(-3)
fmt.Println(minStack.GetMin())
minStack.Pop()
fmt.Println(minStack.Top())
fmt.Println(minStack.GetMin())
}
func Test2(t *testing.T) {
minStack := Constructor()
minStack.Push(1)
minStack.Push(2)
fmt.Println(minStack.Top())
fmt.Println(minStack.GetMin())
minStack.Pop()
fmt.Println(minStack.GetMin())
fmt.Println(minStack.Top())
func Test(t *testing.T) {
fmt.Println(isValid("()"))
fmt.Println(isValid("()[]{}"))
fmt.Println(isValid("(]"))
fmt.Println(isValid("([])"))
fmt.Println(isValid("([)]"))
fmt.Println(isValid("("))
fmt.Println(isValid("]"))
}
+50 -32
View File
@@ -2,42 +2,60 @@ package top100liked
import (
"fmt"
"strings"
"testing"
"unicode"
)
// https://leetcode.cn/problems/decode-string/description/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/min-stack/description/?envType=study-plan-v2&envId=top-100-liked
func decodeString(s string) string {
curStr := ""
curNum := 0
strStack := []string{}
numStack := []int{}
for _, r := range s {
if unicode.IsDigit(r) {
curNum = curNum*10 + int(r-'0')
} else if unicode.IsLetter(r) {
curStr += string(r)
} else if r == '[' {
strStack = append(strStack, curStr)
curStr = ""
numStack = append(numStack, curNum)
curNum = 0
} else if r == ']' {
num := numStack[len(numStack)-1]
prevStr := strStack[len(strStack)-1]
numStack = numStack[:len(numStack)-1]
strStack = strStack[:len(strStack)-1]
curStr = prevStr + strings.Repeat(curStr, num)
}
}
return curStr
type MinStack struct {
slice []int
minSlice []int // 记录每个层级的最小值
}
func Test(t *testing.T) {
fmt.Println(decodeString("3[a]2[bc]"))
fmt.Println(decodeString("3[a2[c]]"))
fmt.Println(decodeString("2[abc]3[cd]ef"))
fmt.Println(decodeString("abc3[cd]xyz"))
func Constructor() MinStack {
return MinStack{}
}
func (this *MinStack) Push(value int) {
if len(this.slice) == 0 {
this.minSlice = append(this.minSlice, value)
} else {
this.minSlice = append(this.minSlice, min(this.minSlice[len(this.minSlice)-1], value))
}
this.slice = append(this.slice, value)
}
func (this *MinStack) Pop() {
this.slice = this.slice[:len(this.slice)-1]
this.minSlice = this.minSlice[:len(this.minSlice)-1]
}
func (this *MinStack) Top() int {
return this.slice[len(this.slice)-1]
}
func (this *MinStack) GetMin() int {
return this.minSlice[len(this.minSlice)-1]
}
func Test1(t *testing.T) {
minStack := Constructor()
minStack.Push(-2)
minStack.Push(0)
minStack.Push(-3)
fmt.Println(minStack.GetMin())
minStack.Pop()
fmt.Println(minStack.Top())
fmt.Println(minStack.GetMin())
}
func Test2(t *testing.T) {
minStack := Constructor()
minStack.Push(1)
minStack.Push(2)
fmt.Println(minStack.Top())
fmt.Println(minStack.GetMin())
minStack.Pop()
fmt.Println(minStack.GetMin())
fmt.Println(minStack.Top())
}
+29 -48
View File
@@ -2,61 +2,42 @@ package top100liked
import (
"fmt"
"strings"
"testing"
"unicode"
)
// https://leetcode.cn/problems/daily-temperatures/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/decode-string/description/?envType=study-plan-v2&envId=top-100-liked
func dailyTemperatures(temperatures []int) []int {
res := make([]int, len(temperatures))
stack := make([]int, 0, len(temperatures))
for i := len(temperatures) - 1; i >= 0; i-- {
for len(stack) > 0 && temperatures[stack[len(stack)-1]] <= temperatures[i] {
stack = stack[:len(stack)-1]
func decodeString(s string) string {
curStr := ""
curNum := 0
strStack := []string{}
numStack := []int{}
for _, r := range s {
if unicode.IsDigit(r) {
curNum = curNum*10 + int(r-'0')
} else if unicode.IsLetter(r) {
curStr += string(r)
} else if r == '[' {
strStack = append(strStack, curStr)
curStr = ""
numStack = append(numStack, curNum)
curNum = 0
} else if r == ']' {
num := numStack[len(numStack)-1]
prevStr := strStack[len(strStack)-1]
numStack = numStack[:len(numStack)-1]
strStack = strStack[:len(strStack)-1]
curStr = prevStr + strings.Repeat(curStr, num)
}
if len(stack) > 0 {
res[i] = stack[len(stack)-1] - i
}
stack = append(stack, i)
return curStr
}
return res
}
// func dailyTemperatures(temperatures []int) []int {
// type item struct {
// Idx int
// Temp int
// }
// stack := []item{}
// res := make([]int, len(temperatures))
// for i := len(temperatures) - 1; i >= 0; i-- {
// cur := temperatures[i]
// if len(stack) == 0 {
// stack = append(stack, item{i, cur})
// res[i] = 0
// } else {
// for {
// if len(stack) == 0 {
// stack = append(stack, item{i, cur})
// res[i] = 0
// break
// }
// top := stack[len(stack)-1]
// if cur < top.Temp {
// res[i] = top.Idx - i
// stack = append(stack, item{i, cur})
// break
// } else {
// stack = stack[:len(stack)-1] // pop
// }
// }
// }
// }
// return res
// }
func Test(t *testing.T) {
fmt.Printf("%+v", dailyTemperatures([]int{73, 74, 75, 71, 69, 72, 76, 73}))
fmt.Println(decodeString("3[a]2[bc]"))
fmt.Println(decodeString("3[a2[c]]"))
fmt.Println(decodeString("2[abc]3[cd]ef"))
fmt.Println(decodeString("abc3[cd]xyz"))
}
+44 -42
View File
@@ -5,56 +5,58 @@ import (
"testing"
)
// https://leetcode.cn/problems/largest-rectangle-in-histogram/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/daily-temperatures/?envType=study-plan-v2&envId=top-100-liked
// 暴力法,超时
// func largestRectangleArea(heights []int) int {
// maxArea := 0
// // i 是每个组合中的元素数量
// for i := 1; i <= len(heights); i++ {
// // j 是在整个 heights 中滑动的次数
// for j := 0; j < len(heights)-i+1; j++ {
// // 计算
// maxArea = max(maxArea, slices.Min(heights[j:j+i])*i)
// }
// }
// return maxArea
// }
func dailyTemperatures(temperatures []int) []int {
res := make([]int, len(temperatures))
stack := make([]int, 0, len(temperatures))
func largestRectangleArea(heights []int) int {
stack := []int{}
maxArea := 0
for i := range len(heights) + 1 {
if len(stack) == 0 {
stack = append(stack, i)
continue
}
var cur int
if i == len(heights) {
cur = 0
} else {
cur = heights[i]
}
if cur >= heights[stack[len(stack)-1]] {
stack = append(stack, i)
} else {
for len(stack) > 0 && heights[stack[len(stack)-1]] > cur {
top := stack[len(stack)-1]
for i := len(temperatures) - 1; i >= 0; i-- {
for len(stack) > 0 && temperatures[stack[len(stack)-1]] <= temperatures[i] {
stack = stack[:len(stack)-1]
width := i
if len(stack) > 0 {
width = i - stack[len(stack)-1] - 1
}
maxArea = max(maxArea, heights[top]*width)
if len(stack) > 0 {
res[i] = stack[len(stack)-1] - i
}
stack = append(stack, i)
}
return res
}
return maxArea
}
// func dailyTemperatures(temperatures []int) []int {
// type item struct {
// Idx int
// Temp int
// }
// stack := []item{}
// res := make([]int, len(temperatures))
// for i := len(temperatures) - 1; i >= 0; i-- {
// cur := temperatures[i]
// if len(stack) == 0 {
// stack = append(stack, item{i, cur})
// res[i] = 0
// } else {
// for {
// if len(stack) == 0 {
// stack = append(stack, item{i, cur})
// res[i] = 0
// break
// }
// top := stack[len(stack)-1]
// if cur < top.Temp {
// res[i] = top.Idx - i
// stack = append(stack, item{i, cur})
// break
// } else {
// stack = stack[:len(stack)-1] // pop
// }
// }
// }
// }
// return res
// }
func Test(t *testing.T) {
fmt.Println(largestRectangleArea([]int{2, 1, 5, 6, 2, 3}))
fmt.Println(largestRectangleArea([]int{1}))
fmt.Println(largestRectangleArea([]int{2, 1, 2}))
fmt.Printf("%+v", dailyTemperatures([]int{73, 74, 75, 71, 69, 72, 76, 73}))
}
+46 -34
View File
@@ -1,48 +1,60 @@
package top100liked
import (
"math/rand"
"fmt"
"testing"
)
// https://leetcode.cn/problems/kth-largest-element-in-an-array/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/largest-rectangle-in-histogram/?envType=study-plan-v2&envId=top-100-liked
func findKthLargest(nums []int, k int) int {
n := len(nums)
target := n - k // 第 k 大 = 升序第 n-k 位
var quickSelect func(l, r int) int
quickSelect = func(l, r int) int {
if l == r {
return nums[l]
// 暴力法,超时
// func largestRectangleArea(heights []int) int {
// maxArea := 0
// // i 是每个组合中的元素数量
// for i := 1; i <= len(heights); i++ {
// // j 是在整个 heights 中滑动的次数
// for j := 0; j < len(heights)-i+1; j++ {
// // 计算
// maxArea = max(maxArea, slices.Min(heights[j:j+i])*i)
// }
// }
// return maxArea
// }
func largestRectangleArea(heights []int) int {
stack := []int{}
maxArea := 0
for i := range len(heights) + 1 {
if len(stack) == 0 {
stack = append(stack, i)
continue
}
pivot := nums[l+rand.Intn(r-l+1)]
// 三路分区:[l..lt-1] < pivot, [lt..gt] == pivot, [gt+1..r] > pivot
lt, gt, i := l, r, l
for i <= gt {
switch {
case nums[i] < pivot:
nums[lt], nums[i] = nums[i], nums[lt]
lt++
i++
case nums[i] > pivot:
nums[gt], nums[i] = nums[i], nums[gt]
gt--
default:
i++
var cur int
if i == len(heights) {
cur = 0
} else {
cur = heights[i]
}
if cur >= heights[stack[len(stack)-1]] {
stack = append(stack, i)
} else {
for len(stack) > 0 && heights[stack[len(stack)-1]] > cur {
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
width := i
if len(stack) > 0 {
width = i - stack[len(stack)-1] - 1
}
maxArea = max(maxArea, heights[top]*width)
}
stack = append(stack, i)
}
}
switch {
case target < lt:
return quickSelect(l, lt-1)
case target > gt:
return quickSelect(gt+1, r)
default:
return pivot
}
}
return quickSelect(0, n-1)
return maxArea
}
func Test(t *testing.T) {
findKthLargest([]int{3, 2, 1, 5, 6, 4}, 2)
fmt.Println(largestRectangleArea([]int{2, 1, 5, 6, 2, 3}))
fmt.Println(largestRectangleArea([]int{1}))
fmt.Println(largestRectangleArea([]int{2, 1, 2}))
}
+37 -221
View File
@@ -1,232 +1,48 @@
package top100liked
import (
"container/heap"
"reflect"
"sort"
"math/rand"
"testing"
)
// https://leetcode.cn/problems/top-k-frequent-elements/?envType=study-plan-v2&envId=top-100-liked
// 347. 前 K 个高频元素
//
// 给一个整数数组 nums 和整数 k,返回出现频率前 k 高的元素,顺序任意。
// 进阶要求时间复杂度优于 O(n log n)。
//
// 通用第一步:统计频率 map[int]int。下面三种解法都基于此。
// https://leetcode.cn/problems/kth-largest-element-in-an-array/?envType=study-plan-v2&envId=top-100-liked
// countFreq 统计每个元素出现次数。
func countFreq(nums []int) map[int]int {
m := make(map[int]int)
for _, v := range nums {
m[v]++
func findKthLargest(nums []int, k int) int {
n := len(nums)
target := n - k // 第 k 大 = 升序第 n-k 位
var quickSelect func(l, r int) int
quickSelect = func(l, r int) int {
if l == r {
return nums[l]
}
return m
pivot := nums[l+rand.Intn(r-l+1)]
// 三路分区:[l..lt-1] < pivot, [lt..gt] == pivot, [gt+1..r] > pivot
lt, gt, i := l, r, l
for i <= gt {
switch {
case nums[i] < pivot:
nums[lt], nums[i] = nums[i], nums[lt]
lt++
i++
case nums[i] > pivot:
nums[gt], nums[i] = nums[i], nums[gt]
gt--
default:
i++
}
}
switch {
case target < lt:
return quickSelect(l, lt-1)
case target > gt:
return quickSelect(gt+1, r)
default:
return pivot
}
}
return quickSelect(0, n-1)
}
// 解法一:最小堆 — O(n log k) / O(n)
// 维护大小为 k 的最小堆,堆顶是当前堆内频率最小者。
// 逐个把 (元素, 频率) 入堆;堆大小超过 k 时弹出堆顶,淘汰频率最小的。
// 遍历结束,堆里剩下的 k 个即前 k 高频元素。
//
// 实现要点(container/heap):
// - 定义 type entry struct{ val, cnt int}
// - 定义 type minHeap []entry,实现 heap.Interface
// - Less 按 cnt 升序(堆顶最小)
// - 遍历 mheap.Push(h, e)len(*h) > k 时 heap.Pop(h)
// - 收集堆中剩余元素返回
type entry struct {
val int
cnt int
}
type minHeap struct {
data []entry
}
// 上浮:新元素放末尾后,与父比较,比父小则交换向上
// i 是需要 siftUp 的元素的 index
func (h *minHeap) siftUp(i int) {
for i > 0 {
p := (i - 1) / 2
if h.data[p].cnt > h.data[i].cnt {
h.data[p], h.data[i] = h.data[i], h.data[p]
i = p
} else {
break
}
}
}
// 下沉:从 i 开始,与较小子比较,比子大则交换向下。n 是有效堆大小
func (h *minHeap) siftDown(i, n int) {
for {
l, r := 2*i+1, 2*i+2
smallest := i
if l < n && h.data[l].cnt < h.data[smallest].cnt {
smallest = l
}
if r < n && h.data[r].cnt < h.data[smallest].cnt {
smallest = r
}
if smallest == i {
break
}
h.data[smallest], h.data[i] = h.data[i], h.data[smallest]
i = smallest
}
}
// push:追加末尾 + 上浮
func (h *minHeap) push(e entry) {
idx := len(h.data)
h.data = append(h.data, e)
h.siftUp(idx)
}
// popMin 弹出堆顶(最小 cnt):用末尾覆盖堆顶,缩容,下沉
func (h *minHeap) popMin() entry {
if len(h.data) == 0 {
return entry{}
}
ret := h.data[0]
h.data[0] = h.data[len(h.data)-1]
h.data = h.data[:len(h.data)-1]
if len(h.data) > 0 {
h.siftDown(0, len(h.data))
}
return ret
}
func (h *minHeap) len() int { return len(h.data) }
func topKFrequentHeap(nums []int, k int) []int {
m := countFreq(nums)
h := minHeap{}
for v, c := range m {
h.push(entry{v, c})
if h.len() > k {
h.popMin()
}
}
res := make([]int, 0, k)
for h.len() > 0 {
res = append(res, h.popMin().val)
}
return res
}
// 解法二:桶排序 — O(n) 平均 / O(n)
// 频率取值范围 [0, n],开长度 n+1 的桶:buckets[f] 存所有频率为 f 的元素。
// 从 buckets[n] 倒序向 buckets[1] 遍历,依次把桶内元素加入结果,凑够 k 个停止。
//
// 实现要点:
// - buckets := make([][]int, len(nums)+1)
// - for val, cnt := range m { buckets[cnt] = append(buckets[cnt], val) }
// - for f := len(buckets)-1; f >= 0 && len(res) < k; f-- { res = append(res, buckets[f]...) }
func topKFrequentBucket(nums []int, k int) []int {
m := countFreq(nums)
buckets := make([][]int, len(nums)+1)
for v, c := range m {
buckets[c] = append(buckets[c], v)
}
res := make([]int, 0, k)
for f := len(buckets) - 1; f >= 0 && len(res) < k; f-- {
res = append(res, buckets[f]...)
}
return res
}
// 解法三:container/heap 维护大小 k 的最小堆 — O(n log k) / O(n)
// 思路同解法一,但用标准库 container/heap 提供堆算法,自己只实现 heap.Interface。
//
// container/heap 两层 API(最容易踩坑的地方):
// - 你实现的 Push/Pop 只是"回调"Push 追加到切片末尾,Pop 删除末尾元素
// - 真正对外的是 heap.Push / heap.Pop:它们调你的 Push/Pop 并负责 siftUp/siftDown
// - Less 用 < 是小顶堆(堆顶最小),用 > 是大顶堆
// - Push/Pop 必须用指针接收者(要改 slice header);Len/Less/Swap 用值接收者即可
//
// 要点:
// - entryHeap []entry 实现 heap.InterfaceLen/Less/Swap + Push/Pop
// - Less 按 cnt 升序,堆顶是堆内频率最小者
// - 遍历 mheap.Push(h, entry{v,c})h.Len() > k 时 heap.Pop(h) 淘汰最小
// - 收集堆中剩余 k 个元素返回
type entryHeap []entry
func (h entryHeap) Len() int { return len(h) }
func (h entryHeap) Less(i, j int) bool { return h[i].cnt < h[j].cnt } // < 小顶堆
func (h entryHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
// Push/Pop 是给 heap 包调用的回调,只负责在切片末尾增删,不要加堆序逻辑
func (h *entryHeap) Push(x any) { *h = append(*h, x.(entry)) }
func (h *entryHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func topKFrequentContainerHeap(nums []int, k int) []int {
m := countFreq(nums)
h := &entryHeap{}
heap.Init(h)
for v, c := range m {
heap.Push(h, entry{v, c})
if h.Len() > k {
heap.Pop(h)
}
}
res := make([]int, 0, k)
for h.Len() > 0 {
res = append(res, heap.Pop(h).(entry).val)
}
return res
}
// equalIgnoreOrder 忽略顺序比较两个 []int。
func equalIgnoreOrder(a, b []int) bool {
if len(a) != len(b) {
return false
}
ca := append([]int(nil), a...)
cb := append([]int(nil), b...)
sort.Ints(ca)
sort.Ints(cb)
return reflect.DeepEqual(ca, cb)
}
func TestTopKFrequent(t *testing.T) {
cases := []struct {
name string
nums []int
k int
want []int
}{
{"basic", []int{1, 1, 1, 2, 2, 3}, 2, []int{1, 2}},
{"single", []int{1}, 1, []int{1}},
{"tie", []int{1, 2}, 2, []int{1, 2}}, // 频率相同,都可返回
{"negative", []int{4, 1, -1, 2, -1, 2, 3}, 2, []int{-1, 2}},
{"duplicate candidates", []int{3, 0, 1, 0}, 1, []int{0}},
}
solvers := []struct {
name string
fn func([]int, int) []int
}{
{"heap", topKFrequentHeap},
{"bucket", topKFrequentBucket},
{"container-heap", topKFrequentContainerHeap},
}
for _, s := range solvers {
t.Run(s.name, func(t *testing.T) {
for _, c := range cases {
// 复制 nums 防止解法修改原切片
nums := append([]int(nil), c.nums...)
got := s.fn(nums, c.k)
if !equalIgnoreOrder(got, c.want) {
t.Errorf("%s/%s: got %v, want %v", s.name, c.name, got, c.want)
}
}
})
}
func Test(t *testing.T) {
findKthLargest([]int{3, 2, 1, 5, 6, 4}, 2)
}
+183 -85
View File
@@ -1,134 +1,232 @@
package top100liked
import (
"fmt"
"container/heap"
"reflect"
"sort"
"testing"
)
// https://leetcode.cn/problems/find-median-from-data-stream/?envType=study-plan-v2&envId=top-100-liked
// https://leetcode.cn/problems/top-k-frequent-elements/?envType=study-plan-v2&envId=top-100-liked
// 347. 前 K 个高频元素
//
// 给一个整数数组 nums 和整数 k,返回出现频率前 k 高的元素,顺序任意。
// 进阶要求时间复杂度优于 O(n log n)。
//
// 通用第一步:统计频率 map[int]int。下面三种解法都基于此。
type heap[T any] struct {
data []T
compareFunc func(T, T) bool
// countFreq 统计每个元素出现次数。
func countFreq(nums []int) map[int]int {
m := make(map[int]int)
for _, v := range nums {
m[v]++
}
return m
}
func NewHeap[T any](compareFunc func(T, T) bool) heap[T] {
return heap[T]{
compareFunc: compareFunc,
}
// 解法一:最小堆 — O(n log k) / O(n)
// 维护大小为 k 的最小堆,堆顶是当前堆内频率最小者。
// 逐个把 (元素, 频率) 入堆;堆大小超过 k 时弹出堆顶,淘汰频率最小的。
// 遍历结束,堆里剩下的 k 个即前 k 高频元素。
//
// 实现要点(container/heap):
// - 定义 type entry struct{ val, cnt int}
// - 定义 type minHeap []entry,实现 heap.Interface
// - Less 按 cnt 升序(堆顶最小)
// - 遍历 mheap.Push(h, e)len(*h) > k 时 heap.Pop(h)
// - 收集堆中剩余元素返回
type entry struct {
val int
cnt int
}
func (h *heap[T]) Up(idx int) {
for (idx-1)/2 >= 0 {
cur := h.data[idx]
p := h.data[(idx-1)/2]
if h.compareFunc(cur, p) {
h.data[idx], h.data[(idx-1)/2] = h.data[(idx-1)/2], h.data[idx]
idx = (idx - 1) / 2
type minHeap struct {
data []entry
}
// 上浮:新元素放末尾后,与父比较,比父小则交换向上
// i 是需要 siftUp 的元素的 index
func (h *minHeap) siftUp(i int) {
for i > 0 {
p := (i - 1) / 2
if h.data[p].cnt > h.data[i].cnt {
h.data[p], h.data[i] = h.data[i], h.data[p]
i = p
} else {
break
}
}
}
func (h *heap[T]) Down(idx int) {
// 下沉:从 i 开始,与较小子比较,比子大则交换向下。n 是有效堆大小
func (h *minHeap) siftDown(i, n int) {
for {
l, r := 2*idx+1, 2*idx+2
m := idx
if l < len(h.data) && h.compareFunc(h.data[l], h.data[m]) {
m = l
l, r := 2*i+1, 2*i+2
smallest := i
if l < n && h.data[l].cnt < h.data[smallest].cnt {
smallest = l
}
if r < len(h.data) && h.compareFunc(h.data[r], h.data[m]) {
m = r
if r < n && h.data[r].cnt < h.data[smallest].cnt {
smallest = r
}
if m == idx {
if smallest == i {
break
}
h.data[idx], h.data[m] = h.data[m], h.data[idx]
idx = m
h.data[smallest], h.data[i] = h.data[i], h.data[smallest]
i = smallest
}
}
func (h *heap[T]) Push(val T) {
h.data = append(h.data, val)
h.Up(len(h.data) - 1)
// push:追加末尾 + 上浮
func (h *minHeap) push(e entry) {
idx := len(h.data)
h.data = append(h.data, e)
h.siftUp(idx)
}
func (h *heap[T]) Pop() T {
if len(h.data) <= 0 {
var zero T
return zero
// popMin 弹出堆顶(最小 cnt):用末尾覆盖堆顶,缩容,下沉
func (h *minHeap) popMin() entry {
if len(h.data) == 0 {
return entry{}
}
old := h.data[0]
ret := h.data[0]
h.data[0] = h.data[len(h.data)-1]
h.data = h.data[:len(h.data)-1]
h.Down(0)
return old
}
func (h *heap[T]) Top() T {
if len(h.data) > 0 {
return h.data[0]
h.siftDown(0, len(h.data))
}
var zero T
return zero
return ret
}
func (h *heap[T]) Len() int {
return len(h.data)
func (h *minHeap) len() int { return len(h.data) }
func topKFrequentHeap(nums []int, k int) []int {
m := countFreq(nums)
h := minHeap{}
for v, c := range m {
h.push(entry{v, c})
if h.len() > k {
h.popMin()
}
}
res := make([]int, 0, k)
for h.len() > 0 {
res = append(res, h.popMin().val)
}
return res
}
type MedianFinder struct {
left heap[int] // 小
right heap[int] // 大
// 解法二:桶排序 — O(n) 平均 / O(n)
// 频率取值范围 [0, n],开长度 n+1 的桶:buckets[f] 存所有频率为 f 的元素。
// 从 buckets[n] 倒序向 buckets[1] 遍历,依次把桶内元素加入结果,凑够 k 个停止。
//
// 实现要点:
// - buckets := make([][]int, len(nums)+1)
// - for val, cnt := range m { buckets[cnt] = append(buckets[cnt], val) }
// - for f := len(buckets)-1; f >= 0 && len(res) < k; f-- { res = append(res, buckets[f]...) }
func topKFrequentBucket(nums []int, k int) []int {
m := countFreq(nums)
buckets := make([][]int, len(nums)+1)
for v, c := range m {
buckets[c] = append(buckets[c], v)
}
res := make([]int, 0, k)
for f := len(buckets) - 1; f >= 0 && len(res) < k; f-- {
res = append(res, buckets[f]...)
}
return res
}
func Constructor() MedianFinder {
return MedianFinder{
left: NewHeap(func(i1, i2 int) bool { return i1 > i2 }),
right: NewHeap(func(i1, i2 int) bool { return i1 < i2 }),
}
// 解法三:container/heap 维护大小 k 的最小堆 — O(n log k) / O(n)
// 思路同解法一,但用标准库 container/heap 提供堆算法,自己只实现 heap.Interface。
//
// container/heap 两层 API(最容易踩坑的地方):
// - 你实现的 Push/Pop 只是"回调"Push 追加到切片末尾,Pop 删除末尾元素
// - 真正对外的是 heap.Push / heap.Pop:它们调你的 Push/Pop 并负责 siftUp/siftDown
// - Less 用 < 是小顶堆(堆顶最小),用 > 是大顶堆
// - Push/Pop 必须用指针接收者(要改 slice header);Len/Less/Swap 用值接收者即可
//
// 要点:
// - entryHeap []entry 实现 heap.InterfaceLen/Less/Swap + Push/Pop
// - Less 按 cnt 升序,堆顶是堆内频率最小者
// - 遍历 mheap.Push(h, entry{v,c})h.Len() > k 时 heap.Pop(h) 淘汰最小
// - 收集堆中剩余 k 个元素返回
type entryHeap []entry
func (h entryHeap) Len() int { return len(h) }
func (h entryHeap) Less(i, j int) bool { return h[i].cnt < h[j].cnt } // < 小顶堆
func (h entryHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
// Push/Pop 是给 heap 包调用的回调,只负责在切片末尾增删,不要加堆序逻辑
func (h *entryHeap) Push(x any) { *h = append(*h, x.(entry)) }
func (h *entryHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func (this *MedianFinder) AddNum(num int) {
if this.left.Len() == 0 {
this.left.Push(num)
return
func topKFrequentContainerHeap(nums []int, k int) []int {
m := countFreq(nums)
h := &entryHeap{}
heap.Init(h)
for v, c := range m {
heap.Push(h, entry{v, c})
if h.Len() > k {
heap.Pop(h)
}
if num >= this.left.Top() {
this.right.Push(num)
} else {
this.left.Push(num)
}
if this.left.Len() > this.right.Len()+1 {
v := this.left.Pop()
this.right.Push(v)
}
if this.right.Len() > this.left.Len()+1 {
v := this.right.Pop()
this.left.Push(v)
res := make([]int, 0, k)
for h.Len() > 0 {
res = append(res, heap.Pop(h).(entry).val)
}
return res
}
func (this *MedianFinder) FindMedian() float64 {
switch (this.left.Len() + this.right.Len()) % 2 {
case 0:
return (float64(this.left.Top()) + float64(this.right.Top())) / 2
case 1:
if this.left.Len() > this.right.Len() {
return float64(this.left.Top())
} else {
return float64(this.right.Top())
// equalIgnoreOrder 忽略顺序比较两个 []int。
func equalIgnoreOrder(a, b []int) bool {
if len(a) != len(b) {
return false
}
}
return 0
ca := append([]int(nil), a...)
cb := append([]int(nil), b...)
sort.Ints(ca)
sort.Ints(cb)
return reflect.DeepEqual(ca, cb)
}
func Test(t *testing.T) {
medianFinder := Constructor()
medianFinder.AddNum(1) // arr = [1]
medianFinder.AddNum(2) // arr = [1, 2]
fmt.Println(medianFinder.FindMedian()) // 返回 1.5 ((1 + 2) / 2)
medianFinder.AddNum(3) // arr[1, 2, 3]
fmt.Println(medianFinder.FindMedian()) // return 2.0
func TestTopKFrequent(t *testing.T) {
cases := []struct {
name string
nums []int
k int
want []int
}{
{"basic", []int{1, 1, 1, 2, 2, 3}, 2, []int{1, 2}},
{"single", []int{1}, 1, []int{1}},
{"tie", []int{1, 2}, 2, []int{1, 2}}, // 频率相同,都可返回
{"negative", []int{4, 1, -1, 2, -1, 2, 3}, 2, []int{-1, 2}},
{"duplicate candidates", []int{3, 0, 1, 0}, 1, []int{0}},
}
solvers := []struct {
name string
fn func([]int, int) []int
}{
{"heap", topKFrequentHeap},
{"bucket", topKFrequentBucket},
{"container-heap", topKFrequentContainerHeap},
}
for _, s := range solvers {
t.Run(s.name, func(t *testing.T) {
for _, c := range cases {
// 复制 nums 防止解法修改原切片
nums := append([]int(nil), c.nums...)
got := s.fn(nums, c.k)
if !equalIgnoreOrder(got, c.want) {
t.Errorf("%s/%s: got %v, want %v", s.name, c.name, got, c.want)
}
}
})
}
}
+156
View File
@@ -0,0 +1,156 @@
package top100liked
import (
"fmt"
"testing"
)
// https://leetcode.cn/problems/find-median-from-data-stream/?envType=study-plan-v2&envId=top-100-liked
//
// 思路:双堆法
// 维护两个堆,把数据流分成"较小的一半"和"较大的一半"
// - left:大顶堆,存较小的一半,堆顶是较小半部分的最大值
// - right:小顶堆,存较大的一半,堆顶是较大半部分的最小值
// 插入时根据当前中位数(left 堆顶)决定放入哪个堆,再平衡两堆大小,
// 保证 |left.Len() - right.Len()| <= 1,这样中位数只需看两堆顶即可。
// 泛型堆,通过 compareFunc 决定是大顶堆还是小顶堆。
// compareFunc(a, b) 返回 true 表示 a 应排在 b 上面(优先级更高)。
type heap[T any] struct {
data []T
compareFunc func(T, T) bool
}
func NewHeap[T any](compareFunc func(T, T) bool) heap[T] {
return heap[T]{
compareFunc: compareFunc,
}
}
// 上浮:从 idx 向上比较,不满足堆序就与父节点交换,直到满足或到根。
func (h *heap[T]) Up(idx int) {
for (idx-1)/2 >= 0 {
cur := h.data[idx]
p := h.data[(idx-1)/2]
if h.compareFunc(cur, p) {
h.data[idx], h.data[(idx-1)/2] = h.data[(idx-1)/2], h.data[idx]
idx = (idx - 1) / 2
} else {
break
}
}
}
// 下沉:从 idx 向下比较,与优先级更高的子节点交换,直到满足堆序或成为叶子。
func (h *heap[T]) Down(idx int) {
for {
l, r := 2*idx+1, 2*idx+2
m := idx
if l < len(h.data) && h.compareFunc(h.data[l], h.data[m]) {
m = l
}
if r < len(h.data) && h.compareFunc(h.data[r], h.data[m]) {
m = r
}
if m == idx {
break
}
h.data[idx], h.data[m] = h.data[m], h.data[idx]
idx = m
}
}
// 插入:追加到末尾后上浮,保持堆序。
func (h *heap[T]) Push(val T) {
h.data = append(h.data, val)
h.Up(len(h.data) - 1)
}
// 弹出堆顶:把末尾元素换到堆顶,删除末尾,再从堆顶下沉。
func (h *heap[T]) Pop() T {
if len(h.data) <= 0 {
var zero T
return zero
}
old := h.data[0]
h.data[0] = h.data[len(h.data)-1]
h.data = h.data[:len(h.data)-1]
h.Down(0)
return old
}
func (h *heap[T]) Top() T {
if len(h.data) > 0 {
return h.data[0]
}
var zero T
return zero
}
func (h *heap[T]) Len() int {
return len(h.data)
}
// MedianFinder 用双堆维护数据流的中位数。
type MedianFinder struct {
left heap[int] // 大顶堆,存较小的一半,堆顶是该半部分最大值
right heap[int] // 小顶堆,存较大的一半,堆顶是该半部分最小值
}
// Constructorleft 用 > 构成大顶堆,right 用 < 构成小顶堆。
func Constructor() MedianFinder {
return MedianFinder{
left: NewHeap(func(i1, i2 int) bool { return i1 > i2 }),
right: NewHeap(func(i1, i2 int) bool { return i1 < i2 }),
}
}
// AddNum 插入一个数并保持两堆平衡:
// 1. 第一个数直接放 left
// 2. 后续数与 left 堆顶比较:>= 堆顶放 right(属于较大半部分),否则放 left;
// 3. 平衡:任一堆比另一堆多超过 1,就搬堆顶到另一侧。
func (this *MedianFinder) AddNum(num int) {
if this.left.Len() == 0 {
this.left.Push(num)
return
}
if num >= this.left.Top() {
this.right.Push(num)
} else {
this.left.Push(num)
}
if this.left.Len() > this.right.Len()+1 {
v := this.left.Pop()
this.right.Push(v)
}
if this.right.Len() > this.left.Len()+1 {
v := this.right.Pop()
this.left.Push(v)
}
}
// FindMedian 根据两堆大小关系求中位数:
// - 总数为偶数:取两堆顶的平均值
// - 总数为奇数:元素多的那堆堆顶就是中位数
func (this *MedianFinder) FindMedian() float64 {
switch (this.left.Len() + this.right.Len()) % 2 {
case 0:
return (float64(this.left.Top()) + float64(this.right.Top())) / 2
case 1:
if this.left.Len() > this.right.Len() {
return float64(this.left.Top())
} else {
return float64(this.right.Top())
}
}
return 0
}
func Test(t *testing.T) {
medianFinder := Constructor()
medianFinder.AddNum(1) // arr = [1]
medianFinder.AddNum(2) // arr = [1, 2]
fmt.Println(medianFinder.FindMedian()) // 返回 1.5 ((1 + 2) / 2)
medianFinder.AddNum(3) // arr[1, 2, 3]
fmt.Println(medianFinder.FindMedian()) // return 2.0
}
+79
View File
@@ -0,0 +1,79 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/?envType=study-plan-v2&envId=top-100-liked
// 121. 买卖股票的最佳时机
//
// 给定一个数组 prices,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
// 你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。
// 设计一个算法来计算你所能获取的最大利润。
// 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0。
//
// 示例 1
// 输入:[7,1,5,3,6,4]
// 输出:5
// 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,
// 最大利润 = 6-1 = 5。注意利润不能是 7-1 = 6,因为卖出价格需要大于买入价格;
// 同时,你不能在买入前卖出股票。
//
// 示例 2
// 输入:prices = [7,6,4,3,1]
// 输出:0
// 解释:在这种情况下,没有交易完成,所以最大利润为 0。
//
// 提示:
// - 1 <= prices.length <= 10^5
// - 0 <= prices[i] <= 10^4
// 暴力法,超时
// func maxProfit(prices []int) int {
// maxProfit := 0
// for i := range prices {
// for j := i + 1; j < len(prices); j++ {
// if prices[j] > prices[i] {
// maxProfit = max(maxProfit, prices[j]-prices[i])
// }
// }
// }
// return maxProfit
// }
func maxProfit(prices []int) int {
minimum := make([]int, len(prices)) // minimum[i] 是 i 天前最小购入价
res := 0
for i := range prices {
if i == 0 {
minimum[0] = prices[0]
} else {
minimum[i] = min(minimum[i-1], prices[i])
res = max(res, prices[i]-minimum[i])
}
}
return res
}
func TestMaxProfit(t *testing.T) {
cases := []struct {
name string
prices []int
want int
}{
{"example1", []int{7, 1, 5, 3, 6, 4}, 5},
{"example2", []int{7, 6, 4, 3, 1}, 0},
{"single", []int{5}, 0},
{"two days profit", []int{1, 5}, 4},
{"two days no profit", []int{5, 1}, 0},
{"profit at end", []int{2, 4, 1}, 2},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := maxProfit(c.prices)
if got != c.want {
t.Errorf("got %d, want %d", got, c.want)
}
})
}
}
+90
View File
@@ -0,0 +1,90 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/jump-game/?envType=study-plan-v2&envId=top-100-liked
// 55. 跳跃游戏
//
// 给你一个非负整数数组 `nums` ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
// 判断你是否能够到达最后一个下标,如果可以,返回 `true` ;否则,返回 `false` 。
// 示例 1
// 输入:nums = [2,3,1,1,4]
// 输出:true
// 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
// 示例 2
// 输入:nums = [3,2,1,0,4]
// 输出:false
// 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
//
// 提示:
// - `1 <= nums.length <= 10^4`
// - `0 <= nums[i] <= 10^5`
// 超时
// func canJump(nums []int) bool {
// l := len(nums)
// if l == 1 {
// return true
// }
// var helper func(end int) bool
// helper = func(end int) bool {
// for i := range end {
// if nums[i]+i >= end {
// if i == 0 {
// return true
// }
// if helper(i) {
// return true
// }
// }
// }
// return false
// }
// return helper(l - 1)
// }
// canJump 判断能否到达最后一个下标。
// 思路:贪心,维护从左到右遍历时能到达的最远下标 maxReach。
// - 遍历到位置 i 时,若 maxReach < i,说明连 i 都到不了,后面更到不了 → false
// - 否则更新 maxReach = max(maxReach, i + nums[i])
// - 一旦 maxReach >= 最后下标 → true
// 时间 O(n),空间 O(1)。
// 超时的旧写法是自顶向下递归判断「能否到 end」,无记忆化,重复子问题导致指数级。
func canJump(nums []int) bool {
maxReach := 0
for i := range nums {
if maxReach < i {
return false
}
maxReach = max(i+nums[i], maxReach)
if maxReach >= len(nums)-1 {
return true
}
}
return false
}
func TestCanJump(t *testing.T) {
cases := []struct {
name string
nums []int
want bool
}{
{"example1", []int{2, 3, 1, 1, 4}, true},
{"example2", []int{3, 2, 1, 0, 4}, false},
{"example3", []int{0}, true},
{"example4", []int{0, 1}, false},
{"example5", []int{1, 0}, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := canJump(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+62
View File
@@ -0,0 +1,62 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/jump-game-ii/?envType=study-plan-v2&envId=top-100-liked
// 45. 跳跃游戏 II
//
// 给定一个长度为 `n` 的 0 索引整数数组 `nums`。初始位置在下标 0。
// 每个元素 `nums[i]` 表示从索引 `i` 向后跳转的最大长度。换句话说,如果你在索引 `i` 处,你可以跳转到任意 `(i + j)` 处:
// - `0 <= j <= nums[i]` 且
// - `i + j < n`
// 返回到达 `n - 1` 的最小跳跃次数。测试用例保证可以到达 `n - 1`。
// 示例 1:
// 输入: nums = [2,3,1,1,4]
// 输出: 2
// 解释: 跳到最后一个位置的最小跳跃数是 `2`。
// 从下标为 0 跳到下标为 1 的位置,跳 `1` 步,然后跳 `3` 步到达数组的最后一个位置。
// 示例 2:
// 输入: nums = [2,3,0,1,4]
// 输出: 2
//
// 提示:
// - `1 <= nums.length <= 10^4`
// - `0 <= nums[i] <= 1000`
// - 题目保证可以到达 `n - 1`
func jump(nums []int) int {
if len(nums) <= 1 {
return 0
}
mR, end, count := 0, 0, 0
for i := 0; i < len(nums)-1; i++ { // 只到 n-2,终点不需要跳
mR = max(i+nums[i], mR)
if i == end { // 当前一跳的范围扫完,结算
count++
end = mR
}
}
return count
}
func TestJump(t *testing.T) {
cases := []struct {
name string
nums []int
want int
}{
{"example1", []int{2, 3, 1, 1, 4}, 2},
{"example2", []int{2, 3, 0, 1, 4}, 2},
{"example3", []int{1}, 0},
{"example4", []int{7, 0, 9, 6, 9, 6, 1, 7, 9, 0, 1, 2, 9, 0, 3}, 2},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := jump(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+49
View File
@@ -0,0 +1,49 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/partition-labels/?envType=study-plan-v2&envId=top-100-liked
// 763. 划分字母区间
//
// 给你一个字符串 `s` 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 `"ababcc"` 能够被分为 `["abab", "cc"]`,但类似 `["aba", "bcc"]` 或 `["ab", "ab", "cc"]` 的划分是非法的。
// 注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 `s` 。
// 返回一个表示每个字符串片段的长度的列表。
// 示例 1
// 输入:s = "ababcbacadefegdehijhklij"
// 输出:[9,7,8]
// 解释:
// 划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
// 每个字母最多出现在一个片段中。
// 像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
// 示例 2
// 输入:s = "eccbbbbdec"
// 输出:[10]
//
// 提示:
// - `1 <= s.length <= 500`
// - `s` 仅由小写英文字母组成
//
func partitionLabels(s string) []int {
}
func TestPartitionLabels(t *testing.T) {
cases := []struct {
name string
s string
want []int
}{
{"example1", "ababcbacadefegdehijhklij", []int{9, 7, 8}},
{"example2", "eccbbbbdec", []int{10}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := partitionLabels(c.s)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+50
View File
@@ -0,0 +1,50 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/climbing-stairs/?envType=study-plan-v2&envId=top-100-liked
// 70. 爬楼梯
//
// 假设你正在爬楼梯。需要 `n` 阶你才能到达楼顶。
// 每次你可以爬 `1` 或 `2` 个台阶。你有多少种不同的方法可以爬到楼顶呢?
// 示例 1
// 输入:n = 2
// 输出:2
// 解释:有两种方法可以爬到楼顶。
// 1. 1 阶 + 1 阶
// 2. 2 阶
// 示例 2
// 输入:n = 3
// 输出:3
// 解释:有三种方法可以爬到楼顶。
// 1. 1 阶 + 1 阶 + 1 阶
// 2. 1 阶 + 2 阶
// 3. 2 阶 + 1 阶
//
// 提示:
// - `1 <= n <= 45`
//
func climbStairs(n int) int {
}
func TestClimbStairs(t *testing.T) {
cases := []struct {
name string
n int
want int
}{
{"example1", 2, 2},
{"example2", 3, 3},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := climbStairs(c.n)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+43
View File
@@ -0,0 +1,43 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/pascals-triangle/?envType=study-plan-v2&envId=top-100-liked
// 118. 杨辉三角
//
// 给定一个非负整数 `numRows`,生成「杨辉三角」的前 `numRows` 行。
// 在「杨辉三角」中,每个数是它左上方和右上方的数的和。
// 示例 1:
// 输入: numRows = 5
// 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
// 示例 2:
// 输入: numRows = 1
// 输出: [[1]]
//
// 提示:
// - `1 <= numRows <= 30`
//
func generate(numRows int) [][]int {
}
func TestGenerate(t *testing.T) {
cases := []struct {
name string
numRows int
want [][]int
}{
{"example1", 5, [][]int{[]int{1}, []int{1, 1}, []int{1, 2, 1}, []int{1, 3, 3, 1}, []int{1, 4, 6, 4, 1}}},
{"example2", 1, [][]int{[]int{1}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := generate(c.numRows)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+48
View File
@@ -0,0 +1,48 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/house-robber/?envType=study-plan-v2&envId=top-100-liked
// 198. 打家劫舍
//
// 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
// 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
// 示例 1
// 输入:[1,2,3,1]
// 输出:4
// 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
// 偷窃到的最高金额 = 1 + 3 = 4 。
// 示例 2
// 输入:[2,7,9,3,1]
// 输出:12
// 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
// 偷窃到的最高金额 = 2 + 9 + 1 = 12 。
//
// 提示:
// - `1 <= nums.length <= 100`
// - `0 <= nums[i] <= 400`
//
func rob(nums []int) int {
}
func TestRob(t *testing.T) {
cases := []struct {
name string
nums []int
want int
}{
{"example1", []int{1, 2, 3, 1}, 4},
{"example2", []int{2, 7, 9, 3, 1}, 12},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := rob(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+45
View File
@@ -0,0 +1,45 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/perfect-squares/?envType=study-plan-v2&envId=top-100-liked
// 279. 完全平方数
//
// 给你一个整数 `n` ,返回 和为 `n` 的完全平方数的最少数量 。
// 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,`1`、`4`、`9` 和 `16` 都是完全平方数,而 `3` 和 `11` 不是。
// 示例 1
// 输入:n = `12`
// 输出:3
// 解释:`12 = 4 + 4 + 4`
// 示例 2
// 输入:n = `13`
// 输出:2
// 解释:`13 = 4 + 9`
//
// 提示:
// - `1 <= n <= 10^4`
//
func numSquares(n int) int {
}
func TestNumSquares(t *testing.T) {
cases := []struct {
name string
n int
want int
}{
{"example1", 12, 3},
{"example2", 13, 2},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := numSquares(c.n)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+52
View File
@@ -0,0 +1,52 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/coin-change/?envType=study-plan-v2&envId=top-100-liked
// 322. 零钱兑换
//
// 给你一个整数数组 `coins` ,表示不同面额的硬币;以及一个整数 `amount` ,表示总金额。
// 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 `-1` 。
// 你可以认为每种硬币的数量是无限的。
// 示例 1
// 输入:coins = `[1, 2, 5]`, amount = `11`
// 输出:`3`
// 解释:11 = 5 + 5 + 1
// 示例 2
// 输入:coins = `[2]`, amount = `3`
// 输出:-1
// 示例 3
// 输入:coins = [1], amount = 0
// 输出:0
//
// 提示:
// - `1 <= coins.length <= 12`
// - `1 <= coins[i] <= 2^31 - 1`
// - `0 <= amount <= 10^4`
//
func coinChange(coins []int, amount int) int {
}
func TestCoinChange(t *testing.T) {
cases := []struct {
name string
coins []int
amount int
want int
}{
{"example1", []int{1, 2, 5}, 11, 3},
{"example2", []int{2}, 3, -1},
{"example3", []int{1}, 0, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := coinChange(c.coins, c.amount)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+55
View File
@@ -0,0 +1,55 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/word-break/?envType=study-plan-v2&envId=top-100-liked
// 139. 单词拆分
//
// 给你一个字符串 `s` 和一个字符串列表 `wordDict` 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 `s` 则返回 `true`。
// 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
// 示例 1
// 输入: s = "leetcode", wordDict = ["leet", "code"]
// 输出: true
// 解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
// 示例 2
// 输入: s = "applepenapple", wordDict = ["apple", "pen"]
// 输出: true
// 解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
// 注意,你可以重复使用字典中的单词。
// 示例 3
// 输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
// 输出: false
//
// 提示:
// - `1 <= s.length <= 300`
// - `1 <= wordDict.length <= 1000`
// - `1 <= wordDict[i].length <= 20`
// - `s` 和 `wordDict[i]` 仅由小写英文字母组成
// - `wordDict` 中的所有字符串 互不相同
//
func wordBreak(s string, wordDict []string) bool {
}
func TestWordBreak(t *testing.T) {
cases := []struct {
name string
s string
wordDict []string
want bool
}{
{"example1", "leetcode", []string{"leet", "code"}, true},
{"example2", "applepenapple", []string{"apple", "pen"}, true},
{"example3", "catsandog", []string{"cats", "dog", "sand", "and", "cat"}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := wordBreak(c.s, c.wordDict)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+51
View File
@@ -0,0 +1,51 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/longest-increasing-subsequence/?envType=study-plan-v2&envId=top-100-liked
// 300. 最长递增子序列
//
// 给你一个整数数组 `nums` ,找到其中最长严格递增子序列的长度。
// 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,`[3,6,2,7]` 是数组 `[0,3,1,6,2,2,7]` 的子序列。
// 示例 1
// 输入:nums = [10,9,2,5,3,7,101,18]
// 输出:4
// 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
// 示例 2
// 输入:nums = [0,1,0,3,2,3]
// 输出:4
// 示例 3
// 输入:nums = [7,7,7,7,7,7,7]
// 输出:1
//
// 提示:
// - `1 <= nums.length <= 2500`
// - `-10^4 <= nums[i] <= 10^4`
// 进阶:
// - 你能将算法的时间复杂度降低到 `O(n log(n))` 吗?
//
func lengthOfLIS(nums []int) int {
}
func TestLengthOfLIS(t *testing.T) {
cases := []struct {
name string
nums []int
want int
}{
{"example1", []int{10, 9, 2, 5, 3, 7, 101, 18}, 4},
{"example2", []int{0, 1, 0, 3, 2, 3}, 4},
{"example3", []int{7, 7, 7, 7, 7, 7, 7}, 1},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := lengthOfLIS(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+48
View File
@@ -0,0 +1,48 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/maximum-product-subarray/?envType=study-plan-v2&envId=top-100-liked
// 152. 乘积最大子数组
//
// 给你一个整数数组 `nums` ,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
// 测试用例的答案是一个 32-位 整数。
// 请注意,一个只包含一个元素的数组的乘积是这个元素的值。
// 示例 1:
// 输入: nums = [2,3,-2,4]
// 输出: `6`
// 解释: 子数组 [2,3] 有最大乘积 6。
// 示例 2:
// 输入: nums = [-2,0,-1]
// 输出: 0
// 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
//
// 提示:
// - `1 <= nums.length <= 2 * 10^4`
// - `-10 <= nums[i] <= 10`
// - `nums` 的任何子数组的乘积都 保证 是一个 32-位 整数
//
func maxProduct(nums []int) int {
}
func TestMaxProduct(t *testing.T) {
cases := []struct {
name string
nums []int
want int
}{
{"example1", []int{2, 3, -2, 4}, 6},
{"example2", []int{-2, 0, -1}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := maxProduct(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+45
View File
@@ -0,0 +1,45 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/partition-equal-subset-sum/?envType=study-plan-v2&envId=top-100-liked
// 416. 分割等和子集
//
// 给你一个 只包含正整数 的 非空 数组 `nums` 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
// 示例 1
// 输入:nums = [1,5,11,5]
// 输出:true
// 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
// 示例 2
// 输入:nums = [1,2,3,5]
// 输出:false
// 解释:数组不能分割成两个元素和相等的子集。
//
// 提示:
// - `1 <= nums.length <= 200`
// - `1 <= nums[i] <= 100`
//
func canPartition(nums []int) bool {
}
func TestCanPartition(t *testing.T) {
cases := []struct {
name string
nums []int
want bool
}{
{"example1", []int{1, 5, 11, 5}, true},
{"example2", []int{1, 2, 3, 5}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := canPartition(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+50
View File
@@ -0,0 +1,50 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/longest-valid-parentheses/?envType=study-plan-v2&envId=top-100-liked
// 32. 最长有效括号
//
// 给你一个只包含 `'('` 和 `')'` 的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。
// 左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 `"(()())"`。
// 示例 1
// 输入:s = "(()"
// 输出:2
// 解释:最长有效括号子串是 "()"
// 示例 2
// 输入:s = ")()())"
// 输出:4
// 解释:最长有效括号子串是 "()()"
// 示例 3
// 输入:s = ""
// 输出:0
//
// 提示:
// - `0 <= s.length <= 3 * 10^4`
// - `s[i]` 为 `'('` 或 `')'`
//
func longestValidParentheses(s string) int {
}
func TestLongestValidParentheses(t *testing.T) {
cases := []struct {
name string
s string
want int
}{
{"example1", "(()", 2},
{"example2", ")()())", 4},
{"example3", "", 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := longestValidParentheses(c.s)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+59
View File
@@ -0,0 +1,59 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/unique-paths/?envType=study-plan-v2&envId=top-100-liked
// 62. 不同路径
//
// 一个机器人位于一个 `m x n` 网格的左上角 (起始点在下图中标记为 “Start” )。
// 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
// 问总共有多少条不同的路径?
// 示例 1
// 输入:m = 3, n = 7
// 输出:28
// 示例 2
// 输入:m = 3, n = 2
// 输出:3
// 解释:
// 从左上角开始,总共有 3 条路径可以到达右下角。
// 1. 向右 -> 向下 -> 向下
// 2. 向下 -> 向下 -> 向右
// 3. 向下 -> 向右 -> 向下
// 示例 3
// 输入:m = 7, n = 3
// 输出:28
// 示例 4
// 输入:m = 3, n = 3
// 输出:6
//
// 提示:
// - `1 <= m, n <= 100`
// - 题目数据保证答案小于等于 `2 * 10^9`
//
func uniquePaths(m int, n int) int {
}
func TestUniquePaths(t *testing.T) {
cases := []struct {
name string
m int
n int
want int
}{
{"example1", 3, 7, 28},
{"example2", 3, 2, 3},
{"example3", 7, 3, 28},
{"example4", 3, 3, 6},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := uniquePaths(c.m, c.n)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+47
View File
@@ -0,0 +1,47 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/minimum-path-sum/?envType=study-plan-v2&envId=top-100-liked
// 64. 最小路径和
//
// 给定一个包含非负整数的 `m x n` 网格 `grid` ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
// 说明:每次只能向下或者向右移动一步。
// 示例 1
// 输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
// 输出:7
// 解释:因为路径 1→3→1→1→1 的总和最小。
// 示例 2
// 输入:grid = [[1,2,3],[4,5,6]]
// 输出:12
//
// 提示:
// - `m == grid.length`
// - `n == grid[i].length`
// - `1 <= m, n <= 200`
// - `0 <= grid[i][j] <= 200`
//
func minPathSum(grid [][]int) int {
}
func TestMinPathSum(t *testing.T) {
cases := []struct {
name string
grid [][]int
want int
}{
{"example1", [][]int{[]int{1, 3, 1}, []int{1, 5, 1}, []int{4, 2, 1}}, 7},
{"example2", [][]int{[]int{1, 2, 3}, []int{4, 5, 6}}, 12},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := minPathSum(c.grid)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+44
View File
@@ -0,0 +1,44 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/longest-palindromic-substring/?envType=study-plan-v2&envId=top-100-liked
// 5. 最长回文子串
//
// 给你一个字符串 `s`,找到 `s` 中最长的 回文 子串。
// 示例 1
// 输入:s = "babad"
// 输出:"bab"
// 解释:"aba" 同样是符合题意的答案。
// 示例 2
// 输入:s = "cbbd"
// 输出:"bb"
//
// 提示:
// - `1 <= s.length <= 1000`
// - `s` 仅由数字和英文字母组成
//
func longestPalindrome(s string) string {
}
func TestLongestPalindrome(t *testing.T) {
cases := []struct {
name string
s string
want string
}{
{"example1", "babad", "bab"},
{"example2", "cbbd", "bb"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := longestPalindrome(c.s)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+54
View File
@@ -0,0 +1,54 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/longest-common-subsequence/?envType=study-plan-v2&envId=top-100-liked
// 1143. 最长公共子序列
//
// 给定两个字符串 `text1` 和 `text2`,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 `0` 。
// 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
// - 例如,`"ace"` 是 `"abcde"` 的子序列,但 `"aec"` 不是 `"abcde"` 的子序列。
// 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
// 示例 1
// 输入:text1 = "abcde", text2 = "ace"
// 输出:3
// 解释:最长公共子序列是 "ace" ,它的长度为 3 。
// 示例 2
// 输入:text1 = "abc", text2 = "abc"
// 输出:3
// 解释:最长公共子序列是 "abc" ,它的长度为 3 。
// 示例 3
// 输入:text1 = "abc", text2 = "def"
// 输出:0
// 解释:两个字符串没有公共子序列,返回 0 。
//
// 提示:
// - `1 <= text1.length, text2.length <= 1000`
// - `text1` 和 `text2` 仅由小写英文字符组成。
//
func longestCommonSubsequence(text1 string, text2 string) int {
}
func TestLongestCommonSubsequence(t *testing.T) {
cases := []struct {
name string
text1 string
text2 string
want int
}{
{"example1", "abcde", "ace", 3},
{"example2", "abc", "abc", 3},
{"example3", "abc", "def", 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := longestCommonSubsequence(c.text1, c.text2)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+58
View File
@@ -0,0 +1,58 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/edit-distance/?envType=study-plan-v2&envId=top-100-liked
// 72. 编辑距离
//
// 给你两个单词 `word1` 和 `word2` 请返回将 `word1` 转换成 `word2` 所使用的最少操作数  。
// 你可以对一个单词进行如下三种操作:
// - 插入一个字符
// - 删除一个字符
// - 替换一个字符
// 示例 1
// 输入:word1 = "horse", word2 = "ros"
// 输出:3
// 解释:
// horse -> rorse (将 'h' 替换为 'r')
// rorse -> rose (删除 'r')
// rose -> ros (删除 'e')
// 示例 2
// 输入:word1 = "intention", word2 = "execution"
// 输出:5
// 解释:
// intention -> inention (删除 't')
// inention -> enention (将 'i' 替换为 'e')
// enention -> exention (将 'n' 替换为 'x')
// exention -> exection (将 'n' 替换为 'c')
// exection -> execution (插入 'u')
//
// 提示:
// - `0 <= word1.length, word2.length <= 500`
// - `word1` 和 `word2` 由小写英文字母组成
//
func minDistance(word1 string, word2 string) int {
}
func TestMinDistance(t *testing.T) {
cases := []struct {
name string
word1 string
word2 string
want int
}{
{"example1", "horse", "ros", 3},
{"example2", "intention", "execution", 5},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := minDistance(c.word1, c.word2)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+49
View File
@@ -0,0 +1,49 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/single-number/?envType=study-plan-v2&envId=top-100-liked
// 136. 只出现一次的数字
//
// 给你一个 非空 整数数组 `nums` ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
// 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
// 示例 1
// 输入:nums = [2,2,1]
// 输出:1
// 示例 2
// 输入:nums = [4,1,2,1,2]
// 输出:4
// 示例 3
// 输入:nums = [1]
// 输出:1
//
// 提示:
// - `1 <= nums.length <= 3 * 10^4`
// - `-3 * 10^4 <= nums[i] <= 3 * 10^4`
// - 除了某个元素只出现一次以外,其余每个元素均出现两次。
//
func singleNumber(nums []int) int {
}
func TestSingleNumber(t *testing.T) {
cases := []struct {
name string
nums []int
want int
}{
{"example1", []int{2, 2, 1}, 1},
{"example2", []int{4, 1, 2, 1, 2}, 4},
{"example3", []int{1}, 1},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := singleNumber(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+47
View File
@@ -0,0 +1,47 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/majority-element/?envType=study-plan-v2&envId=top-100-liked
// 169. 多数元素
//
// 给定一个大小为 `n` 的数组 `nums` ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 `⌊ n/2 ⌋` 的元素。
// 你可以假设数组是非空的,并且给定的数组总是存在多数元素。
// 示例 1
// 输入:nums = [3,2,3]
// 输出:3
// 示例 2
// 输入:nums = [2,2,1,1,1,2,2]
// 输出:2
//
// 提示:
// - `n == nums.length`
// - `1 <= n <= 5 * 10^4`
// - `-10^9 <= nums[i] <= 10^9`
// - 输入保证数组中一定有一个多数元素。
// 进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
//
func majorityElement(nums []int) int {
}
func TestMajorityElement(t *testing.T) {
cases := []struct {
name string
nums []int
want int
}{
{"example1", []int{3, 2, 3}, 3},
{"example2", []int{2, 2, 1, 1, 1, 2, 2}, 2},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := majorityElement(c.nums)
if got != c.want {
t.Errorf("got %v, want %v", got, c.want)
}
})
}
}
+55
View File
@@ -0,0 +1,55 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/sort-colors/?envType=study-plan-v2&envId=top-100-liked
// 75. 颜色分类
//
// 给定一个包含红色、白色和蓝色、共 `n` 个元素的数组 `nums` ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
// 我们使用整数 `0`、 `1` 和 `2` 分别表示红色、白色和蓝色。
// 必须在不使用库内置的 sort 函数的情况下解决这个问题。
// 示例 1
// 输入:nums = [2,0,2,1,1,0]
// 输出:[0,0,1,1,2,2]
// 示例 2
// 输入:nums = [2,0,1]
// 输出:[0,1,2]
//
// 提示:
// - `n == nums.length`
// - `1 <= n <= 300`
// - `nums[i]` 为 `0`、`1` 或 `2`
// 进阶:
// - 你能想出一个仅使用常数空间的一趟扫描算法吗?
//
func sortColors(nums []int) {
}
func TestSortColors(t *testing.T) {
cases := []struct {
name string
nums []int
want []int
}{
{"example1", []int{2, 0, 2, 1, 1, 0}, []int{0, 0, 1, 1, 2, 2}},
{"example2", []int{2, 0, 1}, []int{0, 1, 2}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
sortColors(c.nums)
if len(c.nums) != len(c.want) {
t.Errorf("got %v, want %v", c.nums, c.want)
return
}
for i := range c.nums {
if c.nums[i] != c.want[i] {
t.Errorf("got %v, want %v", c.nums, c.want)
return
}
}
})
}
}
+61
View File
@@ -0,0 +1,61 @@
package top100liked
import (
"testing"
)
// https://leetcode.cn/problems/next-permutation/?envType=study-plan-v2&envId=top-100-liked
// 31. 下一个排列
//
// 整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。
// - 例如,`arr = [1,2,3]` ,以下这些都可以视作 `arr` 的排列:`[1,2,3]`、`[1,3,2]`、`[3,1,2]`、`[2,3,1]` 。
// 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
// - 例如,`arr = [1,2,3]` 的下一个排列是 `[1,3,2]` 。
// - 类似地,`arr = [2,3,1]` 的下一个排列是 `[3,1,2]` 。
// - 而 `arr = [3,2,1]` 的下一个排列是 `[1,2,3]` ,因为 `[3,2,1]` 不存在一个字典序更大的排列。
// 给你一个整数数组 `nums` ,找出 `nums` 的下一个排列。
// 必须 原地 修改,只允许使用额外常数空间。
// 示例 1
// 输入:nums = [1,2,3]
// 输出:[1,3,2]
// 示例 2
// 输入:nums = [3,2,1]
// 输出:[1,2,3]
// 示例 3
// 输入:nums = [1,1,5]
// 输出:[1,5,1]
//
// 提示:
// - `1 <= nums.length <= 100`
// - `0 <= nums[i] <= 100`
//
func nextPermutation(nums []int) {
}
func TestNextPermutation(t *testing.T) {
cases := []struct {
name string
nums []int
want []int
}{
{"example1", []int{1, 2, 3}, []int{1, 3, 2}},
{"example2", []int{3, 2, 1}, []int{1, 2, 3}},
{"example3", []int{1, 1, 5}, []int{1, 5, 1}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
nextPermutation(c.nums)
if len(c.nums) != len(c.want) {
t.Errorf("got %v, want %v", c.nums, c.want)
return
}
for i := range c.nums {
if c.nums[i] != c.want[i] {
t.Errorf("got %v, want %v", c.nums, c.want)
return
}
}
})
}
}