算法中的链表问题

一、判断单链表是否有环

在这里插入图片描述

1.哈希表

遍历链表,把每个结点记录到哈希表中

  • 若最后能够遍历完链表,说明不存在环
  • 若遍历时出现了已经存在于哈希表中的结点,说明存在环,且该节点为环的起点。

注意:不可能存在既有环还能闭合的单链表,若存在则有一个节点必有两个next节点。

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

2.快慢指针

设置一个快指针,一个慢指针,快指针每次走两步,慢指针每次走一步

  • 若快指针走到了终点null,说明不存在环
  • 若快指针和慢指针在某一处相遇,说明存在环,但该处不一定是环的起点。
  • 相遇后将快指针置为头结点,并且之后快指针和慢指针同时一步一步地遍历,相遇之处即为环的起点。
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode l1 = head,l2 = head;
        while(l2!=null&&l2.next!=null){
            l1 = l1.next;l2 = l2.next.next;
            if(l1==l2) return true;
        }
        return false;
    }
}

二、判断两个链表是否相交

1.不存在环的情况

在这里插入图片描述

注意到以下事实:

  • 若两个链表相交,他们必然有一段重复的区域
  • 两个链表相交则终点必然相同

所以判断步骤如下:

  1. 首先分别遍历两个链表,记录终点和长度
  2. 若终点节点不相同则直接返回null
  3. 若终点节点相同,再将长的链表从头开始移动两个链表长度的差值。
  4. 然后依次同时遍历两个链表,第一次出现相同的结点时该结点即为相交的起点。
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        int deltaLen = 0;
        ListNode A = headA,B = headB;
        while(A!=null&&A.next!=null){
            deltaLen++;A= A.next;
        }
        while(B!=null&&B.next!=null){
            deltaLen--;B=B.next;
        }
        if(A!=B) return null;
        if(deltaLen>0){
            while(deltaLen--!=0) headA=headA.next;
        }else{
            while(deltaLen++!=0) headB=headB.next;
        }
        while(headA!=null){
            if(headA==headB) return headB;
            headA=headA.next;
            headB=headB.next;
        }
        return null;
    }
}

三、反转链表

在这里插入图片描述

1.引入额外数组

思路很简单,记录链表的所有值,最后反向赋值回链表。

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode h = head;
        int len = 0,i=0;
        while(head!=null){
            len++;head=head.next;
        }
        int[] help = new int[len];
        head=h;
        while(head!=null){
            help[i++]=head.val;head=head.next;
        }
        head=h;
        while(head!=null){ 
            head.val=help[--len];
            head=head.next;
        }
        return h;
    }
}

2.直接反转

遍历链表,每次遍历使当前结点指向前一个节点,由于正常遍历只会记录当前结点和下一个节点,因此需要引入一个变量记录上一个节点。同时,改变当前结点的指向后,为防止下一个结点丢失,也应该记录。

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null,cur = head;
        while(cur!=null){
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

3.相关题目:回文链表

在这里插入图片描述
很容易想到空间复杂度为o(n)的方法,构造一个辅助数组或者辅助栈然后再判断是否回文,但利用快慢指针+反转链表的方法可以达到o(1)的空间复杂度。具体方法如下:

  1. 慢指针走一步,快指针走两步,快指针走到头时慢指针必然走到链表的中间位置。
  2. 将链表的前半部分反转。
  3. 同时遍历链表的前后部分,判断是否回文。
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head.next==null) return true;
        ListNode l1 = head,l2 = head,preL1 = null;
        while(l2!=null&&l2.next!=null){
            preL1 = l1;
            l1 = l1.next;
            l2 = l2.next.next;
        }
        if(l2!=null){
            preL1.next = null;
            l1 = l1.next;
            l2 = reverse(head);
        }else{
            preL1.next = null;
            l2 = reverse(head);
        }
        while(l1!=null){
            if(l1.val!=l2.val) return false;
            l1 = l1.next;l2 = l2.next;
        }
        return true;

    }

    public ListNode reverse(ListNode head){
        ListNode pre = null,cur = head;
        while(cur!=null){
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

补充:链表中快慢指针的两种写法

  • 写法一
    这种方法遍历结束后,若链表长度为奇数,慢指针会来到链表的中点位置;若链表长度为偶数,慢指针会到链表中点向后取整的位置。
ListNode fast = head,slow = head;
while(fast!=null&&fast.next!=null){
	slow = slow.next;
	fast = fast.next.next;
}

在这里插入图片描述

  • 方法二

这种方法遍历结束后,若链表长度为奇数,慢指针也会来到链表的中点位置;若链表长度为偶数,慢指针会到链表中点向前取整的位置。

ListNode fast = head,slow = head;
while(fast.next!=null&&fast.next.next!=null){
	slow = slow.next;
	fast = fast.next.next;
}

在这里插入图片描述

四、两两交换链表中的结点

不允许使用值交换,必须交换结点对象!

1.遍历方法

每次交换都必须知道四个值,一是需要交换的两个节点,二是需要交换的两个节点的前后两个节点。因此需要保证第二个节点,第二个节点的下一个结点和下下个结点都不为空。
最后再注意一下边界条件。

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null||head.next==null) return head;
        ListNode help = new ListNode();help.next = head;
        ListNode cur1 = head,cur2 = head.next,pre = help,next = cur2.next;
        while(cur2!=null&&cur2.next!=null&&cur2.next.next!=null){
            pre.next = cur2;
            cur2.next = cur1;
            cur1.next = next; 
            pre = cur1;
            cur1 = pre.next;
            cur2 = cur1.next;
            next = cur2.next;
        }
        pre.next = cur2;
        cur2.next = cur1;
        cur1.next  = next;
        return help.next;
    }
}

2.递归法

我们只需要考虑头结点和第二个节点的交换,剩下的结点交给递归处理。

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null||head.next==null) return head;
        ListNode newHead = head.next;
        head.next = swapPairs(head.next.next);
        newHead.next = head;
        return newHead;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值