过去的一年因为疫情的原因, 没有什么机会出门,直到年底才突然放开,刚想出去看看又生病了。回首这一年过的太快太快, 有很多想做的事情还没来得及做,没有能够实现,新的一年需要更好的计划起来。

工作上我做了什么? 我学到了什么?

似乎一直忙忙碌碌,但是没有什么成果,不知道问题到底出在哪里? 这一年我主要的规划是在工作之余能够尽可能的学习新的技术和创业方向, 我主要的做法是项目驱动,当有朋友需要合作开发一个小的产品时候,边做边学,并尽可能的深入进去。

这一年首先学习了NLP处理法律文书,接着是3D游戏开发,学习大量的WEBGL知识。在下半年主要学习了Web3的相关技术,包括合约的开发部署测试,实现DeFI,NFT,Web页面。在年底的时候重新学习网络安全,希望能在这个领域有些创新和实践。

看了不少技术,但是还是没有足够的深入,一方面没有足够的时间,另一方面一直在变,最重要的是一直没有商业化的方向。

家庭中我做了哪些? 有哪些不足和好的

这一年一直和家人呆在一起, 我的计划是每天至少有一个小时陪女儿,基本也做到了。但是还远远不够,女儿现在求知欲很强,有很多的新东西教一遍就能学会,可塑性非常强,但是我还没有充分思考怎么教育女儿,在不同的年龄段如何规划。另外今年家人都有生病,每次看医生都花费很多时间和金钱。

虽然我一直坚持锻炼, 但是身体还是有各种各样的小毛病,主要是关节痛发生了好几次。

理财方面?

今年基金惨淡,很多亏损,所有没有全仓买入。好在今年炒股有一些心得,虽然交了些学费,但基本掌握好一定的节奏了, 希望新的一年能够严格执行。

新的一年我的期望是什么样的?我应该做什么?

继续寻找合适的创业方向,记录下来,最多5个, 选择一个最重要的坚持实践,尝试打开商业方向,必要的时候投入一些资金。
看合适的书,不能太过发散, 需要聚焦,把有限的精力放在定好的方向上。
为女儿制定合适的学习计划,半年到一年动态调整,把握住女儿成长的黄金期。
继续学习理财,找出能够稳定收益的方法, 至少年化收益跑赢银行的5年期利息。
配置合适的保险, 重疾险有限看友邦的,主要考虑未来如果生病能够有一些补偿,不影响家庭生活质量

生活就像是巧克力,你永远不知道下一颗是什么颜色。

作为技术人,你的竞争力来源于你的初心,专注和坚持不懈的努力

8月初公司法人出了一些问题,导致公司投资人撤资,不得已在这个尴尬的季节出来看新的机会。

到今年7月, 我就满10年工作经验了, 在这10年中根据工作的需要, 参与过多年前端/后端软件的开发,也主导过系统从0到1的架构,同时也积累了一些管理上的经验,团队合作。在这10年中早期的两年我便找准了工作的方向: 前端工程师。 在之后的5年, 凭借着对前端极高的兴趣和技术专研精神, 从trident的时代到webkit时代,从jQuery到三大框架(React/AnguarJS/VueJS), 从VanillaJS到CoffeeScript到TypeScript到DSL,
一直紧跟技术的发展,没有掉队。16年出来创业做智能家居,坚持了两年,因为各种原因放弃了, 但过程中收获了技术和业务整合的能力, 懂得了从市场的角度思考问题。
18年开始做技术管理和技术架构相关工作,因为工作需要,学习了大数据架构ETL技术,实现了NLP处理广告的技术,同时在API的实现方面, 整合了SpringBoot和Zuul, 实现阿里和滴滴等客户通过接口稳定的调用。

苏州市场上目前有以下几类公司:

  • 上市公司, 代表: 华为,微软
  • 诞生在本地的大企业, 代表: 同程旅游,智慧芽,食行生鲜, 启信宝等
  • 外企, 代表: 微软,EPAM,艺能软件等
  • 新兴创业公司: Momenta/PlusAI(自动驾驶),Atman(医疗AI),金数数据等

对于第一类公司,主要考察的是算法和数据结构能力,需要候选人掌握最基本的排序和搜索算法, 如果要通过需要足够的算法经验,至少在Letcode或者TopCoder上刷满100题。 这类公司一般待遇在市场上比较优渥,聚集了很多牛人,你可以专心的做技术, 但是因为职位固化, 上升的空间不会很大。

第二类公司, 主要还是看应聘公司同时期项目的需求,主要考察的是候选人项目开发能力,采取的什么技术,解决了什么样的问题,面对了多大的挑战等等,需要实实在在的解决问题的能力, 如果要进入这类公司,需要候选人在某个垂直的技术领域比较精通。这类公司有一定的上升空间, 但不排除内部勾心斗角问题,据我所知同程内部拉帮结派现象严重, 这时候你要进升是不可能的事情

