目录
字符串
- 由于无法直接修改字符串里的字符,一般情况下先把字符串变换为字符数组,然后在进行分析和处理;
- 字符串用连续的内存存储字符;
- 90%的字符串问题都可以⽤动态规划解决,并且90%是采⽤二维数组;例如:LeetCode03题
#使用动态规划(将问题转化成子问题)
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if s == '':
return 0
if len(s) == 1:
return 1
def find_left(s, i): #查找新增字符结尾的无重复子串长度是向左搜索
tmp_str = s[i]
j = i - 1
while j >= 0 and s[j] not in tmp_str:
tmp_str += s[j]
j -= 1
return len(tmp_str)
length = 0
for i in range(0, len(s)):
length = max(length, find_left(s, i)) #状态转移方程
return length
数组
优点:
- 构建非常简单
- 能在 O(1) 时间里根据数组的下标(index)查询某个元素,时间效率高,可以用来实现“哈希表”;例:剑指offer50题
缺点:
- 构建时必须分配一段连续空间来顺序存储数据。创建时需要预先分配内存,经常会有空闲的区域没有得到充分利用,空间效率低
- 查询某个元素是否存在时需要遍历整个数组,耗费 O(n) 的时间(其中,n 是元素的个数)
- 删除和添加某个元素时,同样需要耗费 O(n) 的时间
链表
优点:
- 链表能动态地分配内存空间,每添加一个节点分配一次内存,空间效率高。每个结点是由数据域和指针域组成,逻辑上相邻的节点物理上不必相邻
- 能在 O(1) 时间内删除、插入元素(不必移动节点,只要改变节点中的指针,但是需要先定位到元素上)
缺点:
- 查询第 n个元素需要 O(n) 时间,时间效率低(每个节点地址不连续、无规律,导致按照索引查询效率低下)
- 要在单向链表中找到某个节点的前驱节点,必须从链表的头节点出发依次向后寻找,但是需要Ο(n)时间
【注】应用场景:如果要解决的问题里面需要很多快速查询,链表可能并不适合;如果遇到的问题中,数据的元素个数不确定,而且需要经常进行数据的添加和删除,那么链表会比较合适。而如果数据元素大小确定,删除插入的操作并不多,那么数组可能更适合。
经典解法:
- 利用快慢指针:例如,链表的翻转;寻找倒数第 k 个元素;寻找链表中间位置的元素;判断链表是否有环等等;
- 构建一个虚假链表头:一般用在要返回新的链表的题目中,比如,给定两个排好序的链表,要求将它们整合在一起并排好序。在这类问题里,如果不用一个虚假的链表头,那么在创建新链表的第一个元素时,我们都得要判断一下链表的头指针是否为空,也就是要多写一条 if else 语句。比较简洁的写法是创建一个空的链表头,直接往其后面添加元素即可,最后返回这个空的链表头的下一个节点即可。
【建议】:在解决链表的题目时,可以在纸上或者白板上画出节点之间的相互关系,然后画出修改的方法,既可以帮助你分析问题,又可以在面试的时候,帮助面试官清楚地看到你的思路
栈
特点:后进先出,对于栈中的数据来说,所有操作都是在栈的顶部完成的,只可以查看栈顶部的元素,只能够向栈的顶部压⼊数据,也只能从栈的顶部弹出数据。入栈出栈可以模拟“递归”的过程;
运用:操作系统会给每个线程创建一个内存栈来存储函数调用时各个函数的参数、返回地址及临时变量;
实现:利用一个单链表来实现栈的数据结构。而且,因为我们都只针对栈顶元素进行操作,所以借用单链表的头就能让所有栈的操作在 O(1) 的时间内完成;
应用场景:解决问题时,只关心最近一次操作,且在操作完成后,需要向前查找更前一次的操作。例如:深度优先搜索DFS
队列
特点:先进先出,就好像按顺序排队一样,对队列数据来说,只允许在队尾查看和添加数据,在队头查看和删除数据
实现:可以借助双链表来实现队列。双链表的头指针允许在队头查看和删除数据,而双链表的尾指针允许我们在队尾查看和添加数据
应用场景:广度(宽度)优先搜索BFS是运用队列最多的地方
特例:
双端队列:它允许在队列的头尾两端都能在 O(1) 的时间内进行数据的查看、添加和删除。常用的地方就是实现一个长度动态变化的窗口或者连续区间
树
二叉搜索树性质:每个节点最多有两个子节点,且左子节点值小于或等于根节点值,而右节点值大于或等于根节点值
树的遍历:3种遍历都有递归和循环两种不同的实现方式
- 前序遍历:先访问根节点,然后访问左子树,最后访问右子树(根左右)
应用场景: 在树里进行搜索以及创建一棵新的树。
- 中序遍历:先访问左子树,然后访问根节点,最后访问右子树(左根右)
应用场景:二叉搜索树,对二叉搜索树进行中序遍历的时候,被访问到的节点大小是按顺序进行的。
- 后续遍历:先访问左子树,然后访问右子树,最后访问根节点(左右根)
应用场景:在对某个节点进行分析的时候,需要来自左子树和右子树的信息。收集信息的操作是从树的底部不断地往上进行,好比你在修剪一棵树的叶子,修剪的方法是从外面不断地往根部将叶子一片片地修剪掉。