哈希表的简单介绍
- 哈希表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用HashSet结构
- 如果既有key,又有伴随数据value,可以使用HashMap结构
- 有无伴随数据,是HashMap和HashSet唯一的区别,底层的实际结构是一回事
- 使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为O(1),但是常数时间比较大
- 放入哈希表的东西(key),如果是基础类型,内部按值传递,内存占用就是这个东西的大小
放入哈希表的东西(key),如果不是基础类型,内部按引用传递,内存占用是这个东西内存地址的大小
有序表的简单介绍
有序表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用TreeSet结构(C++中叫OrderedSet)
- 如果既有key,又有伴随数据value,可以使用TreeMap结构(C++中叫OrderedMap)
- 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事5)有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
- 红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现不同
- 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
- 放入哈希表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占用是这个东西内存地址的大小
- 不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复杂度
对于链表如果有换头的操作一定有返回值。
面试时链表解题的方法论
- 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
重要技巧:
额外数据结构记录(哈希表等)
- 快慢指针(例如:一个指针走两步,一个走一步,这个需要根据不同的要求进行改变)
快指针走两步,慢指针走一步,快指针走到末尾,慢指针来到中间节点。
判断一个链表是否为回文结构
【题目】给定一个单链表的头节点head,请判断该链表是否为回文结构。
【例子】1->2->1,返回true;1->2->2->1,返回true;15->6->15,返回true;1->2->3,返回false。
【例子】如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。// need n extra spacepublic static boolean isPalindrome1(Node head) {Stack<Node> stack = new Stack<Node>();Node cur = head;while (cur != null) {stack.push(cur);cur = cur.next;}while (head != null) {if (head.value != stack.pop().value) {return false;}head = head.next;}return true;}// need n/2 extra spacepublic static boolean isPalindrome2(Node head) {if (head == null || head.next == null) {return true;}Node right = head.next;Node cur = head;while (cur.next != null && cur.next.next != null) {right = right.next;cur = cur.next.next;}Stack<Node> stack = new Stack<Node>();while (right != null) {stack.push(right);right = right.next;}while (!stack.isEmpty()) {if (head.value != stack.pop().value) {return false;}head = head.next;}return true;}// need O(1) extra spacepublic static boolean isPalindrome3(Node head) {if (head == null || head.next == null) {return true;}Node n1 = head;Node n2 = head;while (n2.next != null && n2.next.next != null) { // find mid noden1 = n1.next; // n1 -> midn2 = n2.next.next; // n2 -> end}n2 = n1.next; // n2 -> right part first noden1.next = null; // mid.next -> nullNode n3 = null;while (n2 != null) { // right part convertn3 = n2.next; // n3 -> save next noden2.next = n1; // next of right node convertn1 = n2; // n1 moven2 = n3; // n2 move}n3 = n1; // n3 -> save last noden2 = head;// n2 -> left first nodeboolean res = true;while (n1 != null && n2 != null) { // check palindromeif (n1.value != n2.value) {res = false;break;}n1 = n1.next; // left to midn2 = n2.next; // right to mid}n1 = n3.next;n3.next = null;while (n1 != null) { // recover listn2 = n1.next;n1.next = n3;n3 = n1;n1 = n2;}return res;}
将单向链表按某值划分成左边小、中间相等、右边大的形式
【题目】给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。
【进阶】在实现原问题功能的基础上增加如下的要求
【要求】调整后所有小于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有等于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有大于pivot的节点之间的相对顺序和调整前一样
【要求】时间复杂度请达到O(N),额外空间复杂度请达到O(1)。public static Node listPartition1(Node head, int pivot) {if (head == null) {return head;}Node cur = head;int i = 0;while (cur != null) {i++;cur = cur.next;}Node[] nodeArr = new Node[i];i = 0;cur = head;for (i = 0; i != nodeArr.length; i++) {nodeArr[i] = cur;cur = cur.next;}arrPartition(nodeArr, pivot);for (i = 1; i != nodeArr.length; i++) {nodeArr[i - 1].next = nodeArr[i];}nodeArr[i - 1].next = null;return nodeArr[0];}public static void arrPartition(Node[] nodeArr, int pivot) {int small = -1;int big = nodeArr.length;int index = 0;while (index != big) {if (nodeArr[index].value < pivot) {swap(nodeArr, ++small, index++);} else if (nodeArr[index].value == pivot) {index++;} else {swap(nodeArr, --big, index);}}}public static void swap(Node[] nodeArr, int a, int b) {Node tmp = nodeArr[a];nodeArr[a] = nodeArr[b];nodeArr[b] = tmp;}public static Node listPartition2(Node head, int pivot) {Node sH = null; // small headNode sT = null; // small tailNode eH = null; // equal headNode eT = null; // equal tailNode bH = null; // big headNode bT = null; // big tailNode next = null; // save next node// every node distributed to three listswhile (head != null) {next = head.next;head.next = null;if (head.value < pivot) {if (sH == null) {sH = head;sT = head;} else {sT.next = head;sT = head;}} else if (head.value == pivot) {if (eH == null) {eH = head;eT = head;} else {eT.next = head;eT = head;}} else {if (bH == null) {bH = head;bT = head;} else {bT.next = head;bT = head;}}head = next;}// small and equal reconnectif (sT != null) {sT.next = eH;eT = eT == null ? sT : eT;}// all reconnectif (eT != null) {eT.next = bH;}return sH != null ? sH : eH != null ? eH : bH;}
复制含有随机指针节点的链表
【题目】一种特殊的单链表节点类描述如下
class Node { int value; Node next; Node rand; Node(int val) { value = val; } }rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】时间复杂度O(N),额外空间复杂度O(1)public static Node copyListWithRand1(Node head) { HashMap<Node, Node> map = new HashMap<Node, Node>(); Node cur = head; while (cur != null) { map.put(cur, new Node(cur.value)); cur = cur.next; } cur = head; while (cur != null) { map.get(cur).next = map.get(cur.next); map.get(cur).rand = map.get(cur.rand); cur = cur.next; } return map.get(head); } public static Node copyListWithRand2(Node head) { if (head == null) { return null; } Node cur = head; Node next = null; // copy node and link to every node while (cur != null) { next = cur.next; cur.next = new Node(cur.value); cur.next.next = next; cur = next; } cur = head; Node curCopy = null; // set copy node rand while (cur != null) { next = cur.next.next; curCopy = cur.next; curCopy.rand = cur.rand != null ? cur.rand.next : null; cur = next; } Node res = head.next; cur = head; // split while (cur != null) { next = cur.next.next; curCopy = cur.next; cur.next = next; curCopy.next = next != null ? next.next : null; cur = next; } return res; }链表有环无环
入环节点:进入环的第一个节点
如何判断链表是否有环
用快慢指针,快指针一次走两步,慢指针一次走一步。它们一定会在环上相遇。
相遇之后,快指针回到开始,然后一次走一步,慢指针继续在原位置一次走一步,它们必定在入环结点相遇。
两个单链表相交的一系列问题
【题目】给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null
【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。

public class Code07_FindFirstIntersectNode {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static Node getIntersectNode(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if (loop1 == null && loop2 == null) {
return noLoop(head1, head2);
}
if (loop1 != null && loop2 != null) {
return bothLoop(head1, loop1, head2, loop2);
}
return null;
}
// 找到链表第一个入环节点,如果无环,返回null
public static Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node n1 = head.next; // n1 -> slow
Node n2 = head.next.next; // n2 -> fast
while (n1 != n2) {
if (n2.next == null || n2.next.next == null) {
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
// 如果两个链表都无环,返回第一个相交节点,如果不相交,返回null
public static Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
if (cur1 != cur2) {
return null;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2) {
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
while (cur2 != loop2) {
n--;
cur2 = cur2.next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
} else {
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
public static void main(String[] args) {
// 1->2->3->4->5->6->7->null
Node head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
// 0->9->8->6->7->null
Node head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(getIntersectNode(head1, head2).value);
// 1->2->3->4->5->6->7->4...
head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
head1.next.next.next.next.next.next = head1.next.next.next; // 7->4
// 0->9->8->2...
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next; // 8->2
System.out.println(getIntersectNode(head1, head2).value);
// 0->9->8->6->4->5->6..
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(getIntersectNode(head1, head2).value);
}
}
