// 单链表节点的结构
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x; }
}
链表
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含data 域,next域:指向下一个节点.
- 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定.
基础知识
哈希表的简单介绍
- 哈希表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用HashSet结构(C++中叫UnOrderedSet)
- 如果既有key,又有伴随数据value,可以使用HashMap结构(C++中叫UnOrderedMap)
- 有无伴随数据,是HashMap和HashSet唯一的区别,底层的实际结构是一回事
- 使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为
O(1),但是常数时间比较大 - 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
- 放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是这个东西内存地
址的大小
在java中 HashSet就是一个只使用HashMap的key的一个集合。
有序表的简单介绍
- 有序表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用TreeSet结构(C++中叫OrderedSet)
- 如果既有key,又有伴随数据value,可以使用TreeMap结构(C++中叫OrderedMap)
- 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事
- 有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
- 红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现
不同 - 放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
- 放入有序表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占
用是这个东西内存地址的大小 - 不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复
杂度
有序表的固定操作:
- void put(K key, V value):将一个(key,value)记录加入到表中,或者将key的记录
更新成value。 - V get(K key):根据给定的key,查询value并返回。
- void remove(K key):移除key的记录。
- boolean containsKey(K key):询问是否有关于key的记录。
- K firstKey():返回所有键值的排序结果中,最左(最小)的那个。
- K lastKey():返回所有键值的排序结果中,最右(最大)的那个。
- K floorKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,
key的前一个。 - K ceilingKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,key的后一个。
以上所有操作时间复杂度都是O(logN),N为有序表含有的记录数
练习
反转单链表和双链表
节点结构:
public class TreeNode {
public int num;
public TreeNode next;
public TreeNode last;
public TreeNode(int num) {
this.num = num;
}
public TreeNode() {
}
}
public class Node {
public int num;
public Node next;
public Node(int num, Node next) {
this.num = num;
this.next = next;
}
}
两种方式反转链表结构:
/**
* 反转单向链表
* while循环
*/
public static Node reversal1(Node head) {
Node last = head.next;
head.next = null;
while (last != null) {
Node temp = last.next;
last.next = head;
head = last;
last = temp;
}
return head;
}
/**
* 反转单链表
* 递归
* @param head
* @return
*/
public static Node reversal2(Node head) {
if(head.next == null) {
return head;
}
Node last = reversal2(head.next);
head.next.next = head;
head.next = null;
return last;
}
/**
* 反转双向链表
* 循环
* @param head
* @return
*/
public static TreeNode reversal3(TreeNode head) {
TreeNode next = head.next;
head.next = null;
while (next != null) {
head.last = next;
TreeNode temp = next.next;
next.next = head;
head = next;
next = temp;
}
return head;
}
/**
* 反转双向链表
* 递归
* @param head
* @return
*/
public static TreeNode reversal4(TreeNode head) {
if (head.next == null) {
return head;
}
TreeNode node = reversal4(head.next);
head.next.last = head.next.next;
head.next.next = head;
head.next = null;
return node;
}
打印两个有序链表的公共部分
public static void portion(Node n1, Node n2) {
while(n1 != null && n2 != null) {
if (n1.num == n2.num) {
System.out.print(n1.num);
n1 = n1.next;
n2 = n2.next;
}else if (n1.num < n2.num) {
n1 = n1.next;
}else if (n1.num > n2.num) {
n2 = n2.next;
}
}
}
技巧
面试时链表解题的方法论
- 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
- 对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
重要技巧:
- 额外数据结构记录(哈希表等)
- 快慢指针
示例
判断回文链表
/**
* 不使用额外的空间,适用于面试
* @param head
* @return
*/
public static boolean plalindrome2(Node head) {
//首先通过快慢指针找到中间的节点,如果是偶数:中间左边,奇数:中间节点
Node slowNode = head;
Node quickNode = head