第三类公司,主要考察的是技术能力和英语能力,有很多人倒在了英语这个环节。这类公司相对来说职位也比较固定,但是可以享受到欧美弹性的工作环境和多元的企业文化, 有很多针对员工的培训也会很有用,例如:EPAM会定期安排员工的英语培训课程

第四类公司,主要考察候选人多方面的能力,前后端技术,框架工程能力,吃苦耐劳的能力等。这类公司一般有较大的上升空间, 同时有机会放大自己的价值,得到优厚的回报,但是稳定性会差一些。

每个类别我都面试了一些公司,和每家公司的交流都能学习到不少东西, 和微软的面试中我发现自己的算法能力还是不足的,可以在后续重点加强。在和智慧牙的沟通中, 我面试的是技术架构师一职,我的技术全面性得到了对方的认可, 但是他们还是希望找一个垂直领域的专家,这点在JD上有所误导, 所以在应聘之前一定要先沟通清楚职位的描述是否准确, 否则会花费大量时间做无用功。在和EPAM接触的过程中,我面试的是JavaScript技术专家,和面试官聊了很多技术问题,技术方面也得到了对方的认可, 但是在英语方面他们希望可以找一位Native能力的候选人, 这也是很无语的事情,同时他们要找的人准确的说ReactJS技术专家。Atman是一家做机器翻译的公司,我面试的是前端架构师, 面试中沟通了项目从0到1的解决方案,团队管理问题,因为和我的技术栈非常的契合,聊的非常愉快,也很快发了Offer。

还有其他一些公司,面试中聊了各种奇葩和八卦的问题,或者聊完了说这个职位没有了, 等等各种各样的情况。所以在面试前一定要沟通清楚职位的情况, JD描述是否准确,否则会浪费很多时间。

我个人比较倾向的是第三类和第四类公司, 同时根据自己的特长我判断自己适合做垂直领域的技术专家相关的工作, 也很喜欢创业公司的氛围。总体而言,领域专家是最受欢迎的。

你的身价取决于地域和稀缺性

  我大概统计了一下, 对于一个高级开发人员, 相同的一份工作, 在一线城市的收入要比在二线城市高出5000左右。
  另外,可替代性比较强的工作薪资水平必然会低
  • 地域因素

    • 公司竞争激烈,要找合适的人才就要能给出匹配的薪酬;
    • 市场经济规模大,职工的薪酬平均水平高,薪资自然也就水涨船高;
  • 稀缺性

    • 是否是领域专家
    • 所在地是否这方面人才少
    • 是否一专多能, 别人有的我更好, 别人没有的我有
    • 是否是管理层

关于大龄和创业

网上很多人说35岁是开发人员的分水岭, 后续要开始走下坡路, 这点我不是很赞同。

讲一个我在面试中遇到的两家创业公司, 两家公司的创始人都是40岁左右,其中一个CEO是二次创业,第一次创业已经取得了成功,但还是坚决出来做其他方面的创业, 对他而言还有很多有价值的事情等着去做,希望可以通过数据分析来解决商业营销方面的问题。 另外一家做AI机器翻译的, 团队有很多微软研究院出来的,他们希望通过机器翻译技术发现潜在的医药规律,降低新药的研发周期和成本, 市场价值巨大。

另外, 从目前网上所Open出来的职位来看, 架构师和技术总监相关的职位还是有不少的,对开发的要求大部分是5-10年工作经验,如果技术能力够强,完全可以应聘

如果你工作之余有足够的精力, 可以考虑成为一个斜杠青年。

斜杠青年指的是一群不再满足“专一职业”的生活方式,而选择拥有多重职业和身份的多元生活的人群。

在找工作期间, 我也考虑过创业, 并且对行业做了很认真的调研。 少儿编程教育是我比较看好的一个市场, 虽然市场规模不大,只有40亿左右, 但目前市面上做的好的公司还比较少, 如果找准切入点, 还是很有机会的。 后续我会继续跟进行业进展, 写更多的关于这方面的文章。

结语

如果不是CEO出现了问题, 我肯定不会出来尝试新的机会, 也就不会发现自身很多问题。 虽然失去了一些时间和金钱, 但是得到了一些可以喘息和思考的机会, 同时我有更多的时间陪伴家人了, 重新认清了接下来的发展方向,那就是一定要成为某领域的专家, 多增加自己的稀缺性, 如果有机会和时间, 有一份自己的事业最好。

生活和工作两者很难平衡,现在人的压力都大, 没有良好的心态很难在遇到一些困难事情的时候调整过来,容易失去信心。得与失之间,都是公平的,当你失去了一部分, 你也必然会在其他方面会有所收获。

