目录
写在前:
此篇讲解关于单链表的一些面试题目,续上节。
11.反转一个单链表
12.给定一个带有头结点的head的非空单链表,返回链表的中间结点,如果有两个中间结点,则返回第二个中间结点
13.输入一个链表,输出该链表中倒数第k个结点
14.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
15.编写代码,以给定值x为基准将链表分为两部分,所有小于x的结点排在大于或等于x的结点之前
16.链表的回文结构
17.输入两个链表,找出他们的第一个公共结点
18.链表中是否有环
19.给定一个链表,返回链表开始入环的第一个结点,如果链表没有环,则返回null
11.反转一个单链表
给定一个链表:
进行反转后的链表:
这里我们采用的是“头插法”来解决问题。
首先需要进行判断
(1)当头结点不存在的时候返回null;【说明链表中没有元素】
(2)当头结点的next域为空时,返回头结点;【说明链表中只有一个结点】
当上面的两种情况都不是的时候
(1)定义一个cur结点指向head.next;
(2)将head.next的值置为null;
定义结点curNext指向cur.next;
进行“头插操作”:
第一步:
第二步:
第三步:
最后返回head。
public ListNode reverseList(){
if(head == null){
return null;
}
//走到这里就说明链表里只有一个节点
if(head.next == null){
return head;
}
ListNode cur = head.next;
head.next = null;
while (cur != null){
ListNode curNext = cur.next;
//头插法插入cur(任何的插入都是先绑后面)
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
12.给定一个带有头结点的head的非空单链表,返回链表的中间结点,如果有两个中间结点,则返回第二个中间结点
将问题具象化:
如图,当我们有奇数个节点的时候,返回中间结点。
当我们有偶数个结点的时候返回第二个中间结点:
本题求解采用“快慢指针”。
具体思路:
定义两个指针fast和slow,同时指向头结点的位置;每次让fast指针向前走两步,slow指针向前走一步。
当fast指针指向最后的位置的时候,此时fast.next域为空。这个时候slow指针指向的位置就是中间位置。
当链表中是偶数个结点的情况下同理:
以四个结点为例,同样的fast走两步,slow走一步;
当fast走到最后的时候此时fast为空。此时,slow所指向的位置就是中间结点中的第二个位置。
结合以上的分析,可以进行代码的编写:
定义两个结点指向头结点的位置。进入循环的条件为当fast指针域和fast.next域均不为空的时候,执行fast向后走两步,slow向后走一步的。
最后返回slow即为所求。
public ListNode middleNode(){
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
13.输入一个链表,输出该链表中倒数第k个结点
本题仍然使用“快慢指针”的方式来进行编写代码。
这里需要明确的是:在一个链表中倒数第k个结点需要走k-1步到链表的最后一个结点。
定义两个指针fast和slow;先让fast走k-1步,slow和fast再同时向前走;当fast.next为空的时候,此时slow所指的位置就是要求的倒数第k个结点。
编写代码:
首先进行合法性的判断:当求得位置小于0或者当头结点为null的时候返回null;
定义两个指针fast和slow;
先让快指针向前走k-1步:这里需要判断的是当fast为null的时候就证明k的值大了,找不到对应的结点,此时也是返回null;
fast指针和slow指针同时向前走,当fast.next为空的时候,证明此时fast指向的链表的末尾,此时slow指向的位置就是需要找到的倒数第k个结点。
public ListNode findKthToTail(int k){
//当k不合法的时候
if(k <= 0 || head == null){
return null;
}
//当k合法时
ListNode fast = head;
ListNode slow = head;
//1.fast走k-1步
while (k - 1 != 0){
fast = fast.next;
if(fast == null){
return null;
}
k--;
}
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
14.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
public static MySingleList.ListNode mergeTwoLists
(MySingleList.ListNode head1,MySingleList.ListNode head2){
MySingleList.ListNode newHead = new MySingleList.ListNode(0);
MySingleList.ListNode tmp = newHead;
while (head1 != null && head2 != null){
if(head1.val < head2.val){
tmp.next = head1;
head1 = head1.next;
tmp = tmp.next;
}else {
tmp.next = head2;
head2 = head2.next;
tmp = tmp.next;
}
}
if(head1 != null){
tmp.next = head1;
}
if(head2 != null){
tmp.next = head2;
}
return newHead.next;
}
15.编写代码,以给定值x为基准将链表分为两部分,所有小于x的结点排在大于或等于x的结点之前
现在我们有一个下图所示的链表:
根据所给题目的要求,我们最终实现的链表应该是如下的样子:
以上是一种情况:最后的末尾本身就是比28大的数据,所以这里不需要进行置空操作。
当末尾的元素需要变换的时候如图:(这里需要手动将最后一个元素的next域设置为null)
需要注意的点:
(1)是否需要进行对最后一个点进行处理
(2)怎么保证顺序不改变
定义一个cur,遍历链表。分成两段--如果小于28,就将数据放在第一段;如果大于28,就将数据放到第二段。
定义4个指针bs、be、as、ae.
定义cur遍历,当cur为空的时候表示链表遍历完成。
编写代码时需要注意:
当遍历完成后,需要考虑到:
有可能所给出的数据都大于给定的x值,或者都小于给定的x值。
如果第一段为空(bs == null)返回第二段as【这样同时解决了两段均为空的情况】
第一段不为空:be.next指向as;
最后将末尾置空【这里需要判空】
public ListNode partition(int x){
ListNode as = null;
ListNode ae = null;
ListNode bs = null;
ListNode be = null;
ListNode cur = head;
while(cur != null){
if(cur.val < x){
if(bs == null){
bs = cur;
be = cur;
}else{
be.next = cur;
be = be.next;
}
}else {
if(as == null){
as = cur;
ae = cur;
}else{
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
//有可能不会同时存在小于x 和大于等于x的数据
if(bs == null){
return as;
}
//第一段不为空
be.next = as;
//第2段不为空的问题
if(as != null){
ae.next = null;
}
return bs;
}
16.链表的回文结构
链表的回文结构:
此题的解题思路是:首先找到链表的中间结点。
将后半部的链表进行反转。
定义两个指针,一个从头开走,一个从尾走。
走完后head在头部,slow走到了链表末尾。一个向前,一个向后
最后当两个指针如果相遇,则链表是回文结构的。
需要注意的是以上情况是链表中有奇数个结点的时候,
当存在偶数个结点的时候,需要在判断两个指针是否相等的时候进行进一步的进行判断。
public boolean chkPalindrome(){
if(head == null){
return false;
}
if(head.next == null){
return true;
}
ListNode fast = head;
ListNode slow = head;
//1.找中间节点
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//2.翻转
ListNode cur = slow.next;//cur代表当前需要翻转的节点
while(cur != null){
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//3.一个从头往后,一个从后往前
while (slow != head){
if(head.val != slow.val){
return false;
}
//偶数情况的判断
if(head.next == slow){
return true;
}
slow = slow.next;
head = head.next;
}
return true;
}
17.输入两个链表,找出他们的第一个公共结点
求两个链表的公共结点。
首先分别求出两个链表的长度。定义两个指针pl、ps分别指向两个链表的头结点headA和headB;
遍历完后将pl和ps指回两个链表的头部。
根据求得的len值,进行修改指针的指向:pl指向长的链表头结点;ps指向短的链表头结点;
让pl先走len步,然后二者同时向后走。
当相等的时候进行判空,为空返回null,不为空返回对应结点pl;
public static MySingleList.ListNode getIntersectionNode
(MySingleList.ListNode headA,MySingleList.ListNode headB){
//1.分别求两个链表的长度
int lenA = 0;
int lenB = 0;
MySingleList.ListNode pl = headA;
MySingleList.ListNode ps = headB;
while (pl != null){
lenA++;
pl = pl.next;
}
while(ps != null){
lenB++;
ps = ps.next;
}
//求完长度之后将pl和ps指回来
pl = headA;
ps = headB;
int len = lenA - lenB;
//根据len的值,修改指向
if(len < 0){
pl = headB;
ps = headA;
len = lenB - lenA;
}
//1.可以保证len一定是一个正数
//2.pl指向的链表一定是最长的,ps指向的链表一定是最短的
while (len != 0){
pl = pl.next;
len -- ;
}
while (pl != ps){
pl = pl.next;
ps = ps.next;
}
//pl == ps
if(pl == ps && pl == null){
return null;
}
return pl;
}
18.链表中是否有环
判断环的问题其实本质上就是数学上的追击问题。
思路:快慢指针,慢指针一次走一步,快指针一次走两步。两个指针从链表起始位置开始走。如果链表有环则一定会在环中相遇,否则快指针率先到达链表的末尾。
拓展1:
为什么快指针每次走两步,慢指针每次走一步是可行的?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了。最差情况下两个指针之间的距离刚好是环的长度,此时两个指针每移动一次,之间的距离就缩小了一步,不会出现每次刚好套圈的情况。因此,在慢指针走到一圈之前,快指针一定是可以追的上慢指针的。
拓展2:
快指针一次走3步,走4步,...n步行么?
假设:快指针每次走三步,慢指针每次走一步,此时快指针一定是先进环的,慢指针后来才进环,假设慢指针进环的时候,快指针的位置:
此时按照上述方法来环绕移动,每次快指针走3步,慢指针走1步,是永远都不会相遇的,快指针刚好将慢指针套圈了,因此是不行的。
只有快指针走2步,慢指针走一步才可以,因为换的最小长度是1,即使套圈了两个也在相同的位置。
编写代码:
定义两个结点:fast和slow,均指向头结点。
当fast不等于null并且fast.next不等于null时进入循环,fast指针向后走两步,slow指针向后走一步,当fast和slow的指针指向同一位置的时候就说明链表存在环,返回true;否则返回11false。
public boolean hasCycle(){
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
另外一种写法是在循环中判断fast和slow的指向的时候,break跳出循环。
这时在循环外面判断:如果是因为while循环跳出来的,就返回false;
如果是因为if条件跳出来的,返回true。
public boolean hasCycle(){
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
if(fast == null || fast.next == null){
return false;
}
return true;
}
19.给定一个链表,返回链表开始入环的第一个结点,如果链表没有环,则返回null
本题的思路:
涉及到一些数学的内容,下面进行分析
public ListNode detectCycle(){
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
if(fast == null || fast.next == null){
return null;
}
//有环,此时fast和slow就相遇了
slow = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}