持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
哈希表
哈希表的简单介绍
- 哈希表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用HashSet结构(Set:不含重复元素的集合)
- 如果既有key,又有伴随数据value,可以使用HashMap结构
- 使用哈希表进行增删改查的操作,可以认为这些操作的时间复杂度为O(1),但是常数时间比较大
- 放入哈希表的东西,如果是基础类型,内部按照值传递,内存占用就是这个东西的大小
- 放入哈希表的东西,如果不是基础类型,内部按照引用传递,内存占用就是这个东西内存地址的大小,即key相当于一个指针,指向的是这个东西内存的地址
有序表的简单介绍
- 有序表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用TreeSet结构
- 如果既有key,又有伴随数据value,可以使用TreeMap结构
- 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层是实际结构实现是相同的
- 有序表和哈希表的区别是:有序表把key按照顺序组织起来,而哈希表完全不阻止
- 红黑树,AVL树、size-balance-tree和跳表都属于有序表结构,只是底层具体实现不同
- 放入有序表的东西,如果是基础类型,内部按照值传递,内存占用就是这个东西的大小
- 放入有序表的东西,如果不是基础类型,必须提供比较器,内部按照引用传递,内存占用就是这个东西内存地址的大小,即key相当于一个指针,指向的是这个东西内存的地址
- 增删改查的操作时间复杂度都是O(logN)级别
单链表
Class Node<V>{
V value;
Node next;
}
反转单向和双向链表
题目:分别实现反转单向链表和反转双向链表的函数 要求:如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
思路:需要用到3个指针,left,mid,right
public static void reverseLinkList(Node){
Node left;
Node mid;
Node right;
while(mid!=null){
mid.next = left;
left = mid;
mid = right;
if(right!=null){
right = right.next;
}
}
}
打印两个有序链表的公共部分
题目:给定两个有序链表的头指针head1和head2,打印两个链表的公共部分 要求:如果两个链表的长度之和为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
思路:利用两个指针分别指向两条链表的head,同时进行比较和遍历,若两个节点的值相同则打印并同时跳到下一个节点,若是不相等则值较小的节点跳转到下一个节点,值较大的节点位置不变
判断一个链表是否为回文结构
题目:给定一个单链表的头节点,请判断该链表是否为回文结构 要求:如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
思路一(空间复杂度O(N/2):使用栈的特殊性,将前半部分放入栈中,然后从最后开始 如何找到中点位置——快慢指针
代码实现:
public static void boolean isPalindrome(Node head){
if(head==null || head.next==null) return;
Node slow = head;
Node fast = head;
Stack<Node> stack = new Stack<Node>();
while(slow!=null && fast!=null){
stack.push(slow);
slow = slow.next;
fast = fast.next.next;
}
while(!stack.isEmpty()){
if(head.value != stack.pop().value){
return false;
}
head = head.next;
}
return true;
}
思路二(空间复杂度O(1)):修改链表结构,将中间节点之后的链表反转,分别用两个指针指向链表的头部和尾部,同时遍历并比较。若是值相同,两个节点同时向中间移动一个节点并再次比较值,直到两个节点的值不相同或者两个节点重合为止
将单向链表按照某值划分成左边小、中间相等、右边大的形式
题目:给定一个单链表的头节点head,结点的值类型是整形,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右边都是值大于pivot的节点
进阶要求:
- 调整后所有小于pivot的节点之间的相对顺序和调整前一样
- 调整后所有等于pivot的节点之间的相对顺序和调整前一样
- 调整后所有大于pivot的节点之间的相对顺序和调整前一样
- 时间复杂度达到O(N),额外空间复杂度达到O(1)
思路:将链表分成三个部分,小于区,等于区,大于区,用指针遍历链表,分别形成三部分的链表,最后将三个链表连起来
需要注意考虑如果没有小于pivot的数的话,小于区不存在的情况等类似特殊情况
复制含有随机指针节点的链表
题目:一种特殊的单链表节点类描述如下:
class Node{
int value;
Node next;
Node rand;
Node(int val){
value = val;
}
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点
思路一:使用hashMap,将每个节点逐一复制后存储到hashMap当中,然后通过判断来确定复制的链表如何连接
思路二:将复制的节点连在原节点之后,然后根据原节点的连接方式连接复制的节点
代码实现:
public static Node copyListWithRand2(Node head){
if(head == null){
return null;
}
Node cur = head;
Node next = null;
//1 -> 2
//1 -> 1' -> 2 -> 2'
while(cur!=null){
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
//给克隆节点的rand赋值
while(cur!=null){
next = cur.next.next;
curCopy = cur.next;
curCopy.rand = cur.rand != null ? cur.rand : null;
cur = next;
}
//分离出克隆链表
Node resHead = head.next;
cur = head;
while(cur!=null){
next = cur.next.next;//原链表的下一个节点
curCopy = cur.next;
cur.next = next;//恢复原链表结构
curCopy.next = next!=null ? next.next:null;
cur = next;
}
return resHead;
}