Stay hungry, stay foolish, stay thinking deeply!!!

栈(stack)又名堆栈,它是一种运算受限的线性表。 限定仅在表尾进行插入和删除操作的线性表。 这一端被称为栈顶,相对地,把另一端称为栈底。

栈的实现可以通过数组和链表来实现, 用数组实现的可以称为顺序栈, 用链表实现的成为链式栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package stack

// 基于数组的栈实现
type ArrayStack struct {
data []string
length int
size int
}

func NewArrayStack(len int) *ArrayStack{
if len <0 {
return nil
}

return &ArrayStack{
data: make([]string, len),
length: len,
size: 0,
}
}

func (me *ArrayStack) push(data string) bool {
if me.length == me.size {
return false
}

me.data = append(me.data, data)

me.size++

return true
}

func (me *ArrayStack) pop() string{
if me.size == 0 {
return ""
}

item := me.data[me.size - 1]

me.size--

return item

}

// 基于链表的栈实现
type node struct {
next *node
val interface{}
}

type LinkedListStack struct {
top *node
}

func NewLinkedListStack() *LinkedListStack {
return &LinkedListStack{nil}
}

func (me LinkedListStack) IsEmpty() bool {
return me.top == nil
}

func (me *LinkedListStack) Push(data interface{}) bool {
nd := &node{val: data, next: nil}

if me.IsEmpty() {
me.top = nd
} else {
nd.next = me.top
me.top = nd.next
}

return true
}

func (me *LinkedListStack) Pop() interface{} {
if me.IsEmpty() {
return nil
}

result := me.top.val
me.top = me.top.next

return result
}

func (me *LinkedListStack) Top() interface{} {
if me.IsEmpty() {
return nil
}

return me.top.val
}

func (me *LinkedListStack) Print() {
if me.IsEmpty() {
return
} else {
curr := me.top

for nil != curr {
fmt.Println(curr.val)
curr = curr.next
}
}

}

本文讨论关于数组排序算法的实现, 给定一个整形数组,按从小到大排列

本节主要实现的排序算法有:希尔排序,计数排序,基数排序,桶排序,堆排序

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package sort

func ShellSort(arr []int, len int) {
if len <=1 {
return
}

times := 0

gap := len / 2

for gap >=1 {
j := 0

for i:=gap; i<len; i++ {
curr := arr[i]

// 对第i个元素以及之前相同的gap间距元素进行对比排序
for j = i - gap; j>=0 && curr < arr[j]; j -= gap {

times++
arr[j+gap] = arr[j]
}

arr[j + gap] = curr
}

gap /= 2
}

println("time O(", times, ")")
}

计数排序

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,
它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。  当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过
对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列
中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些
元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package sort

import "math"

func CountingSort(a []int, n int) {
if n <= 1 {
return
}

var times = 0

var max int = math.MinInt32
for i := range a {
if a[i] > max {
max = a[i]
}
times++
}

c := make([]int, max+1)
for i := range a {
c[a[i]]++
times++
}
for i := 1; i <= max; i++ {
c[i] += c[i-1]
times++
}

r := make([]int, n)
for i := range a {
index := c[a[i]] - 1
r[index] = a[i]
c[a[i]]--
times++
}

copy(a, r)

println("Time O(", times, ")")
}

基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

基数排序要求基数的量不能太大, 否则做不到O(n)的时间复杂度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package sort

// 基数排序
func RadixSort(arr []int, len int) {
if len<=1 {
return
}

max := arr[0]

for i:=0; i< len; i++ {
if arr[i] > max {
max = arr[i]
}
}

for exp:=1; max/exp > 0; exp *= 10 {
countSort(arr, exp, len)
}
}


func countSort(arr []int, exp int, len int) {

c := make([]int, 10, 10)

for i := range arr {
c[arr[i]/exp % 10]++
}

for i := 1; i < cap(c); i++ {
c[i] += c[i-1]
}

r := make([]int, len, len)

for i := 0; i<len; i++ {
r[c[arr[i]/exp %10] -1] = arr[i]
c[arr[i]/exp %10]--
}

copy(arr, r)
}

桶排序

桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。 每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。 桶排序是鸽巢排序的一种归纳结果。 当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package sort

func getMax(arr []int) int {
max := 0

for i := range arr {
if i > max {
max = i
}
}

return max
}

func BucketSort(arr []int, arrLen int) {
if arrLen <= 1 {
return
}

max := getMax(arr)
buckets := make([][]int, arrLen / 10) // seperate to length/10 buckets

for i := 0; i < arrLen; i++ {
index := arr[i] * ( arrLen - 1 ) / (10 * max) // sperate data to different bucket

buckets[index] = append(buckets[index], arr[i])
}

datalen := 0 // mark current data processed

for j := 0; j < len(buckets); j++ {
bucLen := len(buckets[j])

if bucLen > 0 {
// use quicksort to sort every bucket
QuickSort(buckets[j], bucLen)

copy(arr[datalen:], buckets[j])

datalen += bucLen
}
}
}

