876、链表的中间结点
题目描述:给定一个头结点为 head
的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
解题:
链表一下子没有想法,于是看了一下评论,发现常用的一个方法是快慢指针
于是去查了一下快慢指针的原理:
快慢指针找中间值的原理,其实就跟跑步一样,设置快慢两个指针。最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针,这样当快指针遍历完链表以后,慢指针刚遍历到一半,从而返回中间结点。
这也是本体的解题思路,代码如下:
/** //定义链表结点
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
public class Solution {
public ListNode MiddleNode(ListNode head) {
// 快慢指针
ListNode fast = head, slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
运行结果:
此外快慢指针还可以检查链表中是否有环,因为fast每次比slow多走一格,那么如果有环,fast和slow终究会相遇,只要相遇,必定有环。
总的来说,快慢指针擅长的场景,就是寻找链表中的某个节点。
然后对于这里链表的定义,我一开始有些不理解,但是经过琢磨,大体有了点眉目:
/** 链表结点的定义(递归定义)
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
对于每一个结点,都定义了该节点的值和下一个结点。
题目描述:
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
未进阶版本:
两次遍历,第一次遍历知道链表的长度,知道长度以后,再遍历到指定的结点,将之删除。代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
public class Solution {
public ListNode RemoveNthFromEnd(ListNode head, int n) {
ListNode fast = head ;
int index=0;
while(fast!=null){ //先遍历一遍链表,index是链表的长度
fast = fast.next;
index++;
}
if(index-n==0){
if (index==1){
head = null;
}
else {
head = head.next;
}
}
else{
fast = head;
for (int i = 0;i<index-n-1;i++){
fast = fast.next; //此时fast指向的是倒数第n个数的前一个数
}
fast.next = fast.next.next;
}
return head;
}
}
过是过了,但是结果并不是特别好
换了一个思路,采用一次遍历的方式,原理就是快慢双指针,fast指针比slow指针超前n个结点
代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
public class Solution {
public ListNode RemoveNthFromEnd(ListNode head, int n) {
ListNode fast = head ;
ListNode slow = head;
for(int i =0;i<n;i++){
fast = fast.next;
}
if (fast == null){
head = head.next;
}
else {
if (fast.next == null){
head.next = head.next.next;
}
else {
// fast = fast.next;
while(fast.next != null){
fast = fast.next;
slow = slow.next; //最终slow指向要删除结点的前一个结点
}
slow.next = slow.next.next;
}
}
return head;
}
}
结果却更加不好了。。。
同样的思路,大神的代码又是比我简洁亿点点:
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
public class Solution {
public ListNode RemoveNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; ++i) {
first = first.next;
}
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
}
其实大神和我的差距,就是一个哑结点(dummy ListNode)而已。。。
在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的next 指针指向链表的头节点(即头结点的前驱结点就是哑结点)。这样一来,我们就不需要对头节点进行特殊的判断了,只需要考虑通用的情况即可。
在本题中,通过添加哑结点,在移动后,需要删除的结点就是second指针指向结点的后一个结点,所有情况都是。