
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
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]
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
// 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的下标不在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]的形式
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),为什么是左闭右开呢?首先icost(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
// 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
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 lk{
// 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
// 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
return true
}
for len(stack)>0 && x>stack[len(stack)-1]{
// 从单调栈中舍去的j赋给k,这样就保证了k
// 如果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
}