堆排序

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于它的父节点,

1
// TBD

本文讨论关于数组排序算法的实现, 给定一个整形数组,按从小到大排列

常见的排序算法有:插入排序, 选择排序,冒泡排序,快速排序,归并排序等

插入排序

两种实现方案,第一种方法是新建一个数组并按序排列,遍历原始数组复制元素到新的数组, 时间复杂度为 O(n * n), 空间复杂度为O(n), 同时为不稳定的排序; 另一种方式是在原地选择和排序,数组分为两部分,左边是已排序的部分,右边是带排序的部分,初始状态左侧为数组第一个元素,右侧为第二个元素之后的元素,从数组中第二个元素开始遍历, 依次插入左侧并排序, 时间复杂度为 O(n * n), 稳定排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package sort

// 插入排序
// v1: time O(n * n) space O(n)
func InsertSortArray1 (array []int, length int) []int {
// 1. 创建一个新的数组
// 2. 循环旧的数组数据并复制到插入到新数组的指定位置
if length <= 1 {
return array
}

times := 0

newArray := make([]int, length, length)

newArray[0] = array[0]

for i := 1; i < length; i++ {
index := 0

for j := i-1; j >=0 ; j-- {

times++

if array[i] >= newArray[j] {
index = j + 1

break
}

}

if index < i {
for k:=i-1; k>=index; k-- {
times++

newArray[k + 1] = newArray[k]
}
}

newArray[index] = array[i]
}

println("time O(", times, ")")

return newArray
}

// v2 time o(n * n) space O(0)
func InsertSortArray2 ( array []int, len int) []int {
// 原地交换数据
if len <= 1 {
return array
}

times := 0

for i := 1; i< len; i++ {
curr := array[i]
j := i-1

for ; j>=0; j-- {
times++

if array[j] > curr {
array[j+1] = array[j]
} else {
break
}
}

array[j+1] = curr
}

println("time O(", times, ")")

return array
}

选择排序

遍历数组, 每次取剩下的元素中的最小的元素的下标, 如果找到了就和已排序的元素进行交换, 时间复杂度 O(n * n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package sort

func SelectionSort1(arr []int, len int) {
if len <=1 {
return
}

times := 0

for i:=0; i< len; i++ {
minIndex := i

for j:= i+1; j < len; j++ {

times++

if arr[j] < arr[minIndex] {
minIndex = j
}
}

arr[i], arr[minIndex] = arr[minIndex], arr[i]
}

println(times)
}

冒泡排序

比较相邻的数字,将大数字后移, 遍历完一轮右侧是最大的数字,然后在比较第0个元素到第n-1个元素,直到n=1, 时间复杂度: O(n * n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package sort

func BubleSort(arr []int, len int) {
if len <=1 {
return
}

flag := false

times := 0

for i :=0; i< len; i++ {
for j :=0; j< len-i-1; j++ {

times++

if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]

flag = true
}
}

if !flag {
break
}
}

println("time:", times)
}```


### 快速排序
> 首先选择一个pivot, 一般选最后一个元素, 然后便利数据从start和end, 如果小于pivot, 那么交换正在比较的两个数据, 第一次对比完成后,pivot所在的索引左边都是小于pivot的数据,右侧都是大于pivot的数据, 然后分别对左侧和右侧数据进行排序, 直到start>=end. 时间复杂度O(logN)


```go
package sort

/*
排序过程:

j=0, 1, i=0 1
1 111 23 31 43 54 45
> i=1

j=1 111, i=1 1111
1 111 23 31 43 54 45

j=2 23, i=1 111
1 23 111 31 43 54 45
> i=2 111

j = 3 31
1 23 31 111 43 54 45
> i = 3 111

j = 4, 43
1 23 31 43 111 54 45
> i = 4 111

j = 5, 54
1 23 31 43 111 54 45
> i=4 111

1 23 31 43 45 54 111
> i =4, j =5

*/
func QuickSort(arr []int, len int) {
separate(arr, 0, len-1)

println("time O(", times, ")")
}

func separate(arr []int, start,end int) {
if start >= end {
return
}

i := partition(start, end, arr)

separate(arr, start, i-1)
separate(arr, i+1, end)
}

var times int = 0

func partition(start int, end int, arr []int) int {
pivot := arr[end]

i := start

for j :=start; j< end; j++ {
times++

if arr[j] < pivot {
if i != j {
arr[i], arr[j] = arr[j], arr[i]
}

i++
}
}

arr[i], arr[end] = arr[end], arr[i]

return i
}

