一、数据结构
public class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
}
}
当然,可以在这个基础上构建双向链表、循环链表等,这些都比较简单,本文不再赘述。
二、关于本地测试的小技巧
在刷题的时候,经常遇到需要调试代码的情况,而在本地手动创建链表又比较麻烦,所以在这里提供一个输入一个数组返回链表的函数createListNode(int[] a)
(由调用者保证参数的正确性)。
public ListNode createListNode(int[] a){
ListNode head = new ListNode(a[0]);
ListNode tail = head;
for(int i=1;i<a.length;i++){
tail.next = new ListNode(a[i]);
tail = tail.next;
}
return head;
}
三、常见操作
1、反转链表
最好自己动手画一遍,就可以理清楚整个流程。
public ListNode reverseListNode(ListNode head){
ListNode cur,nxt;
ListNode pre = null;
cur = nxt = head;
while(cur != null){
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
2、找中点
一般利用快慢指针,当快指针到达终点时,慢指针指向中点。 以下分别讨论当节点数为奇数和偶数时的情况:
思考:为什么要构建一个虚拟头结点dummy,slow和fast都从dummy开始?
1、可以解决当节点数只有两个的特殊情况,如果没有dummy:
2、如果没有dummy,节点数为偶数时,slow指向的是中点的后一位:
ListNode dummy,slow,fast;
dummy = new ListNode(-1);
dummy.next = head;
slow = fast = pre;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
四、一些题目
剑指 Offer 35. 复杂链表的复制
// 方法一:利用哈希表
// 时间复杂度O(N) 空间复杂度O(N)
class Solution {
public Node copyRandomList(Node head) {
if(head == null)
return head;
// 构建哈希表 存储<原链表节点,新链表节点>
Map<Node,Node> map = new HashMap<>();
Node cur = head;
while(cur!=null){
Node temp = new Node(cur.val);
map.put(cur,temp);
cur = cur.next;
}
// 复制原链表的next指针和random指针
cur = head;
while(cur!=null){
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
// 返回新链表的头结点
return map.get(head);
}
}
// 方法二:Node1->newNode1->Node2->newNode2...
// 1、在原始链表的基础上加入新的链表
Node cur = head;
while(cur!=null){
Node temp = new Node(cur.val);
temp.next = cur.next;
cur.next = temp;
cur = temp.next;
}
// 2、复制原始链表的random指针
cur = head;
while(cur != null){
// 需要判空 不然会导致本来random指向空的现在指向了一个节点
if(cur.random!=null){
// 新的节点的random域指向的是旧节点的random的下一个节点
cur.next.random = cur.random.next;
}
cur = cur.next.next;
}
// 3、把链表分割为两个
Node pre = head;
Node newHead = head.next;
Node newN = head.next;
while(newN.next!=null){
pre.next = pre.next.next;
newN.next = newN.next.next;
pre = pre.next;
newN = newN.next;
}
// 注意需要手动把原始链表的尾结点改为null
pre.next = null;
return newHead;
剑指 Offer II 023. 两个链表的第一个重合节点
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA;
ListNode pB = headB;
while(pA!=pB){
// pA在链表A上遍历
if(pA!=null){
pA = pA.next;
}else{
// 遍历完链表A转到链表B继续遍历
pA = headB;
}
// pB在链表B上遍历
if(pB != null){
pB = pB.next;
}else {
pB = headA;
}
}
return pA;
}
}
剑指 Offer II 025. 链表中的两数相加
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 思路:对l1 l2进行翻转 从地位开始相加,进位传递
ListNode head1 = reverseListNode(l1);
ListNode head2 = reverseListNode(l2);
// 记录进位
int carry = 0;
// 辅助头结点
ListNode head = new ListNode(-1);
ListNode recor = head;
while(head1 != null || head2 != null){
int sum = 0;
if(head1!=null&&head2 != null){
sum = head1.val + head2.val;
head1 = head1.next;
head2 = head2.next;
}else if(head1 != null){
sum = head1.val;
head1 = head1.next;
}else if(head2 != null){
sum = head2.val;
head2 = head2.next;
}
// 是否有进位
if(carry == 1){
sum += 1;
// 使用了进位之后复零
carry = 0;
}
if(sum>=10){
carry = 1;
sum = sum%10;
}
head.next = new ListNode(sum);
head = head.next;
}
if(carry == 1){
head.next = new ListNode(1);
}
// 翻转链表
ListNode res = reverseListNode(recor.next);
return res;
}
public ListNode reverseListNode(ListNode head){
ListNode cur,nxt;
ListNode pre = null;
cur = nxt = head;
while(cur != null){
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
}
剑指 Offer II 026. 重排链表
public void reorderList(ListNode head) {
ListNode slow,fast,pre;
pre = new ListNode(-1);
pre.next = head;
slow = fast = pre;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
// 把链表从中点分成两部分
ListNode half = slow.next;
slow.next = null;
ListNode reverseHalf = reverseList(half);
ListNode nxt1, nxt2, cur;
cur = pre.next;
while(reverseHalf != null){
nxt1 = cur.next;
cur.next = reverseHalf;
nxt2 = reverseHalf.next;
reverseHalf.next = nxt1;
reverseHalf = nxt2;
cur = nxt1;
}
}
public ListNode reverseList(ListNode head){
ListNode cur, nxt;
ListNode pre = null;
cur = nxt = head;
while(cur != null){
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
剑指 Offer II 027. 回文链表
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode pre,slow,fast;
pre = new ListNode(-1);
pre.next = head;
slow = fast = pre;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
ListNode half = slow.next;
slow.next = null;
ListNode reverseHalf = reverseList(half);
ListNode cur = pre.next;
while(reverseHalf != null){
if(cur.val != reverseHalf.val){
return false;
}
reverseHalf = reverseHalf.next;
cur = cur.next;
}
return true;
}
public ListNode reverseList(ListNode head){
ListNode cur,nxt;
ListNode pre = null;
cur = nxt = head;
while(cur != null){
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
}
剑指 Offer II 031. 最近最少使用缓存
class LRUCache {
Map<Integer,Node> map = new HashMap<>();
LinkedList<Node> list = new LinkedList<>();
int capacity = 0;
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
if(!map.containsKey(key)){
return -1;
}
int value = map.get(key).val;
// 重新插入,更新位置
this.put(key,value);
return value;
}
public void put(int key, int value) {
Node newNode = new Node(key,value);
// 如果这个key已经存在,覆盖map并更新在list中的位置
if(map.containsKey(key)){
// 删掉原来的
Node old = map.get(key);
list.remove(old);
}else{
// 需要考虑容量问题
if(list.size() == capacity){
// 从表尾删除
Node last = list.getLast();
list.removeLast();
// 删除在map中的映射关系
map.remove(last.key);
}
}
// 加到表头
list.addFirst(newNode);
// 覆盖map
map.put(key,newNode);
}
}
class Node{
int key,val;
Node next;
public Node(int key, int val){
this.key = key;
this.val = val;
}
}
剑指 Offer II 077. 链表排序
// 方法一:全部丢到小顶堆里面 时间复杂度O(nlogn) 空间复杂度O(n)
class Solution {
public ListNode sortList(ListNode head) {
PriorityQueue<ListNode> heap = new PriorityQueue<ListNode>((a,b)->a.val-b.val);
while(head != null){
heap.add(head);
head = head.next;
}
ListNode newH = new ListNode(-1);
ListNode pre = newH;
while(!heap.isEmpty()){
newH.next = heap.poll();
newH = newH.next;
}
newH.next = null;
return pre.next;
}
}
// 方法二:归并排序 时间复杂度O(nlogn) 空间复杂度O(1)
class Solution {
// 归并排序
public ListNode sortList(ListNode head) {
// base case:只有一个元素或者为空则认为是有序的
if(head == null || head.next == null){
return head;
}
// 二分
ListNode lastHead = split(head);
// 分解
lastHead = sortList(lastHead);
head = sortList(head);
// 合并
return mergeList(head,lastHead);
}
public ListNode mergeList(ListNode head1, ListNode head2){
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while(head1 != null && head2 != null){
if(head1.val <= head2.val){
cur.next = head1;
head1 = head1.next;
}else{
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
cur.next = head1==null?head2:head1;
return dummy.next;
}
public ListNode split(ListNode head){
ListNode pre,slow,fast;
pre = new ListNode(-1);
pre.next = head;
slow = fast = pre;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
ListNode half = slow.next;
slow.next = null;
return half;
}
}
剑指 Offer II 078. 合并排序链表
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> heap = new PriorityQueue<ListNode>((a,b)->(a.val-b.val));
// 把全部头结点加进去
for(ListNode node:lists){
if(node != null){
heap.add(node);
}
}
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
// 维护一个最大为k(数组长度)的小顶堆
// 每次从堆顶取出一个元素,再将这个元素的下一个节点加入堆
while(!heap.isEmpty()){
ListNode tmp = heap.poll();
if(tmp.next != null){
heap.add(tmp.next);
}
cur.next = tmp;
cur = cur.next;
}
cur.next = null;
return dummy.next;
}
}