排序

快速排序

快排是不稳定的
快速排序采用的是分治的思想:
图片说明

第二步进行区间的调整,最简单的做法是采取暴力的形式:
图片说明

第二种做法:
图片说明
图片说明

第三种做法--优秀做法
图片说明
分界点为x,i从左边查起,j从右边查起;i++,当i遇到一个大于等于x的值,停下;j--,当j遇到一个小于等于x的值,j停下,然后swap(q[i],q[j]),直到i==j,第一次查询结束,此时区间已经调整完毕

func quickSort(q []int,l int,r int) {
    if l>=r {
        return
    }
    x,i,j := q[l],l-1,r+1
    for i < j {
        for{ //类似于C++中的do...while循环
            i++
            if q[i] >= x {
                break
            }
        }
        for{
            j--
            if q[j] <= x {
                break
            }
        }
        if i < j {
            swap(&q[i],&q[j])
        }
    }
    quickSort(q,l,j)
    quickSort(q,j+1,r)
}
func swap(a,b *int) {
    *a,*b = *b,*a
}

算法性能分析
图片说明

归并排序

归并排序是稳定的
归并排序的中心思想也是分治
图片说明

图片说明
将两个已经排好序的数组合二为一,每次将两个数组进行比较,选择最小的那一个插入结果数组中
图片说明

var n int
var q[]int
var temp [N]int

func mergeSort(q []int,l int,r int) {
    if l >= r {
        return 
    }
    mid := (l + r) >> 1
    mergeSort(q,l mid)
    mergeSort(q,mid+1,r)
    k,i,j := 0,l,mid+1
    for i <= mid && j <= r {
        if q[i]<=q[j] {
            temp[k] = q[i]
            k,i = k++,i++
         }else{
            temp[k] = q[j]
            k,j = k++,j++
        }
    }
    for i <= mid {
        temp[k] = q[i]
        k,i = k++,i++
    }
    for j <= r {
        temp[k] == q[j]
            k,j = k++,j++
        }
    //最后还需要把结果重新存回q数组中
    for i,j = l,0 ; i<=r ;i,j = i++,j++ {
        q[i] = temp[j]
    }
}

算法性能分析
图片说明
图片说明

二分

整数二分

找到一个性质,可以将整个区间一分为二,区间不一定非要满足单调的性质。
二分之后可以寻找边界点
图片说明
1、寻找红色边界点
图片说明

//区间[l,r]被划分成[l,mid-1]和[mid,r]时使用
func search_2(l,r int) int{
    for l < r {
        //这里是为了避免出现死循环的情况,比如l=r-1,此时如果不加1,则mid仍然等于l,因为这里是向下取整
        mid := l + r + 1 >> 1 
        if check(mid) {
            l = mid
        }else{
            r = mid-1
        }
    }
    return l
}

2、寻找绿色边界点
图片说明

//区间[l,r]被划分为[l,mid]和[mid+1,r]时使用
func search_2(l,r int) int{
    for(l<r){
        mid := l + r >> 1
        if check(mid){
            r = mid
        }else{
            l = mid+1
        }
    }
    return l
}

浮点数二分

不需要处理边界问题
图片说明

高精度

用数组将整数的每一位存进去,从低位开始存储

两个大整数相加(两数的位数<=10的6次方)

func add(A []int,B []int) []int{
    var C []int
    t := 0
    for i := 0 ; i < len(A) || i < len(B) ; i++ {
        if i < len(A){t += A[i]}
        if i < len(B){t += B[i]}
        C = append(C,t%10)
        t = t / 10
    }
    if t > 0 {
        C = append(C,t)
    }
    return C
}

两个大整数相减(两数的位数<=10的6次方)

图片说明
若A>=B则直接求A-B,否则求-(B-A )

func cmp(A []int,B []int) bool {
    if len(A) != len(B){
        return len(A) > len(B)
    }
    for i := len(A)-1; i >= 0 ; i-- {
        if A[i] != B[i]{
            return A[i] > B[i]
        }
    }
    return true
}
//A>B
func sub(A []int,B []int) []int{
    var C []int
    for i,t := 0,0 ; i < len(A) ; i++ {
        t = A[i] - t
        if i < len(B){
            t = t-B[i]
        }
        C = append(C,(t+10)%10)
        if t < 0 {
            t = 1
        }else{
            t = 0
        }
    }
    for len(C) > 1 && C[len(C)-1] == 0 {
        C = C[:len(C)-1]
    }
    return C
}

