思维
#
第一性原理
#
特点
来源于物理学思维, 是演绎法
唯一、穷尽
佐证: 分形
思维转变
改变归纳法(工程学),人们认识事物的习惯
改变类比思考,它偏离了根源
改变从问题出发不断归因的思考,它只能改进
用演绎法(物理学),更不偏离真相,但需要思考成本
用第一性原理思考:用物理学的角度看待世界,利用基本原则,进行物理推理,再一层层往上走
物理学角度,最不偏离事实
从问题出发找到方案再找到方案的原理,推出其它方案
实施方法
归零: 不断问为什么(奥卡姆剃刀)
解构
重构
限制
归纳法只能证伪、演绎法只能证明
演绎的逻辑来自归纳法,所以才有奇怪的不证自明的公理
逻辑思维
#
同一律、矛盾律、排中律、充足理由律
推理
归纳: 完全归纳、简单枚举归纳、因果分析
演绎
三段论
经典式:大前提、小前提、结论
问题式:what、why、how
类比: 容易外表性偏差
论证论题
论点
论据: 支持度(必然性、或然性),要比论题可信
论证方式
分清事实或观点(态度、判断)
识别推理的好坏
金字塔原理
纵向
表达自上而下
分解:疑问回答式
思考自下而上
罗列、归类、搭建结构、概括总结
横向
演绎、归纳
分类(MECE)
独立、穷尽
逻辑树: 还原论
议题树
假设树
是否树
排序
时间
空间
重点程度
序言(SCQA)
情景(Situation)、冲突(Conflict)、问题(Questiong)、答案(Answer)
使用场景
写作:序言、标题、构架、内容
解决问题
传统方式:提出问题、分析问题、解决问题
金字塔方式:
界定问题:在哪里
结构性分析: 为什么存在
实施方案: 能做什么,应该做什么
构筑金字塔
动力
#
计划
#
为什么
重要性
时间是最重要的资源
做事目的性
有全局性: 有方向, 轻重有序,平时活动与目标联系起来
怎么坚持
实行的决心
养成习惯(抗干扰不费意志)
28定律筛选,降低预期,细想出实施过程
怎么样
聚焦(殚精竭虑,第一)
目标列项: 时间(黄金时间、常规时间、碎片时间)分配
精力管理
#
精力产生
好情绪
针对各焦虑的方案: 进度、身体、家庭、工作
左手温暖右手
好体力
作息:6-10
饮食: 体形
牛逼在于后天努力(关注过程)
付出代价(痛苦),努力获得正反馈
精力使用
简化: 放弃、性价比
锚点:
提示
能力: 控制阻力(直接启动,家无事务)
动机
做事
浪费时间在大局
3号人格: 必须完成, 不情绪内耗
deadline是第一生产力
动力闭环
先做完再说,1次1个,小成功庆祝
张驰有度
学习
#
预习
#
为什么
上课时:扫除知识障碍、针对性解决问题、笔记有针对性
提高自学能力
改变被动局面,成为良性循环
是什么
初步理解
关联旧知识
找不理解
做笔记和习题
怎么样
先粗读, 再反复细读
先读最困惑点
问为什么
请教别人
实践
笔记
重点、结构、摘要
分类
问题
查阅资料
理解思路
逐步提高
由点到面,由浅入深
学习
#
怎么样
集中注意
获取知识主动权
作者思路,比较思维方法
方法
分析综合法、归纳演绎法、比较分类法
规律
同一律、矛盾律、排中律 # 形式逻辑学
对立统一、量变到质变、否定之否定 # 辩证逻辑学
学科特点
理科: 逻辑性,抽象思维
文科: 知识有独立性,形象思维
教师特点
保持连续性,不中断钻牛角尖
笔记
思维方法、过程、结果
完整简洁
当堂掌握
领会、巩固、运用
重点是认知过程而非结论
复习
#
贵在及时
怎么样
尝试回忆
读原文
整理笔记
参考资料
系统复习
为什么
牢固、完整
系统、实用
怎么样
之前之后回忆
有重点阅读
熟记
整理笔记
练习
记忆能力
有记忆意识
理解后记忆
艾宾浩斯曲线
过度学习来记忆
分散记忆好于集中记忆
先整体理解, 后分段记忆, 最后综合复习
多感官
思维能力
积极思维状态
基本思维方法
分类、抽象概括
系统化, 使用时具体化
思维形式
整体思维
相似思维
逆向思维
创造思维
工作
#
组织
简单,分离
管理
无为
沟通
在线文档 > 邮件 > im > 口头/电话
多方口头时,要维护自有重点
谈判
抛回对方问题,使其思考,后做到已方稍强
计划
把握纲领,不能变
先核心再扩展 # 稳定隔离
第一性原理
做重要不紧急
边界分割 # 正交
拆分并只安排步骤
思考框架:现状、目标、实现路径
意义(目标导向)。通常是解决问题
拆分到最细节的方案, 分配到人、工作量、联调提测时间
迷雾即瓶颈
曼陀罗思考法
实行
原型
风险点 # 技术
只输出产品,电梯演讲(简单, 清晰, 轻松, 主线, 一套)
输入时保持输出
把控变化,提供适应方案
内部有 > 外部有 > 半成品 > 自实现
解耦低速设备
update全员参与, 或文档
灰度替换
总结
向流程和工具找问题
发现问题(创造新视角)
#
分类
已知的已知, 已知的未知,未知的未知
知
事实,解释
方法
解决问题到发现问题
从知到无知的视角转换
存量到流量
封闭体系到开放体系
固定维度到可变维度
元级超越维度
抽象化、类推
轴
why型思维
解决问题
#
提问
提原始问题,因为相对自己的看法,可能有更优解
描述问题,尝试与尝试结果
描述环境
要结构化、解决导向的提问, 否则散乱无意义
定义
为什么,是什么,怎么样,展望
zoom in, zoom out
解决
定位、权衡、落地、风控
解决数量级问题
读源码
#
目标
了解思路
难点
理解非自己的思路
了解数据结构、设计模式
原则
跑不起来不读
带目的性读,解决问题就好
一条线索到底
无关细节略过
画类图、类调用泳道图
名词
#
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
近似求解