哈希表
在C++中称为:UnOrderedMap/UnSortedMap/Set
:::info
Map: key-value
Set:only Key
key是唯一的,也只对应一个value值;remove此key时,对应的value也会删除;可以按照key查找;
若是只有key没有value,是set;要是有key又有对应的value,则是map
- 放入hash表的东西,若是基础类型,内部是按照值传递的,内存占据的是该类型的大小
- 若是非基础类型(自定义的类),内部是按照引用传递的,内存是占用此类型的指针(地址)的大小 :::
:::tips Hash表认为增删改查一直是常数时间的! :::
有序表
:::info
C++中称为OrderedSet或者OrderedMap
有序表会把Key按照顺序组织起来,但是Hash表不会
放入OrderedMap的东西,若是非基础类型,需要提供比较器!
:::
链表
//单链表
Class Node{
public:
T value;
Node* next;
}
//双链表
Class DNode{
public:
T value;
DNode* pre;
Dnode* suc;
}
- 笔试时,不用太过在乎空间复杂度,一切为了时间复杂度
- 面试时,时间复杂度仍是放在第一位,但是空间复杂度需要考虑最省的方法
额外重要的技巧
- 额外的数据结构记录(哈希表)
- 快慢指针
- 递归
快慢指针
判断一个链表是否为回文
:::info
回文结构是什么?
1->2->3->2->1
1->20->20->1
:::
笔试中?
依次放到栈里,放完后直接弹出,并且从头开始遍历;
or只放如一半;
不使用额外空间:快慢指针
快指针一次走2步,慢指针一次走1步,快指针走的时候改变链表的指向,
单链表的倒数第 k 个节点
从前往后寻找单链表的第 k 个节点很简单,一个 for 循环遍历过去就找到了,但是如何寻找从后往前数的第 k 个节点呢?
那你可能说,假设链表有 n 个节点,倒数第 k 个节点就是正数第 n - k 个节点,不也是一个 for 循环的事儿吗?
是的,但是算法题一般只给你一个 ListNode 头结点代表一条单链表,你不能直接得出这条链表的长度 n,而需要先遍历一遍链表算出 n 的值,然后再遍历链表计算第 n - k 个节点。
也就是说,这个解法需要遍历两次链表才能得到出倒数第 k 个节点。
:::info
解法是先用一个指针先出发,走k步,然后此时将一个新的指针指向头节点,这样两个指针同时出发,当之前的指针走到末尾时,后出发的指针即走到了n-k个节点!
:::
summary
:::info
- 快慢指针
- 一个指针先出发一个指针后出发
- 快慢指针中,快指针的步数最后总还是慢指针的2倍;记住这个关系 :::
合并链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
合并链表
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* head=new ListNode(-1);
ListNode* p=head;
while(list1!=nullptr&&list2!=nullptr){
if(list1->val>list2->val){
p->next=list2;
p=p->next;
list2=list2->next;
}else{
p->next=list1;
p=p->next;
list1=list1->next;
}
}
if(list1!=nullptr){
p->next=list1;
}
if(list2!=nullptr){
p->next=list2;
}
return head->next;
}
};
:::info 思路是比较简单的,类似归并排序中merge ::: :::tips 代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 dummy 节点。你可以试试,如果不使用 dummy 虚拟节点,代码会复杂很多,而有了 dummy 节点这个占位符,可以避免处理空指针的情况,降低代码的复杂性。 :::
链表的递归
链表是有一定的递归性的,递归有时候可能并不会节省空间,但是很帅。
具体参考labuladong
:::info
递归的注意点在于,有时候在思考的时候不要跳入递归中,因为压栈的操作自己想着想着可能就晕了,而是把递归后的结果弄清楚,每次递归完前一级返回的是什么样的;
:::