归并排序

将数据分成相等两部分(length / 2 = mid), 分别对start到mid和mid到end进行排序, 然后在进行合并, 知道start>=end结束遍历, 时间复杂度O(logN), 空间复杂度O(N)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package sort

var exetimes int = 0

func MergeSort(arr []int, len int) {
if len <=1 {
return
}

sort(arr, 0, len-1)

println("tims O(", exetimes, ")")
}

func sort(arr []int, start, end int) {
if start >=end {
return
}

mid := (start + end) /2

sort(arr, start, mid)
sort(arr, mid+1, end)
merge(arr, start, mid, end)
}

func merge(arr []int, start, mid, end int) {
temp := make([]int, end - start + 1)

i := start
j :=mid+1
k := 0

for ; i<=mid && j<= end; k++ {
exetimes++

if arr[i] > arr[j] {
temp[k] = arr[j]
j++
} else {
temp[k] = arr[i]
i++
}
}

for ;i<=mid;i++ {
exetimes++

temp[k] = arr[i]
k++
}

for ;j<=end;j++ {
exetimes++
temp[k] = arr[j]
k++
}

copy(arr[start:end+1], temp)
}

实现链表的基本数据结构

  • 链表的基本数据结构的实现
  • 插入,删除, 更新

思路

链表是包含头节点指针和数据节点指针的数据表, 查询复杂度为O(n)

实现目标

  • 使用Go语言实现基本的链表数据结构
  • 通过Go实现链表的基本操作

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package linkedlist

import (
"fmt"
"github.com/pkg/errors"
)

type Node struct {
next *Node
data interface{}
}

type LinkedList struct {
head *Node
length uint
}

func NewNode(value interface{}) *Node {
return &Node{nil, value}
}

func NewLinkedList() *LinkedList{
return &LinkedList{NewNode(0), 0}
}

func (self *Node) GetNext(node *Node) *Node {
if nil == node {
return nil
}

return node.next
}

func (self *Node) GetValue(node *Node) interface{} {
if nil == node {
return nil
}

return node.data
}

// insert new node after specific node
func (self *LinkedList) InsertBefore(node *Node, v interface{}) error {
if nil == node {
return errors.New("Try to insert before a empty node")
}

newNode := NewNode(v)
curr := self.head.next
pre := self.head

for nil != curr {
if curr == node {
break
}

pre = curr
curr = curr.next
}

if nil == curr {
return errors.New("Can not find node to insert")
}

pre.next = newNode
newNode.next = curr

self.length++

return nil
}

func (self *LinkedList) InsertAfter(node *Node, v interface{}) error {
if nil == node {
return errors.New("Try to insert after a empty node")
}

curr := self.head

for nil != curr {
if curr == node {
break
}

curr = curr.next
}

if curr == nil {
return errors.New("Can not find node to insert")
}

newNode := NewNode(v)
nextNode := curr.next
curr.next = newNode
newNode.next = nextNode

self.length++

return nil
}

func (self *LinkedList) Prepend(v interface{}) error {
return self.InsertAfter(self.head, v)
}

func (self *LinkedList) Append(v interface{}) error {
newNode := NewNode(v)

head := self.head

if nil == head.next {

head.next = newNode

} else {

curr := head

for nil != curr.next {
curr = curr.next
}

curr.next = newNode
}

self.length++

return nil
}


// find
func (self *LinkedList) FindByIndex(index uint) (*Node, error) {

if index > self.length {
return nil, errors.New("Find out of range")
}

curr := self.head

for i := uint(0); i< index; i++ {
curr = curr.next
}

return curr, nil
}

func (self *LinkedList) FindByData(data interface{}) (*Node, error) {
curr := self.head

for nil != curr {
curr = curr.next
}

return curr, nil
}

func (self *LinkedList) DelNode(node *Node) (bool, error) {
if nil == node {
return false, errors.New("Try to delete empty node")
}

curr := self.head

for nil != curr {

if(curr.next == node) {
curr.next = curr.next.next

return true, nil
}

curr = curr.next
}

return false, nil
}

func (self *LinkedList) Print() {
curr := self.head

var formatStr string

for nil != curr.next {
curr = curr.next

formatStr += fmt.Sprintf("|%+v", curr.data)
}

fmt.Println(formatStr)
}

实现数组的基本数据结构

  • 数组的基本数据结构的实现
  • 插入,删除, 更新

思路

数组是一系列连续的数据集合,有固定的长度和下标,根据下标访问查找时间复杂度O(1),删除时间复杂度也是O(1)。

实现目标

  • 使用Go语言实现基本的数组数据结构
  • 通过Go实现数组的基本操作

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package array

import (
"errors"
"fmt"
)

type Array struct {
data []int
length int
}

