链表(三)——链表中的经典算法

发布于:2023-01-18 ⋅ 阅读:(538) ⋅ 点赞:(0)

反转链表

给定单链表的头节点 head ,请你反转链表,并返回反转后的链表。

思路:反转链表就是总体调整方向,这时候只需遍历一遍链表,注意,开始时,cur在head位置,prev为cur位置的前一个结点,开始时为空,首先需要设置一个保留下一个结点地址的curNext,此时cur位置的指向就可以改变成相反方向prev位置,然后cur走到curNext位置,以cur不为空进行循环,就可以达到翻转的目的
在这里插入图片描述

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

链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。 输入:[1,2,3,4,5] 输出:此列表中的结点 3
输入:[1,2,3,4,5,6] 输出:此列表中的结点 4

思路:找到中间结点,比较简单,运用快慢指针,快的结点一次性走两步,慢的一次性走一步,最终快指针fast到达空指针位置或者fast.next到空指针的位置,循环停止,这时候满指针flow走的距离就是快指针的一般,对应的位置就是中间结点

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

链表的回文结构

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:1->2->2->1 返回:true

思路:回文链表面试中比较常见,其最优解法就是运用前面两道题的思想,并结合起来,先用快慢指针的方法,找到中间的位置,再将后面的序列与之倒序,此时slow就是这个链表的尾端,通过尾端和首端,都相向移动,如果相遇,则是回文链表,如果过程中,不相等,两边的值,则不是回文链表。还需判断特殊情况,当链表长度为偶数,可以通过相邻时,某个链表的next.val是否等于当前的val;在这里插入图片描述在这里插入图片描述

public class PalindromeList {
    public boolean chkPalindrome(ListNode A) {
        //判断是否为回文链表,首先找到中间值
        if(A == null)return false;
        if(A.next == null)return true;
        ListNode fast = A;
        ListNode slow = A;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next; 
        }
        //此时slow就是中间值,然后反转后面的结点
        ListNode cur = slow;
        ListNode nextNode = null;
        while(cur != null){
            nextNode = cur.next;
            cur.next = slow;
            slow = cur;
            cur = nextNode;
        }
        while(slow != A){
            if(slow.val != A.val){
                return false;
            }
            slow=slow.next;
            A = A.next;
            //判断偶数
            if(A.next==slow && A.val==slow.val){
                return true;
            }
        }
        return true;  
    }
}

合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

思路:合并两个有序列表成新的链表,则此时可以创建一个链表,通过比较合并前的两个链表的val值,判断大小,小的先放进新的链表中,最终新的链表就是完全时有序的链表。当一个链表为空时,那新的链表只需要将最后一个结点的next值与另一个链表连接起来,就完成了合并两个链表。

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
       
        ListNode newHead = new ListNode(1);
        ListNode temp = newHead;
        while(list1 != null && list2 != null){
        if(list1.val<list2.val){
            temp.next = list1;
            temp = list1;
            list1 = list1.next;

        }else{
            temp.next = list2;
            temp = list2;
            list2 = list2.next;
        }
        
        }
        if(list1 == null){
            temp.next =list2;
        }
        if(list2 == null){
            temp.next = list1;
        }
        return newHead.next;
    }
}

链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。
输入:{1,2,3,4,5},1
返回值:{5}

思路:这道题还是可以借助快慢指针来解决,这倒数k个结点,就相当于快的指针夺走了k-1步,如果快的真正fast.next为null,此时的slow就是慢指针的位置就是倒数第k个结点

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
 
        if(k <= 0)
            return null;
        ListNode low = head;
        ListNode fast = head;
        for(int i=1; i < k;i++){
            if(fast == null)
                return null;
            else
                fast = fast.next;
        }
        if(fast == null)
            return null;
        while(fast.next != null){
            low = low.next;
            fast = fast.next;
        }
        return low;
    }
}

相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5]
输出:Intersected at ‘8’

思路:两个结点相交,则后续的结点是重合的,也就是说它们公用这几个结点,这两个链表的长度只差的绝对值,就是解题的关键,这时候求出长度差后,只需要让长链表先走长度差的距离,最后两个链表同时遍历,每一次走过一个结点,相遇的结点,就是要找的相交结点。如果遍历完,两者的指向还是不相等,则不存在相交结点,此时返回空结点null。

在这里插入图片描述

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if((headA == null) || (headB == null)) return null;
        //先求出链表长度的差值
         int lengthSub = length(headA)-length(headB);
         if(lengthSub<0){
             lengthSub=-lengthSub;
         }

        //假设headA链表长度要长一些
        if(length(headA) > length(headB)){
            for(int i=0;i<lengthSub;i++){
            headA = headA.next;
            }
        }else{
            for(int i=0;i<lengthSub;i++){
            headB = headB.next;
            }
        }
        
        
        while(headA != headB){
            headA = headA.next;
            headB = headB.next;
        }
        if(headA == headB){
            return headA;
        }else{
            return null;
        }

    }
    public static int length(ListNode head){
        ListNode cur = head;
        int length = 0;
        while(cur != null){
            cur = cur.next;
            length++;
        }
        return length;
    }
}

链表分割

现有一链表的头结点pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

思路:给出了分界值,大体思路只需要创建两个链表,如果给出的pHead链表上的结点val值小于x值,则放在前面一个链表,反之放在后面一个链表,最后两个链表进行拼接,就形成了一个新的链表,就是重新排序后的链表。但是最后需要将第二个链表置为空,防止形成环。

public class Partition {
    public ListNode partition(ListNode Head, int x) {
        if(Head==null){
            return null;
        }
        ListNode firstStare= null;
        ListNode firstEnd= null;
        ListNode laterStare= null;
        ListNode laterEnd= null;
        ListNode cur=Head;
        while(cur != null){
            if(cur.val < x ){
                if(firstStare == null){
                    firstStare = cur;
                    firstEnd = cur;
                    cur = cur.next;
                }else{
                    firstEnd.next = cur;
                    firstEnd = cur;
                    cur = cur.next;
                }
                
            }else{
                if(laterStare == null){
                    laterStare = cur;
                    laterEnd = cur;
                    cur = cur.next;
                }else{
                    laterEnd.next = cur;
                    laterEnd = cur;
                    cur = cur.next;
                }
                
            }
        }
        if(firstStare == null){
            return laterStare;
        }
        if(laterEnd != null){
            laterEnd.next = null;
        }
        firstEnd.next = laterStare;
        return firstStare;
    }
}

环形链表

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

快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。如果相遇,就代表形成了一个环。快指针一次性可以走三步,四步吗?答案是,不能!因为可能会出现特殊情况,当慢指针走一步,快指针走三步,这时候两个指针永远也不会相遇,即使是一个环形链表。

public class Solution {
    public boolean hasCycle(ListNode head) {
        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;
    }
}

环形链表 II

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

思路:让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇

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