单链表反转,快慢指针解决链表的常见问题

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单链表反转,快慢指针解决链表的常见问题相关的知识,希望对你有一定的参考价值。

单链表反转

可一直向下递归,找到最后一个节点;
然后让head节点的next指向最后一个节点,逐个向上返回,达到反转链表效果;

具体实现
在之前的单向链表OneDirectionLinked基础上,添加链表反转的方法;

//反转整个链表的方法;
public void reverseLinkList()
    //首先对链表进行判空处理;
    if(isEmpty())
        return;
    
    reversePartList(head.next);

//底层方法
private ListNode reversePartList(ListNode node)
    //递归的结束点;到达最后一个节点;
    if(node.next==null)
        head.next = node;
        return node;
    
    //若不是最后一个节点;递归处理;
    ListNode front = reversePartList(node.next);
    front.next = node;
    node .next = null;
    return node;

测试使用

public class Test2 
    //测试
    public static void main(String[] args) 
        OneDirectionLinked<Integer> one = new OneDirectionLinked<>();
        //添加元素;
        one.addNodeLast(12);
        one.addNodeLast(13);
        one.addNodeLast(12);
        one.addNodeLast(14);
        one.addNodeLast(15);
        one.addNodeLast(16);
        //遍历;
        one.iterator().forEachRemaining(a-> System.out.print(a +"->"));
        System.out.println();
        //反转链表;
        one.reverseLinkList();
        one.iterator().forEachRemaining(a-> System.out.print(a +"->"));
        System.out.println();
        //12->13->12->14->15->16->
        //16->15->14->12->13->12->
    

初探快慢指针

快慢指针这种策略,在很多题目中都见到过的,比如说要对一段螺旋链表进行排序查找元素;比如说要判断链表中是否有环,比如说需要计算链表的环入口;

快指针一次跳过两个节点,而慢指针一次跳一个节点;当快指针到达链表末尾时,慢指针也就到达链表的中心位置了;

需要注意的是,有的快慢指针模板中,快指针是从头结点开始,而有的快慢指针是从头指针的下一个节点开始;那么就需要注意了

这里就让fast指针从头结点开始;

/**
 * @author by @CSDN 小智RE0
 * @date 2021-12-23 12:09
 */
//快慢指针学习
public class Demo 
    //链表的节点;
    static class ListNode 
        //节点元素;
        String data;
        //下一个;
        ListNode next;

        //初始化;
        public ListNode(String data, ListNode next) 
            this.data = data;
            this.next = next;
        
    

    //测试调用;
    public static void main(String[] args) 
        //构造节点之间的连接;
        ListNode h = new ListNode("H", null);
        ListNode g = new ListNode("G", h);
        ListNode f = new ListNode("F", g);
        ListNode e = new ListNode("E", f);
        ListNode d = new ListNode("D", e);
        ListNode c = new ListNode("C", d);
        ListNode b = new ListNode("B", c);
        ListNode a = new ListNode("A", b);

        //调用快慢指针方法1;
        String s = fastAndSlowStart(a);
        System.out.println("慢指针最终指向节点->" + s);
        //慢指针最终指向节点->E
    

    //学习快慢指针的基础用法1;
    public static String fastAndSlowStart(ListNode head) 
        if (head == null) return null;
        //快指针,慢指针定义;
        ListNode fast = head;
        ListNode slow = head;

        while (fast != null && fast.next != null) 
            //快指针一次移动两个位置;慢指针一次一个位置;
            slow = slow.next;
            fast = fast.next.next;
        
        return slow.data;
    

试着画图,看看快指针从头结点开始和快指针从头结点的下一个开始有什么区别;
其实看着都差不多的;

链表节点个数为奇数位;

(1)fast快指针若是从头结点开始,判断这是奇数位的链表; 就看最终快指针所在的节点值不是null即可;
(2)fast快指针若是从头结点的下一个开始;判断这是奇数位的链表;就看最终快指针所在的节点值为null即可

链表个数为偶数位;

(1)fast快指针若是从头结点开始,判断这是偶数位的链表; 就看最终快指针所在的节点值是null即可;
(2)fast快指针若是从头结点的下一个开始;判断这是偶数位的链表;就看最终快指针所在的节点值不是null即可

判断是否为环形链表问题;

方式1;链表快慢指针使用

