链表是数据结构中较为基础的一种数据结构,其在我们日常的开发中用的比较多,关于链表的一些基础知识,我在我之前的博客中已经介绍过,并且也做了一些简单的实现,然而这个博客主要是讲链表的应用问题,具体体现为在LeetCode上面与链表相关的题目做了一些总结,如果错误,还希望各位不吝,给予指正。链表相关知识可参考此链接。
闲话不多说直接上题目进行分析:
第一类,改变单个链表的结构的,这一类以删除为核心操作的:
例如:LeetCode 删除链表中的节点:第203 和 237题;第203题是就爱那个连表中的含有给定值的链表中的元素进行删除,而237则是直接指定要删除的节点,所以首先要分清楚干什么,一个是通过元素的值判断,另一个则是直接指定了要删除的节点。
对于203题的解:
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode pre = dummyHead;
while(pre.next != null){
if(pre.next.val == val){
pre.next = pre.next.next;
}else
pre = pre.next;
}
return dummyHead.next;
}
}
对于207题的解则较为直接:
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
再入第83题的删除链表中的重复元素,这个需要删除重复的元素:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode now=head;
if(now==null)return head;
while(now.next!=null){
if(now.val==now.next.val){
now.next=now.next.next;
}
else{
now=now.next;
}
}
return head;
}
}
再如第19题:删除倒数第n个节点,其实就是一个利用指针进行寻址然后进行删除操作的过程:所以我们可以使用多个指针进行操作。在使用指针进行操作的时候不要忘记判断边界。核心点有两个,一个是找到要删除元素的前驱元素,另一个要点就是删除操作:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if( n < 1 || head == null){
return head;
}
ListNode dummyHead = new ListNode(0);
ListNode prev = dummyHead, curr = dummyHead;
dummyHead.next = head;
//核心1:寻找要删除的元素的前驱
while(n >= 0 && curr != null){
curr = curr.next;
n--;
}
while(curr != null){
prev = prev.next;
curr = curr.next;
}
//核心2:进行删除操作
prev.next = prev.next.next;
return dummyHead.next;
}
}
可以看到,核心的内容是在删除操作的基础上,增加边界判断,然后确定好指针位置就即可。同时,如LeetCode第·82 删除排序链表中的重复元素:此题的核心是,把含有重复元素的元素全部删去,也就是说,一旦出现重复,就将此值为val的全部删去:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode dummy = new ListNode(0);
dummy.next = head;
head = dummy;
while (head.next != null && head.next.next != null) {
if (head.next.val == head.next.next.val) {
int val = head.next.val;
// val 记录的是重复元素的值,借用下面的while删除元素
while (head.next != null && head.next.val == val) {
head.next = head.next.next;
}
} else {
head = head.next;
}
}
return dummy.next;
}
}
回环链表类:会换链表类说到本质,还是对指针的控制,判定是不是回环链表也是有技巧的,例如,平时链表我们是一个一个节点走的,但是,我们可以在判断回环链表时借助回环链表的特性,可以得到按照两倍的速度走,必然后重新碰到,如果遇到了,则说明这个是一个回环链表:
例如LeetCode第141. 环形链表,判断连表中是不是有回环:
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
}
再例如,在上面题目基础上加点佐料的第142. 环形链表 II题目中,给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。我们需要改变的地方在判断期待起点的地方,如果不是很明白可以参考下图:
在上图中我们假如fast和slow都从1开始,那么运行过程如下:
fast->3; slow ->2;
fast->5; slow ->3;
fast->7; slow ->4;
fast->3; slow ->5;
fast->5; slow ->6;
fast->7; slow ->7; 此时相遇了,所以我们只需要将fast移回开端,然后同时使得fast 和 slow共同移动就可以了:
slow = slow.next;
fast = fast.next;
如果相遇则,说明是起始位置,在图中,则表示为:
fast ->1;操作完成后同时开始移动fast 和 slow;
fast->2;
slow->2;
此时,节点而即为环形链表的开始节点,之后在上个问题代码的基础上增加发现的这个条件,或者是判据:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(fast == slow){
//操作一:返回head节点
fast = head;
//判断是否有环形链表的开始节点
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
return null;
}
}
链表算法的题,还有其他类型,具体的可参考后续的博客,同时也希望能够批评指正,给予更好的建议。共同交流,共同进步。