一个大整数*一个较小的数(大数的位数<=10的6次方,小数<=10000)

图片说明
用A的每一位分别和B相乘,再考虑后面的计算

func mul(A []int,b int) []int{
    var C []int
    t := 0 //这是进位
    for i := 0 ; i < len(A) || t > 0 ; i++ {
        if i < len(A) {
            t += A[i]*b
        }
        C = append(C,t%10)
        t = t / 10
    }
    return C
}

一个大整数/一个较小的数

func div(A []int,b int) []int{
    var C []int
    t := 0
    for i := len(A)-1 ; i >= 0 ; i++ {
        t = t *10 + A[i]
        C = append(C,t/b)
        t = t % b
    }
    for len(C) > 1 && C[0] == 0 {
        C = C[1:]
    }
    return C
}

前缀和与差分

一维前缀和

图片说明
前缀和:

for i := 1 ; i <= n ; i ++ {
    S[i] = S[i-1] + a[i]
}

作用:可以用来求一段区间内的和
求[l,r]上的和:S[r]-S[l-1]

var a []int = make([]int,N+1)
var s []int = make([]int,N+1)
s[0] = 0
func pre_sum(l,r int) int {
    for i := 1 ; i <= N ; i++ {
         s[i] = s[i-1] + a[i]
    }
    res := s[r] - s[l-1]
    return res
}

二维前缀和

Si,j是以(i,j)为界点的左上角所有元素的和
图片说明

要求中间蓝色小矩形的和
图片说明

图片说明

var n int
var m int
var a  [][]int = make([][m+1]int,n+1)
var s [][]int = make([][m+1]int,n+1)
func pre_Sum(x1 int,x2 int,y1 int,y2 int) int {
    for i:= 1 ; i <= n ; i++ {
        for j:= 1 ; j<= m ; j++{
            s[i][j] = s[i][j-1] + s[i-1][j] - s[i-1][j-1] + a[i][j] //求前缀和
        }
    }
    res := s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1] //算子矩阵的和
    return res
}

一维差分

已知数组a,构造数组b,使得数组a成为数组b的前缀和,则b就称为a的差分
图片说明

用处:假如现在想让a[l]到a[r]内的数都加上一个常数c,那么只需要让b[l]+c并且b[r+1]-c即可
图片说明

insert(i,i,a[i])是在构造差分数组b
图片说明

二维差分

想让蓝色小方框内的元素都加常数c
图片说明
图片说明
图片说明

位运算

操作一:n>>k & 1

n的二进制表示中第k位是几
图片说明

操作二:lowbit操作

lowbit(x):返回二进制数x的最后一位1
x = 101000
lowbit(x) = 1000 (返回的是一个二进制数,该二进制数的最高位是1)
lowbit(x) = x & -x = x & (~x+1)
图片说明
应用:用来统计x中1的个数
图片说明
图片说明
计算机在底层实现的时候是没有减法的,所以要利用加法来实现减法,因此负数要用补码来进行表示
图片说明

离散化

值域跨度很大,但是值的个数很少

应用
图片说明
图片说明
图片说明
图片说明

区间合并

将所有有交集的区间合并成同一个区间
/*排序+合并
1.先对区间首部从小到大排序。
2.遍历、合并重合区间:
如果cs的尾部小于当前区间的头,即不相交,添加到结果集,并将尾部指到当前区间的尾部。
如果cs的尾部小于等于当前区间的尾,即有重合区域,合并: 将当前区间尾赋到cs的尾。

*/
图片说明

func merge(intervals [][]int) [][]int {
    var res [][]int
    if len(intervals) == 0 || len(intervals[0]) == 0 {
        return res
    }
    sort.Slice(intervals,func(i,j int)bool{
        return intervals[i][0] < intervals[j][0]
    })
    l := intervals[0][0]
    r := intervals[0][1]
    i := 1
    for  i < len(intervals)  {
        if intervals[i][0] > r {
            ans := []int{l,r}
            res = append(res,ans)
            l = intervals[i][0]
            r = intervals[i][1]
        }else{
            r = max(r,intervals[i][1])
        }
        i++
    }
    res = append(res,[]int{l,r})
    return res
}

func max(a,b int) int{
    if a > b {
        return a
    }
    return b
}