名词
#
graph 图
TSP traveling salesman problem 旅行商问题
graph-coloring-problem 图填色问题
combinatorial problems 组合问题
geometric algorithm 几何问题
closest-pair problem 最近对时间
convex-hull problem 凸包问题
numerical problem 数值问题
lexicographic order 字典序
on-line algorithm 联机算法
ADT abstract data type 抽象数据类型
activation record 活动记录, 递归栈所存的信息
stack frame 栈桢, 同 activation record
circular array 循环数组
amoritzed 摊还
biased deletion 偏删除, 二叉树删除节点引起平衡不足问题的删除
symbol table 符号表, 编译器用
tranposition table 变换表, 游戏用
tick 滴答, 模拟的一份时间
external sorting 外部排序
comparison-based sorting 基于比较的排序
transposition table 置换表
排序
#
插入排序(insert sort)
#
/*
插入排序
时间 n^2, 稳定
向前插入,先找位置再移动
*/
func InsertSort(arr []int) {
length := len(arr)
for i := 1; i < length; i++ {
j := i - 1
for j >= 0 {
if arr[j] < arr[i] {
break
}
j--
}
if j != i-1 {
temp := arr[i]
k := i - 1
for ; k > j; k-- {
arr[k+1] = arr[k]
}
arr[k+1] = temp
}
}
}
// 向前插入,边移动边找位置
func InsertSort2(arr []int) {
length := len(arr)
for i := 1; i < length; i++ {
if arr[i-1] > arr[i] {
temp := arr[i]
j := i - 1
for ; j >= 0 && arr[j] > temp; j-- {
arr[j+1] = arr[j]
}
arr[j+1] = temp
}
}
}
// 向前插入,值向前冒泡
func InsertSort3(arr []int) {
length := len(arr)
for i := 1; i < length; i++ {
for j := i - 1; j >= 0 && arr[j] > arr[j+1]; j-- {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
冒泡排序(bubble sort)
#
/*
时间 n^2, 稳定
*/
func BubbleSort(arr []int) {
length := len(arr)
for i := 0; i < length; i++ {
for j := 1; j < length-i; j++ {
if arr[j-1] > arr[j] {
arr[j-1], arr[j] = arr[j], arr[j-1]
}
}
}
}
func BubbleSortSwapFlag(arr []int) {
flag := true
for i := len(arr); flag == true; i-- {
flag = false
for j := 1; j < i; j++ {
if arr[j-1] > arr[j] {
arr[j-1], arr[j] = arr[j], arr[j-1]
flag = true
}
}
}
}
func BubbleSortTailFlag(arr []int) {
flag := len(arr)
for flag > 1 {
i := flag
flag = 0
for j := 1; j < i; j++ {
if arr[j-1] > arr[j] {
arr[j-1], arr[j] = arr[j], arr[j-1]
flag = j
}
}
}
}
选择排序(select sort)
#
/*
时间 n^2, 不稳定
*/
func SelectSort(arr []int) {
length := len(arr)
for i := 0; i < length; i++ {
ind := i
for j := i + 1; j < length; j++ {
if arr[ind] > arr[j] {
ind = j
}
}
arr[i], arr[ind] = arr[ind], arr[i]
}
}
希尔排序(shell sort)
#
概念
diminishing increment sort(缩减增量排序)
increment sequence(增量序列)
/*
1 循环:选gap为一半长度
2 循环:第一段gap
3 循环gap间隔序列
4 选择排序
时间 n^(1.3-2), 不稳定
*/
func ShellSort(arr []int) {
length := len(arr)
for gap := length / 2; gap > 0; gap /= 2 {
for i := 0; i < gap; i++ {
for j := i + gap; j < length; j += gap {
k := j - gap
if arr[k] > arr[j] {
temp := arr[j]
for k >= 0 && arr[k] > temp {
arr[k+gap] = arr[k]
k -= gap
}
arr[k+gap] = temp
}
}
}
}
}
/*
1 循环:选gap为一半长度
2 循环:第一个gap之后每个元素为j
3 j向前的gap间隔序列做选择排序
*/
func ShellSort2(arr []int) {
length := len(arr)
for gap := length / 2; gap > 0; gap /= 2 {
for j := gap; j < length; j++ {
k := j - gap
if arr[k] > arr[j] {
temp := arr[j]
for k >= 0 && arr[k] > temp {
arr[k+gap] = arr[k]
k -= gap
}
arr[k+gap] = temp
}
}
}
}
/*
..
最后用冒泡排序
*/
func ShellSort3(arr []int) {
length := len(arr)
for gap := length / 2; gap > 0; gap /= 2 {
for i := gap; i < length; i++ {
for j := i - gap; j >= 0 && arr[j] > arr[j+gap]; j -= gap {
arr[j], arr[j+gap] = arr[j+gap], arr[j]
}
}
}
}
桶排序(bucket sort)
#
介绍
将数据分到有限数量的桶子里,每个桶分别排序(可能再使用别的排序办法)
当数据均匀分配时,时间复杂度是O(n), 不受O(nlogn)下限的影响
适用于小范围、独立均匀分布的整数数据。可以计算数据量大,符合线性期望时间的排序
步骤
# 排序7, 36, 65, 56, 33, 60, 110, 42, 42, 94, 59, 22, 83, 84, 63, 77, 67, 101
设置5个桶, 找到最大值110, 最小值7, 每个桶范围是(110 - 7 + 1)/5 = 20.8
遍历原始数据,以链表结构分组放入桶中,公式为: 桶编号n = floor((a - 7) / 20.8)
桶第二次插入数据时,进行插入排序的一次插入
拼接5个桶
快速排序(quick sort)
#
/*
分割,快排左右
分割:
1 取轴
2 中位为start(轴本身占1位),循环除轴的节点
2.1 节点值小时,中位后移,交换节点和中位的值
3 交换轴与中位的值
时间 n^2, 平均时间 nlog(n), 不稳定
*/
func QuickSort(arr []int, start int, end int) {
swap := func(i int, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
partition := func(start int, end int) int {
pivot := start
val := arr[start]
pos := start
for i := start + 1; i <= end; i++ {
if arr[i] < val {
pos++
swap(pos, i)
}
}
swap(pivot, pos)
return pos
}
if start < end {
pivot := partition(start, end)
QuickSort(arr, start, pivot-1)
QuickSort(arr, pivot+1, end)
}
}
/*
..
双指针法分割
1 取轴为开头(或交换到开头),开头为左点,缓存值
2 向中循环
2.1 找到右点,向左点赋值
2.2 找到左点,向右点赋值
3. 找到中点,赋缓存值
*/
func QuickSort2(arr []int, start int, end int) {
partition := func(start int, end int) int {
i := start
j := end
val := arr[start]
for i < j {
for i < j && arr[j] >= val {
j--
}
if i < j {
arr[i] = arr[j]
i++
}
for i < j && arr[i] < val {
i++
}
if i < j {
arr[j] = arr[i]
j--
}
}
arr[i] = val
return i
}
if start < end {
pivot := partition(start, end)
QuickSort2(arr, start, pivot-1)
QuickSort2(arr, pivot+1, end)
}
}
test
func TestQuickSort(t *testing.T) {
assert := assert.New(t)
arr := []int{8, 2, 4, 65, 2, 4, 7, 1, 9, 0, 2, 34, 12}
QuickSort(arr, 0, len(arr)-1)
assert.Equal([]int{0, 1, 2, 2, 2, 4, 4, 7, 8, 9, 12, 34, 65}, arr)
}
堆排序(heap sort)
#
/*
数组表示大顶堆,pos为父节点时, pos*2+1为第一个子节点
调整堆:父值非最大时下降
1 给出父节点pos,找到儿子child。
2 循环直到child越界
2.1 找大儿子下降,更新pos,计算child
构建堆: 默认给出数组为堆,从最后一个父节点pos=halfLen开始,向前一个个父节点调整堆
堆排序:
1 构建堆,重复2
2 循环堆节点
2.1 交换堆顶与末节点叶子, 下降堆顶
时间 nlog(n), 不稳定
*/
func HeapSort(elements []int) {
swap := func(i int, j int) {
elements[i], elements[j] = elements[j], elements[i]
}
headAdjust := func(pos int, len int) {
val := elements[pos]
child := pos*2 + 1
for child < len {
if child+1 < len && elements[child] < elements[child+1] {
child++
}
if elements[pos] >= elements[child] {
break
}
elements[pos] = elements[child]
pos = child
child = pos*2 + 1
elements[pos] = val
}
}
buildHeap := func() {
halfLen := len(elements) / 2
for i := halfLen; i >= 0; i-- {
headAdjust(i, len(elements))
}
}
buildHeap()
for i := len(elements) - 1; i > 0; i-- {
swap(0, i)
headAdjust(0, i)
}
}
func HeapSort2(arr []int) {
length := len(arr)
swap := func(i int, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
parentPos := func(pos int) int {
return (pos - 1) / 2
}
child1Pos := func(pos int) int {
return pos*2 + 1
}
fixDown := func(pos int, l int) {
val := arr[pos]
child := child1Pos(pos)
for child < l {
if child+1 < l && arr[child+1] > arr[child] {
child++
}
if arr[child] <= val {
break
}
arr[pos] = arr[child]
pos = child
child = child1Pos(pos)
}
arr[pos] = val
}
buildHeap := func() {
lastParentPos := parentPos(length - 1)
for i := lastParentPos; i >= 0; i-- {
fixDown(i, length)
}
}
buildHeap()
for i := length - 1; i > 0; i-- {
swap(0, i)
fixDown(0, i)
}
//fixUp := func(child int) {
// val := arr[child]
// pos := parentPos(child)
// for pos >= 0 && child > 0 {
// if arr[pos] >= val {
// break
// }
// arr[child] = arr[pos]
// child = pos
// pos = parentPos(child)
// }
// arr[child] = val
//}
}
test
func TestHeapsort(t *testing.T) {
assert := assert.New(t)
elements := []int{3, 1, 5, 7, 2, 4, 9, 6, 10, 8, 33, 2, 21, 2, 15, 22, 77, 11, 0, -1, 23345, 12}
HeapSort(elements)
assert.Equal(
[]int{-1, 0, 1, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 21, 22, 33, 77, 23345},
elements,
)
}
归并排序(merge sort)
#
/*
时间 nlog(n)
*/
func MergeSort(arr []int) {
length := len(arr)
temp := make([]int, length)
merge := func(start int, mid int, end int) {
i := start
j := mid + 1
m := mid
n := end
k := 0
for i <= m && j <= n {
if arr[i] <= arr[j] {
temp[k] = arr[i]
k++
i++
} else {
temp[k] = arr[j]
k++
j++
}
}
for i <= m {
temp[k] = arr[i]
k++
i++
}
for j <= n {
temp[k] = arr[j]
k++
j++
}
for r := 0; r < k; r++ {
arr[start+r] = temp[r]
}
}
recurse := func(start int, end int) {}
recurse = func(start int, end int) {
if start < end {
mid := (start + end) / 2
recurse(start, mid)
recurse(mid+1, end)
merge(start, mid, end)
}
}
recurse(0, length-1)
}
查找
#
二分查找(binary search)
#
/*
时间 log(n)
*/
func BinarySearch(nums []int, low int, high int, val int) int {
for low <= high {
mid := (low + high) / 2
if nums[mid] == val {
return mid
} else if nums[mid] > val {
high = mid - 1
} else {
low = mid + 1
}
}
return -1
}
/*
递归
*/
func BinarySearch2(nums []int, low int, high int, val int) int {
if low > high {
return -1
}
mid := (low + high) / 2
if nums[mid] == val {
return mid
} else if nums[mid] > val {
return BinarySearch2(nums, low, mid-1, val)
} else {
return BinarySearch2(nums, mid+1, high, val)
}
}
topK
#
/*
第k大数
用快排划分
时间 n, 空间 n
*/
func topK(arr []int, low int, high int, k int) {
start, end, mid := low, high, arr[low]
for low < high {
for low < high && arr[high] <= mid {
high--
}
if low < high {
arr[low] = arr[high]
low++
}
for low < high && arr[low] >= mid {
low++
}
if low < high {
arr[high] = arr[low]
high--
}
}
arr[low] = mid
if low > k {
topK(arr, start, low-1, k)
} else if low < k {
topK(arr, low+1, end, k)
}
}
/*
大顶堆
时间 nlog(k), 空间k
*/
func topK2(arr []int, k int) []int {
heap := make([]int, k)
for i := 0; i < k; i++ {
heap[i] = arr[i]
}
adjust := func(start int, end int) {
parent := start
child := parent*2 + 1
for child <= end {
if child+1 <= end && heap[child+1] < heap[child] {
child++
}
if heap[parent] <= heap[child] {
return
}
heap[parent], heap[child] = heap[child], heap[parent]
parent = child
child = parent*2 + 1
}
}
buildHeap := func() {
for i := k / 2; i >= 0; i-- {
adjust(i, k-1)
}
}
buildHeap()
for i := k; i < len(arr); i++ {
if arr[i] > heap[0] {
heap[0] = arr[i]
adjust(0, k-1)
}
}
return heap
}
test
func main() {
arr := []int{1, 10, 2, 20, 3, 30, 4, 40, 5, 50, 6, 60, 7, 70, 8, 80, 9, 90}
topK(arr, 0, 17, 10)
fmt.Print(arr)
}
倒排索引
#
数字
#
斐波那契
#
/*
返回第n个斐波那契数
时间 n
*/
func Fibonacci(n int) int {
if n < 3 {
return 1
}
a, b := 1, 1
for i := 3; i <= n; i++ {
r := a + b
a, b = b, r
}
return b
}
/*
时间 n
*/
func Fibonacci2(n int) int {
if n < 3 {
return 1
}
return Fibonacci2(n-2) + Fibonacci2(n-1)
}
正整数和
#
/*
正整数和
递归
*/
func CommonSum(n int) int {
if n == 1 {
return 1
}
return CommonSum(n-1) + n
}
最大公约数/最小公倍数
#
/*
最大公约数
每执行一次循环, m或n至少缩小了2倍,故时间复杂度上限为log(2)(M),M是循环次数
对于大量随机测试样例, 每次循环能便m与n值缩小一个10进位,所以平均复杂度为O(lgM)
*/
func GCD(m int, n int) int {
var max, min int
if m > n {
max, min = m, n
} else {
max, min = n, m
}
for max%min != 0 {
r := max % min
max = min
min = r
}
return min
}
/*
..
递归
时间 logM 空间 logM
*/
func GCD2(m int, n int) int {
var max, min int
if m > n {
max, min = m, n
} else {
max, min = n, m
}
if max%min == 0 {
return min
}
return GCD2(min, max%min)
}
/*
最小公倍数
*/
func LCM(m int, n int) int {
return m * n / GCD(m, n)
}
最大子序列和
#
/*
最大子序列和
时间 n
*/
func SumArr(arr []int) int {
tmp := arr[0]
max := tmp
for i := 1; i < len(arr); i++ {
if tmp >= 0 {
tmp += arr[i]
} else {
tmp = arr[i]
}
if max < tmp {
max = tmp
}
}
return max
}
判断素数
#
/*
判断素数
合数一定有因子小于sqrt(n)
时间 n
*/
func isPrime(n int) bool {
if n < 2 {
return false
}
m := int(math.Sqrt(float64(n)))
for i := 2; i <= m; i++ {
if n%i == 0 {
return false
}
}
return true
}
/*
arr中false的是素数
j从i开始, 因小于i的数已筛过
时间 n*loglog(n)
n(1/2 + 1/3 + ... 1/n)
*/
func filterPrime(n int) {
if n < 2 {
return
}
arr := make([]bool, n+1)
m := int(math.Sqrt(float64(n)))
for i := 2; i <= m; i++ {
for j := i; i*j <= n; j++ {
arr[i*j] = true
}
}
}
连续正整数和
#
/*
描述:给正整数s, 写出连续正整数和为s的所有序列
双指针法
由等差数列求和公式知,数列最开始不能过一半
移end加sum, 移start减sum
sum找到后,只能同时加减sum,即同时移start、end
*/
func SeriesSum(s int) {
half := (s + 1) / 2
start := 1
end := 2
sum := 0
for start < half {
sum = (start + end) * (end - start + 1) / 2
if sum == s {
println(start, end)
start++
end++
} else if sum < s {
end++
} else {
start ++
}
}
}
有序数组两元素和
#
/*
升序不重复数组中,所有两个元素和为s的组合
时间 n^2
*/
func TwoSum(arr []int, s int) {
n := len(arr)
for i := 0; i < n-1; i++ {
for j := i + 1; j < n; j++ {
if arr[i]+arr[j] == s {
println(i, j)
break
}
}
}
}
/*
二分查找
时间 nlog(n)
*/
func TwoSum2(arr []int, s int) {
n := len(arr)
for i, j := 0, 0; i < n-1; i++ {
another := s - arr[i]
j = sort.SearchInts(arr, another)
if j > i {
println(i, j)
}
}
}
/*
双指针
移i加sum, 移j减sum
sum找到后,只能同时加减sum,即同时移i,j
时间 n
*/
func TwoSum3(arr []int, s int) {
i := 0
j := len(arr) - 1
for i < j {
if arr[i]+arr[j] == s {
println(i, j)
i++
j--
} else if arr[i]+arr[j] < s {
i++
} else {
j--
}
}
}
test
func TestTwoSum(t *testing.T) {
TwoSum([]int{ 1, 2, 3, 4, 5, 6, 7, 8, 9 },10)
}
猴子吃桃
#
/*
猴子吃桃:每天吃一半加1个,第n天剩1个,一共几个
*/
func MonkeyEat(n int) int {
if n == 1 { //指倒数第一天
return 1
}
return MonkeyEat(n-1)*2 + 2
}
滑动平均数
#
/*
滑动平均数
先算sum数组
隔n位的两sum之差即此n位的sum
除位数取均值
*/
func movingAverage(arr []float64, n int) {
sum := 0.0
for i, _ := range arr {
arr[i] += sum
sum = arr[i]
}
result := make([]float64, len(arr))
for i, v := range arr {
start := i - n
count := n
if start < 0 {
count += start + 1
start = 0
}
result[i] = (v - arr[start]) / float64(count)
}
}
爬楼梯
#
/*
爬楼梯问题:一次走1或2级,爬上n级有多少走法
要走到n+1层,只能从n层或n-1层走。n+1层方法数为n层与n-1层方法数的和。所以是个斐波那契数列问题
*/
func ClimbStairs(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 2
}
return ClimbStairs(n-1) + ClimbStairs(n-2)
}
几瓶汽水
#
/*
1元两瓶水, 两空瓶一瓶水, n元几瓶水
*/
func sodaBottle(n int) int {
if n == 0 {
return 0
}
bottle := 0
sum := 0
for i := 1; i <= n; i++ {
sum++
bottle += 1
}
return sodaBottle(bottle/2) + sum
}
数组
#
翻转
#
/*
翻转数组
双指针
时间 n,空间 1
*/
func Inverse(arr []int) {
for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
arr[i], arr[j] = arr[j], arr[i]
}
}
/*
单指针
*/
func Inverse2(arr []int) {
length := len(arr)
for i, half := 0, length/2; i < half; i++ {
j := length - 1 - i
arr[i], arr[j] = arr[j], arr[i]
}
}
轮换
#
/*
轮换,如:[1,2,3,4,5],指定2时,结果[4,5,1,2,3]
翻转前段,翻转后段,再整体翻转
时间 n,空间 1
*/
func Rotate(arr []int, k int) {
if k == 0 {
return
}
length := len(arr)
if k >= length {
k = k % length
}
inverse := func(start int, end int) {
for i, j := start, end; i < j; i, j = i+1, j-1 {
arr[i], arr[j] = arr[j], arr[i]
}
}
inverse(0, length-1-k)
inverse(length-k, length-1)
inverse(0, length-1)
}
洗牌
#
/*
洗牌(shuffle) Fisher-Yates
随机选择, 删除, 插入新数组
*/
func shuffle(arr []int) {
rand.Seed(time.Now().UnixNano())
length := len(arr)
result := make([]int, length)
for i := 0; i < length; i++ {
k := rand.Intn(len(arr))
result[i] = arr[k]
arr = append(arr[:k], arr[k+1:]...)
}
}
/*
洗牌 Knuth-Durstenfeld
随机选择, 放尾部
*/
func shuffle2(arr []int) {
rand.Seed(time.Now().UnixNano())
length := len(arr)
for i := length - 1; i >= 0; i-- {
k := rand.Intn(i + 1)
arr[k], arr[i] = arr[i], arr[k]
}
fmt.Print(arr)
}
/*
洗牌 Inside-Out Algorithm
复制到新数组
遍历原数组位置i, 随机插入新数组(新数组位置i存插入前数据)
*/
func shuffle3(arr []int) {
rand.Seed(time.Now().UnixNano())
length := len(arr)
result := make([]int, length)
for i, v := range arr {
result[i] = v
}
for i := 0; i < length; i++ {
k := rand.Intn(i + 1)
result[i] = result[k]
result[k] = arr[i]
}
fmt.Print(result)
}
有序数组去重
#
/*
有序数组去重
时间 n,空间 n
*/
func RemoveDuplicates(arr []int) int {
if arr == nil || len(arr) == 0 {
return 0
}
if len(arr) == 1 {
return 1
}
end := len(arr) - 1
list0 := list.New()
for i := 0; i <= end; {
if i == end {
list0.PushBack(arr[i])
i++
} else {
j := i + 1
if arr[i] == arr[j] {
for j <= end && arr[i] == arr[j] {
j++
}
}
list0.PushBack(arr[i])
i = j
}
}
eleInd := 0
for ele := list0.Front(); nil != ele; ele = ele.Next() {
arr[eleInd] = ele.Value.(int)
eleInd++
}
return eleInd
}
/*
..
时间 n,空间 1
*/
func RemoveDuplicates2(arr []int) int {
if arr == nil || len(arr) == 0 {
return 0
}
if len(arr) == 1 {
return 1
}
length := len(arr)
num := 1
for i, tmp := 1, arr[0]; i < length; i++ {
if arr[i] != tmp {
arr[num] = arr[i]
tmp = arr[i]
num++
}
}
return num
}
数组积水
#
// 无论中间多少高低柱子,当前点积水量都是min(最左, 最右)高度
func savewater(arr []int) int {
point_l := 0
max_l := arr[0]
point_r := len(arr) - 1
max_r := arr[len(arr)-1]
volume := 0
for point_l < point_r {
if max_l < max_r {
point_l++
if max_l < arr[point_l] {
max_l = arr[point_l]
} else {
volume = volume + max_l - arr[point_l]
}
} else {
point_r--
if max_r < arr[point_r] {
max_r = arr[point_r]
} else {
volume = volume + max_r - arr[point_r]
}
}
}
return volume
}
test
func main() {
arr := []int{1, 2, 3, 4, 2, 1, 1, 5, 3, 2,4}
volume := savewater(arr)
fmt.Println(volume)
}
字符串
#
字符统计
#
/*
字符统计
*/
func charSum(s string) {
sumMap := make([]int, 26)
for _, c := range s {
sumMap[c-97]++
}
fmt.Print(sumMap)
}
最后一词长度
#
func LastWordLen(s string) int {
isLetter := func(c uint8) bool {
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
return true
} else {
return false
}
}
length := len(s)
if length < 1 {
return 1
}
pos := length - 1
for pos >= 0 {
if isLetter(s[pos]) {
break
} else {
pos--
}
}
retLen := 0
for pos >= 0 {
if !isLetter(s[pos]){
break
} else {
pos--
retLen++
}
}
return retLen
}
翻转词序
#
/*
翻转词序
*/
func ReverseWords(s []rune) {
reverse := func(start int, end int) {
for start < end {
s[start], s[end] = s[end], s[start]
start++
end--
}
}
if s == nil || len(s) <= 1 {
return
}
n := len(s)
i := 0
for i < n {
j := i
for j < n {
if s[j] == ' ' {
break
} else {
j++
}
}
reverse(i, j-1)
for j < n && s[j] == ' ' {
j++
}
i = j
}
reverse(0, n-1)
}
test
func TestReverseWords(t *testing.T) {
assert := assert.New(t)
s := []rune("in the world")
ReverseWords(s)
assert.Equal([]rune("world the in"), s)
}
同字异序
#
const ALPHABET_LENGHT = 26
/*
是否同字异序, 如eel, lee
*/
func Anagram(source string, target string) bool {
if len(source) != len(target) {
return false
}
length := len(source)
table1 := make([]int, ALPHABET_LENGHT)
table2 := make([]int, ALPHABET_LENGHT)
for i := 0; i < length; i++ {
table1[source[i]-'a']++
table2[target[i]-'a']++
}
for i := 0; i < ALPHABET_LENGHT; i++ {
if table1[i] != table2[i] {
return false
}
}
return true
}
count and say
#
/*
题目解释:原题的意思就是用一个新的字符串描述上一个字符串,用数字表示上一个:
当n=1时:输出1;
当n=2时,解释1,1读作1个 ,表示为11;
当n=3时,解释上一个11,读作2个1,表示为21;(注意相同数字的描述)
当n=4时,解释上一个21,读作1个2,一个1,表示为1211;
当n=5时,解释上一个1211,读作1个1,1个2,2个1,表示为111221;
当n=6时,解释上一个111221,读作3个1,2个2,1个1,表示为312211;
时间 n^3, 空间 n^2, n是输入的值
*/
func CountAndSay(n int) string {
if n <= 0 {
return ""
}
if n == 1 {
return "1"
}
if n == 2 {
return "11"
}
s := "11"
result := ""
for i := 3; i <= n; i++ {
temp := s[0]
count := 1
for j := 1; j < len(s); j++ { // len的时间复杂度在for中越来越接近n^2
if s[j] == temp {
count++
} else {
// result的长度越来越接近n^2
result += strconv.Itoa(count) + strconv.Itoa(int(temp-48))
count = 1
temp = s[j]
}
}
result += strconv.Itoa(count) + strconv.Itoa(int(temp-48))
s = result
result = ""
}
return s
}
回文
#
/*
回文
*/
func IsPalindrome(s string) bool {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
if s[i] != s[j] {
return false
}
}
return true
}
/*
回文数
1. 计算位
2. 循环到half
2.1 判断高低位余数。
如 num = 12321, 则 t = 10000
第一步 1 == (12321 / 10000) % 10 == 12321 % 10
t /= 10 , 12321 /= 10
第二步 2 == (12321 / 1000) % 10 == 1232 % 10
判断成功
*/
func IsPalindromeNum(num int) bool {
if num < 10 {
return true
}
countWei := func() int {
num := num
count := 0
for num > 0 {
num /= 10
count++
}
return count
}
wei := countWei()
t := math.Pow(10, float64(wei-1))
half := wei / 2
n := num
for i := 0; i < half; i++ {
if (num/int(t))%10 == n%10 {
t /= 10
n /= 10
} else {
return false
}
}
return true
}
/*
最长回文子串
时间 n^3
*/
func LongestPalindrome(s string) string {
isPalindrome := func(start int, end int) bool {
for start < end {
if s[start] != s[end] {
return false
}
start++
end--
}
return true
}
length := len(s)
from, to, max := 0, 0, 0
for i := 0; i < length; i++ {
for j := i; j < length; j++ {
if isPalindrome(i, j) && (j-i) > max {
from, to = i, j
max = to - from
}
}
}
return s[from : to+1]
}
/*
中心扩展法
1 循环所有元素i(i为中心)
2 循环向两边扩展 start, end。(start=i, end=i+1查找偶数串)
2.1 记录left, right回文子串
3 记录此i中心边缘子串是否最大
*2 ..(start=i-1, end=i+1查找奇数串)
*3 ..
时间 n^2,空间 1
*/
func LongestPalindrome2(s string) string {
maxLeft, maxRight, max := 0, 0, 1
length := len(s)
for i := 0; i < length; i++ {
start, end, length1 := i, i+1, 0
left, right := start, end
for start >= 0 && end < length {
if s[start] == s[end] {
left, right, length1 = start, end, length1+2
start, end = start-1, end+1
} else {
break
}
}
if length1 > max {
maxLeft, maxRight, max = left, right, length1
}
start, end, length1 = i-1, i+1, 1
left, right = start, end
for start >= 0 && end < length {
if s[start] == s[end] {
left, right, length1 = start, end, length1+2
start, end = start-1, end+1
} else {
break
}
}
if length1 > max {
maxLeft, maxRight, max = left, right, length1
}
}
return s[maxLeft : maxRight+1]
}
test
func TestLongestPalindrome(t *testing.T) {
assert := assert.New(t)
assert.Equal("woabcbaow", LongestPalindrome("sdbsdaswoabcbaowe"))
}
func TestLongestPalindrome2(t *testing.T) {
assert := assert.New(t)
assert.Equal("woabcbaow", LongestPalindrome2("sdbsdaswoabcbaowe"))
}
模式匹配
#
/*
模式匹配,brute force法
时间 m*n,空间1
*/
func BruteForce(s string, matcher string) int {
m := len(s)
n := len(matcher)
for i := 0; i < m; i++ {
count := 0
for j := 0; j < n && i+j < m; j++ {
if s[i+j] != matcher[j] {
break
} else {
count++
}
}
if count == n {
return i
}
}
return -1
}
/*
时间m+n
*/
func KMP() {
}
BFS(breadth first search)
#
DFS(depth first search)
#
A*
#
启发式搜索
Dijkstra
#
Bellman-Ford
#
Floyd-Warshall
#
走迷宫
#
/*
检测方向 -> 走 -> 递归 -> 回退
*/
const (
UP = iota
DOWN
LEFT
RIGHT
)
const MAX = 10
type Direction int
var m = [MAX][MAX]int{
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 1, 0, 1, 1, 1, 1},
{1, 0, 1, 0, 1, 0, 1, 1, 1, 1},
{1, 0, 1, 0, 0, 0, 0, 0, 1, 1},
{1, 0, 1, 0, 1, 1, 0, 1, 1, 1},
{1, 0, 1, 0, 0, 1, 0, 1, 1, 1},
{1, 0, 1, 1, 0, 1, 0, 0, 0, 1},
{1, 0, 1, 1, 1, 1, 1, 1, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 1, 1, 1, 1, 1, 1, 0, 1},
}
func printM() {
emt := " #R"
print(" ")
for j := 0; j < MAX; j++ {
fmt.Printf(" %d", j)
}
fmt.Println()
for i := 0; i < MAX; i++ {
fmt.Printf("%d", i)
for j := 0; j < MAX; j++ {
fmt.Printf("%c ", emt[m[i][j]])
}
fmt.Println()
}
}
func move(x *int, y *int, d Direction) {
switch d {
case UP:
*x--
break
case DOWN:
*x++
break
case LEFT:
*y--
break
case RIGHT:
*y++
break
}
}
func inspect(x int, y int, d Direction) bool {
move(&x, &y, d)
if x < 0 || y < 0 || x >= MAX || y >= MAX || m[x][y] != 0 {
return false
}
return true
}
func printProcess(n int) {
fmt.Printf("步数:%d\n", n)
printM()
}
func maze(s [2]int, e [2]int, n int) {
if s[0] == e[0] && s[1] == e[1] {
printProcess(n)
}
for i := Direction(0); i < 4; i++ {
if inspect(s[0], s[1], i) {
tmpX, tmpY := s[0], s[1]
move(&tmpX, &tmpY, i)
m[tmpX][tmpY] = 2
maze([2]int{tmpX, tmpY}, e, n+1)
m[tmpX][tmpY] = 0
}
}
}
func main() {
s, e := [2]int{9, 1}, [2]int{9, 8}
m[s[0]][s[1]] = 2
maze(s, e, 0)
println()
}
动态规划
#
背包问题
#
背包有大小,物品有大小和价值,向背包装最大价值物品
最长公共子串
#
捏小球
#
/*
重量不同多个小球,合并用力为重量和。两两合并到剩一个,总用力最小。写代码求最优
公式 max[n] = max[n-1] + min2(arr)
每次捏重量最小两球,都达到全局最优
*/
func cutMin(arr []int) (int, []int) {
min := 0
for ind := range arr {
if arr[ind] < arr[min] {
min = ind
}
}
v := arr[min]
return v, append(arr[:min], arr[min+1:]...)
}
func pinch(arr []int) int {
if len(arr) == 1 {
return 0
}
v1, arr := cutMin(arr)
v2, arr := cutMin(arr)
return v1 + v2 + pinch(append(arr, v1+v2))
}
func main() {
print(pinch([]int{2, 3, 1, 4}))
}
分类与回归
#
分类是编组,回归是预测
k最近邻(KNN, k-nearest neighbours)
#
实现
特征抽取
应用
推荐系统
贝叶斯
#
概率算法
#
布隆过滤器
#
介绍
判断值一定不存在,或可能存在
高效插入和查询
占空间中,hash占空间大
实现
bitmap,多个hash函数
插入值计算多个hash, bit数组位置变1
查找值计算多个hash, 找bit数组位置是否全为1
不可删除
拆分到多bitmap时,key先hash到bitmap
应用
redis用setbit()和getbit()
hyperLogLog
#
介绍
来值, 去重计数(基数)
占空间小, hash占空间大
实现
来值, 计算hash再转bit数组,取前导0的数量n。
比较保存所有n的最大值为max
基数就为 2^(max+1)
o-> 减误差, 用桶
分桶m个,来值的bit数组为, 前几位映射到桶, 后面位记录前导0数量
# 分桶数根据需要的RSD(相对标准误差)来计算
每桶有个max
基数为 桶数x2^(avg(max1+1, max2+1...))
# LogLog算法avg用平均数, hyperLogLog的avg用调和平均数
基数乘一个常数来修正到具体值
# 常数用桶数计算: switch(log2(桶数)), 分情况取参数v, 常数就为 参数x桶数^2
o-> 减误差, 数量小时预测偏大问题
算出基数小于(5/2)*桶数时
基数为 桶数xlog2(桶数/空桶数)
应用
redis用pfadd()和pfcount()
线性规划
#
图算法是线性规划的子集
单纯形法(simplex)
#
每一次迭代比前次更优
hash
#
介绍
将任意长度二进制值映射到较短固定长度二进制值。改一个值会生成不同的哈希
同一个哈希(散列)的二进制值是不存在的
常见的有: md5, sha, sha1, sha256, sha512, RSA-SHA
问题
冲突,拉链
填装因子: 元素数/总位置数
散列函数
傅里叶变换
#
描述
所有波都可以用多个正弦波叠加表示
应用
声音中分离出噪声、人声
图像增大高频信号提高对比度,相机找高频分量最大来自动对焦
加密
#
签名
公钥
dsa
ecdsa
rsa
散列
sha
特点
sha-0, sha-1已发现缺陷。用sha-2, sha-3和bcrypt
局部修改敏感
应用
比较文件
计算比较密码
diffie-hellman
特点
双方不需知道加密算法
公钥和私钥,客户用公钥加密,服务端用私钥解密
种群统计
#
/*
统计二进制串置位(位值是1)数
8位全1数为255, 缓存0-255数的置位数到pc。值为0,1,1,2,1,2,...
byte()会截断8位,如byte(256) = 0
byte(x>>(0*8))得到x低8位截断数值,byte(x>>(1*8))得到x低9-16位截断数值
累加8段数值在pc中映射的置位数,得到x的置位数
时间 n/8
*/
func Count(x uint64) int {
var pc [256]byte
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
count := int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
return count
}
资源分配
#
token bucket
# 令牌桶
通过多少流量,删除多少令牌
突发流量
丢弃
排队
特殊标记发送,网络过载时丢弃加标记的包
过程
产生令牌
消耗令牌
判断数据包是否通过
作用
限制平均传输速率,允许突发传输
leaky bucket
# 漏桶
作用
强行限制数据传输速率
max-min fairness
# 最大最小公平算法, 加权分配
dominant resource fairness (DRF)
# 一种 max-min fairness实现,可以多资源分配
分布式算法
#
特点
分布性,并发性
一致性hash
# 负载
取余hash
服务器号 % 节点数 # 容错性和扩展性不好
一致性hash
建立环坐标, hash每个ip到坐标, 称为节点
hash每个请求到坐标,顺序向后找第一个节点处理
均匀一致性hash
设置虚拟节点, 使请求分配尽量均匀
虚拟槽 # redis
建固定数个槽, 节点负责多个槽,请求映射到槽
paxos
# 共识(consensus)算法
角色
proposer # 提案发起者
acceptor # 提案投票者
learner # 提案chosen后,同步到其它acceptor
第一阶段
proposer向超过半数acceptor发送prepare(带编号和value)
acceptor收到prepare, 编号最大时回复promise(带上之前最大value), 小则不理会
第二阶段
proposer收到超过半数promise, 选取最大value, 发送accept
acceptor收到accept, 编号同自身时更新value, 回复accepted
raft
# 共识算法
角色
leader # 接受请求,向follower同步请求日志并通知提交日志
follower
candidate # leader选举中的临时角色
过程
开始一个leader,其它follower
leader挂掉,一个follower timeout变为candidate, 发送选举请求(RequestVote)
超一半同意,该节点变leader, 并发送heartbeat持续刷新timeout
两个candidate未过半,等timeout后重试
旧leader重连,选举编号小,自动变follower
BFT(拜占庭算法)
# 在部分捣乱中达成一致
总数大于3m, 背叛m,可达成一致
PBFT(实用拜占庭算法)
pre-prepare
prepare
commit
map-reduce
映射函数
归并函数
NP问题
#
介绍
polynomial problem(p问题), 可以在多项式时间内解决的问题
non-deterministic polynomial problem(np, 非确定性多项式问题),指可以在多项式时间内得到一个解的问题
non-deterministic polynomial hard problem(np-hard, np-hard问题)很难找到多项式时间算法的问题
non-deterministic polynomial complete problem(npc,np完全问题)很难找到多项式时间算法的np问题, 包含np-hard
np完全问题的某些特点 # 到处都是np完全问题, 有时与普通问题差别不大
不能再细分成小问题,且必须考虑所有可能的情况
涉及"所有组合"
涉及序列且难解决 # 旅行商问题
涉及集合且难解决 # 广播台集合
可转换为集合覆盖问题或旅行商问题
旅行商问题
#
# TSP, travelling salesman problem, 组合优化中的np困难问题
介绍
遍历n地点,找总路程最短的路径
分别以n地点开始,找下个起点(n-1个), 时间复杂度为O(n!)。方向变路程不变时为n!/2
近似求解