ref:https://leetcode-cn.com/leetbook/read/linked-list/x6ybqh/
链表
与数组相似,链表也是一种线性数据结构。链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。
与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引来访问元素平均要花费 O(N) 时间,其中 N 是链表的长度。
单链表
// Definition for singly-linked list.
public class SinglyListNode {
int val;
SinglyListNode next;
SinglyListNode(int x) { val = x; }
}
双链表:
class DoublyListNode {
int val;
DoublyListNode next, prev;
DoublyListNode(int x) {val = x;}
}
单链表
添加节点
1 使用给定值初始化新结点 cur;
2 将 cur 的 next 字段链接到 prev 的下一个结点 next ;
3 将 prev 中的 next 字段链接到 cur 。
时空复杂度都是O(1) :与数组不同,不需要将所有元素移动到插入元素之后。
删除节点
方法一:删除当前节点
1 找到 cur 的上一个结点 prev 及其下一个结点 next
2 接下来链接 prev 到 cur 的下一个节点 next
删除结点的时间复杂度将是 O(N)。我们需要找出 prev 和 next。使用 cur 的参考字段很容易找出
next,但是,必须从头结点遍历链表,以找出 prev,它的平均时间是 O(N),其中 N 是链表的长度。 空间复杂度为
O(1),因为我们只需要常量空间来存储指针。
方法二:不需要遍历寻找前序节点
1 将当前节点的后继节点值赋给当前节点,val = val->next,相当于把后继节点提前;
2 这时有两个重复的节点,然后index->next = index->next->next,相当于删除了后继节点。
总结来说,也就是把后继节点的值保留在当前节点上,然后删除后继节点,避免寻找前序节点的复杂操作。
作者:北枝
链接:https://leetcode-cn.com/leetbook/read/linked-list/j1wom/?discussion=cDuWfL
遍历
ListNode cur = head;
while (cur != null) {
cur = cur.next;
}
双链表
添加节点
如果我们想在现有的结点 prev 之后插入一个新的结点 cur,我们可以将此过程分为两个步骤:
1 链接 cur 与 prev 和 next,其中 next 是 prev 原始的下一个节点;
2 用 cur 重新链接 prev 和 next。
与单链表类似,添加操作的时间和空间复杂度都是 O(1)。
删除节点
如果我们想从双链表中删除一个现有的结点 cur,我们可以简单地将它的前一个结点 prev 与下一个结点 next 链接起来。
与单链表不同,使用“prev”字段可以很容易地在常量时间内获得前一个结点。
因为我们不再需要遍历链表来获取前一个结点,所以时间和空间复杂度都是O(1)。
经典例题
反转链表
力扣24
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null, curr = head;
while (curr != null) {
ListNode temp = curr.next;
curr.next = prev;
prev = curr;
curr = temp;
}
return prev;
}
}
快慢指针技巧
判断链表中是否有环。
1、 可用HashSet
2、 快慢指针
道理有点像小学奥赛的追击问题,速度不一样一定会相遇的。
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null) return false;
ListNode slow = head;
ListNode fast = head.next;
while(fast != slow ){
if( fast==null || fast.next == null) return false; //走到末端
slow = slow.next;
fast = fast.next.next; //走两步
}
return true; //fast追上了, 有环
}
时间复杂度:O(N),其中 NN 是链表中的节点数。 当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。
当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 NN 轮。空间复杂度:O(1)。我们只使用了两个指针的额外空间。
找到倒数第k个节点
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode right = head,left = head;
for(int i = 0; i < k; i++){
right = right.next;
}
while(right != null){
right = right.next;
left = left.next;
}
return left;
}
}
删除倒数第N个节点
注意 dummy head 这种用法!
ListNode dummy =new ListNode(0);;
dummy.next = head;
return dummy.next; // 不是head
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode right = head,left2 = head, dummy =new ListNode(0);;
dummy.next = head;
ListNode left1 = dummy;
for(int i = 0; i < n; i++){
right = right.next;
}
while(right != null){
right = right.next;
left2 = left2.next;
left1 = left1.next;
}
left1.next = left2.next;
return dummy.next; //不能是head,不然 [1] 1时,结果是[1],不正确
}
合并两个有序链表
ListNode ans=new ListNode();
ListNode p=ans;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){ //保存l1
p.next=l1;
l1=l1.next;
}else{ //保存l2
p.next=l2;
l2=l2.next;
}
p=p.next;
}
if(l1!=null)p.next=l1;
if(l2!=null)p.next=l2;
return ans.next;
递归
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){ return list2; }
if(list2 == null){ return list1; }
if(list1.val<=list2.val){
list1.next=Merge(list1.next, list2);
return list1;
}else{
list2.next=Merge(list1, list2.next);
return list2; }
}
}
第一个公共节点
//1. 使用SET
//2. 双指针法:奥数题啊。两个链表长度分别为L1+C、L2+C, C为公共部分的长度,。 第一个人走了L1+C步后,回到第二个人起点走L2步;第2个人走了L2+C步后,回到第一个人起点走L1步。 当两个人走的步数都为L1+L2+C时在第一个节点相遇了。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//tempA和tempB我们可以认为是A,B两个指针
ListNode tempA = headA;
ListNode tempB = headB;
while (tempA != tempB) {
//如果指针tempA不为空,tempA就往后移一步。
//如果指针tempA为空,就让指针tempA指向headB(注意这里是headB不是tempB)
tempA = tempA == null ? headB : tempA.next;
//指针tempB同上
tempB = tempB == null ? headA : tempB.next;
}
//tempA要么是空,要么是两链表的交点
return tempA;
}
}