func NewArray(capcity int) *Array {
if capcity <= 0 {
return nil
}

return &Array{
data: make([]int, capcity, capcity),
length: 0,
}
}

func (self *Array) IsOutOfRange(index int) bool {
if index < 0 || index >= cap(self.data) {
return true
}

return false
}

func (self *Array) Len() int {
return self.length
}

// find specfic data index value
func (self *Array) FindIndexByData(data int) int {
for index, value := range self.data {
if value == data {
return index
}
}

return -1
}

func (self *Array) FindDataByIndex(index int) (int, error) {
if self.IsOutOfRange(index) {
return -1, errors.New("Out of range when find")
}

return self.data[index], nil
}

// Delete elements by sepecific value
func (self *Array) Delete(index int) (int, error) {
if self.IsOutOfRange(index) {
return -1, errors.New("Out of range when delete!")
}

v := self.data[index]

for i:=index; i< self.Len() - 1; i++ {
self.data[i] = self.data[i] + 1
}

self.length--

return v, nil
}

func (self *Array) Insert(index int, data int) (bool, error) {
if self.IsOutOfRange(index) {
return false, errors.New("Out of range when insert")
}

if self.Len() == cap(self.data) {
return false, errors.New("Capcity is full when insert")
}

for i := self.length-1; i >= index ; i-- {
fmt.Println("Insert: ", self.data, i, index)

self.data[i] = self.data[i-1]
}

self.data[index] = data

self.length++

return true, nil
}

func (self *Array) Append(data int) (bool, error) {

if self.Len() == cap(self.data) {
return false, errors.New("Capcity is full when insert")
}

return self.Insert(self.length, data)
}

func (self *Array) Prepend(data int) (bool, error) {
if self.Len() == cap(self.data) {
return false, errors.New("Capcity is full when insert")
}

return self.Insert(0, data)
}

func (self *Array) Print() {
var formatString string
for i :=0; i < self.length; i++ {
formatString += fmt.Sprintf("|%+v", self.data[i])
}

fmt.Println(formatString)
}

契机

最近参加了一轮大厂的面试, 直接手撕算法和数据结构, 感觉到很是挺生疏的, 简单的算法也不能很快写出来。 因此, 决定个人博客开立算法板块, 一方面重学数据结构和算法, 一方面将自己的心得记录下来。 最终目标, 可以手写常见的算法和数据结构实现,同时兼顾时间复杂度和空间复杂度, 每周至少学习一个算法。

计划

  • 基础部分

    • 数组: 查询, 插入, 删除
    • 链表: 单链表/循环链表, 插入,删除
    • 栈: 入栈,出栈, 查找
    • 队列 入队,出队,循环队列, 查找
    • 二叉树 查找(前中后序), 删除, 添加节点
    • 堆 搜索, 删除, 添加
    • HashTable 查找,添加,删除
    • 图 插入,删除,查找最短路径
  • 中等

    • 红黑树/B+树
    • 字符串匹配算法
    • 动态规划
    • 回溯算法
    • 贪心算法
  • 实用案例分析

    • 数据库索引算法
    • 实现正则表达式的匹配算法
    • 实时统计TOP N的信息
    • 推荐算法
    • 机器学习算法原理
    • 分布式算法
  • 实践

    • topcoder刷题
    • letcode刷题

反馈机制

  1. 每个课题手写完整的代码, 必须没有语法错误, 边界检查
  2. 验证时间复杂度和空间复杂度, 持续优化找出不足之处该进
  3. 到topcoder或者letcode上找一个相关的案例提交解题答案

一直对物联网安全很感兴趣, 很想从事安全方向的职业, 机缘巧合, 去年加入了一家做业务安全的公司。

安全行业

网络安全是指网络系统的硬件、软件及其系统中的数据受到保护,不因偶然的或者恶意的原因而遭受到破坏、更改、泄露,系统连续可靠正常地运行,网络服务不中断

业务安全是指保护业务系统免受安全威胁的措施或手段。广义的业务安全应包括业务运行的软硬件平台(操作系统、数据库等)、业务系统自身(软件或设备)、业务所提供的服务的安全;狭义的业务安全指业务系统自有的软件与服务的安全。

业务安全的本质是为客户省钱, 那么省钱的方式有哪些?哪些客户需要?

先来看看客户的痛点有哪些:

  1. 营销活动没有吸收到新的用户,反而被黄牛党大把大把的薅羊毛
  2. 刷量刷单现象严重,严重扰乱市场正常竞争
  3. 公司内部敏感信息泄漏,被放到暗网上销售,例如公司商业机密数据,联系方式等
  4. 二三类银行卡被用于洗钱,赌博,贷款诈骗等
  5. 业务流程存在漏洞, 例如通过修改正常的订单价格和数量来达到非法获利的目的

