HashMap map = new HashMap;
map.put("zuochengyun", 30);
System.out.println(map.containsKey("zuochengyun”)); // true
System.out.println(map.containsKey("zuo”)); // false
System.out.println(map.get("zuochengyun”)); // 30
map.put("zuochengyun", 32);
System.out.println(map.get("zuochengyun”)); // 32
map.remove("zuochengyun");
System.out.println(map.containsKey("zuochengyun”)); // false
System.out.println(map.get("zuochengyun”)); // null
map.put("zuochengyun", 30);
String test1 = "zuochengyun";
String test2 = "zuochengyun";
System.out.println(map.containsKey(test1)); // true
System.out.println(map.containsKey(test2)); // true
HashMap map2 = new HashMap<>();
map2.put(123, "hello");
Integer a = 123;
Integer b = 123;
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
System.out.println(map2.containsKey(a)); // true
// Integer, Double, Flow, Char, String 在哈希表中都按值传递
public static class Node() {
public int value;
public Node(int v) {
value = v;
}
}
Node node1 = new Node(1);
Node node2 = new Node(1);
HashMap map3 = new HashMap<>();
map3.put(node1, "hello");
System.out.println(map3.containsKey(node1)); // true
System.out.println(map3.containsKey(node2)); // false
// 非原生类型按引用传递
TreeMap treeMap1 = new TreeMap<>();
treeMap1.put(7, "我是7");
treeMap1.put(5, "我是5");
treeMap1.put(4, "我是4");
treeMap1.put(3, "我是3");
treeMap1.put(9, "我是9");
treeMap1.put(2, "我是2");
// 最小的key
System.out.println(treeMap.firstKey()); // 2
// 最大的key
System.out.println(treeMap.lastKey()); // 9
// <=8的key中离8最近的
System.out.println(treeMap.floorKey(8)); // 7
// >=8的key中离8最近的
System.out.println(treeMap.ceilingKey(8)); // 9
Node node3 = new Node(3);
Node node4 = new Node(4);
TreeMap treemMap2 = new treeMap<>();
// 会报错。treeMap中添加的key必须是可以比较的东西。需在上一行的括号中添加比较器
treeMap2.put(node3, "hello3");
treeMap2.put(node4, "hello4");
public static class NodeComparator implements Comparator {
@Override
public int compare(Node o1, Node o2) {
return o1.value-o2.value; // 升序排列
}
}
TreeMap treemMap3 = new treeMap<>(new NodeComparator);
// 不再报错
treeMap2.put(node3, "hello3");
treeMap2.put(node4, "hello4");
链表的各个节点不一定是连续存放的
单链表的节点结构:
Class Node {
V value;
Node next;
}
双链表的节点结构:
Class Node {
V value;
Node next;
Node last;
}
单链表和双链表只需给定一个头部节点head,就可以找到剩下的所有节点
public ListNode reverseList(ListNode head) {
ListNode next = null; //用于暂存当前节点的next
ListNode prev = null;
while (head != null) {
next = head.next;
head.next = prev;
prev = head;
head = next;
}
return prev;
}
public DoubleNode reverseDoubleList(DoubleNode head) {
DoubleNode prev = null;
DoubleNode next = null;
while (head != null) {
next = head.next;
head.next = prev;
head.prev = next;
prev = head;
head = next;
}
return prev;
}
要求时间复杂度O(N),额外空间复杂度O(1)
思路:类似mergeSort中merge的过程。从头开始遍历两个链表,每次移动值小的指针(因为大的要等小的追上来才有相等的可能),相等的时候就打印节点的值,并同时移动两个链表的指针,直到某个链表的遍历结束。
思路:和上一题类似,也是使用merge的过程。首先比较head1和head2的value大小,小的作头和返回值;然后一次比较两个指针,取值小的作next,直至其中一个指向null,再将另一个非空指针作next。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
ListNode head = list1.val <= list2.val ? list1 : list2;
ListNode cur1 = head.next;
ListNode cur2 = head == list1 ? list2 : list1;
ListNode temp = head;
while (cur1 != null && cur2 != null) {
if (cur1.val <= cur2.val) {
temp.next = cur1;
cur1 = cur1.next;
} else {
temp.next = cur2;
cur2 = cur2.next;
}
temp = temp.next;
}
temp.next = cur1 != null ? cur1 : cur2;
return head;
}
要求时间复杂度O(N),额外空间复杂度O(1)
空间复杂度O(N)的做法:开辟一个stack,将链表中的节点按顺序放入栈。放完后依次弹出栈里的内容,顺序即是和链表相反。依次比较弹出的内容和链表的节点,如果每个都一样即为回文链表。
省一半空间的做法:只把链表的后半部分放入栈,然后依次比较链表节点(from head)和弹出的东西。得到后半部分的方法:快慢指针
空间复杂度O(1)的做法:
左:快慢指针找到终点和结尾,反转后半部分链表,中点指向null;从头和尾分别开始check是否一致,有一个走到null停止;若每一步都一样则为回文链表。返回结果之前要将后半部分反转回去。
改:快慢指针找到中点(偶数时右中点,奇数时中点),找中点的同时反转前部半分链表
偶数情况: 1 <- 2 <- 3 4 -> 5 -> 6 (prev=3, slow=4, fast=null)
奇数情况: 1 <- 2 <- 3 4 -> 5 -> 6 -> 7 (prev=3, slow=4, fast=7)
奇数时从slow.next开始和prev进行比较;比较的同时恢复前半部分链表
// O(N) extra space
public static boolean isPalindrome1(ListNode head) {
Stack stack = new Stack<>();
ListNode curr = head;
while (curr != null) {
stack.push(curr);
curr = curr.next;
}
while (!stack.isEmpty()) {
if (head.val != stack.pop().val) {
return false;
} else {
head = head.next;
}
}
return true;
}
// O(N/2) extra space
public static boolean isPalindrome2(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode slow = head;
ListNode fast = head;
Stack stack = new Stack<>();
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
while (slow != null) {
stack.push(slow);
slow = slow.next;
}
while (!stack.isEmpty()) {
if (head.val != stack.pop().val) {
return false;
} else {
head = head.next;
}
}
return true;
}
// O(1) extra space
public static boolean isPalindrome3(ListNode head) {
ListNode slow = head;
ListNode fast = head;
ListNode prev = null;
ListNode nextTemp = null;
while (fast != null && fast.next != null) { // slow在偶数时为右中点,奇数时为中点
fast = fast.next.next;
// 更新slow的同时反转链表(slow前一个node,即prev开始往前是逆序的)
nextTemp = slow.next;
slow.next = prev;
prev = slow;
slow = nextTemp;
}
// 此时的状况:
// 偶数情况: 1 <- 2 <- 3 4 -> 5 -> 6 (prev=3, slow=4, fast=null)
// 奇数情况: 1 <- 2 <- 3 4 -> 5 -> 6 -> 7 (prev=3, slow=4, fast=7)
ListNode prepre = slow; // 之后要恢复前半段链表,slow应作为prev.next但比较时slow会后移,所以先记下slow的位置
if (fast != null) { // fast在奇数是是最后一个数,偶数时是null;这里为奇数情况
slow = slow.next; // slow本来为中点,但不需要比较中点,从中点后一个开始和prev比较
}
// prev和slow分别作为前半段和后半段的头开始比较
boolean flag = true;
while (prev != null) {
if (prev.val != slow.val) {
flag = false;
}
slow = slow.next;
// 恢复前半段链表
nextTemp = prev.next;
prev.next = prepre;
prepre = prev;
prev = nextTemp;
}
return flag;
}
思路:
开辟6个空间:SH, ST, EH, ET, BH, BT (small head/tail, equal head/tail, Big head/tail);
遍历链表,若小于pivot:若SH为null,则将其设置为小于区的头和尾;若SH不为null,则将ST.next设为该节点,并将ST更新为该节点。其他情况同理。
最后将SH, ST, EH, ET, BH, BT连接起来,但要注意其中有区域为空的情况。
要求时间复杂度O(N),额外空间复杂度O(1)
思路:
空间复杂度O(N)的做法:新建一个hashmap,遍历原链表,将原节点存为key,copy节点存为value {node1->node1’, node2->node2’, node3, node3’…}。再遍历一遍原链表,原节点中的next,rand对应的copy同样赋给copy节点。
空间复杂度O(1)的做法:遍历原链表,对于每个原节点创建一个copy节点放在原节点和原节点.next之间,node1 -> node1’ -> node2 -> node2’ -> node3 -> node3’…之后再遍历一遍原链表,将原节点的rand对应的copy赋给原节点的copy,即node1’.rand = node1.rand.next。最后第三遍遍历原链表,分离原节点和copy节点。
// extra space O(N)
public Node copyRandomList1(Node head) {
HashMap map = new HashMap<>();
Node curr = head;
while (curr != null) {
map.put(curr, new Node(curr.val));
curr = curr.next;
}
curr = head;
while (curr != null) {
// curr 老节点
// map.get(curr) 复制节点
map.get(curr).next = map.get(curr.next);
map.get(curr).random = map.get(curr.random);
curr = curr.next;
}
return map.get(head);
}
// extra space O(1)
public Node copyRandomList2(Node head) {
if (head == null) {
return null;
}
Node curr = head;
while (curr != null) {
Node nextTemp = curr.next;
Node copy = new Node(curr.val);
curr.next = copy;
copy.next = nextTemp;
curr = nextTemp;
}
curr = head;
while (curr != null) {
Node copy = curr.next;
Node next = copy.next;
copy.random = curr.random != null ? curr.random.next : null;
curr = next;
}
curr = head;
Node res = head.next;
while (curr != null) {
Node copy = curr.next;
Node next = copy.next;
curr.next = next;
copy.next = next != null ? next.next : null;
curr = next;
}
return res;
}
给定两个可能有环也可能无环的单链表head1和head2。若两链表相交则返回相交的第一个节点;否则返回null。
LC160版本中明确两个链表均不带环。
step1: 判定两个链表是否带环
思路:快慢指针从头开始走,若快指针走到null则说明无环,若两者相遇则说明有环。此时将快指针移动到链表头,慢指针不动,两个指针每次都走一步,它们再次相遇的地方即为入环处。
证明:
(1) 假设从链表头到入环点有 a a a个节点,环的长度为 b b b个节点,则整个链表的长度为 a + b a+b a+b。快指针走两步,慢指针走一步,则有 f = 2 s f=2s f=2s。快指针和慢指针相遇时,快指针比慢指针多走的长度一定是环长的倍数,即 f = s + n b f=s+nb f=s+nb。
(2) 由这两个式子可得 s = n b s=nb s=nb, f = 2 n b f=2nb f=2nb,即快、慢指针分别走了 2 n b 2nb 2nb、 n b nb nb个环长。
(3) 如果让一个指针从链表头部一直向下走并统计步数 k k k,那么所有 走到链表入口节点时的步数 是: k = a + n b k=a+nb k=a+nb(先走 a a a 步到入环处,之后每绕 1 圈环( b 步)都会再次到入口节点)。
(4) 目前,慢指针走过的步数为 n b nb nb步,即只要再走 a a a步就可以到入环处。所以此时若另一个指针从链表头开始走,每次走一步,则该指针会和慢指针在 a a a步后在入环处相遇。
public ListNode detectCycle(ListNode head) {
if (head==null || head.next==null || head.next.next==null) {
return null;
}
ListNode slow = head.next;
ListNode fast = head.next.next;
while (fast != slow) {
if (fast == null || fast.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
}
fast = head;
while (fast != slow) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
step2:分类讨论
// 如果两个链表都无环,返回第一个相交节点,如果不想交,返回null
public ListNode noLoopFindIntersect(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode cur1 = headA;
ListNode cur2 = headB;
int len = 0;
boolean intersect = false;
while (cur1.next != null) {
cur1 = cur1.next;
len++;
}
while (cur2.next != null) {
cur2 = cur2.next;
len--;
}
if (cur1 != cur2) { // 结尾点不同则说明不相交
return null;
} else { // 否则说明相交
// cur1为长链表的头,cur2为短链表的头
cur1 = len>0 ? headA : headB;
cur2 = cur1==headA ? headB : headA;
len = Math.abs(len);
while (len != 0) {
cur1 = cur1.next;
len--;
}
while (cur1 != null) {
if (cur1 == cur2) {
return cur1;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
}
return null;
}
// 主函数
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode loop1 = detectCycle(headA);
ListNode loop2 = detectCycle(headB);
if (loop1 == null && loop2 == null) { //两个无环链表
return noLoopFindIntersect(headA, headB);
}
if (loop1 != null && loop2 != null) { //两个链表都有环
return bothLoopFindIntersect(headA, loop1, headB, loop2);
}
return null; // 一个有环一个无环,必定无交点
}
// 两个有环链表,返回第一个相交节点,如果不想交返回null
public ListNode bothLoopFindIntersect(ListNode headA, ListNode loop1, ListNode headB, ListNode loop2) {
ListNode cur1 = null;
ListNode cur2 = null;
if (loop1 == loop2) { // 情况2
// 和无环链表相同的处理方法,唯独把终点判断条件从null改成了loop1
int len = 0;
boolean intersect = false;
while (cur1.next != loop1) {
cur1 = cur1.next;
len++;
}
while (cur2.next != loop2) {
cur2 = cur2.next;
len--;
}
if (cur1 != cur2) { // 结尾点不同则说明不相交
return null;
} else { // 否则说明相交
// cur1为长链表的头,cur2为短链表的头
cur1 = len>0 ? headA : headB;
cur2 = cur1==headA ? headB : headA;
len = Math.abs(len);
while (len != 0) {
cur1 = cur1.next;
len--;
}
while (cur1 != null) {
if (cur1 == cur2) {
return cur1;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
}
return null;
} else { // 情况1或3
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1; // 情况3
}
cur1 = cur1.next;
}
return null; // 情况1
}
}
Reference:
Java的Comparator升序降序的记法
Leetcode234-题解-蓝黑R9
Leetcode138-题解-林小鹿
Leetcode142-题解-jyd
LeetCode160-官方题解