链表面试题(中等)--删除链表中的重复元、找两个链表的交点、判断链表是否带环、找环的入口点

1.删除链表中的重复元素

题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。

例如:链表1->2->3->3->4->4->-5处理后,变为1->2->5.

思路:定义两个结点引用,依次遍历链表,由于链表有序,因此如果p1.val!=p2.val,说明p1.val不可能与p2后面结点的元素值相等,因此同时往后走。
当找到p1.val==p2.val时,避免中间有多个相同的元素,因此寻找第一个与p1值不相等的p2,修改结点引用,删除掉[p1,p2)中的结点,这里要用到p1的前驱prev。
特殊情况:pHead==null时,返回pHead.
最后要返回头结点指针,但是如果第一个结点有多个结点值与它相等,则不易找出结果链表的头结点,因此设置一个假的头结点dummy,返回dummy.next即可。
假头结点的作用:消除第一个结点没有前驱的特殊性;便于返回结果链表。

该题主要关注三个问题:

1)p1和p2是进行比较的结点,如果它们的值相等怎么办?不相等怎么办?

值相等,查找p2后面的结点中,第一个值不与p1相等的结点,此时[p1,p2)中的结点是要删除的结点(重复元素结点);值不相等,由于是有序链表,因此p2后面的结点值一定也不和p1相等,所以p1,p2以及prev同时都向后走。

2)p2在相等时,一直走到空。--->在循环或者p2=p2.next时,对p2是否为空进行判断。

3)p1是链表的第一个结点,没有前驱--->找一个假头结点dummy,处理。

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

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead){
        if(pHead==null){
            return pHead;
        }
        ListNode dummy=new ListNode(0);//提供一个假的头结点,消除第一个结点没有前驱的特殊性
       dummy.next=pHead;
        ListNode p1=pHead;
        ListNode p2=pHead.next;
        ListNode prev=dummy;
        while(p2!=null){
            //p1值与p2值不相等,因为有序,所以p1也不可能与p3值相等,因此同时向后走。
            if(p1.val!=p2.val){
                prev=prev.next;
                p1=p1.next;
                p2=p2.next;
            }else{
                //说明p1值与p2值相等 下面寻找第一个与p1值不相等的p2,并删除[p1,p2)
            //寻找第一个与p1值不相等的p2
            while(p2!=null&&p1.val==p2.val){
                p2=p2.next;
            }
            //删除[p1,p2)的结点(将结点依次后移)
            prev.next=p2;//prev始终是p1的前驱,通过修改prev.next修改原来的链表
            p1=p2;
            if(p2!=null){
                p2=p2.next;
            }
          }
        }
        return dummy.next;
    }
}

2.相交链表

编写一个程序,找到两个单链表相交的起始结点。

两个链表相交,因为结点的特性(只有一个next),因此下面这种情况是不可能出现的。

两个链表相交,只可能有一种情况,那就是“殊途同归”。

判断是否会交叉的思路:“殊途同归”。如果两个链表的最后结点引用相同,说明会交叉。

求两个链表的交点:分别求出每个链表的长度,再求出长度之差,让长的先走长度之差步,然后两个链表结点同时走,直到它们的引用相同,说明该结点是交点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pa=headA;
        ListNode pb=headB;
        int countA=0;
        int countB=0;
        while(pa!=null){
            countA++;
            pa=pa.next;
        }
        while(pb!=null){
            countB++;
            pb=pb.next;
        }
        pa=headA;
        pb=headB;
        int bs=0;
        //长度长的先走
        if(countA>countB){
            bs=countA-countB;
            //A较长,A先走。
            while(bs!=0){
                bs--;
                pa=pa.next;
            }
        }else{
            bs=countB-countA;
            //B较长,B先走。
            while(bs!=0){
                bs--;
                pb=pb.next;
            }
        }
        //同时走
        while(pa!=pb){
            pa=pa.next;
            pb=pb.next;
        }
        return pa;      
    }
}

3.判断链表是否带环

链表带环的情形有:

思路:设置两个引用,一个快的,一个慢的。快的一次走两步,慢的一次走一步。如果有环,快的和慢的一定会在某个时刻引用同一块内存。

快的不能走太多步,因为如果太快,可能会把慢的跳过,甚至之后永远不和慢的相遇。如:

如果快的一次走三步,慢的一次走一步,它们永远都不会相遇。

规律:如果快的和慢的引用间的距离差能够被它们的步数之差整除,则可以相遇;有于数,可能不会相遇。1可以被所有整数整除,因此设置快的一次走两步,慢的一次走一步。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
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){
            slow=slow.next;
            fast=fast.next.next;
            if(fast==slow){
                return true;
            }
        }
        return false;
    }
}

4.环型链表

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

方式一:找到快慢引用的相遇点p,设置相遇点.next=null。那么该问题转化为求两个链表的交点问题。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null){
            return null;
        }
        ListNode fast=head;
        ListNode slow=head;
        ListNode node=null;//记录第二条链表
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(fast==slow){
              //fast为相遇点
                node=fast.next;
                fast.next=null;
            }
        }
        int l1=getLength(head);
        int l2=getLength(node);
        int bs=0;
        ListNode node1=head;
        ListNode node2=node;
        if(l1>l2){
            bs=l1-l2;
            while(bs!=0){
                bs--;
                node1=node1.next;
            }
        }else{
            bs=l2-l1;
             while(bs!=0){
                bs--;
                node2=node2.next;
            }
        }
        //同时走
        while(node1!=node2){
            node1=node1.next;
            node2=node2.next;
        }
        return node1;
        
    }
    public int getLength(ListNode head){
        ListNode cur=head;
        int count=0;
        while(cur!=null){
            cur=cur.next;
            count++;
        }
        return count;
    }
}

方式二:设置两个结点引用,一个结点从头出发,一个结点从相遇点(第三题中的相遇点)出发,每个结点每次都走一步,最终相遇在环的入口点。

证明:设从头结点到环的入口点长度是L,环的周长是R,从环的入口到它们的相遇点长度为C。相遇的时候,慢的引用不可能环里走一圈以上。慢的从开始到相遇共走了L+C步,快的从一开始走了L+C+NR(N>1)步,而快的一次走两步,慢的一次走一步,因此L+C+NR=2(L+C),得到L=(N-1)R+R-C.左边是开始结点走的路程,右边是从相遇点开始走的路程。所以,最终相遇在环的入口点。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null){
            return null;
        }
        ListNode fast=head;
        ListNode slow=head;
       do{
           fast=fast.next;
           if(fast!=null){
               fast=fast.next;
               slow=slow.next;
           }
       }while(fast!=null&&slow!=fast);
        //fast引用相遇点或者为null.
        if(fast==null){
            return null;
        }
        ListNode p1=head;
        ListNode p2=fast;
        while(p1!=p2){
            p1=p1.next;
            p2=p2.next;
        }
        return p1;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值