链表

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;

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值