单链表典型OJ题(详细图解+核心思路讲解+题目链接)

目录

一.合并链表

二、环形链表

三、求环形链表入口节点

四、移除链表元素

五、反转链表

六、求链表中间节点

七、删除链表中间节点

八、分割链表

九、回文链表

十、求链表倒数第k个结点

十一、删除链表倒数第k个结点

十二、删除有序链表中重复元素

十三、相交链表


          今日良言:等风来不入追风去

一、合并链表

 链接:21. 合并两个有序链表 - 力扣(LeetCode)

1.思路图解

解题思路:首先创建一个虚拟结点,然后依次比较两个链表有序链表的结点值,将值较小的结点采用尾插法每次链接到以虚拟节点为头结点的链表中,所以实现思路应当是,每次记录下两个链表的结点的位置,然后一次插入即可,最后返回虚拟节点的下一个结点就是新的链表的头结点,需要注意的是,如下图list2后面的三个结点都比list1的结点中的值大,所以当循环结束后,直接将不为空的那个链表的结点全部连接到新链表的后面即可。

如下图:

 2.代码实现:

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null) {
            return list2;
        }
        if(list2 == null) {
            return list1;
        }
        ListNode newHead = new ListNode(-1);
        ListNode tmp = newHead;
        while ((list1 != null)&&(list2 != null)) {
            if(list1.val <= list2.val) {
                tmp.next = list1;
                list1 = list1.next;
                tmp = tmp.next;
            } else {
                tmp.next = list2;
                list2 = list2.next;
                tmp = tmp.next;
            }
        }
        // 跳出循环以后,可能只有一个链表空了,应该将不为空的连接到后面
       if (list1 != null) {
           tmp.next = list1;
       }
       if (list2 != null) {
           tmp.next = list2;
       }
       return newHead.next;
    }
}

 while ((list1 != null)&&(list2 != null))

二、环形链表

 链接:141. 环形链表 - 力扣(LeetCode)

1.思路图解:

解题思路:快慢指针

设置一个快指针fast和一个慢指针slow同时指向头结点,让fast指针一次走两个结点,而slow指针一次走一个结点,如果有环,fast指针在slow指针进入环的时候开始追赶,在某一个结点处,fast和slow相遇,当fast指针为空,或者fast指针的下一个结点为空时,循环停止

2.代码实现:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        while ((fast != null) && (fast.next != null)) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
  }
}

这里的循环条件顺序是不可以改变的

因为,如果顺序更改以后,就会出现空指针异常。

 while ((fast != null) && (fast.next != null))

注:细心的小伙伴会提出疑问,为什么fast一次走一步,而slow走一步一定会追上呢?为什么要让fast走两步,走三步可不可以,走四步呢?  基于这个问题的基础上,我们不妨来解释一下。

1.为什么fast一次走一步,而slow走一步一定会追上呢?

slow进环以后,fast就开始追slow了,假设此时它们之间的距离是N,每次fast走两步,slow走一步,它们之间的距离就是N-1   N-2   N-3 ......  所以说,fast最终一定会追上slow

2.如果fast走三步呢?

结论:slow走一步,fast走三步,不一定能追得上,甚至可能会死循环永远也追不上。

证明:

slow进入环以后,fast开始追slow,假设此时它们之间的距离是N,fast每次走三步,slow每次走一步,它们之间的距离变化就是: N-2      N-4    N-6 ......        这个最终的结果可能是 0或者-1    

当N是偶数的时候,最终可以追上,但是如果是奇数的话,此时距离是-1意味着,fast反超了slow,fast又重新开始追slow,所以可能会死循环永远也追不上

三.求环形链表入口节点

 链接:142. 环形链表 II - 力扣(LeetCode)

1.思路图解

解题思路:快慢指针 + 找出相遇时快慢指针走的路程的关系

1.1 设快慢指针

设置一个快指针fast和一个慢指针slow同时指向头结点,让fast指针一次走两个结点,而slow指针一次走一个结点,当slow进环以后,fast开始追slow

1.2. 求相遇结点meet

我们将fast和slow相遇的点设为meet,此时meet == fast == slow

1.3. 求环长度以及其他长度

