2022-10-31 LinkedList

Summary

  1. LinkedList
  2. 移除链表元素 203. Remove Linked List Elements💚
  3. 创建链表 707. Design Linked List🧡
  4. 翻转链表 206. Reverse Linked List💚
  5. 两两交换链表中的节点 24. Swap Nodes in Pairs🧡
  6. 删除链表的倒数第N个节点 19. Remove Nth Node From End of List🧡
  7. 链表相交 160. Intersection of Two Linked Lists💚
  8. 环形链表
    8.1 141. Linked List Cycle 💚
    8.2 142. Linked List Cycle II 🧡

1. LinkedList

1. 单链表

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链接的入口节点称为链表的头结点也就是head。


单链表

2. 双链表

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表 既可以向前查询也可以向后查询。

双链表

3. 循环链表

链表首尾相连。

循环链表可以用来解决约瑟夫环问题。


循环链表

4. 链表的储存方式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

5. 链表定义

public 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;
   }
}

6. 性能分析

数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。(长度不确定时,可以使用ArrayList)

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
性能分析

2. 移除链表元素 203. Remove Linked List Elements

虚拟头节点:移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。

所以为了操作方便和统一,设置一个虚拟头结点。
return 头结点的时候,别忘了 return dummyNode.next, 这才是新的头结点

虚拟头节点

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null){
            return head;
        }
        ListNode dummy = new ListNode(-1, head);
        ListNode pre = dummy;
        ListNode cur = head;
        while(cur != null){
            if(cur.val == val){
                pre.next = cur.next;
            }else{
                pre = cur;
            }
            cur = cur.next;
        }
        return dummy.next;
    }
}

3. 创建链表 707. Design Linked List

class ListNode{
    int val;
    ListNode next;
    ListNode (){};
    ListNode (int val){
        this.val = val;
    }
}

class MyLinkedList {
    int size;
    ListNode dummy ;

    public MyLinkedList() {
        size = 0;
        dummy = new ListNode(0);
        
    }
    
    public int get(int index) { 
        if(index < 0 || index >= size){
            return -1;
        }
        ListNode cur = dummy;
        for(int i = 0; i <= index; i++){
            cur = cur.next;
        }
        return cur.val;
        
    }
    
    public void addAtHead(int val) {
        //顺序很重要,先用插入节点指向head,再用dummy指向插入
        addAtIndex(0,val);
        
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
        // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
        // 如果 index 大于链表的长度,则返回空
        if (index > size){
            return;
        }
        if (index < 0){
            index = 0;
        }
        size ++;
        ListNode pre = dummy;
        for(int i = 0; i < index; i++){
            pre = pre.next;
        }
        ListNode newnode = new ListNode(val);
        newnode.next = pre.next;
        pre.next = newnode;
        
        
    }
    
    public void deleteAtIndex(int index) {
        if(index < 0 || index >= size){
            return;
        }
        size --;
        if (index == 0){
            dummy = dummy.next;
            return;
        }
        ListNode pre = dummy;
        for (int i = 0; i < index; i++){
            pre = pre.next;
        }
        pre.next = pre.next.next;
        
    }
}

4. 翻转链表 206. Reverse Linked List

4.1 双指针+虚拟头节点:

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。

然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。

为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。

接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode cur = head;
        ListNode temp = null;
        while (cur != null) {
            temp = cur.next;// 保存下一个节点
            cur.next = prev;
            prev = cur;
            cur = temp;
        }
        return prev;
    }
}

4.2 递归

5. 两两交换链表中的节点 24. Swap Nodes in Pairs

虚拟头节点:
初始时,cur指向虚拟头结点,然后进行如下三步:


操作

操作之后,链表如下:


操作完成后

操作后链表

Time: O(n)/Space: O(1)

注:

  • node1 = node2,把node2的数据(val+next)给node1
  • node1.next = node2,把node1指向node2,即node1的next中储存node2的地址
  • node1.next.next = node2, 如果node1指向node3,即node3指向node2
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyNode = new ListNode(0);
        dummyNode.next = head;
        ListNode prev = dummyNode;

    while (prev.next != null && prev.next.next != null) {
          ListNode temp = head.next.next; // 缓存 next
          prev.next = head.next;          // 将 prev 的 next 改为 head 的 next
         // System.out.println(prev.next.val);
          head.next.next = head;          // 将 head.next(prev.next) 的next,指向 head
          head.next = temp;               // 将head 的 next 接上缓存的temp
          prev = head;                    // 步进1位
          head = head.next;    // 步进1位
          System.out.println(prev.val);
    }
    return dummyNode.next;
        
    }
}

6. 删除链表的倒数第N个节点 19. Remove Nth Node From End of List

双指针+虚拟头节点
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

  1. 定义fast指针和slow指针,初始值为虚拟头结点


    step1
  2. fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)


    step2
  3. fast和slow同时移动,直到fast指向末尾


    step3
  4. 删除slow指向的下一个节点
    step4

    注意: return dummy.next,因为head可能被删除
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode slow = dummy;
        ListNode fast = dummy;
        for (int i = 0; i <= n; i++){
            fast = fast.next;
        }
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return dummy.next;//注意要返回dummy.next,因为head可能被删除
    }
}

7. 链表相交 160. Intersection of Two Linked Lists

简单来说,就是求两个链表交点节点的指针。 这里同学们要注意,交点不是数值相等,而是指针相等。

  1. 看如下两个链表,目前curA指向链表A的头结点,curB指向链表B的头结点:


    step1
  2. 我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置


    step2
  3. 此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针。
    Time : O(n+m)/Space: O(1)

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0;
        int lenB = 0;
        while(curA != null){
            lenA++;
            curA = curA.next;
        }
        while(curB != null){
            lenB++;
            curB = curB.next;
        }
        curA = headA;
        curB = headB;
        if(lenB > lenA){
            int temp = lenA;
            lenA = lenB;
            lenB = temp;
            
            curA = headB;
            curB = headA;
        }
        
        int dif = lenA - lenB;
        for (int i = 0; i < dif; i++){
            curA = curA.next;
        }
        
        while(curA != null ){
            if(curA == curB){
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}

8. 环形链表

8.1 141. Linked List Cycle

HashSet
把链表中的节点都存进去,并判断是否已经存在,因为HashSet中元素不重复。

public class Solution {
    public boolean hasCycle(ListNode head) {
        HashSet<ListNode> set = new HashSet<ListNode>();
        while(head != null){
            if(set.contains(head)){
                return true;
            }
            set.add(head);
            head = head.next;
        }
        return false;
    }
}

8.2 142. Linked List Cycle II

双指针
fast每次走两步,slow每次走一步,若有环,两指针必定相遇。
相遇后fast回到head,fast改为每次走一步,slow继续每次走一步,相遇点即是入环节点

public class Solution {
    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的判断条件时相不相同
        while (fast != slow){
            if(fast.next == null  || fast.next.next == null){
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        fast = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容