链表部分的算法

链表部分:

链表部分:

[图片上传失败...(image-b2bb7d-1649069467758)]

1.反转单双链表

//迭代的方式
 private ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }
    //2.反转链表的递归方式
    private ListNode reverse(ListNode head){
        if(head == null || head.next == null){
            return head;
        }
        ListNode node = reverse(head.next);
        head.next.next = head;
        head.next = null;
        return node;
    }

2.打印俩有序链表的公共部分

方法论:

时间复杂度最低即可

1.额外的数据结构记录

2.快慢指针的方式

进阶

1.回文链表

笔试版:

1.借助栈

2.快慢指针

整个流程可以分为以下五个步骤:

  1. 找到前半部分链表的尾节点。
  2. 反转后半部分链表。
  3. 判断是否回文。
  4. 恢复链表。
  5. 返回结果。
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
 class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) {
            return true;
        }

        // 找到前半部分链表的尾节点并反转后半部分链表
        ListNode firstHalfEnd = endOfFirstHalf(head);
        // ListNode secondHalfStart = reverseList(firstHalfEnd.next);
          ListNode secondHalfStart = reverse(firstHalfEnd.next);

        // 3.判断是否回文
        ListNode p1 = head;
        ListNode p2 = secondHalfStart;
        boolean result = true;
        while (result && p2 != null) {
            if (p1.val != p2.val) {
                result = false;
            }
            p1 = p1.next;
            p2 = p2.next;
        }        

        // 4.还原链表并返回结果
        // firstHalfEnd.next = reverseList(secondHalfStart);
        firstHalfEnd.next = reverse(secondHalfStart);
        return result;
    }
//2.反转后半部分链表。
    private ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }
    //2.反转链表的递归方式
    private ListNode reverse(ListNode head){
        if(head == null || head.next == null){
            return head;
        }
        ListNode node = reverse(head.next);
        head.next.next = head;
        head.next = null;
        return node;
    }
//1.找到前半部分链表的尾节点。利用快慢指针。
    private ListNode endOfFirstHalf(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

2.将单链表按照值划分为左边小,右边大,中间相等的形式

数组Node[N]

玩快排

面试:有限的变量去做

[图片上传失败...(image-76ef68-1649069467758)]

[图片上传失败...(image-c32a99-1649069467758)]

存在 5 既没有小的也没有等的也没有大的所以我们要判断

防止空指针加入报错

public static Node listPivot (Node head,int pivot){
    //六个变量保持有序且符合题意
    Node SH = null;//small head
     Node ST = null;//small tail
     Node ET = null;//equal head 
     Node EH = null;//qual tail
     Node MH = null;//big head
     Node MT = null;//big tail
    Node next = null;//save nexxt node
    
    while(head != null){
        //互指用来保存首部尾部
        next = head.next;
        head.next = null;
        
        if(head.value < pivot){
            //如果当前最小头是新的节点。那么我们初始化最小头和最小尾都指向该链表的头。
            if(SH == null){
                SH = head;
                ST = head;
            }
             //如果当前节点不是最新的话。把老的Next指向当前节点head。 NBA。把head作为新的最小头的尾巴。
        }else{
                ST.next = head;
                ST = head;
        }else  if(head.value = pivot){
            if(EH == null){
                EH = head;
                ET = head;
            }       
        }else{
                ET.next = head;
                ET = head;
        } else if(head.value > pivot){
            if(MH == null){
                MH = head;
                MT = head;
            }
             
        }else{
                MH.next = head;
                MT = head;
        }
        head = head.next;
    }
    
    
    //链接:
    // ST -> EH 和 ET -> MH;
    //充分讨论有没有大于下雨区域
    if(ST != null){ //如果有小于的区域
        ST.next = EH;
        ET = ET == null ? ST : ET;//谁去来凝结大的区域的头谁就变成eT
    }
    if(ET != null){
        ET.next = MH;
        }
    //**************
    //最后定返回头节点
    //**************
    return SH != null ? SH :(EH != null ? EH : MH);      
    }
}

3.复制含有随机指针节点的链表

[图片上传失败...(image-82b98b-1649069467758)]

Hash表来存储

<key,value> 老节点对应的新节点

[图片上传失败...(image-e21780-1649069467758)]

不用HashMap

1.跟随设置,复制节点不要设置

[图片上传失败...(image-1fcb63-1649069467758)]

2.跟随的next位置设置

[图片上传失败...(image-28d6df-1649069467758)]

3.分离出来