头结点到环形链表的入口节点的距离为L,环的长度为C,那么,当fast和slow相遇时,入口节点到相遇节点meet之间的距离是y,环长度为C,剩下环的长度就是S,所以C-y = S

1.4. 计算fast和slow路程间的关系

fast走的总路程就是:nC+L+y (n是圈数),slow走的总路程就是: L+y ,  由fast一次走两步,slow一次走一步可以知道,fast走的总路程是slow走的路程的两倍,所以我们可以得出表达式: nC+L+y = 2(L+y)    化简以后可以得到:nC-y=L

1.5. 确定最终关系求入口接点

我们假设n=1,此时C-y=L,由上面3中C-y=S,我们可以得到:S=L   所以说,此时让meet和head的一起走,每次走一步,它们相遇处就是环形链表的入口节点

 2.代码实现


public class Solution {
    public ListNode detectCycle(ListNode head) {
    if (head == null) {
        return null;
    }
    ListNode fast = head;
    ListNode slow = head;
    while (fast !=null && fast.next!=null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) {
            ListNode meet = slow;
            while (meet != head) {
                head = head.next;
                meet = meet.next;   
            }
            // 此时head 和 meet相遇就是结点
            return meet;
        }
    }
    return null;
    }
}

四、移除链表元素

链接:203. 移除链表元素 - 力扣(LeetCode)

1.思路图解

解题思路:设置两个指针prev 和cur

我们假设perv是头结点,cur是prev的下一个结点,当cur不为空,就进入一个循环判断cur的数据域与给定值val的值是否相等,如果相等,就让cur等于cur后面的第一个结点, 同时让prev.next =cur ,跳过这个相等的结点,如果不相等,就将此时cur的结点给prev,然后再让cur = cur.next ,这样我们就可以将中间节点数据域等于val的结点全部移除了,但是,我们没有判断过头结点的数据域是否等于val,因此,在循环结束以后,我们判断head的数据域是否等于val,如果等于的话,就将head的下一个结点当做头节点即可,因为结束循环以后,链表中已经没有了数据域等于val的节点

 2.代码实现:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        ListNode prev = head;
        ListNode cur = prev.next;
        while (cur != null) {
            // 相等就跳过
            if (cur.val == val) {
                cur = cur.next;
                prev.next = cur;
            } else {
                // 不相等就链接
                prev = cur;
                cur = cur.next;
            }
        }
        
        if (head.val == val) {
            head = head.next;
        }
        return head;
    }
}

五、反转链表

链接:206. 反转链表 - 力扣(LeetCode)

1.思路图解

设置思路:三指针法

首先设置三个指针:prev 为空  cur 为头结点  curNext 为cur的下一个结点,先让cur指向prev,然后再将cur的位置给prev, 再将将curNext位置给cur,这样循环下去,直到cur为空,遍历完链表

 由上图我们可以知道,当curNext为空时,此时的cur就是新链表的头结点,将此时的cur位置给newHead,最后返回newHead结点即可。

2.代码实现

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode prev = null;
        ListNode cur = head;
        ListNode newHead = null;
        while (cur != null) {
            ListNode curNext = cur.next;
            // 判断curNext是不是已经为空
            if (curNext == null) {
                newHead = cur;
            } 
            cur.next = prev;
            prev = cur;
            cur = curNext;
        }
        return newHead;
}
}

六、求链表中间节点

链接:876. 链表的中间结点 - 力扣(LeetCode)

1.思路图解

解题思路: 快慢指针

设置一个快指针fast和一个慢指针slow同时指向头结点,让fast指针一次走两个结点,而slow指针一次走一个结点,如果链表结点个数为奇数,则fast.next为空时,slow就是中间节点,如果链表结点为偶数,则fast为空时,slow就是中间节点,循环结束后,然后slow即可

链表节点个数为偶数

链表节点个数为奇数 

 2.代码实现

class Solution {
    public ListNode middleNode(ListNode head) {
     if (head == null) {
         return null;
     }
     ListNode fast = head;
     ListNode slow = head;
     while ((fast != null) && (fast.next != null)) {
         fast = fast.next.next;
         slow = slow.next;
     }
     return slow;
    }
}

