常见OJ题:
1.现在有一个链表的头指针ListHead,用户输入一个固定的值,编写一段代码将所有小于X的节点排在其余结点之前,况且是不可以改变原来的数据顺序,返回重新排列之后链表的头指针;最后的链表,左边都小于X右边都大于X(原来的X不一定在链表当中)
思路:
1)我们现在来写一个循环,定义一个current来遍历原来的单链表,循环条件是current!=null
2)在循环里面,我们要做两件事(我们要创建两个单链表A和B,A是存放比X小的节点,B是存放比X大的节点)
3)在每一个链表里面,都要定义头节点和尾节点,HeadA和TailA,HeadB和TailB;我们让头节点保持不动,每次进行插入元素时,只进行移动链表的尾巴节点;
把比X小的数据都放到HeadA里面,比X大的数据都放到HeadB里面
4)出循环之后,会出现三种情况
在原链表中,既有比X小的数据,也有比X大的数据,所以HeadA与HeadB里面都有数据,这是我们可以让TailA.next=HeadB;
在原链表中,只有比X大的数据,这时HeadA指向的链表就是空的
在原链表中,只有比X小的数据,这时HeadB指向的链表就是空的
5)出了链表之后,还有一种极端情况,HeadB里面只有一个元素(HeadB=current),此时TailA.next=HeadB;这是一个新的链表就合成成功了,但是此时最后一个结点的Next值不是空,所以我们还要手动置为空;
public ListNode partition(ListNode head, int x) {
ListNode HeadA=null;
ListNode HeadB=null;
ListNode TailA=null;
ListNode TailB=null;
ListNode current=head;
while(current!=null)
{
if(current.val<x){
if(HeadA==null)
{
HeadA=current;
TailA=current;
}else{
TailA.next=current;
TailA=TailA.next;
}
}else
{
if(HeadB==null)
{
HeadB=current;
TailB=current;
}else{
TailB.next=current;
TailB=TailB.next;
}
}
current=current.next;
}
if(HeadB==null)
{
return HeadA;
}else if(HeadA==null){
TailB.next=null;
return HeadB;
}else{
TailA.next=HeadB;
TailB.next=null;
return HeadA;
}
2.删除已经排序过的链表中所有重复的节点
1)这里面的重复的节点一定是连接在一起的,是紧挨在一起的
2)重复的元素是不止一个的
思路:创建我们还是创造一个虚拟节点,把所有不重复的节点都放到虚拟节点的后面;
主要判断条件是定义一个节点,current一开始要指向头节点,外层循环里面遍历原来的链表,只要发现current.val!=current.next.val那么就把current放到新创建的链表当中
public ListNode deleteDuplicates(ListNode head) {
if(head==null)
{
return null;
}
ListNode HeadA=new ListNode(-1);
ListNode TailA=HeadA;
ListNode current=head;
while(current!=null)
{
if(current.next!=null&¤t.val==current.next.val)//这里面的条件不可以写成if(current.val!=current.next.val)如果此时链表只有一个节点,那么此时就会发生空指针异常
{
//如果这里面的数据是34,34,34,34,34,34,此时如果条件是current.val==current.nextval就会发生空指针异常,因为current会一直向后走,直到最后一个节点发现current.next此时就会发生空指针异常
while(current.next!=null&¤t.val==current.next.val)
{
current=current.next;
}
//此时出来的节点是重复结点的最后一个节点
current=current.next;
}else {
TailA.next=current;
TailA=TailA.next;
current=current.next;
}
}
TailA.next=null;//因为这里面是节点之间进行相互赋值,所以如果说12,12,34,45,45,整个循环走完之后发现新创建的链表的next值不为空,此时链表就会发生错误
一定要注意新创建的链表的最后一个节点一定要是空
return HeadA.next;
}
3.判断链表的回文结构,空间复杂度(O(1));
输入:1,2,2,1 输出:true
输入:1,2 输出:true
解题思路:我们还是希望他是可以从后向前进行遍历的,所以我们要反转中间节点以后的单链表;
1)运用快慢指针,先找到链表的中间节点
2)进行翻转,真正进行翻转操作的是两个节点,第三个节点用来进行计算前两个节点中的一个较靠后的结点的next,况且要放到循环的第一行
3)我们进行回溯判断的时候,一定要从slow往前走(只有四个节点,拿fast看一下,判断中间节点之后,并进行翻转,此时的fast的值已经指向空了,所以不能再用fast进行回溯;
4)进行翻转之后,我们要让slow从后往前走,head从前往后走,知道两个引用相遇(奇数情况)
5)如果发现slow.next=head或者slow=head在中间位置说明已经相遇了,我们在循环中都要比较head.data是否等于slow.data,如果不相等,直接返回false;
public boolean isPalindrome(ListNode head) {
ListNode fast=head;
ListNode slow=head;
ListNode current=null;
ListNode currentNext=null;
if(head==null)
{
return true;
}
//1找到中间节点
while(fast!=null&&fast.next!=null)
{
fast=fast.next.next;
slow=slow.next;
}
//2进行反转单链表
current=slow.next;
while(current!=null)
{
currentNext=current.next;
current.next=slow;
slow=current;
current=currentNext;
}
//3进行判断是否会文,先按照奇数来进行处理(先把偶数抛到一边),写出完整的代码,全部写完之后,再从循环里面写出完整的代码
while(head!=slow)
{
if(head.val!=slow.val)
{
return false;
}
if(head.next==slow)
{
return true;
}
slow=slow.next;
head=head.next;
}
return true;
}
4. 链表相交,返回中间节点
1)首先我们先要搞明白一个问题,如果两个链表相交,那么形状是Y的形状还是X的形状
2)如果两个链表相交,那么是val域相同还是next域相同呢?
是Y的形状,并且相交链表的next域是相同的;
做题思路:
1)如果两个单链表长度是不相同的,那么他们分别从头节点走,那么他们是永远不会相遇的
2)我们先要知道两个链表的长度,先让长的那个链表走差值步(两个链表长度的差值)
3)我们在让他们同时一步一步地走
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null||headB==null)
{
return null;
}
int count1=0;
int count2=0;
int len=0;
ListNode current1=headA;
ListNode current2=headB;
while(current1!=null)
{
current1=current1.next;
count1++;
}
while(current2!=null)
{
current2=current2.next;
count2++;
}
current1=headA;
current2=headB;
if(count1>count2)
{
len=count1-count2;
while(len!=0)
{
current1=current1.next;
len--;
}
}else{
len=count2-count1;
while(len!=0)
{
current2=current2.next;
len--;
}
}
while(current1!=current2)//为什么不进行判断current1.next!=current2.next作为判断条件,最终我们进行返回current1.next
{
current1=current1.next;
current2=current2.next;
}
return current1;
}
5.判断链表是否有环
我们定义一个快慢指针,定义一个快指针fast,一次走两步,我们再定义一个慢指针,一次走一步;
为啥不能一个走三步,一个走一步呢?
我们必须等到每一次走完之后才可以进行对比,有可能比一次走两步慢,也有可能永远相遇不到;
我们举例的时候,举只有两个节点的环,再进行演示走两步和走三步进行演示,画图
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(slow==fast)
{
return true;
}
}
return false;
}
6.返回链表入环时候的第一个节点(OJ),链表无环那么返回空,空间复杂度是O(1)
题解:在上述图中,我们可以找到定义快指针和慢指针的相遇节点,我们肯定实现要进行找到相遇的节点;
设:起始点到入口点的距离是X,我们设相遇点和入口点之间的距离是Y,环的长度是C
那么这时fast走的路程是slow走的路程的2倍
下面我们要算在相遇的时候slow走的路程和fast走的路程(沿顺时针方向走)
因为fast的速度是slow的2倍,他们有在相同的时间进行了相遇,所以fast的路程是slow的2倍;
slow走的路程:X+C-Y
fast走的路程:X+C+C-Y
假设只走了一圈,fast只走了一圈,在第二圈就相遇了;进行连立,发现X=Y;
那么我们就得出结论:起始点到入口点之间的距离,和入口点到相遇点之间的距离是相同的;
我们定义两个节点一个从链表的头位置走,一个从中间位置向回走,相遇节点就是环的入口结点;
但是实际上可能fast会走很多圈,slow的路程是不变的
2(X+C-Y)=X+NC+C-Y
X=(N-1)C+Y
N是任意的,X肯定是确定的,因为起始点到入口点之间的距离是确定的,现在N和C是不确定的,转的圈越多,说明C就越小,也就说明一圈的路程是特别小的;
slow不可能在环里面转很多圈
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和slow还会一直向下走
//所以我们约定,只要进行了相遇,那么就退出
if(fast==slow)
{
break;
}
}
//此时代码可以走到这里有两种情况,一个是fast=null或者fast.next=null,这种属于fast指针已经走到了链表的末尾,此时slow是中间节点
if(fast==null||fast.next==null)
{
return null;
}
while(slow!=head)
{
head=head.next;
slow=slow.next;
}
return slow;
}