public static Node copyListWithRand(Node head){
    if(head == null){
        return null;
    }
    Node cur = head;
    Node next = null;
    //1. 在每一个原始节点后面跟随设置他的copy节点。例如1 - > 1"
    while(cur !=null){
        next = cur.next;
        cur.next = new Node(cur.val);
        cur.next.next = next;
        cur = next;
    }
    //重置car为head即将当前的节点,作为该链表的头。再来一次便利。
    cur = head;
    Node curCopy = null;
    //2.按照该链表原来的方式设置。它的Next和Random。
    while(cur != null){
        next = cur.next.next;
        curCopy = cur.next;
        curCopy.rand = cur.rand != null ? cur.rand.next : null;
        cur = next;
    }
    Node res = head.next;
    cur = head;
    
    //将拷贝节点全部切分出去。
    while(cur != null){
        next = cur.next.next;
        curCopy = cur.next;
        cur.next = next;
        curCopy.next = next != null? next.next : null;
        cur = next;
    }
    return res;
}

4.单链表相交

[图片上传失败...(image-24cb9e-1649069467758)]

利用HashSet来遍历

有没有重复

[图片上传失败...(image-b6a312-1649069467758)]

快慢指针经典

相遇后:

快指针回到head 以后每次走一步,就一定会在入口相遇

链表题目练习

链表

提纲

链表相关的核心点

  • null 异常处理
  • dummy node 哑巴节点
  • 快慢指针
  • 插入一个节点到排序链表
  • 从一个链表中移除一个节点
  • 翻转链表
  • 合并两个链表
  • 找到链表的中间节点

基本操作

链表删除

删除排序链表中的重复元素

83. 删除排序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

public ListNode deleteDuplicates(ListNode head) {
    ListNode p = head;
    while (p != null) {
        // 全部删除完再移动到下一个元素
        while (p.next != null && p.val == p.next.val) {
            p.next = p.next.next;
        }
        p = p.next;
    }
    return head;
}

删除排序链表中的重复元素 II

82. 删除排序链表中的重复元素 II

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现的数字。

思路:链表头结点可能被删除,所以用 dummy node 辅助删除

public ListNode deleteDuplicates(ListNode head) {
    if (head == null) {
        return null;
    }
    ListNode newHead = new ListNode(-1, head);
    ListNode p = newHead;
    int n = 0;
    while (p.next != null && p.next.next != null) {
        if (p.next.val == p.next.next.val) {
            // 记录已经删除的值,用于后续节点判断
            n = p.next.val;
            while (p.next != null && p.next.val == n) {
                p.next = p.next.next;
            }
        } else {
            p = p.next;
        }
    }
    return newHead.next;
}

注意点

  • A->B->C 删除 B,A.next = C
  • 删除用一个 Dummy Node 节点辅助(允许头节点可变)
  • 访问 X.next 、X.value 一定要保证 X != nil

链表反转

反转链表

206. 反转链表

反转一个单链表。

思路:用一个 prev 节点保存向前指针,temp 保存向后的临时指针

public ListNode reverseList(ListNode head) {
    ListNode pre = null, p = head;
    while (p != null) {
        // 保存当前head.Next节点,防止重新赋值后被覆盖
        // 一轮之后状态:nil<-1 2->3->4
        //               prev p
        ListNode temp = p.next;
        p.next = pre;
        // pre 移动
        pre = p;
        // p 移动
        p = temp;
    }
    return pre;
}

反转链表 II

92. 反转链表 II

反转从位置 mn 的链表。请使用一趟扫描完成反转。

思路:先遍历到 m 处,翻转,再拼接后续,注意指针处理

public ListNode reverseBetween(ListNode head, int m, int n) {
    // 思路:先遍历到m处,翻转,再拼接后续,注意指针处理
    // 输入: 1->2->3->4->5->null, m = 2, n = 4
    ListNode newHead = new ListNode(0, head);
    ListNode p = newHead;
    // 最开始:0(p)->1->2->3->4->5->null
    for (int i = 0; i < m-1; i++) {
        p = p.next;
    }
    // 遍历之后: 0->1(p)->2(cur)->3->4->5->null
    ListNode pre = null;
    ListNode cur = p.next;
    for (int i = m; i <= n; i++) {
        ListNode next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    // 循环结束:0->1(p)->2->null 5(cur)->null 4(pre)->3->2->null
    p.next.next = cur;
    p.next = pre;
    return newHead.next;
}

链表合并

合并两个有序链表

21. 合并两个有序链表

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路:通过 dummy node 链表,连接各个元素

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode head = new ListNode(0);
    ListNode p = head;
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            p.next = l1;
            l1 = l1.next;
        } else {
            p.next = l2;
            l2 = l2.next;
        }
        p = p.next;
    }
    // 连接未处理完节点
    p.next = l1 == null ? l2 : l1;
    return head.next;
}

合并K个升序链表

23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

思路:使用分治的方法两个两个地合并链表

public ListNode mergeKLists(ListNode[] lists) {
    return merge(lists, 0, lists.length - 1);
}