public class HasCycle 
    //节点类;
    static class ListNode 
        int val;
        ListNode next;
        ListNode(int x) 
            val = x;
            next = null;
        
    
    
    //判断链表是否为环形链表;
    public boolean hasCycle(ListNode head) 
        //特殊情况判断;
        if(head == null || head.next == null) return false;
        //使用快慢指针;
        ListNode  slow = head;
        ListNode  fast = head;
        //快指针向后快速移动;
        while(fast!=null && fast.next!= null)
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast) return true;
        
        return false;
    

当然,也可使用hashset的不重复特性来完成这道题;

public boolean hasCycle(ListNode head) 
       //特殊情况判断;
        if(head == null || head.next == null) return false;
        //使用辅助结构解决;
        Set<ListNode> set = new HashSet<>();
        set.add(head);
        //实际上只需要判断是否添加过;
        ListNode curNode = head.next;
        while(curNode!=null)
            //判断是否重复;若添加时发现和之前的一样就说明为环形链表;
            if(set.contains(curNode))
                return true;
            
            
            set.add(curNode);
            curNode = curNode.next;
        
        return false;

查询链表的环入口

比如说怎么得到这个环形链表的环入口

在解决上一个问题时,发现只要快慢指针相遇就说明为环形链表,
那么在断定这是一个环形链表时,这时的慢指针位置不一定就是入口位置;特殊情况时确实可能指向到入口位置;
可以再重新定义一个指针,步长为1,这时让辅助指针移动,同时慢指针也移动;
当他和慢指针相遇的位置时就说明,那个指向的节点就是环形链表的入口;

public class FindEntryCircularList 
    //节点类;
    static class ListNode 
        int val;
        ListNode next;

        ListNode(int x) 
            val = x;
            next = null;
        
    

    //使用指针法;
    public ListNode EntryNodeOfLoop1(ListNode pHead) 
        //排除特殊情况;
        if (pHead == null || pHead.next == null) return null;
        //定义快慢指针;
        ListNode fast = pHead;
        ListNode slow = pHead;
        while (fast != null && fast.next != null) 
            fast = fast.next.next;
            slow = slow.next;
            //若快慢指针相遇,则说明有环,跳出循环;
            if (fast == slow) break;
        

        //若快指针所在为空,则说明没有环;退出;
        if (fast == null || fast.next == null) return null;

        //重新定义一个辅助指针;
        ListNode fu = pHead;
        //找到慢指针;
        while (fu != slow) 
            fu = fu.next;
            slow = slow.next;
        
        return fu;
    
    

单链表的排序使用

例如有这样一段无序的单链表;

先用快慢指针;找到链表的中点附近;

import java.util.*;

/*
 * public class ListNode 
 *   int val;
 *   ListNode next = null;
 * 
 */

public class Solution 
    /**
     * 
     * @param head ListNode类 the head node
     * @return ListNode类
     */
    public ListNode sortInList (ListNode head) 
       //需要对单链表进行排序;
        if(head == null || head.next == null)
            return  head;
        //使用快慢指针,将链表排查完毕;
        ListNode fast = head.next;
        ListNode slow = head;
        while(fast!=null && fast.next!=null)
            //快指针移动块;
            slow = slow .next;
            fast = fast.next.next;
        
        //分隔链表;这里的temp就是后一段分离出的链表头结点;
        ListNode temp = slow.next;
        slow.next = null;
        //这时的slow重置到初始的头结点,这一段就是分离的前一段链表;
        slow = head;
        //递归两部分链表;
        ListNode left = sortInList(slow);
        ListNode right = sortInList(temp);

        ListNode dummy = new ListNode(0);
        ListNode resNode = dummy;

        //合并两部分链表;
        while(left!=null && right!=null)
            if(left.val < right.val)
                dummy.next = left;
                left = left.next;
            else
                dummy.next = right;
                right = right.next;
            
            dummy = dummy.next;
        
        
        //把不为空的一段拼接上去;
        dummy.next = left!=null ? left: right;
        
        return resNode.next;
    

以上是关于单链表反转,快慢指针解决链表的常见问题的主要内容,如果未能解决你的问题,请参考以下文章

快慢指针---不就是快指针走两步慢指针走一步嘛?

LeetCode 234:Palindrome Linked List

LeetCode 234:Palindrome Linked List

每天学习亿点点系列——单链表OJ题

链表反转

高效代码之反转单链表