从以上的痛点来看,如果能够做到在以上行为发生的早期发现并预警,那么就可以为客户减少很多不必要的损失。例如去年发生的PDD被一晚上薅羊毛200亿的事件

X公司的业务模式

通过收集事件上下游的数据,来做到事前阻断,事中止损, 事后追查, 主要通过以下渠道:

  • 收码平台
    • 通过收码平台打码信息,可以知道黑灰产人员最近在注册/关注/提现等行为,然后通过提取出产品名称并且和客户做关联
  • 信息交流论坛: 如卡农社区,脉脉
    • 通过论坛贴吧知乎等渠道的沟通交流, 可以获知黑灰产人员将来或者正在对哪些产品采取非法的行动
  • 聊天工具:QQ,TG等
    • 通过获取群聊的消息,提取聊天关键字来发现潜在的恶意行为
  • 售卖平台:淘宝,京东,暗网
    • 通过对销售渠道监控,获取哪些服务被售卖和销赃

X公司的技术架构

业务模式和场景决定架构选择,架构的本质是降低复杂性, 降低成本,增加效益。业务层面主要面临的挑战:

  1. 数据渠道众多, 需要实现不同的爬虫程序来获取数据
  2. 数据量大,不同数据渠道来源的数据汇总之后每天有几十G
  3. 带宽有限, 在有限的时间传输大量的数据很快会因为带宽沾满问题导致不能及时回传
  4. 下游消费能力有限, 未优化前每秒只能处理几百条数据
  5. 数据应用T+1延迟, 当天的数据不能及时获取, 一般会有半天到一天的延迟,在某些场景下是对业务来说是致命的

针对以上问题, 我一起参与了公司的架构实现:

  1. 针对多渠道的数据定义统一的数据接口, 这样在预处理的时候可以统一处理,不需要按渠道分别处理
  2. 通过队列和缓冲,先将数据缓存到Kafka队列里, 然后通过下游多个消费者消费, 并且可以用于实时计算, 大大减少数据的延迟
  3. 数据采集层通过增加redis集群, 采用分布式爬虫协同获取数据,利用多节点带宽减少对单机节点的压力, 同时提高了爬虫的效率

生活像巧克力,你永远不知道下一颗是什么颜色

在技术架构稳定, 产品输出正常的状态下, 公司内部资金链出现了断裂, 导致业务开展困难。 创业公司所面临的处境你永远不要高估,能活下去是第一要务, 其他的都是华而不实的外衣, X公司所遇到的处境着实令人可惜, 也许只要再多努力一把, 就可以起死回生。连续经历了两家失败的创业公司, 深有感触,都说成功不可复制, 但是失败一定是可以学习的, 今天我想再总结下失败的原因:

  1. 业务模式线太多,不够专注
  2. 无论什么样的业务都需要合法合规, 否则得不偿失
  3. 人员管理有些松散,一切以业务驱动的方式快跑, 但是没有考虑到内部的效率和费用的优化

Anyway,又失业了

一直希望自己在一家创业公司发挥自己最大的价值,做成功一件事情, 走向财务自由。 但是随着年龄越来越大, 似乎也变得有点越来越惶恐, 是继续放手一博继续创业, 做自己喜欢的事情, 追求自己的梦想? 还是进入一家稳定的大公司, 对自己的妻子和孩子负责, 有更多的时间可以陪家人?如果选择一个安稳的工作,可能会逐渐失去创业的动力, 一切求稳,人生会变得非常简单和无趣。 如果选择继续冒险创业, 有99%的概率失败,一无所有,风险会比较大。What do you think?

这不是开始的结束, 这是结束的开始。

kick the ball

两年前我和两个朋友一次聊到了物联网, 我们彼此都对这个新生事物充满了兴趣, 因为万物物联是一件非常神奇的事情,它会影响我们的生活方方面面,小到一个智能插座, 大到物流供应链, 都有应用的场景, 这个行业未来一定前途光明。

契机

我的合伙人在一次坐飞机时认识了一个做智能门锁公司的CEO 张工,聊的很投机,在经过了短暂的接触之后, 张工明确表示了想和我们合作开发智能家居产品,因为我们具备软件研发能力, 对方公司具备硬件研发能力, 优势互补, 双方合作应该会取得双赢。 当时我们还有各自的工作, 我们抽了一个周末讨论了之后就决定要出来创业。我们主要考虑的是首先可以和张工公司以项目的方式合作起来, 同时我们可以借机了解物联网市场, 同时积累物联网方面的开发技术, 一举多的, 所以我们很快就决定了要出来做这件事情。

历程