七、删除链表中间节点

链接:2095. 删除链表的中间节点 - 力扣(LeetCode)

1.思路图解

解题思路:快慢指针

设置一个快指针fast和一个慢指针slow同时指向头结点,让fast指针一次走两个结点,而slow指针一次走一个结点,每次先让快指针走两步,然后判断快指针是否为空或快指针的下一个结点是否为空,如果为空的话,跳出循环,此时slow就是中间节点的前一个节点,然后让slow的指针域指向slow后面第二个节点,即可实现删除中间结点。

链表结点个数为奇数

 链表结点个数为偶数

 2.代码实现

class Solution {
    public ListNode deleteMiddle(ListNode head) {
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while ((fast != null)&&(fast.next!= null)) {
            fast = fast.next.next;
            // 此时跳出循环,slow的下一个结点就是要删除结点
            if ((fast == null )|| (fast.next == null)) {
                break;
            }
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return head;
    }
}

八、分割链表

链接:面试题 02.04. 分割链表 - 力扣(LeetCode)

1.思路图解

解题思路: 将比x大的结点和比x小的结点分别放在两个链表中,最后连接这两个链表即可

思路图解:需要注意的是,如果bs链表为空,则返回as链表,如果as链表为空,则返回bs链表,

如果都不为空,就要链接两个链表,如果原链表的最后一个结点在bs链表,那么,就让ae.next==null;

2.代码实现

class Solution {
    public ListNode partition(ListNode head, int x) {
        if (head == null) {
            return null;
        }
        ListNode ba = null;
        ListNode be = null;
        ListNode aa = null;
        ListNode ae = null;
        ListNode cur = head;
        // cur不为空就循环
        while (cur != null) {
            if (cur.val < x) {     //比x小的链表
                // 判断是否为第一个结点
                if (ba == null) {
                    ba = cur;
                    be = ba;
                } else {           // 如果不为空就说明有结点了,将cur连接到后面即可
                    be.next = cur;
                    be = be.next;
                }
            } else {    // 比x大的链表
            // 判断第一个节点是否为空
            if (aa == null) {
                aa = cur;
                ae = aa;
            } else {
                ae.next = cur;
                ae = ae.next;
            }
            }
            cur = cur.next;
        }
        // 此时已经将cur中的节点重新进行了排序
        //需要对ba 和 aa进行判断 (因为可能所有值都比x小或大)
        if (ba == null) {
            return aa;
        } else if (aa == null) {
            return ba;
        } else {
            // 此时需要连接两个链表
            be.next = aa;
            ae.next = null;
            return ba;
        }
    }
}

九、回文链表

链接:剑指 Offer II 027. 回文链表 - 力扣(LeetCode)

1.思路图解

解题思路:1.先找到链表的中间节点  2.再反转后半段链表 3.判断是否为回文链表

1.先找链表的中间节点:  通过快慢指针,结束循环时,slow的位置就是中间节点

2.反转后半段链表:三指针法,反转链表

3.判断是否为回文链表:原来的头结点和后半段反转的链表的头结点开始比较

 上面图片中的单链表的节点个数为奇数位,如果是偶数位,就要考虑下图中的情况

 此时,当prev和head一起走,出现下图情况时:

 显然此时prev.next又指向右边第一个值为4的结点,而head.next指向右边第一个值为5的结点,此时两个结点的值不同,返回false,但是事实上这是一个回文链表,因此,我们需要在第三部判断是否为回文链表时,加上如下条件,解决链表节点个数为偶数时的情况

           if (head.next == slow) {
                return true;
            }

2.代码实现

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) {
            return false;
        }
        // 只有一个节点的情况下
        if (head.next == null) {
            return true;
        }
        // 1.先找中间节点 快慢指针
        ListNode fast = head;
        ListNode slow = head;
        while ((fast != null)&&(fast.next != null)) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 2.翻转后半段链表 三指针法
        ListNode prev = slow;
        ListNode cur = prev.next;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = prev;
            prev = cur;
            cur =curNext;
        }
        // 3.判断
        while (head != prev) {
            // 不相等
            if (head.val != prev.val) {
                return false;
            }
            // 偶数节点的情况
            if (head.next == prev) {
                return true;
            }
            head = head.next;
            prev = prev.next;
        }
        // 相等
        return true;
    }
}

