数据结构的本质:
数据结构的本质其实就是链表和数组,其它数据结构不过是更上层的建筑。都是用最底的的数组或者链表构造,抽象出来的,然后提供更易用的api。比如树,可以用数组,也可以用链表构建。数组优点是可以索引,省空间。而链接因为用指针,会多用些空间,且链表可以很好的插入删除。
所以我们看到redis上有字符串,有集合等数据结构,但每一种类型在内部都有两种实现方案。
其实,我们自己也可以根据具体的业务场景,构造出自己想要的个性化 数据结构,然后定义出一些适合当前业务,好用的API。每种语言都实现了很多上层的数据结构。python常用的字典,数组外,还有一些定义在collections模块中,用于扩展内置的数据类型。这些数据类型更灵活和高效,如defaultdict,双端队列,Counter。java亦然,大多定义在java.util包中。
综上所述,数据结构底层就是数组和链表。集合,字典也不例外,它们不过是通过哈希函数映射到索引上罢了。
那么算法的本质是什么呢?
算法的本质是穷举。穷举不是那么容易的,第一要保证不漏,第二要聪明的穷举。
因为计算机思维就是通过算力求解问题(参见《编码》一书),完全不同于人解决问题的思路。比如数学发明一个公式,可能就把一个问题求解了。计算机不是这样的,计算机最大优点就是速度快,不“怕累”。算法就是通过计算机语言把人的任务转换成机器一遍遍执行。
扩展说下上面两点。先说不漏。所谓不漏,就是算法给出的答案要完整。比如很多求组合的问题,往往用到回溯算法。这种问题就要确保答案完备。
聪明的穷举就更好理解了。一道算法题,通常可以暴力求解。但时间复杂复不好,所以要优化。所有优化的方法和手段,都是为聪明的穷举服务的。比如双指针技巧,滑动窗口,都是为了节省时间。我们知道,它们比双循环这种暴力求解更省时。再比如,有些问题,我们可以选择更优化的数据结构来减少穷举,比如用双链表而不是单链表。
还有细节一点的:比如写循环时,有些时候是可以提前结束的,比如遍历树的时候去枝,又比如利用缓存记忆,减少些重复的操作。
举个例子:
下面是力扣改编的一道题解法,给定一个数组和一个目标数,找出两数之和为target的下标组合,且不可重复。
def twoSum(nums, target):
# nums 数组必须有序
nums.sort()
lo, hi = 0, len(nums) - 1
res = []
while lo < hi:
sum_ = nums[lo] + nums[hi]
left, right = nums[lo], nums[hi]
if sum_ < target:
while lo < hi and nums[lo] == left:
lo += 1
elif sum_ > target:
while lo < hi and nums[hi] == right:
hi -= 1
else:
res.append([left, right])
while lo < hi and nums[lo] == left:
lo += 1
while lo < hi and nums[hi] == right:
hi -= 1
return res
if __name__ == '__main__':
numbers = [2, 2, 3, 5, 5, 8, 9,11, 13, 13, 15, 16, 16, 17]
targetNum = 18
result = twoSum(numbers, targetNum)
print(result)
这里用到两个聪明的穷举:
1:双指针
2:在if判断里,碰到相同元素,用while快速过滤掉。