在决定了和张工合作之后, 我们同时思考了自己的创业方向, 初步定位在做物联网解决方案,因为我们不具有硬件研发能力,所以我们要做的就是集成市面上的硬件或者直接和硬件厂家合作, 我们负责开发软件产品,然后和硬件打包成一个解决方案。

和张工的合作起初谈的也是非常的顺利, 最后的合作方式是我们负责张工公司所有的软件开发工作, 张工负责给我们每月固定的一笔收入。

事实上, 越保险的事情越是不靠谱, 事后想想,这是一个最失败的合同。

在合作期间,我们一边帮助张工做好智能家居以及其他的软件开发工作, 同时也在不断的思考创业方向, 因为那段时间我和合伙人是住在一起的, 所以我们每天都会沟通创业的方向和机会,我们曾经设想过的解决方案有:

装修结局方案平台: 装修公司,设计师,用户可以在一个平台上公开透明的交流,实现三方最优智能装修方案
垂直领域的解决方案:养老院/美容院解决方案, 帮助特定的人群通过物联网的方式解决疑难问题
酒店智能化监控解决方案:帮助酒店解决能源管控的问题
以上更多的想法只是停留在了思想里,并没有真正的执行到位, 这也是给后续创业失败提供了导火索。

在经历了一年的合作之后,我们在查看合同的细则时,发现漏洞重重, 得出的结论是,我们为张工免费开发了很多软件项目, 同时由于在一些验收细节上没有定义清楚,导致了后续的收入分配出现争执,这是我们在一开始万万没有想到的。

后来经过深思熟虑决定自己出来创业,我们尝试了找了两个硬件合作伙伴,一个负责硬件PCB板设计, 一个负责硬件嵌入式开发, 直到这个时候,我们才意识到这才是创业的开始。

为了不使之前的工作全部白费, 我们把战略目标定位: 短期内面向酒店客户提供RCS解决方案。长期内做智能家居解决方案, 面向个人家庭和别墅。

然而,在实际操作的过程中,远非我们想象那般简单。首先RCS解决方案对酒店来说是个负担, 因为每年都会有维护费用;同时, RCU厂家也不倾向于和软件开发商合作, 和自己分一杯羹。

对于智能家居整体思路, 一开始我们是瞄准房地产开发商,因为有量, 并且是清一色复制实现即可,避免了个人家庭的客单价底和个性化需求带来的诸多人力花费。但是事实上房地产开发商要的并不是我们的这一套不成体系的解决方案, 他们要的是有自己的团队,可以自由把控发展方向和资金投入, 同时我们因为没有大型项目的实施方案, 对产品整体的稳定性没有太大的信心, 所以后来我们定位在别墅。 介于个人家庭和房地产开发商之间的一个群体, 有一定体量,并且也不会有太多的定制需求。

接着, 我们在实际操作的过程中,又碰到了更多的问题:

  1. 产品开发周期问题
  2. 前期垫资问题
  3. 装修问题
  4. 售后维护问题

对于装修问题和售后问题可以直接通过合作的方式来达到,但是对于开发周期长和垫资问题如何来填补?

思考

我们不缺理想和目标, 但是现实面前我们必须作出抉择。由于家庭各方面压力,加上我们并没有去融资,很多开销成本都是我们硬撑着,两年之后, 我们在经历了各种尝试, 找合作机会之后发现还是没有太大的成果, 于是我们放弃了继续创业下去的想法。

纵观这段经历, 我们的失败原因归纳有一下几点:

  1. 自身财力不够:物联网是重资产, 我们的个人能力无力支撑长期的硬件投入和项目垫资需求, 长久发展需要有资本的支持
  2. 市场能力不够:更多的依赖于一个人去谈判和接触, 另外两个合伙人参与的相对较少, 没有在这方面有自己的更多的思考
  3. 执行力: 在思考可能的创业方向时,缺少行之有效的快速验证方法和调研,停留在纸面上的想法居多, 有一些想法在开始的时候因为没有信心也就直接放弃了
  4. 过于理想化:从最开始创业坚持只做有持续长尾效应的工作,没有先考虑养家糊口的事情, 有点冒险
  5. 人脉资源短缺:创业的初衷想的是做有价值的解决方案,然后通过口碑宣传, 发展更多的客户。但是在做到那一步, 需要先解决从无到有的问题, 这一步却是最难迈的。
  6. 市场不成熟:智能家居对于个人家庭只是有个锦上添花的东西,不是刚性需求,消费者人群很狭窄,和工业物联网相比没有解决迫切的问题
  7. 定位有误:物联网解决方案是以个非常宽泛的想法, 在没有具体的落地项目开发经验之前,更多的只是空想

以上是我对第一次创业经历的简单总结, 对于每个创业者来说, 往前走一步都很艰难。在创业的路上,我们要做的是,努力向前迈进,同时不断抬头看路,修正改变,做最好的自己。共勉!

0%