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;
}
}