public ListNode merge(ListNode[] lists, int begin, int end) {
    if (begin == end) return lists[begin];
    if (begin > end) return null;
    int mid = (begin + end) >> 1;
    return mergeTwoLists(merge(lists, begin, mid), merge(lists, mid + 1, end));
}

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    // 同上
}

快慢指针

链表中点

使用两个指针变量,慢指针每次前进一步,快指针每次前进两步。这样当快指针到达链表末尾时,慢指针恰好在链表的中间位置。要注意链表长度为偶数的情况。

876. 链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

public ListNode middleNode(ListNode head) {
    ListNode p = head;
    ListNode q = head;
    while (q != null && q.next != null) {
        p = p.next;
        q = q.next.next;
    }
    return p;
}

重排链表

143. 重排链表
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,

将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

public void reorderList(ListNode head) {
    if (head == null || head.next == null) return;
    // 通过快慢指针找中点
    ListNode slow = head, fast = head;
    while (fast.next != null && fast.next.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    ListNode p = head;
    // 反转链表
    ListNode q = reverseList(slow.next);
    slow.next = null;
    while (p != null && q != null) {
        ListNode qNext = q.next;
        q.next = p.next;
        p.next = q;
        p = q.next;
        q = qNext;
    }
}

回文链表

234. 回文链表

请判断一个链表是否为回文链表。

public boolean isPalindrome(ListNode head) {
     // fast如果初始化为head.Next则中点在slow.Next
    // fast初始化为head,则中点在slow
    ListNode slow = head, fast = head, pre = null;
    // 这里顺便做了反转链表的操作
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        ListNode next = slow.next;
        slow.next = pre;
        pre = slow;
        slow = next;
    }
    if (fast != null){
        slow = slow.next;
    }
    // 与另一半链表依次比较
    while (slow != null) {
        if (slow.val != pre.val) return false;
        slow = slow.next;
        pre = pre.next;
    }
    return true;
}

结构判断

环形链表

141. 环形链表

给定一个链表,判断链表中是否有环。

如果链表中存在环,则返回 true。 否则,返回false

思路:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1
[图片上传失败...(image-8b9f58-1649069467758)]

public boolean hasCycle(ListNode head) {
    ListNode p = head, q = head;
    // 思路:快慢指针 快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减1
    while (p != null && q != null && q.next != null) {
        p = p.next;
        q = q.next.next;
        // 比较指针是否相等(不要使用val比较)
        if (p == q) {
            return true;
        }
    }
    return false;
}

环形链表 II

142. 环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点
[图片上传失败...(image-b266f5-1649069467758)]

public ListNode detectCycle(ListNode head) {
    // 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点
    ListNode p = head, q = head;
    while (p != null && q != null && q.next != null) {
        p = p.next;
        q = q.next.next;
        if (p == q) {
            // 指针重新从头开始移动
            ListNode m = head;
            // 比较指针对象(不要比对指针Val值)
            while (m != p) {
                m = m.next;
                p = p.next;
            }
            return p;
        }
    }
    return null;
}

其他

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。(尝试使用一趟扫描实现)

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode newHead = new ListNode(0, head);
    ListNode p1 = newHead;
    ListNode p2 = newHead;
    // 提前前进n个位置
    while (n >= 0) {
        p2 = p2.next;
        n--;
    }
    while (p2 != null) {
        p1 = p1.next;
        p2 = p2.next;
    }
    p1.next = p1.next.next;
    return newHead.next;
}

138. 复制带随机指针的链表

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的 深拷贝。

思路:1、hash 表存储指针,2、复制节点跟在原节点后面

public Node copyRandomList(Node head) {
    if (head == null) {
        return null;
    }
    // 复制节点,紧挨到到后面
    // 1->2->3  ==>  1->1'->2->2'->3->3'
    Node cur = head;
    while (cur != null) {
        Node cloneNode = new Node(cur.val);
        cloneNode.next = cur.next;
        Node temp = cur.next;
        cur.next = cloneNode;
        cur = temp;
    }
    // 处理random指针
    cur = head;
    while (cur != null) {
        if (cur.random != null) {
            cur.next.random = cur.random.next;
        }
        cur = cur.next.next;
    }
    // 分离两个链表
    cur = head;
    Node cloneHead = cur.next;
    while (cur != null && cur.next != null) {
        Node temp = cur.next;
        cur.next = cur.next.next;
        cur = temp;
    }
    // 原始链表头:head 1->2->3
    // 克隆的链表头:cloneHead 1'->2'->3'
    return cloneHead;
}

总结

链表必须要掌握的一些点,通过下面练习题,基本大部分的链表类的题目都是手到擒来~

  • null 异常处理
  • dummy node 哑巴节点
  • 快慢指针
  • 插入一个节点到排序链表
  • 从一个链表中移除一个节点
  • 翻转链表
  • 合并两个链表
  • 找到链表的中间节点
  • 链表的末尾指针最后指向null
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容