赞
踩
func numberOfWays(n int, x int) int { f := make([]int, n+1) f[0] = 1 for i := 1; pow(i, x) <= n; i += 1 { v := pow(i, x) for s := n; s >= v; s-- { f[s] += f[s-v] } } return f[n] % (1e9 + 7) } func pow(x, n int) int { res := 1 for ; n > 0; n /= 2 { if n%2 > 0 { res *= x } x *= x } return res }
func minimumString(a string, b string, c string) string { // 合并s和t,将t插入s后面 f := func(s string,t string) string{ if strings.Contains(s,t){ return s } if strings.Contains(t,s){ return t } n := len(s) start := min(n,len(t)) for i:=start;i>=0;i--{ if s[n-i:]==t[:i]{ return s+t[i:] } } return s+t } ans := "" ss := []string{a,b,c} turn := [][]int{{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,0,1},{2,1,0}} for _,nums := range turn{ res := f(f(ss[nums[0]],ss[nums[1]]),ss[nums[2]]) // fmt.Println(res) if ans=="" || len(ans)>len(res) || len(ans)==len(res) && ans>res{ ans=res } } return ans } func min(a int,b int)int{ if a>=b{ return b }else{ return a } }
合并的函数中,比较s的后缀和t的前缀,在调用时需要使用全排列,即turn二维数组,最终结果比较长度,如果一样长比较字典序,注意go中两个字符串比较字典序可以直接比较
这里需要使用全排列,因为在合并函数中每次返回的是最小的结果,但是两次合并贪心最优不代表最终结果最优,可能会出现这种情况
"gfaa" + "aaccf" + "faaacc"
如果不适用全排列,两次贪心的结果是 >> "gfaaccfaaacc"
但是全排列就考虑到021这种情况,结果是 >> "gfaaaccf"
所以我们采用全排列+贪心的方法来避免每次最短结果不是最短这种尴尬情况
!isLimit && isNum
这种情况,因为这种情况最多func countSpecialNumbers(n int) int { s := strconv.Itoa(n) m := len(s) // dp数组,模拟记忆化搜索,只记录!isLimit && isNum的情况,其他情况不能合并并且发生次数少 cache := make([][1<<10]int, m) for i := range cache{ for j := range cache[i]{ cache[i][j] = -1 } } // 第i位填数字,前i位选过的数字记录在mask中 // isLimit表示,如果前i位全是n[i],那么这一位最多填n[i] // isNum表示,如果前i位选过数字,那么这一位只能选数字,不能跳过 var f func(int, int64, bool, bool) int f = func(i int,mask int64,isLimit bool,isNum bool) int{ if i==m{ if isNum{ return 1 }else{ return 0 } } res := 0 // 先检查cache是否已经记录本次递归,如果是直接返回结果,最终记录结果 if !isLimit && isNum{ p := &cache[i][mask] if *p>=0{ return *p } defer func(){*p = res}() } // 跳过 if !isNum{ res += f(i+1,mask,false,false) } // 不跳过 start := 1 if isNum{ start = 0 } end := 9 if isLimit{ end = int(s[i]-'0') } for j:=start;j<=end;j++{ if mask>>j & 1 == 0 { res += f(i+1,mask|(1<<j),isLimit && j==end,true) } } return res } return f(0,0,true,false) }
func maximumSafenessFactor(grid [][]int) int { n := len(grid) // 查找 1 的下标 type pair struct{ x, y int} q := []pair{} dist := make([][]int, n) for i, row := range grid{ dist[i] = make([]int, n) for j, x := range row{ if x==1{ q = append(q, pair{i, j}) }else{ dist[i][j] = -1 } } } // fmt.Println(q,dist) // 从1开始扩散,记录到1的距离,根据距离分层,记录下标 // 由于有多个1,所以用多源BFS,滚动数组遍历grid,来更新距离,保存下标 dir4 := []pair{{-1,0}, {1,0}, {0,-1}, {0,1}} groups := [][]pair{q} for len(q) > 0{ temp := q q = nil for _,p := range temp{ for _,d := range dir4{ x, y := p.x+d.x, p.y+d.y if 0<=x && x<n && 0<=y && y<n && dist[x][y]<0{ q = append(q,pair{x,y}) dist[x][y] = len(groups) } } } groups = append(groups,q) } // fmt.Println(groups,dist) // 由层数从大到小遍历,对每个点遍历上下左右,只可以去层数更大的点 // 如果当前层数遍历完成后左上能到右下,就返回层数,判断左上右下两点是否联通使用并查集 fa := make([]int, n*n) for i := range fa{ fa[i] = i } var find func(int)int find = func(x int)int{ if fa[x] != x{ fa[x] = find(fa[x]) } return fa[x] } for ans := len(groups)-2; ans>0; ans-=1{ for _,p := range groups[ans]{ i, j := p.x, p.y for _,d := range dir4{ x, y := p.x+d.x, p.y+d.y if 0<=x && x<n && 0<=y && y<n && dist[x][y] >= dist[i][j]{ fa[find(x*n+y)] = find(i*n+j) } } } if find(0) == find(n*n-1){ // fmt.Println(fa) return ans } } return 0 }
输出示例:
// 所有的1 [{0 3} {3 0}] // 距离记录前的样子 [[-1 -1 -1 0] [-1 -1 -1 -1] [-1 -1 -1 -1] [0 -1 -1 -1]] // 距离更新后的样子 [[3 2 1 0] [2 3 2 1] [1 2 3 2] [0 1 2 3]] // 根据层数来分组的点 [[{0 3} {3 0}] [{1 3} {0 2} {2 0} {3 1}] [{2 3} {1 2} {0 1} {1 0} {2 1} {3 2}] [{3 3} {2 2} {1 1} {0 0}] []] // 并查集维护的数组 [14 14 2 3 14 4 9 7 8 14 9 14 12 13 14 14]
github.com/emirpasic/gods/tree/v1.18.1
,提供了一个红黑树,插入数据后直接根据key值大小排列成一个有序数组,我们将key设为nums[i],value设为空func minAbsoluteDifference(nums []int, x int) int { ans := math.MaxInt // 第三方库,把红黑树当做有序队列 tree := redblacktree.NewWithIntComparator() // 只用排序功能,只需要设置key值, // 首先插入两个哨兵,分别位于有序队列的头和尾,保证每次查找都能返回一个结果 tree.Put(math.MaxInt, nil) tree.Put(math.MinInt/2, nil) // 双指针遍历数组,把前一个数添加到队列中,后一个数在队列中查找刚好大于和小于的叶子节点 for i,y := range nums[x:]{ tree.Put(nums[i], nil) c,_ := tree.Ceiling(y) f,_ := tree.Floor(y) ans = min(ans, min(y-f.Key.(int), c.Key.(int)-y)) } return ans } func min(i,j int)int{if i<j{return i}else{return j}}
dp[n] = max( dp[n-1], max( dp[starti]) + goldi )
// DP问题两个思路, // 一个是选或不选,是否卖当前房子, // 一个是枚举选哪个,以当前房子结尾最多可以赚多少钱 func maximizeTheProfit(n int, offers [][]int) int { // 线性dp,选或不选,不选f[n+1]=f[n],选,枚举选哪个f[n+1]=max(f[starti]+goldi) groups := make([][][2]int, n) for _,arr := range offers{ groups[arr[1]] = append(groups[arr[1]], [2]int{arr[0], arr[2]}) } dp := make([]int, n+1) // 从递推式可知,计算n+1需要用到f[start],所以从前往后遍历 for i:=1;i<=n;i+=1{ // 不选 dp[i] = dp[i-1] // 选,枚举选哪个 for _,arr := range groups[i-1]{ dp[i] = max(dp[i], dp[arr[0]]+arr[1]) } } return dp[n] } func max(i,j int)int{if i>=j{return i}else{return j}}
nums = [1,3,2,3,1,3], k = 3
,元素分组后的结果是[[] [0 4] [2] [1 3 5] [] [] []]
,如何遍历才能考虑全所有情况,找到最大的数组长度呢?答案是使用双指针,遍历右端点,移动左端点,寻找以当前右端点结尾数组满足题目要求的左端点。//对nums进行预处理,根据值对下标分组 //对每组进行双指针遍历,寻找满足要求的子数组,维护最大长度 func longestEqualSubarray(nums []int, k int) int { //数据范围 0<nums[i]<n,所以不用map,直接用[][]int n := len(nums) pos := make([][]int, n+1) for i,x := range nums{ pos[x] = append(pos[x], i) } fmt.Println(pos) //对每一组进行遍历,维护子数组最大值 ans := 0 for _,arr := range pos{ //如何遍历才能考虑全所有情况?我们只需要找数组最长的情况,所以使用双指针遍历 //遍历右端点,移动左端点,使得中间删除元素刚好小于k,这个长度就是这个右端点的长度 l := 0 for r,idx := range arr{ //删除次数=下标数组记录的nums长度-下标数组arr的长度 // =一共有几个数字-不需要删除的数字 for (idx-arr[l]+1) - (r-l+1) > k{ l += 1 } ans = max(ans,r-l+1) } } return ans } func max(i,j int)int{if i>=j{return i}else{return j}}
数组问题一个通用有效方法是计算前缀和、前缀最大值、后缀最大值等等
func countInterestingSubarrays(nums []int, m int, k int) int64 { // 如果能除尽,算作1,否则为0,为了得到数量,还需要求前缀和,用p[r]-p[l]来表示[l, r]中的这个数量 pre := make([]int, len(nums)+1) for i,x := range nums{ pre[i+1] = pre[i] if x%m == k{ pre[i+1]+=1 } } fmt.Println(pre) // 求 (p[r] - p[l]) % m == k // => (p[r] - p[l] + m) % m == k % m // => (p[r] - k + m) % m == p[l] % m // 有多少个上式成立,就可以往ans中加几个值, // 遍历前缀数组,使用哈希表查询以当前值为right,记录以当前值为left ans := 0 cnt := map[int]int{} for _, v := range pre { ans += cnt[(v - k + m) % m] cnt[v%m]+=1 } return int64(ans) }
这个题最初打算先对矩阵预处理,分别记录石头多的和缺石头的,然后缺石头的从距离最近处取石头
还有另外一种解法,首先预处理,石头多的,多几次就记录几次,如下
[[0 1] [0 1] [2 2] [2 2]] : [[0 2] [1 1] [1 2] [2 1]]
然后求石头多的全排列,分别和缺石头的对应,计算距离,如此难点就成了计算全排列
func minimumMoves(grid [][]int) int { from := make([][]int, 0) to := make([][]int, 0) for i,arr := range grid{ for j,x := range arr{ if x>1{ for c:=1; c<x; c+=1{ from = append(from, []int{i,j}) } }else if x==0{ to = append(to, []int{i,j}) } } } fmt.Println(from," : ",to) // 枚举from的全排列,分别计算与to的移动次数 ans := math.MaxInt permute(from, 0, func(){ temp := 0 for i,f := range from{ temp += abs(f[0] - to[i][0]) + abs(f[1] - to[i][1]) } ans = min(ans, temp) }) return ans } func permute(arr [][]int, idx int, do func()){ if idx==len(arr){ do() return } permute(arr, idx+1, do) for j:=idx+1;j<len(arr);j+=1{ arr[idx], arr[j] = arr[j], arr[idx] permute(arr, idx+1, do) arr[idx], arr[j] = arr[j], arr[idx] } } func abs(x int) int { if x < 0 { return -x }; return x } func min(a, b int) int { if b < a { return b }; return a }
方法一:
func maximumTripletValue(nums []int) int64 { // 分别计算前缀最大值和后缀最大值 n := len(nums) pre := make([]int,n) pre[0] = nums[0] suf := make([]int,n) suf[n-1] = nums[n-1] for i:=0;i<n-1;i+=1{ pre[i+1] = max(pre[i],nums[i+1]) } for i:=n-1;i>0;i-=1{ suf[i-1] = max(suf[i], nums[i-1]) } // fmt.Println(pre, suf) res := 0 for i,x := range nums[1:n-1]{ res = max(res, (pre[i]-x)*suf[i+2]) } return int64(res) } func max(i,j int)int{if i>=j{return i}else{return j}}
方法二:
func maximumTripletValue(nums []int) int64 { res := 0 // 遍历k,维护一个max_diff,res=max_diff*nums[k],而max_diff=max_pre-nums[k] max_diff := 0 max_pre := 0 for _,x := range nums{ // 把k当做k res = max(res, max_diff*x) // 把k当做j max_diff = max(max_diff, max_pre-x) // 把k当做i max_pre = max(max_pre, x) } return int64(res) } func max(i,j int)int{if i>=j{return i}else{return j}}
// 双指针,指向0和1的下一个位置,遍历数组,0放到p0,原先p0数字放到p1,1放到p1 func sortColors(nums []int) { p0,p1 := 0,0 for i,x := range nums{ if x==0{ nums[i],nums[p0] = nums[p0],nums[i] if p1>p0{ nums[i],nums[p1] = nums[p1],nums[i] } p0++ p1++ }else if x==1{ nums[i],nums[p1] = nums[p1],nums[i] p1++ } } } // 双指针,指向0和2的下一个位置,遍历数组,0放到p0,遇2则循环放到p2 func sortColors1(nums []int) { n := len(nums) p0,p2 := 0,n-1 for i := range nums{ for ;i<=p2 && nums[i]==2;p2-=1{ nums[i],nums[p2] = nums[p2],nums[i] } if nums[i]==0{ nums[i],nums[p0] = nums[p0],nums[i] p0+=1 } } }
d[i][j]
表示word1前i个字符和word2前j个字符的编辑距离// if word1[i]==words2[j] 则 d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]) // if word1[i]!=words2[j] 则 d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+1) func minDistance(word1 string, word2 string) int { n,m := len(word1),len(word2) d := make([][]int, n+1) for i := range d{ d[i] = make([]int, m+1) } for i:=0;i<=n;i+=1{ d[i][0] = i } for j:=0;j<=m;j+=1{ d[0][j] = j } for i:=1;i<=n;i+=1{ for j:=1;j<=m;j+=1{ if word1[i-1]==word2[j-1]{ d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]) }else{ d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+1) } } } return d[n][m] } func min(i,j,k int)int{if i<=j{if i<=k{return i}else{return k}}else{if j<=k{return j}else{return k}}}
func largestRectangleArea(heights []int) int { n := len(heights) stack := make([]int,n+2) idx := 0 heights = append(append([]int{0},heights...),0) ans := 0 for i,x := range heights{ if idx==0 || x>=heights[stack[idx-1]]{ stack[idx] = i idx++ continue } for j:=idx-1;j>=0;j--{ if x>=heights[stack[j]]{ stack[j+1] = i idx = j+2 break }else{ ans = max(ans,heights[stack[j]]*(i-stack[j-1]-1)) } } } return ans } func max(i,j int)int{if i>=j{return i}else{return j}}
延伸:
这道题只需把每一行看做一道单调栈的题,例如第一行是10100,第二行是20211,第三行是31322,第四行是40030,维护一个最大矩形面积值即可
func merge(nums1 []int, m int, nums2 []int, n int) {
i1,i2 := m-1,n-1
for i:=m+n-1;i>=0;i--{
if i2<0 || (i1>=0 && nums1[i1]>nums2[i2]){
nums1[i] = nums1[i1]
i1--
}else{
nums1[i] = nums2[i2]
i2--
}
}
}
// 选出若干选项组成target,属于01背包问题 func lengthOfLongestSubsequence(nums []int, target int) int { dp := make([]int,target+1) // 最开始只有target==0可以直接获得,其他初始化为一个负值 for i:=1;i<=target;i++{ dp[i] = math.MinInt } for _,x := range nums{ for i:=target;i>=x;i--{ dp[i] = max(dp[i],dp[i-x]+1) } } if dp[target]>0{ return dp[target] } return -1 }
增量法:
观察例子abbca,有以下推论式
a -> ab -> abb -> abbc -> abbca
1 -> 21 -> 211 -> 3221 -> 33321
1 -> 3 -> 4 -> 8 -> 12
增量: 1 -> 2 -> 1 -> 4 -> 4
增量正好是当前字符下标与上一次出现同样字符下标之差,我们可以记录每个字符上一次出现的下标,维护一个总和sum,遍历s计算增量加到总和sum上,然后将当前sum加到结果res
// a -> ab -> abb -> abbc -> abbca
// 1 -> 21 -> 211 -> 3221 -> 33321
// 1 -> 3 -> 4 -> 8 -> 12 sum=28
func appealSum(s string) int64 {
sumg,res := 0,0
last := [26]int{}
for i := range last{last[i] = -1}
for i,c := range s{
c-='a'
sumg += i-last[c]
res += sumg
last[c] = i
}
return int64(res)
}
lazy线段树:
如果后面又来了一个更新,破坏了有 lazy tag 的区间,那么这个区间就得继续递归更新了。
// 线段树 // 保存每个线段的信息,左右端点,1的个数,是否翻转(lazy标志) type seg []struct{ l,r,cnt1 int flip bool } // 维护线段内1的个数 func (t seg) maintain(o int){ t[o].cnt1 = t[o*2].cnt1+t[o*2+1].cnt1 } // 构建线段树 func (t seg) build(a []int, o,l,r int){ t[o].l,t[o].r = l,r if l==r{ t[o].cnt1 = a[l-1] return } m := (l+r)/2 t.build(a,o*2,l,m) t.build(a,o*2+1,m+1,r) t.maintain(o) } // 执行区间翻转,只需要更新lazy标签以及区间内1的个数 func (t seg) do(O int){ o := &t[O] o.cnt1 = o.r-o.l+1-o.cnt1 o.flip = !o.flip } // 更新lazy标签,传给左右儿子 func (t seg) spread(o int){ if t[o].flip{ t.do(o*2) t.do(o*2+1) t[o].flip = false } } // 更新线段,区间翻转,lazy更新 func (t seg) update(o,l,r int){ if l<=t[o].l && r>=t[o].r{ t.do(o) return } // 当前区间不是全在[l,r]内,需要将flip标签传递下去 t.spread(o) m := (t[o].l+t[o].r)/2 if l<=m{ t.update(o*2,l,r) } if m<r{ t.update(o*2+1,l,r) } t.maintain(o) } func handleQuery(nums1 []int, nums2 []int, queries [][]int) (ans []int64) { sum := 0 for _,x := range nums2{ sum += x } // 总线段数不会超过最短线段数的四倍 t := make(seg,len(nums1)*4) t.build(nums1,1,1,len(nums1)) for _,arr := range queries{ if arr[0]==1{ t.update(1,arr[1]+1,arr[2]+1) }else if arr[0]==2{ sum += arr[1]*t[1].cnt1 }else{ ans = append(ans,int64(sum)) } } return ans }
增量法+lazy线段树:
(x+1)2-x2=2x+1
,同样是与上一次出现方位内元素累加,等于2sum(xi)+N
,这儿道题分别用回溯和动态规划以及空间优化来解决
回溯:
func minIncrementOperations(nums []int, k int) int64 { n := len(nums) memo := make([][3]int,n) for i := range memo{ memo[i] = [3]int{-1,-1,-1} } // fmt.Println(memo) var dfs func(int,int)int dfs = func(i,j int)int{ if i==n{ return 0 } if memo[i][j]!=-1{ return memo[i][j] } res := 0 defer func(){memo[i][j] = res}() if j<2{ res = min(dfs(i+1,0)+max(k-nums[i],0), dfs(i+1,j+1)) }else{ res = dfs(i+1,0)+max(k-nums[i],0) } return res } // defer func(){fmt.Println(memo)}() return int64(dfs(0,0)) } func max(i,j int)int{if i>=j{return i}else{return j}} func min(i,j int)int{if i<=j{return i}else{return j}}
动态规划:
func minIncrementOperations(nums []int, k int) int64 {
n := len(nums)
dp := make([]int,n)
for i := range dp{
if i<3{
dp[i] = max(k-nums[i],0)
}else{
dp[i] = min(min(dp[i-3],dp[i-2]),dp[i-1])+max(k-nums[i],0)
}
}
return int64(min(min(dp[n-1],dp[n-2]),dp[n-3]))
}
func max(i,j int)int{if i>=j{return i}else{return j}}
func min(i,j int)int{if i<=j{return i}else{return j}}
数组空间优化:
func minIncrementOperations(nums []int, k int) int64 {
n := len(nums)
dp := make([]int,3)
for i:=0;i<n;i++{
if i<3{
dp[i] = max(k-nums[i],0)
}else{
cur := min(min(dp[0],dp[1]),dp[2])+max(k-nums[i],0)
dp[0],dp[1],dp[2] = dp[1],dp[2],cur
}
}
return int64(min(min(dp[0],dp[1]),dp[2]))
}
func max(i,j int)int{if i>=j{return i}else{return j}}
func min(i,j int)int{if i<=j{return i}else{return j}}
/** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func generateTrees(n int) []*TreeNode { var dfs func(int,int)[]*TreeNode dfs = func(l,r int)(ans []*TreeNode){ // 这一句不能少[]*TreeNode{nil}和[]*TreeNode{}不一样 if l>=r{ return []*TreeNode{nil} } for i:=l;i<r;i++{ for _,leftNode := range dfs(l,i){ for _,rightNode := range dfs(i+1,r){ ans = append(ans,&TreeNode{i,leftNode,rightNode}) } } } // 最后的return也不能少 return ans } return dfs(1,n+1) }
/** * Definition for a Node. * type Node struct { * Val int * Left *Node * Right *Node * Next *Node * } */ func connect(root *Node) *Node { if root!=nil{ dfs(root.Left,root.Right) } return root } func dfs(p,q *Node){ if p==nil{ return } p.Next = q dfs(p.Left,p.Right) dfs(p.Right,q.Left) dfs(q.Left,q.Right) }
/** * Definition for a Node. * type Node struct { * Val int * Left *Node * Right *Node * Next *Node * } */ func connect(root *Node) *Node { head := &Node{0,nil,nil,root} for head.Next!=nil{ temp := head cur := head.Next temp.Next = nil for cur!=nil{ if cur.Left!=nil{ temp.Next = cur.Left temp = temp.Next } if cur.Right!=nil{ temp.Next = cur.Right temp = temp.Next } cur = cur.Next } } return root }
递归字符串,遍历当前字符串可能的切割位置,这样做会超时,需要使用记忆化搜索,但这样就不能让递归的参数是两个字符串
func isScramble(s1 string, s2 string) bool {
var dfs := func(s1,s2 string)bool{
return false
}
return dfs(s1,s2)
}
正确解法是传入字符串在原始字符串中的下标,注意到递归的两个字符串参数是等长的,所以只需要两个起始下标和长度这三个参数,同时记忆化数组就是一个三维数组
func isScramble(s1 string, s2 string) bool { n := len(s1) if n==1{ return s1==s2 } memo := make([][][]int,n) for i := range memo{ arr := make([][]int,n) for j := range arr{ temp := make([]int,n+1) for k := range temp{ temp[k] = -1 } arr[j] = temp } memo[i] = arr } var dfs func(int,int,int)bool dfs = func(i1,i2,length int) bool { if length==1{ return s1[i1]==s2[i2] } if memo[i1][i2][length]!=-1{ return memo[i1][i2][length]==1 } for i:=1;i<length;i++{ if dfs(i1,i2,i) && dfs(i1+i,i2+i,length-i){ memo[i1][i2][length]=1 return true } if dfs(i1,i2+length-i,i) && dfs(i1+i,i2,length-i){ memo[i1][i2][length]=1 return true } } memo[i1][i2][length]=0 return false } return dfs(0,0,n) }
这道题通过动态规划思考,首先思考每一天有几种状态,一共有五种,不买不卖,第一次买入,第一次卖出,第二次买入,第二次卖出,这样就能保证最多两次买入卖出,如果没有两次这个限制条件,状态可以分成持有股票和未持有股票
第一种状态利润为0,只需记录后四种状态获得的利润,buy1,sell1,buy2,sell2
在知道第i-1天这四种状态后,即可计算出第i天的对应状态,所以无需创建dp[n][4]
数组
同一天买入卖出,这种情况题目没说是否允许,但是带来的收益是0,所以无论是否这么做都不影响结果,所以传递公式如下:
b1 = max(b1,-prices[i])
s1 = max(s1,b1+prices[i])
b2 = max(b2,s1-prices[i])
s2 = max(s2,b2+prices[i])
考虑初始条件,第一天buy1=-prices[i],s1=0,buy2=-prices[i],视为第一天买入卖出再买入,s2=0
动态规划结束后,由于完成的交易数可以是0/1/2,所以结果=max(0,s1,s2),由于遍历时一直用max维护一个最大值,所以s1>=0,s2>=0,如果最优解是只买入卖出一次,由于同一天可以两次买入卖出,所以最优解会从s1传递到s2
func maxProfit(prices []int) int {
n := len(prices)
b1,s1,b2,s2 := -prices[0],0,-prices[0],0
for i:=1;i<n;i++{
b1 = max(b1,-prices[i])
s1 = max(s1,b1+prices[i])
b2 = max(b2,s1-prices[i])
s2 = max(s2,b2+prices[i])
}
return s2
}
func max(i,j int)int{if i>=j{return i}else{return j}}
func findMaximumXOR(nums []int) int { ans := 0 // slices.Max求nums的最大值,bits.Len求最大值的位数 highBit := bits.Len(uint(slices.Max(nums)))-1 seen := map[int]bool{} mask := 0 for i:=highBit;i>=0;i--{ // 清空哈希表 clear(seen) mask |= 1<<i newAns := ans | 1<<i for _,x := range nums{ x &= mask if seen[newAns^x]{ ans = newAns break } seen[x] = true } } return ans }
func maximumStrongPairXor(nums []int) int { sort.Ints(nums) ans := 0 // slices.Max求nums的最大值,bits.Len求最大值的位数 highBit := bits.Len(uint(slices.Max(nums)))-1 seen := map[int]int{} mask := 0 for i:=highBit;i>=0;i--{ // 清空哈希表 clear(seen) mask |= 1<<i newAns := ans | 1<<i for _,x := range nums{ mask_x := x&mask if seen[newAns^mask_x]>0 && seen[newAns^mask_x]*2>=x{ ans = newAns break } seen[mask_x] = x } } return ans }
func sortedListToBST(head *ListNode) *TreeNode { length := 0 for cur:=head;cur!=nil;cur=cur.Next{ length++ } cur := head var dfs func(int,int)*TreeNode dfs = func(left, right int) *TreeNode { if left >= right { return nil } mid := (left+right)/2 l := dfs(left,mid) res := &TreeNode{cur.Val,nil,nil} cur = cur.Next res.Left = l res.Right = dfs(mid+1,right) return res } return dfs(0, length) }
题目意思是在保证树的每一条叶子结点路线值之和都不为0的情况下,求可以选择最大值,也就是所有数总和-每条线路至少挑选一个值,即总和-损失之和
从根节点开始,如果挑选根节点作为损失值,那么与根节点连接的所有线路都不需要损失,如果不损失根节点,那么与根节点连接的每条线路都需要损失一个值,子问题与母问题类似,所以递推式如下
lossi = min(values[i], sum(dfs(j)))
其中j是与i相连的所有节点,创建树状dp,dp[i][j]
做题正向思考困难则反向思考,选择那些数字困难,就思考损失哪个数字
func maximumScoreAfterOperations(edges [][]int, values []int) int64 { // 树状DP,dp[i][j] dp := make([][]int,len(values)) // dfs中判断叶子结点是根据该节点连接数量判断的,可以为根节点额外连接一个-1,以免误判 dp[0] = append(dp[0],-1) for _,arr := range edges{ dp[arr[0]] = append(dp[arr[0]],arr[1]) dp[arr[1]] = append(dp[arr[1]],arr[0]) } var dfs func(int,int)int dfs = func(idx,from int)int{ // 在叶子节点上,只能损失该节点 if len(dp[idx])==1{ return values[idx] } // 损失当前节点 loss1 := values[idx] // 选择该节点,则损失为后续节点之和 loss2 := 0 for _,x := range dp[idx]{ if x!=from{ loss2 += dfs(x,idx) } } return min(loss1,loss2) } res := -dfs(0,-1) for _,x := range values{ res += x } return int64(res) }
func singleNumber(nums []int) int {
bit := [32]int{}
for _,x := range nums{
for i := range bit{
bit[i] += x>>i&1
}
}
var res int32 = 0
for i,b := range bit{
if b%3==1{
res |= 1<<i
}
}
return int(res)
}
容斥原理:符合条件的方案数=总方案数-不满足要求的方案数
一共n个球,如何分给三个人,在可以分0个的情况下,答案等于C(n+2, 2),相当于从n+2个隔板中选择两个隔板,正好分成三份
基于上述两条原理,答案如下,abc分别是三位小朋友得到超过limit颗糖果的情况,那么分别从总数n中减去limit+1,然后取两个隔板即可,abc三种情况有交集,所以需要减去交集,加上共同交集,只需要单独写个函数求C(n, 2)
ans = all - (a+b+c-ab-ac-bc+abc)
= c(n+2, 2)-(3*c(n-(limit+1)+2, 2)-3*c(n-2*(limit+1)+2, 2)+c(n-3*(limit+1)+2, 2))
func distributeCandies(n int, limit int) int64 {
// ans = all - (a+b+c-ab-ac-bc+abc)
return int64(c(n+2)-(3*c(n-(limit+1)+2)-3*c(n-2*(limit+1)+2)+c(n-3*(limit+1)+2)))
}
// return C(n,2)
func c(n int)int{
if n<=1{
return 0
}
return n*(n-1)/2
}
const MOD = int(1e9+7) func stringCount(n int) int { // 求最少有1个l,至少有2个e这种比较难算,可以利用容斥原理求最多有几个... // a = 没有l // b = 没有t // c = 没有e/只有一个e // ans = all - (a+b+c-ab-ac-bc+abc) all := pow(26,n) a := pow(25,n) b := pow(25,n) c := pow(25,n)+n*pow(25,n-1) ab := pow(24,n) ac := pow(24,n)+n*pow(24,n-1) bc := pow(24,n)+n*pow(24,n-1) abc := pow(23,n)+n*pow(23,n-1) // 保证结果非负 return ((all - (a+b+c-ab-ac-bc+abc))%MOD+MOD)%MOD } // 快速幂,注意MOD取余的位置 func pow(x,y int)int{ res := 1 for y>0{ if y%2==1{ res=res*x%MOD } x=x*x%MOD y/=2 } return res }
// 结构体,可以添加一个表示数组元素个数+1的属性 // 由于idx=0时没有最低位1,所以不能在arr[0]存入数据 type NumArray struct { Tree []int } // 初始化 func Constructor(nums []int) NumArray { res := NumArray{Tree:make([]int,len(nums)+1)} for i,x := range nums{ res.Update(i,x) } return res } // 更新第idx位数字 func (this *NumArray) Update(idx int, val int) { val = val-this.SumRange(idx,idx) n := len(this.Tree) idx++ for ;idx<n;idx+=(idx&-idx){ this.Tree[idx]+=val } return } // 获得前idx位数字和 func (this *NumArray) Get(idx int) int { res := 0 for ;idx>0;idx-=(idx&-idx){ res+=this.Tree[idx] } return res } // 计算[left, right]的和 func (this *NumArray) SumRange(left int, right int) int { return this.Get(right+1)-this.Get(left) }
对于float格式变量而言,运算是有误差的,例如:
1 - 0.22222222 * 3 = 0.3333333
4 - 0.22222222 * 12 = 0.33333325
避免除法的浮点误差有两种方法:
除法改乘法,z=1/3=4/12,1*12==4*3
除数和被除数除以最大公约数,4/gcd(4,12),12/gcd(4,12)
// 计算最大公约数
func gcd(a, b int) int {
for a != 0 {
a, b = b%a, a
}
return b
}
要求常数时间返回最小值,可以创建一个栈保存当前值,一个辅助栈保存当前下标对应的最小值
stack = 5 6 1 4 0 2
austack = 5 5 1 1 0 0
如果进一步要求不能使用额外空间,只需要一个栈,一个最小值变量,栈内保存当前值与上一个最小值的差值,例如6-5,1-5,4-1,如果差值<=0,当前值小于上一个最小值,那么最小值就是当前值,如果差值>0,当前值大于上一个最小值,那么最小值+差值就是当前值
返回当前值的之后根据保存的diff大小决定最小值变量是否需要加上diff
put = 5 6 1 4 0 2
min = 5 5 1 1 0 0
diff = 5 1 -4 3 -1 2
pop = if diff<0 return min
if diff>=0 return min+diff
= 5 6 1 4 0 2
func getIntersectionNode(headA, headB *ListNode) *ListNode { cur1,cur2 := headA,headB for cur1!=cur2{ if cur1==nil{ cur1 = headB }else{ cur1 = cur1.Next } if cur2==nil{ cur2 = headA }else{ cur2 = cur2.Next } } return cur1 }
计数排序:
nums = [2 3 8 7 1 2 2 2 7 3 9 8 2 1 4 2 4 6 9 2]
// arr[i]记录数字i出现的次数,arr的长度就是出现数字的max-min+1 = 9-1+1 = 9
arr = [2 6 2 2 0 1 2 2 2]
1 2 3 4 5 6 7 8 9
sort = [11 222222 33 44 6 77 88 99]
基数排序:
每次排序需要把每个分组数据保存在二维数组的桶里,如果从百位开始比较,就不能使用桶排序,可以用计数排序
func convertToTitle(n int) string {
s := []byte{}
for n>0{
// 从1开始的26进制,最开始减去1,实现整体偏移,接下来就按10进制正常算
n--
s = append(s,byte(n%26+'A'))
n/=26
}
res := strings.Builder{}
for i:=len(s)-1;i>=0;i--{
res.WriteByte(s[i])
}
return res.String()
}
func majorityElement(nums []int) int { // 摩尔投票法 cand,cnt := nums[0],1 for _,x := range nums[1:]{ if x==cand{ cnt++ }else{ cnt-- if cnt==0{ cand=x cnt=1 } } } return cand }
升级问题,找出nums中出现次数超过n/k次的元素
// 摩尔投票算法的核心是对拼消耗 func majorityElement(nums []int) []int { n := len(nums) // 出现次数超过n/k的元素种类不超过k-1个 k := 3 // 先选出所有可能的选举者 voters := make(map[int]int,k-1) for _,x := range nums{ // 已有3个候选者,并且x不是候选者 if len(voters)==k-1 && voters[x]==0{ for key,val := range voters{ if val==1{ delete(voters,key) }else{ voters[key]-- } } }else{ voters[x]++ } } // 找到k-1个可能的选举者,二次遍历统计每个选举者的出现次数 for key := range voters{ voters[key]=0 } for _,x := range nums{ voters[x]++ } // 把可能选举者中,出现次数大于n/k的放入答案中 ans := make([]int,0,k-1) for key,val := range voters{ if val>n/k{ ans = append(ans,key) } } return ans }
func trailingZeroes(n int) (res int) {
for n>0{
n/=5
res += n
}
return res
}
对于32位数字,如果前16和后16位数字分别颠倒,将两个结果颠倒位置,就能得出结果,如何将两个结果颠倒位置呢?将数字与上一个掩码0xffff0000,就能得到前16位,后移16位,后半截做相同处理,与上掩码再移位,就可以实现结果颠倒,利用分治的思想,最小单位是1位。
func reverseBits(n uint32) uint32 {
// 32位数字n前后16位交换
n = (n >> 16) | (n << 16)
// 每个16位中前后8位交换
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8)
// 每个8位中前后4位交换
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4)
// 每个4位中前后2位交换
n = ((n & 0b11001100110011001100110011001100) >> 2) |
((n & 0b00110011001100110011001100110011) << 2)
// 每个2位中前后1位交换
n = ((n & 0b10101010101010101010101010101010) >> 1) |
((n & 0b01010101010101010101010101010101) << 1)
return n
}
class Solution: def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]: res = [-1]*len(queries) # 根据b分类保存a的高度和在queries中的位置 temp = [[] for _ in heights] for qi,(i,j) in enumerate(queries): # 确保ab先后顺序 if i>j: i,j = j,i # 如果ab出发位置相同,或者h[a]<h[b],那么答案就是b if i==j or heights[i]<heights[j]: res[qi] = j else: temp[j].append((heights[i], qi)) heap = [] # 遍历heights,如果x大于最小堆中最小的高度,即堆顶,那么i就是堆顶的答案 for i,x in enumerate(heights): while heap and heap[0][0]<x: qi = heappop(heap)[1] res[qi] = i # 确保后续遍历的i都在temp中b的后边,这样x>h[a]时,能从a和b跳到i for arr in temp[i]: heappush(heap, arr) return res
func rangeBitwiseAnd(left int, right int) int {
i:=0
for ;left!=right;i++{
left>>=1
right>>=1
}
return left<<i
}
func rangeBitwiseAnd(left int, right int) int {
for left<right{
right -= right&-right
}
return right
}
埃氏筛的原理,是将2n-1假设为质数,从2根号n遍历,遍历数字的倍数全都不是质数,遍历结束剩下未被标记的就是质数。但是这有个问题,例如12,12=2*6=3*4
,会被标记多次,所以埃氏筛时间复杂度是O(nlognlogn)
线性筛的时间复杂度可以降低到o(n),一个质数只会被标记一次,不会被重复标记,它的原理是假设2n-1是质数,从2n-1遍历,将质数保存在数组中,遍历的数字x乘以所有质数,乘积标记为非质数,当遍历的数字x可以除尽遍历的质数时,就不用继续乘之后的质数了,因为当前遍历的数字x中包含了其他质因子,而x的倍数已经被这个质因子排除过了,这样就保证了所有数只会被标记一次
埃氏筛和线性筛的区别,如下图所示,埃氏筛是一列一列标记数字的,而线性筛是一行一行标记数字的,红色是线性筛跳过的,例如3*4,4可以除尽质数2,所以4以及4的倍数已经被标记过了
func countPrimes(n int) int { primes := []int{} isPrime := make([]bool, n) // 假设2~n-1全是素数 for i := range isPrime{ isPrime[i] = true } for i:=2;i<n;i++{ if isPrime[i]{ primes = append(primes, i) } for _,p := range primes{ if i*p>=n{ break } // i的倍数不是素数 isPrime[i*p] = false // i中除了p还包含其他质因子,那么i的倍数都已经被这个质因子排除了 if i%p==0{ break } } } return len(primes) }
func findOrder(numCourses int, prerequisites [][]int) []int { // 广度优先遍历,从最先要学的科目开始遍历 // 建图,一个二维数组存储有向图,一个一维数组存储每个节点入度,入度个数就是要学的科目数 edges := make([][]int,numCourses) indeg := make([]int,numCourses) for _,arr := range prerequisites{ edges[arr[1]] = append(edges[arr[1]], arr[0]) indeg[arr[0]]++ } // 第一轮要遍历的科目 q := []int{} for i,x := range indeg{ if x==0{ q = append(q,i) } } // 广度优先遍历 res := make([]int,0,numCourses) for len(q)>0{ cour := q[0] q = q[1:] res = append(res,cour) for _,c := range edges[cour]{ indeg[c]-- if indeg[c]==0{ q = append(q,c) } } } // 有向图闭环,则不可能完成所有课程 if len(res)!=numCourses{ return []int{} } return res }
func sumSubarrayMins(arr []int) int { MOD := int(1e9)+7 n := len(arr) // 单调栈 stack := make([]int,n+1) stack[0] = -1 // idx指向单调栈的末位下标 idx := 1 res := 0 for i,x := range arr{ for idx>1 && x<=arr[stack[idx-1]]{ // 对于单调栈中大于x的值,res = res + 值*值的贡献 // 本题中值的贡献就是值作为最小值的范围[l,r]中子数组个数 res += arr[stack[idx-1]]*(stack[idx-1]-stack[idx-2])*(i-stack[idx-1]) res %= MOD idx-- } stack[idx] = i idx++ } for idx>1{ res += arr[stack[idx-1]]*(stack[idx-1]-stack[idx-2])*(n-stack[idx-1]) res %= MOD idx-- } return res }
func maxSlidingWindow(nums []int, k int) []int { // 单调队列,滑动窗口左右端点向同一方向移动 res := []int{} // 单调队列中元素顺序递减,首位元素下标不在窗口内时弹出队列, q := []int{} for i,x := range nums{ // 入队,保证队列的元素递增 n := len(q) for n>0 && x>=nums[q[n-1]]{ q = q[:n-1] n-- } q = append(q,i) // 出队,如果队首元素不在滑动窗内,就抛出 if i-k>=q[0]{ q = q[1:] } // 保存队列第一个元素,即最大值 if i>=k-1{ res = append(res,nums[q[0]]) } } return res }
func calculate(s string) int { // 避免-2这种情况 nums := []int{0} ops := []byte{} // 算完或者算到'(' cal := func(){ i,j := len(nums)-1,len(ops)-1 for ;i>0 && j>=0 && ops[j]!='(';j--{ if ops[j]=='+'{ nums[i-1] += nums[i] }else{ nums[i-1] -= nums[i] } i-- } nums = nums[:i+1] ops = ops[:j+1] } for i:=0;i<len(s);{ if s[i]==' '{ i++ }else if s[i]=='('{ ops = append(ops,s[i]) i++ }else if s[i]=='+' || s[i]=='-'{ // 如果有新操作+/-加入队列,就把能算的都算了,这样就避免2-1+2=-1这种问题 cal() if s[i]=='-'{ ops = append(ops,'+') nums = append(nums,0) } ops = append(ops,s[i]) i++ }else if s[i]==')'{ cal() ops = ops[:len(ops)-1] i++ }else{ j := i for ;j<len(s) && s[j]>='0' && s[j]<='9';j++{} num,_ := strconv.Atoi(s[i:j]) nums = append(nums,num) i = j } } cal() return nums[len(nums)-1] }
func singleNumber(nums []int) []int { //首先将所有数字异或 n1 := 0 for _,x := range nums{ n1 ^= x } //所有数字异或结果就是答案两个数字异或的结果,最低位1就是两个数字不相同的最低位 mask := n1&(-n1) //通过mask将nums分成两组,答案的两个数字就分别在两组中 res := []int{0,0} for _,x := range nums{ if x&mask==mask{ res[0] ^= x }else{ res[1] ^= x } } return res }
func carPooling(trips [][]int, capacity int) bool { nums := make([]int,1001) for _,arr := range trips{ nums[arr[1]]+=arr[0] nums[arr[2]]-=arr[0] } fmt.Println(nums) sum := 0 for _,x := range nums{ sum += x if sum>capacity{ return false } } return true }
第二种思路就是在hash中添加区间的左右端点,然后排序,根据端点顺序进行遍历相加,比1001长度数组要快
func carPooling(trips [][]int, capacity int) bool { d := map[int]int{} for _, t := range trips { d[t[1]] += t[0] d[t[2]] -= t[0] } keys := make([]int, 0, len(d)) for k := range d { keys = append(keys, k) } slices.Sort(keys) s := 0 for _, k := range keys { s += d[k] if s > capacity { return false } } return true }
n%(m+1)!=0
,那么先手总是赢func canWinNim(n int) bool {
return n%(3+1)!=0
}
func minimumAddedCoins(coins []int, target int) int { sort.Ints(coins) // 表示可以组成[0:s-1]的所有数字 s := 1 idx := 0 res := 0 for s<=target{ if idx<len(coins) && s>=coins[idx]{ // [0:s-1]+x -> [x:s+x-1] s += coins[idx] idx++ }else{ // coins用完了,或者缺失了一个中间值s s += s res++ } } return res }
func countCompleteSubstrings(word string, k int) int { res := 0 // 根据条件将问题分成两部分,实现解耦 for i,n := 0,len(word); i<n; { // 根据第二个条件,截取[start:i]的部分 start := i for i++; i<n && abs(int(word[i])-int(word[i-1]))<=2; i++{} // 根据第一个条件,判断截取部分有几个完全字符串 res += getNum(word[start:i], k) } return res } func abs(i int)int{ if i<0{return -i}else{return i} } // 分组循环 func getNum(s string, k int)(res int){ // s中每个字符恰好出现k次 =>> len(s) = s中字符种类*k = m*k // 枚举m种字符,用m*k的滑动窗口,判断窗口内每种是否恰好出现k次 for m:=1;m<=26 && m*k<=len(s);m++{ cnt := [26]int{} check := func(){ for i := range cnt{ if cnt[i]>0 && cnt[i]!=k{ // 如果不写成函数,这里只能用goto,或者设置一个bool值 // 写成函数后可以用return来跳过res++ return } } res++ } for r,in := range s{ cnt[in-'a']++ // 窗口左端点l+1,如果l>=0,即窗口长度大于m*k,左边需要出队列 if l:=r+1-m*k;l>=0{ // 滑动窗口内的字符出现次数是否满足条件二 check() cnt[s[l]-'a']-- } } } return }
dp[i][j]
是从当前位置到右下所需的最小生命值,最终返回dp[0][0]
func calculateMinimumHP(dungeon [][]int) int { n,m := len(dungeon),len(dungeon[0]) dp := make([][]int,n+1) for i := range dp{ dp[i] = make([]int,m+1) for j := range dp[i]{ dp[i][j] = math.MaxInt } } // 对于终点,该点所需最小体力是1-nums[n-1][m-1] dp[n][m-1], dp[n-1][m] = 1,1 // 从右下往左上走,nums[i][j]是从这里出发能走到终点所需最小体力 for i:=n-1;i>=0;i--{ for j:=m-1;j>=0;j--{ dp[i][j] = max(min(dp[i+1][j],dp[i][j+1])-dungeon[i][j], 1) } } return dp[0][0] }
func getGoodIndices(variables [][]int, target int) []int { res := make([]int,0,len(variables)) for i,arr := range variables{ a,b,c,m := arr[0],arr[1],arr[2],arr[3] if pow(pow(a,b,10),c,m)==target{ res = append(res,i) } } return res } func pow(x,n,MOD int)int{ res := 1 for n>0{ if n%2==1{ res = res*x%MOD } x = x*x%MOD n/=2 } return res }
[3,1,2,1,2,4,5]
,下标就是1和4,左边分割子数组个数乘以右边分割子数组个数就是答案,但是求分割子数组很难。而且这种想法是错误的,例如[3,1,1,4,2,2,5]
,左右下标可能不唯一[3,1,2,1,2,4,5]
,3有一条,1,2,1,2有一条,4有一条,5有一条,每条分割线可以选也可以不选,将数组分割成长短不一的子数组,总共有2^m-1种分割情况,m就是分割线的个数func numberOfGoodPartitions(nums []int) int { lastIdx := map[int]int{} for i,x := range nums{ lastIdx[x] = i } maxi := -1 // 分割线的个数 m := 0 for i,x := range nums{ maxi = max(maxi,lastIdx[x]) if maxi==i{ m++ } } return pow(2,m-1,int(1e9)+7) } func pow(x,n,MOD int)int{ res := 1 for n>0{ if n%2==1{ res = res*x%MOD } x = x*x%MOD n /= 2 } return res }
Flyod可以应用于正负权图,多源最短路径,时间复杂度O(n3),核心思想从i->j,枚举k当中间节点,更新最短路径
Dijkstra只能应用于正权图,单源最短路径,时间复杂度O(n2),核心思想从0->i,枚举0连接的最短路径的节点k,能否用k更新0->其他i
这道题显然是多源最短路径,用Flyod,Flyod算法精髓是用中间节点来优化最短路径,i->j优化成i->k->j
递归选或不选,dfs(i,j,k)表示i->j的中间节点都小于k
dfs(i,j,k)=min(dfs(i,j,k-1),dfs(i,k,k-1)+dfs(k,j,k-1))
递归改动态规划,递归的终止条件就是dp数组的初始值
dp[k][i][j] = min(dp[k-1][i][j],dp[k-1][i][k]+d[k-1][k][j])
dp数组空间优化,枚举k,对所有i和j进行遍历,计算k能否成为中间节点
d[i][j] = min(d[i][j], d[i][k]+d[k][j])
// 多源最短路径,Flyod func findTheCity(n int, edges [][]int, distanceThreshold int) int { // 建图 d := make([][]int,n) for i := range d{ d[i] = make([]int,n) for j := range d[i]{ d[i][j] = 10001 } } for _,arr := range edges{ from,to,weight := arr[0],arr[1],arr[2] d[from][to] = weight d[to][from] = weight } // Flyod求最短路径 for k:=0;k<n;k++{ for i:=0;i<n;i++{ for j:=0;j<n;j++{ d[i][j] = min(d[i][j], d[i][k]+d[k][j]) // 如果要记录i->j的中间节点 // 取中间节点时递归path[i][j]->path[k][j]->... // path[i][j] = k } } } // 返回符合要求的答案 res := -1 minc := n for i,arr := range d{ sum := 0 for j,x := range arr{ if i==j{ continue } if x<=distanceThreshold{ sum++ } } if sum<=minc{ minc = sum res = i } } return res }
二分查找:
type pair struct{x,y int} func minimumEffortPath(heights [][]int) int { // 二分,在[0,1e6]中对结果进行二分,判断条件是以mid为最大差值能否走到终点 n,m := len(heights),len(heights[0]) d := []pair{{-1, 0}, {1, 0}, {0, -1}, {0, 1}} l,r := 0,int(1e6) mid := 0 visit := make([]bool,n*m) // 广度优先遍历,从上下左右四个方向同时走,只要能走到终点就返回true var bfs func(int,int)bool bfs = func(i,j int)bool{ clear(visit) visit[i*m+j] = true q := []pair{{i,j}} for { temp := []pair{} for _,p := range q{ if p.x==n-1 && p.y==m-1{ return true } for _,pd := range d{ x,y := p.x+pd.x,p.y+pd.y if x>=0 && x<n && y>=0 && y<m && !visit[x*m+y] && abs(heights[x][y]-heights[p.x][p.y])<=mid{ visit[x*m+y] = true temp = append(temp,pair{x,y}) } } } if len(temp)==0{ break } q = temp } return false } // 从(0,0)开始走 for l<r{ mid = (l+r)/2 if bfs(0,0){ // 走得通,mid偏大,还可以更小 r = mid }else{ // 假定的结果mid太小了,走不通 l = mid+1 } } return l } func abs(x int)int{if x<0{return -x}else{return x}}
并查集:
// h表示x->y之间的高度差 type pair struct{x,y,h int} func minimumEffortPath(heights [][]int) int { n,m := len(heights),len(heights[0]) fa := make([]int, n*m) for i := range fa{ fa[i] = i } var find func(int)int find = func(x int)int{ if fa[x] != x{ fa[x] = find(fa[x]) } return fa[x] } // 只需记录左上点到本点的边 edges := []pair{} for i,arr := range heights{ for j,h := range arr{ if i>0{ edges = append(edges,pair{(i-1)*m+j,i*m+j,abs(h-heights[i-1][j])}) } if j>0{ edges = append(edges,pair{i*m+j-1,i*m+j,abs(h-heights[i][j-1])}) } } } // 根据边权值排序 sort.Slice(edges, func(i,j int)bool{return edges[i].h<edges[j].h}) // 从小到大将边加入并查集中,如果加入一条边后,可以从起点到终点,那么答案就是这条边边权 for _,p := range edges{ fa[find(p.x)] = find(p.y) if find(0)==find(n*m-1){ return p.h } } // 对于单点图,edges为空,没有其他点和它有边,结果是0 return 0 } func abs(x int)int{if x<0{return -x}else{return x}}
func numberOfSets(n int, maxDistance int, roads [][]int) int { // 暴力枚举,mask=0b1100110011,表示编号为0,1,4,5,8,9的6个分部保留,其余分部删除 // 建图 d := make([][]int,n) for i := range d{ d[i] = make([]int,n) for j := range d[i]{ if i==j{continue} // 防止加法溢出 d[i][j] = math.MaxInt/2 } } for _,arr := range roads{ x,y,v := arr[0],arr[1],arr[2] d[x][y] = min(d[x][y],v) d[y][x] = min(d[y][x],v) } // Floyd算法 var f func(int)int f = func(mask int)int{ // 保留的部分 idx := make([]int,0,n) for i:=0;i<n;i++{ if mask>>i&1==1{ idx = append(idx,i) } } nums := make([][]int,n) for _,i := range idx{ nums[i] = make([]int,n) copy(nums[i],d[i]) } for _,k := range idx{ for _,i := range idx{ for _,j := range idx{ nums[i][j] = min(nums[i][j],nums[i][k]+nums[k][j]) } } } for _,i := range idx{ for _,j := range idx{ if nums[i][j]>maxDistance{ return 0 } } } return 1 } // 暴力枚举所有可能情况 res := 0 for i:=0;i<1<<n;i++{ res += f(i) } return res }
func getWordsInLongestSubsequence(n int, words []string, groups []int) []string { // dp[i]表示i~n-1中选出的最长子序列长度 dp := make([]int,n) // 下一个子序列的下标,用于记录子序列 from := make([]int,n) start := n-1 for i:=n-1;i>=0;i--{ for j:=i+1;j<n;j++{ if dp[j]>dp[i] && groups[i]!=groups[j] && isOk(words[i],words[j]){ dp[i] = dp[j] from[i] = j } } // 子序列以i为起始字符串 dp[i]+=1 if dp[i]>dp[start]{ start = i } } // 从maxi开始,记录字符串 res := make([]string,dp[start]) for i := range res{ res[i] = words[start] start = from[start] } return res } // 判断字符串是否满足题目要求 func isOk(s1,s2 string)bool{ if len(s1)!=len(s2){ return false } cnt := 0 for i := range s1{ if s1[i]!=s2[i]{ if cnt==1{ return false } cnt++ } } return cnt==1 }
// 回文数打表,找[1,1e9]内所有回文数 var pal = make([]int,0) func initPalindrome(){ // 插入前哨兵 pal = append(pal,0) // 对于数字123,12321是回文数,123321也是回文数 // 遍历[1,int(1e5)],对所有数字计算对称得到的两个回文数,按顺序组合 // 注意到12->121和1221,123->12321,123321,如果每10倍计算一次再按顺序组合,就不需要排序,得到[121 1221 12321 123321],所以遍历[1,int(1e4)] for i:=1;i<int(1e5);i*=10{ for j:=i;j<i*10;j++{ // 123->12321 temp := j/10 x := j for temp>0{ x = x*10+temp%10 temp /= 10 } pal = append(pal,x) } if i>int(1e4){ // 对于12345->123454321和1234554321,只需要计算前一个,不需要计算后一个 continue } for j:=i;j<i*10;j++{ // 123->123321 temp := j x := j for temp>0{ x = x*10+temp%10 temp /= 10 } pal = append(pal,x) } } // 插入后哨兵,防止二分查找时找不到 pal = append(pal,int(1e9)+1) return } func minimumCost(nums []int) int64 { if len(pal)==0{ initPalindrome() } // 对于数组[10 20 30 40],小于10的数字离10越近总代价越小,大于40的数字同理,大于10小于20的数字对于10和40的代价和是恒定的,离20越近总代价越小,所以需要找一个离nums中位数最近的回文数 // 一共两个情况,偶数时找离20和30最近的两个数,奇数时,找离中位数最近的中位数,统一一下,二分找i,计算比较i和i-1 sort.Ints(nums) n := len(nums) i := sort.SearchInts(pal,nums[n/2]) fmt.Println(pal[i],pal[i-1]) sum1 := 0 sum2 := 0 for _,x := range nums{ sum1 += abs(x-pal[i]) sum2 += abs(x-pal[i-1]) } return int64(min(sum1,sum2)) } func abs(x int)int{if x<0{return -x}else{return x}}
小于中位数的操作数=m*k-sum,大于中位数的操作数=sum-m*k
,可以用前缀和快速计算一段数组的sum// 选一个数作为目标元素,这个数肯定是中位数,奇数选中间n/2,偶数选(n-1)/2 func maxFrequencyScore(nums []int, k int64) (res int) { n := len(nums) sort.Ints(nums) // 对于一个有序数组,小于中位数的操作次数=m*k-总和,大于中位数的操作次数=总和-m*k // 利用前缀和快速计算总和 pre := make([]int,n+1) for i,x := range nums{ pre[i+1] = pre[i]+x } // 计算窗口内的操作次数 cal := func(l,r int)int64{ m := (l+r)/2 x := nums[m] return int64((m-l)*x-(pre[m]-pre[l])+(pre[r+1]-pre[m+1])-(r-m)*x) } // 滑动窗口,如果窗口内计算的操作次数大于k,就移动左端点 l := 0 for r := range nums{ for cal(l,r)>k{ l++ } // fmt.Println(nums[l:r+1],cal(l,r),r-l+1) res = max(res,r-l+1) } return res }
这道题还可以用贡献法来做
func findPeakGrid(mat [][]int) []int { n := len(mat) // 二分查找,按照每一行的最大值进行二分 maxi := make([]int,n) for i,arr := range mat{ mi := 0 for j,x := range arr{ if x>arr[mi]{ mi = j } } maxi[i] = mi } l,r := 0,n-1 for l<r{ mx := (l+r)/2 my := maxi[mx] // fmt.Println(l,r,mx,my,mat[mx][my]) if mat[mx][my]<=mat[mx+1][my]{ // 下面的更大 l = mx+1 }else{ // 该行及上面的更大 r = mx } } return []int{l,maxi[l]} }
递归,dfs(i)返回以nums[i]为开头或结尾的递增子序列的长度,每次枚举可以选的数字,使用dp进行记忆化搜索。
贪心+二分,用一个栈保存递增子序列,与单调栈不同的是,省略出栈这一步,直接把小数添加到适合的位置,由于添加的数字在二分查找到的位置,依旧能保持有序,最终返回栈的长度
[10,9,2,5,3,7,101,18] ->
[10]
[9]
[2]
[2,5]
[2,3]
[2,3,7]
[2,3,7,101]
[2,3,7,18]
func lengthOfLIS(nums []int) int {
// 二分+贪心
n := len(nums)
g := make([]int,0,n)
for _,x := range nums{
idx := sort.SearchInts(g,x)
if idx==len(g){
g = append(g,x)
}else{
g[idx] = x
}
}
return len(g)
}
如果直接在nums中进行修改,用一个变量记录g的长度,就能做到O(1)的空间复杂度
func lengthOfLIS(nums []int) int {
// 二分+贪心+空间优化
length := 0
for _,x := range nums{
idx := sort.SearchInts(nums[:length],x)
nums[idx] = x
if idx==length{
length++
}
}
return length
}
func findMinHeightTrees(n int, edges [][]int) (res []int) { // 建图 nums := make([][]int,n) for _,arr := range edges{ nums[arr[0]] = append(nums[arr[0]],arr[1]) nums[arr[1]] = append(nums[arr[1]],arr[0]) } parents := make([]int,n) // 广度优先遍历,用先进后出队列保存节点 bfs := func(start int)(i int){ vis := make([]bool,n) vis[start] = true q := []int{start} for len(q)>0{ i,q = q[0],q[1:] for _,j := range nums[i]{ if !vis[j]{ q = append(q,j) vis[j] = true parents[j] = i } } } return } // 找到与节点0最远的节点x x := bfs(0) // 找到与节点x最远的节点y y := bfs(x) // 记录x->y的路径节点,返回中间节点 path := make([]int,0,n) for { path = append(path,y) if y==x{ break } y = parents[y] } if len(path)%2==0{ return []int{path[len(path)/2-1],path[len(path)/2]} }else{ return []int{path[len(path)/2]} } }
方法二
func findMinHeightTrees(n int, edges [][]int) []int { if n==1{ return []int{0} } // 建图 g := make([][]int,n) deg := make([]int,n) for _,arr := range edges{ x,y := arr[0],arr[1] g[x] = append(g[x],y) g[y] = append(g[y],x) deg[x]++ deg[y]++ } // 删除度为1的节点,直到最后剩下1个或2个节点,即是答案 q := make([]int,0,n) for i,x := range deg{ if x==1{ q = append(q,i) } } remainNodes := n for remainNodes>2{ remainNodes -= len(q) temp := q q = nil for _,i := range temp{ for _,j := range g[i]{ deg[j]-- if deg[j]==1{ q = append(q,j) } } } } return q }
2n(n+1)(2n+1)
,这道题的关键是如何证明这一步,最开始的思路是找到一条边的,然后乘4倍,但是正方形内部也有点,不只要计算边长ax+by=z
,首先系数a和b可以是负数,但对结果没有影响。如果a是负数,即要把x往外倒水,可以先往 y 中倒水,再把 y 壶的水倒入 x 壶,如果 y 壶不为空,那么 x 壶肯定是满的,把 x 壶倒空,然后再把 y 壶的水倒入 x 壶。重复以上操作直至某一步时 x 壶进行了 a 次倒空操作,y 壶进行了 b 次倒水操作。func canMeasureWater(x int, y int, z int) bool { if x+y<z{return false} // 每个杯子的状态是[0:x]和[0:y] // 通过深度优先遍历直到出现dp[z][z]的状态 // 每次有如下此操作x->y,y->x,->x,x->,->y,y-> dp := map[int]map[int]bool{} var dfs func(int, int)bool dfs = func(i,j int)bool{ if _,ok := dp[i][j];ok{return false} if i==z || j==z || i+j==z{return true} if dp[i]==nil{ dp[i] = make(map[int]bool) } dp[i][j] = true res := dfs(x,j)||dfs(0,j)||dfs(i,y)||dfs(i,0) if i+j>y{ res = res||dfs(i+j-y,y) }else{ res = res||dfs(0,i+j) } if i+j>x{ res = res||dfs(x,i+j-x) }else{ res = res||dfs(i+j,0) } return res } res := dfs(0,0) return res }
func canMeasureWater(x int, y int, z int) bool {
if x+y<z{return false}
// 如果z能除尽x和y的最大公约数,就返回true
return z%gcd(x,y)==0
}
func gcd(x,y int)int{
for x!=0{
x,y = y%x,x
}
return y
}
暴力枚举:
func minCost(nums []int, x int) int64 { n := len(nums) res := math.MaxInt for k:=0;k<n;k++{ sum := k*x for i := range nums{ temp := math.MaxInt for j:=i;j<=i+k;j++{ temp = min(temp,nums[j%n]) } sum += temp } res = min(res,sum) } return int64(res) }
枚举巧克力->操作次数,记录最小值:
func minCost(nums []int, x int) int64 { n := len(nums) cost := make([]int,n) for i := range cost{ // 换i次的操作成本 // 换一次操作成本为x,第j个巧克力花费是min(j,j+1) // 换二次操作成本为2x,第j个巧克力花费是min(j,j+1,j+2)...依此类推 cost[i] = i*x } // 暴力枚举操作次数->每个巧克力->每个巧克力的最小花费,O(n3) // 遍历左端点i,维护子数组[i:j]最小值minCost,即每个巧克力->每个巧克力的最小花费 // 将最小花费加到操作次数中 // 从nums[i]取到nums[j]需要操作j-i次,所以最小值加到cost[j-i] for i,x := range nums{ minCost := x for j:=i;j<n+i;j++{ minCost = min(minCost, nums[j%n]) cost[j-i] += minCost } } // 答案就是n次操作cost的最小值 res := math.MaxInt for _,x := range cost{ res = min(res,x) } return int64(res) }
func removeDuplicateLetters(s string) string { // 记录26个字母最后一次出现的位置 last := make([]int,26) for i := range last{ last[i] = -1 } for i,c := range s{ last[int(c)-97] = i } // 单调栈,最后结果应该是a,b,c,d... stack := make([]byte,26) idx := 0 // 如果当前遍历的s[i]已经在栈里,就不要入栈 visit := make([]bool,26) for i := range s{ for idx>0 && s[i]<=stack[idx-1] && last[int(stack[idx-1])-97]>i && !visit[int(s[i])-97]{ visit[int(stack[idx-1])-97] = false idx-- } if !visit[int(s[i])-97]{ visit[int(s[i])-97] = true stack[idx] = s[i] idx++ } } // 返回答案 res := strings.Builder{} res.Write(stack[:idx]) return res.String() }
年份能整除4,且不能整除100,或者能整除400的才是闰年,2月有29天
if (year%4==0 && year%100!=0) || year%400==0{
// 闰年
}
1971年1月1日是周五
这道题根据分治的思想,戳[i:j]的气球中找一个中间气球k,那么dfs(i,j)=dfs(i,k)+nums[i]*nums[k]*nums[j]+dfs(k,j)
,初始条件是dfs(0,n-1)
如何从自顶向下的记忆化搜索改成自底向上的动态规化呢?看下面这个图
这个图像比较形象,记忆化搜索就是递归,从初始条件不断向下递归,而记忆化搜索保证了递归过程的唯一性,那我们就可以自底向上计算,用dp数组保存计算结果,最后计算的就是答案
递归时对k进行遍历,状态规划时需要自底向上计算,i从n-1到0,注意遍历顺序
func maxCoins(nums []int) int { nums = append(append([]int{1},nums...),1) n := len(nums) dp := make([][]int,n) for i := range dp{ dp[i] = make([]int,n) } // 自顶向下的记忆化搜索 // var dfs func(int,int)int // dfs = func(i,j int)int{ // if dp[i][j]!=0{ // return dp[i][j] // } // res := 0 // defer func(){dp[i][j] = res}() // for k:=i+1;k<j;k++{ // res = max(res, dfs(i,k)+nums[i]*nums[k]*nums[j]+dfs(k,j)) // } // return res // } // return dfs(0,n-1) // 自底向上的动态规化 // 注意i,j,k的遍历顺序 for i:=n-1;i>=0;i--{ for j:=i+2;j<n;j++{ for k:=i+1;k<j;k++{ dp[i][j] = max(dp[i][j], dp[i][k]+nums[i]*nums[k]*nums[j]+dp[k][j]) } } } return dp[0][n-1] }
func maxNumber(nums1 []int, nums2 []int, k int) []int { n1,n2 := len(nums1),len(nums2) res := make([]int,k) for i:=max(0,k-n2);i<=min(k,n1);i++{ // 从两个数组得到指定长度的最大子序列 arr1 := getSequence(nums1,i) arr2 := getSequence(nums2,k-i) // 将两个子序列合并 temp := conbined(arr1,arr2) // 维护一个最大的结果 for i,x := range res{ if x<temp[i]{ copy(res,temp) break }else if x>temp[i]{ break } } } return res }
func nthSuperUglyNumber(n int, primes []int) int { m := len(primes) // 保存所有丑数 dp := make([]int,n+1) dp[1] = 1 // 多指针 pointers := make([]int,m) for i := range pointers{ // 最初所有指针都指向1 pointers[i] = 1 } for i:=2;i<=n;i++{ // 求primes与对应指针指向dp乘积的最小值 mn := math.MaxInt for j,k := range pointers{ mn = min(mn,primes[j]*dp[k]) } // 可能不止一组值可以计算出最小值,例如7*2和2*7,对于所有能计算出mn的pointers都需要加一 for j,k := range pointers{ if mn==primes[j]*dp[k]{ pointers[j]++ } } dp[i] = mn } return dp[n] }
对一个数组,例如[1,3,2,2,2,1,1,3,1,1,2],如何快速找到其中位数,即第n/2大的数字,并对数组按中位数排序,排成[<x, =x, >x]的形式,即[1,1,1,1,1,2,2,2,2,3,3]?
分治算法,找任意一个值当中位数x,对数组特定区域排序,如果排序之后,x的下标不在n/2,就移动目标区间,直到找到一个数x,其下标包括了n/2
扩展:如果找的不是中位数,而是第k大的数字,那就把比较的下标换成k
func sortByMedian(arr *[]int){ nums := *arr n := len(nums) // 分治查找中位数 var dfs func(int,int) dfs = func(l,r int){ x := nums[(l+r)/2] // 将nums[l:r]排序成[<x,=x,>x]的形式 i,j := l,l for k:=l;k<=r;k++{ if nums[k]<x{ nums[i],nums[k] = nums[k],nums[i] if j>i{ nums[j],nums[k] = nums[k],nums[j] } i++ j++ }else if nums[k]==x{ nums[j],nums[k] = nums[k],nums[j] j++ } } if i>n/2{ dfs(l,i) }else if j-1<n/2{ dfs(j,r) } } dfs(0,n-1) }
var nums = []int{}
func countBits(n int) []int {
nums = make([]int,n+1)
for i:=1;i<n+1;i++{
// 规律一,去掉i最右边的1
nums[i] = nums[i&(i-1)]+1
// 规律二,右移一位
nums[i] = nums[i>>1]+(i&1)
}
return nums
}
byte是1字节,8位,本质是uint8
rune,占4个字节,共32位,所以它和 uint32 本质上也没有区别
go语言中的int的大小是和操作系统位数相关的,如果是32位操作系统,int类型的大小就是4字节;如果是64位操作系统,int类型的大小就是8个字节
一般int是8字节,与int64相同
注意byte本质是uint,所以可以用位运算,例如交换字符串中两个位置的字符
s[i] ^= s[j]
s[j] ^= s[i]
s[i] ^= s[j]
题目要求数字每一位都不同,思路有两个,直接找出所有满足要求的数字个数,总数字个数-不满足要求的数字个数
注意到n比n-1多了一位,n的结果等于n-1的结果+n位满足要求的数字的个数,例如n=1的结果是10,n=2的结果是10+81,这个81是怎么算出来的呢
对于n位数字,第一位从1-9九个数字中选一个,第二位可以从0-9十个数字中选一个和第一位不同的数字,即9种选择,第三位可以选8个,第四位7个…,所以dp[n]=dp[n-1]+9*9*8*7*...=dp[n-1]+9*A(9,n-1)
Cnk = n!/k!(n-k)!
a^b + (a&b)<<1
,相当于将a+b拆分成无进位加法结果与进位结果的和,可以用递归的方法表示中间的+号func getSum(a int, b int) int {
if a==0 || b==0{
return a^b
}
// return a^b + (a&b)<<1
return getSum(a^b, (a&b)<<1)
}
(nums1[i]+nums2[j], i, j)
(nums1[i]+nums2[0], i, 0)
,第二是注意到nums1和nums2都是非递减的,所以最小值肯定是nums1[0]+nums2[0]
,每次找完最小值将(i, j+1)和(i+1, 0)入堆因为用到最小堆,所以用Python写,方法一:
class Solution: def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: n,m = len(nums1),len(nums2) ans = [] # 最小堆,记录下标对 h = [] # 初始化将nums1的所有下标对(i,0)入堆 for i in range(n): heappush(h,(nums1[i]+nums2[0],i,0)) while h and len(ans)<k: # 最小堆根据第一维元素大小排序 # 堆顶的下标对就是ans下一个结果 _, i, j = heappop(h) ans.append([nums1[i],nums2[j]]) # 移动对应的下标对,并入堆 if j+1<m: heappush(h,(nums1[i]+nums2[j+1],i,j+1)) return ans
方法二:
class Solution: def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: n,m = len(nums1),len(nums2) ans = [] # 最小堆,记录下标对 # 初始化只入堆(nums1[i]+nums2[0],i,0) h = [(nums1[0]+nums2[0],0,0)] while h and len(ans)<k: # 最小堆根据第一维元素大小排序 # 堆顶的下标对就是ans下一个结果 _, i, j = heappop(h) ans.append([nums1[i],nums2[j]]) # 入堆(i+1, 0) # 这里有一个判断j==0,1.避免重复,2.如果j>0,那(i,j)和(i+1,0)大小无法判断 if j==0 and i+1<n: heappush(h,(nums1[i+1]+nums2[0],i+1,0)) # 入堆(i, j+1) if j+1<m: heappush(h,(nums1[i]+nums2[j+1],i,j+1)) return ans
cost(i, j) = min(k + max(cost(i, k-1), cost(k+1, j))
,k的取值范围是[i, j),为什么是左闭右开呢?首先i<j<=n,已知cost(i, i) = 0
,即cost(n, n) = 0
,如果k=j,那就需要求cost(n+1,n),超出范围。var dp = [][]int{} func getMoneyAmount(n int) int { if len(dp)==0{ // 初始化dp m := 201 dp = make([][]int,m) for i:=0;i<m;i++{ dp[i] = make([]int,m) } // 初始化条件,dp[i][i]==0 // dp公式,dp[i:j] = min(dp[i:j],k+max(dp[i:k-1],dp[k+1:j])) for j:=1;j<m;j++{ for i:=j-1;i>=1;i--{ // 初始化一个最大值 dp[i][j] = m*m // 遍历[i,j]的中间值,包括i和j,因为dp[i][i]是已知的初始条件 for k:=i;k<j;k++{ dp[i][j] = min(dp[i][j],k+max(dp[i][k-1],dp[k+1][j])) } } } } return dp[1][n] }
func combinationSum4(nums []int, target int) int { // 与完全背包不同的是,本题的解有顺序之分 // 例如6=1+2+3,[1,2,3]和[3,2,1]是两个不同的答案 f := make([]int,target+1) f[0] = 1 // 由于每个数值可以被选择无限次,因此保证 nums 中的每一位都会被考虑到即可 // 即对组合总和 target 的遍历在外,对数组 nums 的遍历在内 for s:=1;s<target+1;s++{ for _,x := range nums{ if s-x>=0{ f[s] += f[s-x] } } } return f[target] }
解法一,注意1.统计不大于x个数的方法、2.左闭右闭区间二分的写法、3.避免mid==right的取值方法:
func kthSmallest(matrix [][]int, k int) int { // 从左下角或右上角出发,如果大于就往上走,如果小于就往右走 n,m := len(matrix),len(matrix[0]) // 返回小于等于x的数字的个数 count := func(x int)int{ i,j := n-1,0 res := 0 for i>=0 && j<m{ if matrix[i][j]<=x{ res += i+1 j++ }else{ i-=1 } } return res } l,r := matrix[0][0],matrix[n-1][m-1] // [l,r]这种二分的写法 for l<r{ // 可能会出现m==r,从而陷入死循环 // m := (l+r)/2 // 这种写法可以避免m==r这种情况 m := l+(r-l)/2 // fmt.Println(l,m,r) if count(m)>=k{ r = m }else{ l = m+1 } } return l }
解法二,最小堆Python:
class Solution:
def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
n,m = len(matrix),len(matrix[0])
h = [(matrix[i][0],i,0) for i in range(n)]
# heapq.heapify(h)简写成heapify
heapify(h)
for _ in range(k-1):
_,i,j = heappop(h)
if j+1<m:
heappush(h,(matrix[i][j+1],i,j+1))
return heappop(h)[0]
func (this *Solution) GetRandom() int {
res := -1
i,k := 1,1
for cur:=this.Root.Next;cur!=nil;cur=cur.Next{
// 这里应该写成概率的格式 (rand.Intn(i)+1)/i <= k/i
// 但是golang中自动舍去小数,所以直接比较分子即可
if (rand.Intn(i)+1) <= k{
res = cur.Val
}
i++
}
return res
}
type Solution struct { OldNums []int Nums []int } func Constructor(nums []int) Solution { arr := make([]int,len(nums)) copy(arr,nums) return Solution{arr,nums} } func (this *Solution) Reset() []int { copy(this.Nums, this.OldNums) return this.OldNums } func (this *Solution) Shuffle() []int { n := len(this.Nums) for i:=0;i<n;i++{ // 将nums中第i个元素和后n-i个元素中某个元素交换 idx := rand.Intn(n-i)+i this.Nums[idx],this.Nums[i] = this.Nums[i],this.Nums[idx] } return this.Nums }
对于一个整数 number,它的下一个字典序整数对应下面的规则:
尝试在 number 后面附加一个零,即 number×10,如果 number×10≤n,那么说明 number×10 是下一个字典序整数;
如果 number mod 10=9 或 number+1>n,那么说明末尾的数位已经搜索完成,退回上一位,即 number=[number/10],然后继续判断直到 number mod 10≠9 且 number+1≤n 为止,那么 number+1 是下一个字典序整数。
字典序最小的整数为 number=1,我们从它开始,然后依次获取下一个字典序整数,加入结果中。
func lexicalOrder(n int) []int { nums := make([]int,n) num := 1 for i := range nums{ nums[i] = num if num*10<=n{ num *= 10 }else{ for num%10==9 || num+1>n{ num /= 10 } num++ } } return nums }
func lastRemaining(n int) int { // 结果,从第一个元素1开始 res := 1 // true为从左往右,false为从右往左 flag := true // res移动的步长 step := 1 for n>1{ if flag || n%2==1{ res += step } flag = !flag step *= 2 n /= 2 } return res }
func longestSubstring(s string, k int) (res int) { // 滑动窗口,由于求最长子串,所以不能直接遍历r移动l // 对于滑动窗口 s[l:r] ,它的长度一定大于等于k*t,t是出现字符的种类 // 所以对t进行遍历,维护l和r以及实际出现的字符种类tReal,并保存最大长度 for t:=1;t<=26;t++{ nums := make([]int,26) l,tReal := 0,0 for r,c := range s{ if nums[int(c)-97]==0{ tReal++ } nums[int(c)-97]++ for l<r && tReal>t && (r-l+1)>k*t{ nums[int(s[l])-97]-- if nums[int(s[l])-97]==0{ tReal-- } l++ } if (r-l+1)>=k*t && tReal==t && isOk(nums,k){ // 长度对了,种类对了,每个字母出现次数也对了 res = max(res,r-l+1) } } } return res } func isOk(nums []int, k int)bool{ for _,x := range nums{ if x>0 && x<k{return false} } return true }
idx := sort.Search(n, func(num int)bool{return true})
这个函数计算0~n中满足条件的最小的数字,由于题目求小于等于k的最大数字,所以在判断的函数中判断条件是大于k,结果-1即可func findMaximumNumber(k int64, x int) int64 { // 本题可以二分,因为随着数字的增大,结果是非递减的 // 计算1~mid中第a*x位上1的个数,与k进行二分比较 // 如何计算1的个数?数位dp,在二进制的数位上进行dp // l,r := 1,math.MaxInt>>4 // for l<r-1{ // m := (l+r)/2 // if countDigitOne(m)>k{ // r = m // }else{ // l = m // } // } // return int64(l) // 计算1~n在二进制位上1的个数 countDigitOne := func(m int) int64 { n := bits.Len(uint(m)) //数位dp,递归生成所有的数字,递归到终点时返回1的个数 dp := make([][]int,n) for i := range dp{ dp[i] = make([]int,n) for j := range dp[i]{ dp[i][j] = -1 } } //下标,前面填了几个1,是否顶格选值 var dfs func(int,int,bool)int dfs = func(idx,cnt int, isLimit bool)(res int){ if idx>=n{ // 递归完毕,返回计算得到的1的个数 return cnt } if !isLimit{ // 顶格选的值只有一个,所以只记录非顶格选的情况 if dp[idx][cnt]!=-1{ return dp[idx][cnt] }else{ defer func(){dp[idx][cnt] = res}() } } up := 1 if isLimit{ up = m>>(n-1-idx)&1 } // 枚举要填入的数字 for i:=0;i<=up;i++{ if i==1 && (n-idx)%x==0{ res += dfs(idx+1, cnt+1, isLimit&&i==up) }else{ res += dfs(idx+1, cnt, isLimit&&i==up) } } return res } return int64(dfs(0,0,true)) } ans := sort.Search(math.MaxInt>>4, func(num int)bool{return countDigitOne(num)>k})-1 return int64(ans) }
[2,2,3,4]
,那最后结果依然是1,因为可以通过4%3得到1,用这一个1把所有数字都消掉,所以遍历数组,如果有数字取余最小值可以得到更小的数,就返回1func minimumArrayLength(nums []int) int {
sort.Ints(nums)
minn := nums[0]
cnt := 0
for _,x := range nums{
if x==minn{
cnt++
}else if x%minn!=0{
return 1
}
}
return (cnt+1)/2
}
people[i][1]
就是排序的下标。func reconstructQueue(people [][]int) [][]int {
sort.SliceStable(people,func(i,j int)bool{
return people[i][0]>people[j][0] || (people[i][0]==people[j][0] && people[i][1]<people[j][1])
})
for i,arr := range people{
// [[7,0], [7,1], [6,1], [5,0], [5,2],[4,4]]
copy(people[arr[1]+1: i+1], people[arr[1]: i+1])// p[1]的元素后移一位
people[arr[1]] = arr
}
return people
}
/* 填位法: 对于示例[5,2,3,6],二进制[101,010,011,110],k=2 从最高位开始,有两个1,消除这两个1需要2次与操作 第二位,有三个1,需要进行几次与操作使得前两位都是1呢?需要3次与操作,大于k=2,所以第二位是1 第三位,有两个1,由于第二位一定是1了,所以后续无需考虑第二位,需要进行几次与操作使得1、3位都是1呢?,需要2次与操作 所以答案是010,即2 */ func minOrAfterOperations(nums []int, k int) int { res := 0 // 通过掩码来控制是否考虑某些位 mask := 0 // 数组最大值的位数 b := bits.Len(uint(slices.Max(nums))) for i:=b-1;i>=0;i--{ // 考虑第i位,以及前面不是1的位 mask = mask|(1<<i) // 操作次数 cnt := 0 // 每次与操作的结果,-1的二进制全是1 and := -1 // 遍历数字,将选定数位进行与操作,如果当前与操作结果不为0,就说明消耗了1次操作次数 // 举个例子,1,1,1,0,前三位相与为1,消耗三次操作,第四次操作结果为0,那这一段消除的操作次数是3 // 如果打乱顺序,1,1,0,1,前三次操作次数为2,第四个数操作次数为1,在结果为0时重置操作结果 // 如果不止一位,10,01,11,01,前两次结果为0,操作次数为1,后两次结果不为0,操作次数为2 for _,x := range nums{ and = and&(x&mask) if and!=0{ // 操作结果不为0,操作次数+1 cnt++ }else{ // 重置操作结果 and = -1 } } // 如果这一位根本不能消除成0,那消除次数是n,题目条件k<n,所以无需特别判断这种情况 if cnt>k{ // 操作次数大于k,那这一位的结果只能是1,之后无需考虑 mask ^= 1<<i res |= 1<<i } } return res }
/* 滑动窗口有两种: 一种是,right每次往右走,只要不满足条件,left就一直收敛。 一种是,right每次往右走,如果不满足条件,left最多收敛一次(进阶)。 前者用res维护最长子字符串,后者用r-l+1维护最长子字符串,如果当前情况没有贡献,子字符串长度就不变 */ func characterReplacement(s string, k int) int { cnt := [26]int{} l,maxn := 0,0 for r,c := range s{ cnt[int(c)-65]++ // maxn表示的是所有情况下区间最好的元素个数,类似传统解法的res // 如果本次情况区间中最大元素个数小于maxn,那计算的子字符串长度必不可能是最长的 maxn = max(maxn,cnt[int(c)-65]) // 这里只挪动一次,表示本次右指针移动没有对结果产生贡献 if r-l+1-maxn>k{ cnt[int(s[l])-65]-- l++ } } return len(s)-l // 传统的滑动窗口解法,遍历r,移动l直到满足条件,维护最长子字符串 // l,res := 0,0 // for r,c := range s{ // cnt[int(c)-65]++ // maxn := -1 // for _,x := range cnt{ // maxn = max(maxn,x) // } // for l<r && r-l+1-maxn>k{ // cnt[int(s[l])-65]-- // l++ // for _,x := range cnt{ // maxn = max(maxn,x) // } // } // res = max(res,r-l+1) // } // return res }
从[0,n]中找到最小的符合条件的数字
func arrangeCoins(n int) int {
// l,r := 1,n
// for l<r-1{
// m := (l+r)/2
// if cal(m)>n{
// r = m
// }else{
// l = m
// }
// }
// return l
return sort.Search(n, func(m int)bool{m++; return cal(m)>n})
}
func reversePairs(record []int) int { // 在归并排序的合并阶段统计「逆序对」数量 // 完成归并排序时,也随之完成所有逆序对的统计 n := len(record) var dfs func(int,int)int dfs = func(l,r int)int{ if l>=r-1{ return 0 } m := (l+r)/2 cnt := dfs(l,m)+dfs(m,r) // 从temp中选值填入record n1,n2 := m-l,r-l temp := make([]int,n2) copy(temp,record[l:r]) i,j,idx := 0,m-l,l for i<n1 && j<n2{ if temp[i]<=temp[j]{ record[idx] = temp[i] idx++ i++ }else{ record[idx] = temp[j] idx++ j++ // 左大右小,将逆序对添加到结果中 cnt += n1-i } } // 剩下一堆大的直接覆盖到record中即可,逆序对已经在遍历时更新过了 if i<n1{ copy(record[idx:r],temp[i:n1]) } if j<n2{ copy(record[idx:r],temp[j:n2]) } // fmt.Println(cnt,record) return cnt } return dfs(0,n) }
[1,3,2,4,5,6,7,8,9,10]
,单调递减栈是[10 9 8 7 6 5 4 3]
,2出栈成为了k,只有最后一个3成为了j,而i是1func find132pattern(nums []int) bool { // 遍历i,单调递减栈记录最大值j // 当前元素如果要入栈成为j,栈内更小的元素出栈成为k // 出栈的所有元素中将最大值赋给k,以便i有更多选择空间 n := len(nums) if n<=2{ return false } stack := []int{nums[n-1]} k := int(-1e9) for i:=n-2;i>=0;i--{ x := nums[i] if x<k{ // 按照上示解法,默认k<j return true } for len(stack)>0 && x>stack[len(stack)-1]{ // 从单调栈中舍去的j赋给k,这样就保证了k<j // 如果i==j不能舍去,否则会导致j==k k = stack[len(stack)-1] stack = stack[:len(stack)-1] } if x>k{ // 如果i>k,则i有潜力成为j stack = append(stack,x) } } return false }
2*(3*3+1)
,要么3只猪喝2桶水,要么2只猪喝2桶水,要么1只猪喝2桶水,第三轮只需喝一桶水,死了这桶是毒药,没死另外一桶是毒药。func poorPigs(buckets int, minutesToDie int, minutesToTest int) int { // 如果只有1个桶,按照题目条件必有一桶毒药,那直接秒了 if buckets==1{ return 0 } // 最多n轮得出结果 n := minutesToTest/minutesToDie // dp(i,j) 表示 i 只小猪测试 j 轮最多可以在多少桶液体中确定有毒的是哪一桶 // 在确定最大测试轮数为 n 的情况下,需要计算使得 dp(i,n)≥buckets 成立的最小的 i // buckets个桶最坏情况需要buckets只猪进行buckets轮测试 dp := make([][]int,buckets+1) for i := range dp{ dp[i] = make([]int,buckets+1) // 初始条件,0只猪或者0轮测试只可以判断1桶 dp[i][0] = 1 } for j := range dp[0]{ dp[0][j] = 1 } // 对于dp(i,j),测试1轮存活k只猪,k=0~i,i只存活k只,毒液的位置一共有C(i,k)种可能,则有 // dp(i,j) = sum(dp(k,j-1)*C(i,k)) for i:=1;i<=buckets;i++{ for j:=1;j<=n;j++{ for k:=0;k<=i;k++{ dp[i][j] += dp[k][j-1]*C(i,k) } if dp[i][j]>=buckets{ return i } } } return -1 } // 从n中选x,概率论 func C(n,x int)int{ // n*...*(n-(x-1))/x! res := 1 for i:=0;i<x;i++{ res = res*(n-i)/(i+1) } return res }
func poorPigs(buckets int, minutesToDie int, minutesToTest int) int { n := minutesToTest/minutesToDie // 一只小猪能有多少种状态?第一轮就死、第二轮死、...、第n轮死,以及生还,所以一共有n + 1种状态 // n + 1种状态所携带的信息为log_2(n + 1)比特,这也是一只小猪最多提供的信息量 // k只小猪一共可以提供 k*log_2(n + 1) 比特信息量 // buckets瓶液体中哪一瓶是毒这件事,也有buckets种可能性,所以需要的信息量是log_2(buckets) for k:=0.0;;k++{ if k*log2(n+1)>=log2(buckets){ return int(k) } } } func log2(x int)float64{ // Log()以自然常数e为底,用换底公式 return math.Log(float64(x))/math.Log(2) }
func repeatedSubstringPattern(s string) bool {
// 假设s由某个子串至少重复2次构成,则s+s中至少包含1个s
str := s+s
return strings.Contains(str[1:len(str)-1],s)
}
func hammingDistance(x int, y int) int { z := x^y // 方法一,直接使用bits包 return bits.OnesCount(uint(z)) // 方法二,不断将最低位的1加入答案 res := 0 for z>0{ res += z&1 z>>=1 } return res // 方法三,方法二中需要一位一位的右移,能不能一次统计多个位上的1 // 分治的思想,分别统计两位二进制数中的低位数字中1的数量和高位数字中1的数量 // 例如13=0x1101,13&0x5 = 1101&0101 = 0101,13>>1&0x5 = 110&0101 = 0100 // 二者相加,0101+0100=1001,即高两位有10,即2个1,低两位有01,即1个1 // 这是一次统计两位的,然后一次统计四位的、八位的、16位的、32位的,最后得到结果 // 5=0000_0101,3=0000_0011,0f=0000_1111,ff=1111_1111 z = (z & 0x55555555) + ((z >> 1) & 0x55555555) z = (z & 0x33333333) + ((z >> 2) & 0x33333333) z = (z & 0x0f0f0f0f) + ((z >> 4) & 0x0f0f0f0f) z = (z & 0x00ff00ff) + ((z >> 8) & 0x00ff00ff) z = (z & 0x0000ffff) + ((z >> 16) & 0x0000ffff) return z // 方法四,Brian Kernighan算法,x&(x-1)==x去掉最低位的1,这样只遍历1不遍历0 res := 0 for z:=x^y;z>0;z&=z-1{ res++ } return res }
func minMoves2(nums []int) int {
// 结论,x取数组排序后的中间值,所需代价最小
// 首先,x一定在[min,max]这个区间内,排序之后就是在[1:n]之中
// 其次,x在[1:n]中任意一点对于这两点的代价是恒等的,在[2:n-1]、在[3:n-2]...
// 取到最后一个最小的区间就是[n/2,n/2+1],或者[n/2,n/2]
// x取最小的区间对于nums中所有两两成对数的代价是恒定且最小的
sort.Ints(nums)
mid := nums[len(nums)/2]
res := 0
for _,x := range nums{
res += abs(x-mid)
}
return res
}
func abs(x int)int{if x<0{return -x}else{return x}}
func rand10() (res int) {
// rand7是1~7等概分布,要求1~10等概分布
// 构造1~49等概分布,只取前10,此时1~10是等概分布
// 如何构造1~49,如果直接rand7*rand7,那是49个有重复的数字,例如1*4==2*2
// 把一个1~7当成基准量,那我们只需构建出每7步的偏移0/7/14/21/28/35/42
for {
// 1~49等概分布
res = (rand7()-1)*7+rand7()
if res<=10{
// 1~10等概率分布
return res
}
}
return -1
}
func findMaxForm(strs []string, m int, n int) int { dp := make([][]int,m+1) for i := range dp{ dp[i] = make([]int,n+1) } for _,s := range strs{ c0,c1 := 0,0 for _,c := range s{ if c-'0'==0{ c0++ }else{ c1++ } } // 二维背包问题 for i:=m;i>=c0;i--{ for j:=n;j>=c1;j--{ dp[i][j] = max(dp[i][j],dp[i-c0][j-c1]+1) } } } return dp[m][n] }
x=rcosθ, y=rsinθ
。注意如果直接在 [0,1) 范围内生成 r 以及 [0,2π) 范围内生成 θ,得到的随机点是不均匀的,设想半径围绕圆心均匀转动,离圆心越近,点越密集,而float64是有精度的,所以离圆心越近点越密集,被随机到的可能越高。func RandPoint(radius, xCenter, yCenter float64) []float64 {
// 单位圆
r := math.Sqrt(rand.Float64())
sin, cos := math.Sincos(rand.Float64() * 2 * math.Pi)
return []float64{xCenter + r*cos*radius, yCenter + r*sin*radius}
}
class MedianFinder: # 维护一个最大堆min记录比中位数小的值,和一个最小堆max记录比中位数大的值 # 中位数是max(min),或者(max(min)+min(max))/2 # 所以min长度等于max,或者比max大1 # 注意:python的最大堆只维护堆顶元素,直接返回nums[n/2]是行不通的 def __init__(self): self.minArr = list() self.maxArr = list() def addNum(self, num: int) -> None: minArr_,maxArr_ = self.minArr,self.maxArr if not minArr_ or num<=-minArr_[0]: # 初始化,或者小于等于minArr中最大值就加到minArr中 # 最大堆 heappush(minArr_,-num) # 两个堆的长度最多相差1,这样求出来的才是中位数 if len(maxArr_)<len(minArr_)-1: heappush(maxArr_,-heappop(minArr_)) else: # 最小堆 heappush(maxArr_,num) # 根据约定好的,minArr长度要么等于、要么比max大1,不可能大于 if len(maxArr_)>len(minArr_): heappush(minArr_,-heappop(maxArr_)) def findMedian(self) -> float: minArr_,maxArr_ = self.minArr,self.maxArr if len(minArr_)==len(maxArr_): return (-minArr_[0] + maxArr_[0])/2 else: return -minArr_[0]
max<=min<=max+1
。class DualHeap: # 注意初始化方法的函数名,左右各两个_ def __init__(self, k:int): # 维护两个堆,分别记录大于和小于中位数的值, # 中位数就是小于中的最大值,或者其和大于中的最小值的平均 # 由于堆只维护堆顶元素,所以延迟删除旧值,用Counter维护,只删除堆顶的元素 # 由于延迟删除,所以要额外记录两个堆中元素个数,用于判断是否移动堆顶到另一个堆 # 相当于记录有效元素个数,堆中存在无效元素,成为堆顶时会被延迟删除 self.min,self.max = list(),list() # # 记录两个堆中应有的元素个数 self.minn,self.maxn = 0,0 # # 延迟删除 self.delay = Counter() self.k = k def delete(self, hp:List[int]): k,min_,max_,delay = self.k,self.min,self.max,self.delay while hp: num = -hp[0] if hp is self.min else hp[0] if delay[num]>0: delay[num]-=1 heappop(hp) else: break # 调整两个堆中元素个数 def balence(self): k,min_,max_,delay = self.k,self.min,self.max,self.delay # 加入元素之后,调整两个堆元素个数 if self.minn>self.maxn+1: heappush(max_, -min_[0]) heappop(min_) # print("min -> max") self.minn-=1 self.maxn+=1 # min_堆顶元素调走后可能需要删除 self.delete(min_) elif self.maxn>self.minn: heappush(min_, -max_[0]) heappop(max_) # print("max -> min") self.minn+=1 self.maxn-=1 # max_堆顶元素调走后可能需要删除 self.delete(max_) # 向堆中加入元素 def add(self, x:int): k,min_,max_,delay = self.k,self.min,self.max,self.delay if not min_ or x<=-min_[0]: heappush(min_, -x) self.minn+=1 else: heappush(max_, x) self.maxn+=1 # 堆元素个数发生改变 self.balence() # 从堆中删除元素 def remove(self, x:int): k,min_,max_,delay = self.k,self.min,self.max,self.delay delay[x]+=1 if x<=-min_[0]: self.minn-=1 # 从min_中删除 self.delete(min_) else: self.maxn-=1 # 从max_中删除 self.delete(max_) # 堆元素个数发生改变 self.balence() # 获得中位数 def getMedian(self) -> float: k,min_,max_ = self.k,self.min,self.max return -min_[0] if k%2==1 else (-min_[0]+max_[0])/2 class Solution: def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]: # 对方法进行封装,调用添加,删除,获得中位数这三个方法 dualHeap = DualHeap(k) # 先把前k个加入到min_中 for x in nums[:k]: # 加入新元素 dualHeap.add(x) res = [dualHeap.getMedian()] # 加入新元素,删除旧元素,将中间值加入res for i,x in enumerate(nums[k:]): # 加入新元素 dualHeap.add(x) # 删除旧元素 dualHeap.remove(nums[i]) # 获取中位数 res.append(dualHeap.getMedian()) return res
func smallestGoodBase(n string) string { // 假设n可以转化成k进制长为m的111..111,求最小的k // 值n的大小固定的情况下,k越小,m越大 // k最小是2,2^m<=n,n最大10^18,大概是2^10*(18/3)=2^60,m最大60,最小是1 // 注意:在go中我们可以直接求出n二进制的位数,就不用手动计算60了 // 从大到小枚举m,对固定的m,k越小,值越小,呈单调性 // 所以对k进行二分,如果有正好等于n的就return,如果大于n了就continue // k最大是n-1,最小是2,即2<=k<=n-1 // 知道了进制k和长度m如何求十进制的值呢? // res=k^m-1+k^m-2+...+k+1,即每轮res*k+1,重复m轮即可 num,_ := strconv.Atoi(n) cal := func(k,m int)int{ // 进制k长为m的111..111,返回十进制 res := 0 // k进制怎么算?res=k^m-1+k^m-2+...+k+1,即每轮res*k+1 for i:=0;i<m;i++{ // 判断是否超过num,一方面剪枝,一方面防止溢出 if res>(num-1)/k{ // 9223372036854775807 return math.MaxInt } res = res*k+1 } return res } for m:=bits.Len(uint(num));m>=1;m--{ // k=[2,n-1] l,r := 2,num for l<r-1{ k := (l+r)/2 if cal(k,m)>num{ r = k }else{ l = k } } if cal(l,m)==num{ return strconv.Itoa(l) } // fmt.Println(l,m,cal(l,m),num) } return strconv.Itoa(num-1) }
func findMinStep(board string, hand string) int { // 每轮,用哪个球?放在哪个位置最优? // 红+红红、红红+红、红+红+红,这三种情况没有区别,那设定统一放左边 // 每轮放球只有三种可能,与右球颜色相同,与两侧球颜色不同、且两侧球颜色相同,与两侧球颜色不同、且两侧球颜色不同 // 前两种情况都有可能达成最优解,第三种情况并不比前两种更优,下面分别给出两个示例 // 11223311 - 1123,插2插3,只需两个即可消除 // 1122113311 - 23,插2插3,112211(3)331(2)1,直接全消除,所需两个 // 如何实现连续消除? // 遍历桌上球cur,用一个栈维护遍历球的种类和数量,栈中最后一种球last // 每次遍历到cur回头检查last是否颜色不同且超过3个,true则出栈,如果栈空或者cur与last不同,cur入栈且cur++,否则last++ clean := func(s string)string{ n := len(s) // 分别记录种类和数量 stack1 := make([]byte, n) stack2 := make([]int, n) idx := 0 for i := range s{ cur := s[i] for idx>0 && cur!=stack1[idx-1] && stack2[idx-1]>=3{ idx-- } if idx==0 || cur!=stack1[idx-1]{ stack1[idx] = cur stack2[idx] = 1 idx++ }else if cur==stack1[idx-1]{ stack2[idx-1]++ } } for idx>0 && stack2[idx-1]>=3{ idx-- } res := "" for i:=0;i<idx;i++{ res += strings.Repeat(string(stack1[i]), stack2[i]) } return res } // 假设board+hand组成一种状态,不同顺序放球所达到的状态有可能是相同的 // 穷尽所有情况求最少放球数,用广度优先遍历,或者深度+记忆化搜索,我们用广度 // 状态用字符串表示,方便插入消除字符 type states struct{ board string hand string step int } q := []states{states{board,hand,1}} // 已访问过的状态 cnt := map[string]bool{(board+"-"+hand): true} for len(q)>0{ curBoard,curHand,step := q[0].board,q[0].hand,q[0].step for i,c1 := range curBoard{ isUsed := map[byte]bool{} for j,c2 := range curHand{ // 剪枝,对于同一个位置,每种球用几次都是一样的 if isUsed[byte(c2)]{ continue } isUsed[byte(c2)] = true // 两种情况才放球:与右球颜色相同,与两侧球颜色不同、且两侧球颜色相同 if c1==c2 || (c1!=c2 && i>0 && byte(c1)==curBoard[i-1]){ // 将c2插入c1前面 newBoard := curBoard[:i]+string(c2)+curBoard[i:] // 重复消除 newBoard = clean(newBoard) newHand := curHand[:j]+curHand[j+1:] if len(newBoard)==0{ // 全部消除,广度的第一个结果就是最短的 return step } if !cnt[newBoard+"-"+newHand]{ cnt[newBoard+"-"+newHand] = true q = append(q, states{newBoard,newHand,step+1}) } } } } fmt.Println(step) q = q[1:] } return -1 }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。