十、求链表倒数第k个结点

链接:

1.思路图解

解题思路:设置两个指针,一个走k步然后一起走

首先设置两个指针fast 和 slow ,先让fast走k步,如果k值过大,当fast已经为空时,说明k值不合法,结束程序,然后让fast和slow一起走,当fast为空时,slow的位置就是倒数第K个结点

2.代码实现

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if (head == null) {
            return null;
        }
        if (k < 0) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (k > 0) {
            // k值太大,导致fast已经为空,说明k值不合法
            if (fast == null) {
                return null;
            }
            fast = fast.next;
            k--;
        }
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

十一、删除链表倒数第n个结点

链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

1.思路图解

解题思路:设置两个指针,一个走k步,然后一起走

首先设置两个指针fast 和 slow ,先让fast走n步,如果k值过大,当fast已经为空时,说明n值不合法,结束程序,然后让fast和slow一起走,当fast是最后一个结点是,结束循环,此时slow的下一个结点就是倒数第k个结点,slow.next=slow.next.next 删除即可

2.代码实现

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while(n > 0) {
            fast = fast.next;
            n--;
        }
        // 如果此时的fast已经为空,说明要删除的结点就是头结点
        if (fast == null) {
            return head.next;
        }
        // 此时让fast 和 slow 一起走
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        // 此时slow的下一个节点就是要删除
        slow.next = slow.next.next;
        return head;
    }
}

十二、删除有序链表中重复元素

链接:83. 删除排序链表中的重复元素 - 力扣(LeetCode)

1.思路图解 

解题思路: 设置虚拟节点,连接不重复的节点

首先创建一个虚拟节点newHead,再设置两个指针prev 和 cur,如果prev 和cur的结点值相等,就跳过cur此时的这个结点,如果不相等就将prev链接到以虚拟节点为头结点的链表中,再将cur的位置给prev,让cur继续指向后一个结点,最后返回虚拟节点的后一个结点即可

2.代码实现

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode cur = head;
        ListNode newHead = new ListNode(-1);
        ListNode tmp = newHead;
        while (cur != null) {
            // 避免空指针异常问题
            if ((cur.next != null) && (cur.val == cur.next.val)) {
                // 可能是多个重复结点
                while ((cur.next !=null) && (cur.val == cur.next.val))
                {
                    cur = cur.next;
                }

            } else {
                tmp.next = cur;
                tmp = tmp.next;
                cur = cur.next;
            }
        }
        tmp.next = null;
        return newHead.next;
    }
}

十三、相交链表

链接:160. 相交链表 - 力扣(LeetCode)

1.思路图解

解题思路:求两个链表的长度,然后相减,让长链表的头结点向前后差值步,然后一起走进行判断

1.首先默认headA是长链表,lenB是短链表

2.让pl 和 ps 分别指向headA和headB的头结点

3.分别求headA 和 headB结点的个数lenA 和 lenB,如果lenA-lenB 为负数,就让pl 指向headB,ps指向headA

4.让pl向前后差值步,然后再让pl 和 ps一起走,如果pl和ps相遇,就说明是相交链表

2.代码实现

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 默认headA是长链表
        ListNode pl = headA;
        ListNode ps = headB;
        int lenA = 0;
        int lenB = 0;
        // 求长度
        while (pl != null) {
            lenA++;
            pl = pl.next;
        }
        while (ps != null) {
            lenB++;
            ps = ps.next;
        }
        int len = lenA - lenB;
        // 求长度的时候已经走到空了,重新指向头节点
        pl = headA;
        ps = headB;
        // 交换长短链表
        if (len < 0) {
            pl = headB;
            ps = headA;
            len = -len;
        }
        // 此时长链表一定是pl, 让pl走差值步
        while (len > 0) {
                pl = pl.next;
                len--;
        }
        // 差值步已经走完,开始一起走
    while ( (pl != null)&&(ps != null)) {
        if (pl == ps) {
            return pl;
        }
        pl = pl.next;
        ps = ps.next;
    }
    return null;
}
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值