一、删除链表
1、递归
public ListNode removeElements(ListNode head, int val) {
if (head == null) return head;
head.next = removeElements(head,val);
return head.val == val ? head.next : head;
}
时间复杂度:O(n),因为每次递归只遍历头节点,有n个节点所以相当于遍历了一遍链表,所以是O(n)。
空间复杂度:O(n),每次递归不开辟新的空间但是递归会开辟栈空间,需要开辟n个栈空间用于递归,所以空间复杂度也是O(n)。
2、迭代
public ListNode removeElements(ListNode head, int val) {
ListNode newHead = new ListNode();
newHead.next = head;
ListNode cur = newHead;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
}else {
cur = cur.next;
}
}
return newHead.next;
}
时间复杂度:O(n),其中 nn 是链表的长度。需要遍历链表一次。
空间复杂度:O(1)。
二、定义链表节点和链表
class ListNode{
int val;
ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
size = 0;
head = new ListNode();
}
public int get(int index) {
if (index < 0 || index >= size) return -1;
ListNode cur = head;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index > size) return;
size++;
if (index < 0) index = 0;
ListNode cur = head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
ListNode tmp = cur.next;
cur.next = new ListNode(val);
cur.next.next = tmp;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
ListNode cur = head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
size--;
}
}
三、反转链表
1、迭代
// 迭代
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
// 前后指针一起
while (cur != null) {
// 保存下一个节点
ListNode tmp = cur.next;
// 让cur.next指向pre
cur.next = pre;
// pre、cur移动
pre = cur;
cur = tmp;
}
return pre;
}
2、递归
// 递归
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
private ListNode reverse(ListNode prev, ListNode cur) {
// 如果cur为null,则说明pre是最后一个节点,返回该节点
if (cur == null) return prev;
// 记录下cur的下一个节点,所以此时的排序是prev、cur、tmp
ListNode tmp = cur.next;
// 使cur的next指向prev
cur.next = prev;
// 下一轮迭代,prev就是cur,cur就是tmp
return reverse(cur, tmp);
}
四、交换链表
1、迭代
// 迭代
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = new ListNode();
newHead.next = head;
ListNode cur = newHead;
// 只有要交换的两个节点cur.next和cur.next.next,都不为空才可以进行交换
while (cur.next != null && cur.next.next != null){
ListNode first = cur.next;
ListNode second = first.next;
ListNode tmp = second.next;
// 进行交换
cur.next = second;
second.next = first;
first.next = tmp;
cur = first;
}
return newHead.next;
}
复杂度分析
时间复杂度:O(n),其中 n 是链表的节点数量。需要对每个节点进行更新指针的操作。
空间复杂度:O(1)。
2、递归
// 递归
public ListNode swapPairs(ListNode head) {
// 如果节点的长度等于0或者1,就直接返回这个几点即可
if (head == null || head.next == null) return head;
// 保存第二个节点和第三个节点
ListNode node = head.next;
ListNode tmp = node.next;
// 第二个节点的next指向head
node.next = head;
// head的节点指向交换后的第三个节点
head.next = swapPairs(tmp);
// 返回交换后的第二个节点
return node;
}
复杂度分析
时间复杂度:O(n),其中 n是链表的节点数量。需要对每个节点进行更新指针的操作。
空间复杂度:O(n),其中 n是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。
五、删除倒数第n个节点
1、快慢指针
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode newHead = new ListNode();
newHead.next = head;
ListNode fast = newHead;
ListNode slow = newHead;
// 快指针指到最后一个停止
while (fast != null) {
if (n >= 0) {
n--;
}else {
slow = slow.next;
}
fast = fast.next;
}
slow.next = slow.next.next;
return newHead.next;
}
时间复杂度:O(L),其中 L是链表的节点数量。需要对每个节点进行更新指针的操作。
空间复杂度:O(1)。
2、栈
时间复杂度和空间复杂度都是O(L)
六、链表的交点
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 双指针
// 假设A的长度是a+c,B的长度是b+c
// preA从A链表开始遍历,如果遍历到null,就指向B链表
// preB从B开始,遇到null就指向A链表
// 1、如果两个链表有共同的节点,那么两个指针相遇时就是共同的节点
// 因为相遇时,A走了a+c+b,B走了b+c+a
// 2、如果两个链表没有共同节点,如果两个链表长度相同,会在同一时间共同指向null
// 如果两个链表长度不通,那么都会走a+b然后指向null
ListNode preA = headA;
ListNode preB = headB;
while (preA != preB) {
if (preA == null) preA = headB;
else preA = preA.next;
if (preB == null) preB = headA;
else preB = preB.next;
}
return preA;
}
时间复杂度:O(n)
空间复杂度:O(1)
七、环形链表入环口
假如从head到入环口为x,入环口到相遇点为y,相遇点再到入环口为z,那么第一次相遇时,快指针走了x+n (y+z) + y,慢指针走了x+y,所以就是2(x+y) = x+y + n(y+z)
x = (n-1)(y+z) + z
如果n为1,那么x=z,说明从head到入环口的长度为相遇点再到入环口的长度。
n不为1时,从head到入环口的长度x就为从相遇点在环内绕了(n-1)圈之后回到相遇点再走了z的长度。
所以再让两个指针一个从head一个从相遇点开始,再次相遇时,就是入环口了。
public ListNode detectCycle(ListNode head) {
ListNode first = head;
ListNode second = head;
// 判断是否有环
while (first != null && first.next != null) {
first = first.next.next;
second = second.next;
// 第一次相遇了,就说明有环,进行第二次相遇
if (first == second) {
// 那么快指针在环里一步一步走
// 慢指针从头开始一步一步走,就会在入环口相遇
second = head;
while (first != second) {
first = first.next;
second = second.next;
}
return first;
}
}
return null;
}