思路:所谓环形单链表是指收尾相连的链表,这道题目有几种情况:
注意:这道题目中给出的是结点值的数组和下一个结点值得位置的数组,需要两个数组配合才能得到一个完整的链表。
要求是先根据数组构造出环形链表,即构造出一个由A数组元素作为结点,按照nxt数组作为顺序,且收尾相连的链表。然后再将val插入的合理的位置。
特殊情况:
对于数组为null,则返回的链表也为null;
对于数组中只有一个元素的情况,将其next指向自身并且返回即可。
对于其他情况:
①先将数组A按照nxt中的顺序创建结点并且逐个连接起来,注意对于最后一个结点,不能放在循环中进行,需要特殊处理,因为不需要再创建结点,只需要将最后一个结点的next指向头结点dummy.next即可。此时返回的一个环形链表的某个结点,他不一定是最小的。(这里题目有歧义,如果A本身就是按照顺序排列的,那么根本就不需要再找最小结点,这个返回的dummy.next必然就是最小结点);
②然后对于这个环形链表,为了确定val插入的位置,如果val处于某两个值之间,那么可以简单插入相应的位置,如果val是最大值或者最小值,那么只能插在原来最小结点的前面,因此必须找到最小结点minNode。再找最小结点时通过一个规则:如果下一个结点list.next比list大,那么list一定不是最小结点,如果list.next比list小,那么此时list.next必然就是最小值,且是最小值的第一个结点,直接返回这个值即可。
③得到最小结点minNode、以及环形链表cycleList之后,要将val结点插入到链表中合理的位置。
从cycleList的位置开始找,如果找到位置直接插入,结束返回minNode作为环形链表的头结点,如果找不到则list一直向下移动,注意只能移动到最大的那个结点上面,不要在往下移动,然后将结点插入即可,即结点插入到了最小结点的前面,最后就是返回最小结点,如果val是最大值,那么返回的是minNode,如果val是最小值,那么返回的是这个结点。
我的代码:
public ListNode insert(int[] A,int[] nxt, int val) {
//特殊输入:空数组;null不能使用length属性
if(A==null) return null;
//如果输入的元素只有一个,则将其自身形成环后直接返回
if(A.length==1){
ListNode node=new ListNode(A[0]);
node.next=node;
return node;
}
//先构造出环形链表
ListNode cycleList=this.makeCycleList(A,nxt);
//找出环形链表中的最小结点
ListNodeminNode=this.minNodeInCycle(cycleList,A.length);
//新的链表,用来进行遍历插入新结点
ListNode list=minNode;
//将val结点插入到环中的合适的位置,已知minNode为最小结点
for(int i=0;i<A.length-1;i++){
if(val>=list.val&&val<=list.next.val){
//插入到list和list.next之间;并且结束所有工作
ListNode newNode=newListNode(val);
newNode.next=list.next;
list.next=newNode;
returnthis.breakCycle(minNode,A.length+1);
//return minNode;
}else{
//不是合适的位置,向下移动
list=list.next;
}
}
//在整个环中找不到可以插入的中间位置,说明val是最大值或者是最小值结点,则插入到minNode结点之前
ListNode newNode=new ListNode(val);
newNode.next=minNode;
list.next=newNode;
if(val<=minNode.val){
returnthis.breakCycle(newNode,A.length+1);
// return newNode;
}else{
returnthis.breakCycle(minNode,A.length+1);
//return minNode;
}
}
//根据输入的元素数组和各元素的下一元素位置数组创建出一张环形链表
public ListNode makeCycleList(int[] A,int[] nxt){
//输入元素为0个,返回null;
if(A==null||nxt==null) return null;
//如果输入的元素只有一个,则将其自身形成环后直接返回
if(A.length==1){
ListNode node=new ListNode(A[0]);
node.next=node;
return node;
}
//创建链表的固定方法
ListNode dummy=new ListNode(-1);
ListNode list=dummy;
//第一个结点要特殊处理
ListNode node=new ListNode(A[0]);
list.next=node;
list=list.next;
//下一个结点的下标
int nextIndex=nxt[0];
for(int count=0;count<A.length-1;count++){
//先创建出下一个结点
ListNode newNode=newListNode(A[nextIndex]);
//将新结点连接到list上面去
list.next=newNode;
list=list.next;
//确定下一个结点所在的下标位置
nextIndex=nxt[nextIndex];
}
list.next=dummy.next;
return dummy.next;
}
//输入一个链表(已知是环,且是排序的环),找出其中的最小结点进行返回;思路:对于全相等的环,随意返回一个结点即可;对于非全相等的环,如果有
//pcur.next<pcur,那么pcur.next必然就是最小的结点
public ListNode minNodeInCycle(ListNodecycleList,int length){
ListNode minNode=cycleList;
for(int i=0;i<length;i++){
if(cycleList.next.val>=cycleList.val){
cycleList=cycleList.next;
}else{
minNode=cycleList.next;
return minNode;
//break;return表示方法结束,必然已经break,不能再return后面加break;否则编译会出错
}
}
//循环完成还没返回最小值说明这个环中的所有元素相同,随意返回一个借口
return minNode;
}
//输入一个已知最小结点的环链表,将其打断成为单链表返回
public ListNode breakCycle(ListNodepHead,int length){
ListNodelist=pHead;
for(inti=0;i<length-1;i++){
list=list.next;
}
//打断头结点之前的那个结点
list.next=null;
returnpHead;
}
题目要求的代码:
按照答案来看,题目简化了很多:
①题目默认认为给定的数组A和nxt都是有序的,即nxt[i]就是用来指定A[i]的下一个结点的,所以直接对i按照1234遍历进行遍历即可连接得到有序的链表。
②题目最终的输出不是循环链表,而是有序的单链表,对于循环链表在提交时会出现循环超时。
③对于已经得到的单链表tmp,根本不需要找最小结点,head就是最小的结点
④插入val结点时,逻辑很简单,如果val比头结点还小就插入到头结点的前面并且返回val结点;否则遍历连续的两个结点pre和cur,如果val属于某两个结点之间就插入到这两个之间;如果遍历到最后即cur为null的位置,那么表示val是最大的结点,将其插入到最后并返回head即可。
总结:其实编程题目很多时候是看对于题目的理解,有时候题目的要求很简单,不用考虑太多因素,但是题目的描述通常不会太准确,因此多做题目接触很关键。
public class InsertValue {
publicListNode insert(int[] A, int[] nxt, int val) {
//先把要插入的结点创建出来,为node
ListNode node = new ListNode(val);
//考虑特殊的空输入直接返回插入的结点
if(A==null||A.length<=0){
return node;
}
//用head记住链表的头结点
ListNode head=new ListNode(A[0]);
//用一个变量tmp代表链表最其进行遍历
ListNode tmp=head;
//将数组A中的元素创建结点连接到链表tmp上面去
for(int i=0;i<A.length-1;i++){
/*nxt[0]表示A[0]的下一个结点A[1]:注意:这道题目描述不够严谨,最终题目认为给定的数组A和nex都是已经排好序的;
即数组A总是{1,3,4,5,7}而nxt总是{1,2,3,4,0}即nex[i]总是制定A[i]的下一个结点,于是在创建链表时,直接按照下标i
对数组A和nex进行遍历即可;并且特殊而的,虽然是要得到环形链表,但是最后一个结点没有与第一个结点连接起来。
*/
ListNode newNode= new ListNode(A[nxt[i]]);
tmp.next=newNode;
tmp=newNode;
}
//对于已经排序的单链表tmp,将node结点插入到合理的位置
//如果val是最小的结点那么直接插入到head的前面,并且返回的是这个结点
if(val<head.val){
tmp.next=node;
node.next=head;
return node;
}
//如果val不是最小的结点,那么val应该插入到某两个结点之间(如果val最大其实也是插入到最后一个结点和null结点之间)
//使用pre、cur两个指针来进行遍历寻找合适的位置
ListNode pre=head;
ListNode cur=pre.next;
while(cur!=null){
if(pre.val<=val&&val<=cur.val)
//表示val找到合适的位置
break;
//否则继续向下移动遍历
pre=cur;
cur=cur.next;
}
//如果cur为null,表示val是最大值,应当插入到最后一个结点的后面
node.next=cur;
pre.next=node;
return head;
}
}