目录
概念:
链表是一种常见的基础数据结构,链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。说到这里你应该就明白了,链表就如同车链子一样,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
例题:
1、合并两个有序链表
2、寻找单链表的中点
3、寻找单链表的倒数第k
个节点
4、判断单链表是否包含环
5、判断两个单链表是否相交并找出交点
6、删除链表中重复元素||
这些题目都是LeetCode上链表模块最具有代表性的题目,每个题都有它极具特色的解法而且有些题的解法并不唯一,下面我们开始学习这些题目里的骚套路。
---------------------------------------------------------------------------------------------------------------------------------
这个题我说两种解题思路,一个是巧用尾插法来实现,另一个就是利用递归思想来解决。
解法一:尾插法
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 边界
if (list1 == null) { //判断特殊情况
return list2;
}
if (list2 == null) {
return list1;
}
// 此时l1和l2都不为空
ListNode dummyHead = new ListNode(101);//虚拟头结点,需注意题中数据的范围
ListNode last = dummyHead;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
last.next = list1;
last = list1;
list1 = list1.next;
}else {
last.next = list2;
last = list2;
list2 = list2.next;
}
}
// 此时l1或l2为空
if (list1 == null) {
last.next = list2;
}else {
last.next = list1;
}
return dummyHead.next;
}
递归法
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
if (list1.val <= list2.val) {
// 说明此时要把list1.next和整个list2交给merge
// 然后将结果拼接到list1.next
list1.next = mergeTwoLists(list1.next,list2);
return list1;
}else {
list2.next = mergeTwoLists(list1,list2.next);
return list2;
}
}
}
思路:把两个链表分成头结点和其他链表两部分,绿色部分即为递归函数要解决的问题,咱们只需要处理两个头结点即可。
递归函数的语义:接收两个不为空链表并且将它们合并升序排列
思路:这里我们用一个快慢指针来解决,快慢指针之间的距离差为2倍,当快指针走到完时满指针所在的节点即为中间结点。但是这里我们需要注意链表数目是奇数还是偶数,偶数我们就让fast.next==null作为循环的结束条件,奇数的话令fast==null即可。
public class Num876 {
// 快慢指针法
public ListNode middleNode(ListNode head) {
// fast一次走两步
ListNode fast = head;
// low一次走一步
ListNode low = head;
// 当fast为null(偶数个节点) || fast.next == null(奇数个节点)
while (fast != null && fast.next != null) {
low = low.next;
fast = fast.next.next;
}
return low;
}
}
思路:这个题跟上一个题目解题方法一样都是使用双指针,让fast指针比low指针差K步,当fast指针走到最后一个结点时,low指针就指向了需要被删除的结点了。
public class Offer_22 {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head,low = head;
for (int i = 0; i < k; i++) {
fast = fast.next;
}
// 此时再让low和fast同时向后移动,直到fast为空
while (fast != null) {
low = low.next;
fast = fast.next;
}
return low;
}
}
思路: 双指针叒来了,这个问题类似于运动会跑步项目,如果成环的话跑的快的人一定会和跑的慢的人相遇。废话不多说直接上代码:
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast=head;
ListNode low=head;
while(fast!=null&&fast.next!=null)
{
low=low.next;
fast=fast.next.next;
if(fast==low)
{
return true;
}
}
return false;
}
}
思路:这个题可以说是上一个题的升级版,大题思路差不多当fast和low相遇的时候我们创建一个新的结点,让它从头结点开始移动当它和low或者fast指针相遇时返回节点索引位置。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode low=head;
while(fast!=null&&fast.next!=null)
{
fast=fast.next.next;
low=low.next;
if((low==fast))
{
ListNode third=head;
while(third!=low)
{
third=third.next;
low=low.next;
}
return low;
}
}
return null;
}
}
思路:我们这题使用虚拟头结点,虚拟头结点可以简化一些关于头结点的一些操作。继续使用双指针cur和prev,当cur和prev结点的值不相同继续遍历,如果相同就让prev先走。直到找到下一个不相同的值,然后把cur移动到prev结点,prev结点继续变量重复上述操作。当prev为空时,将最前面不相同的结点连接到最后一个节点即可。
public class Num82 {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummyHead = new ListNode(101);
dummyHead.next = head;
ListNode prev = dummyHead;
ListNode cur = prev.next;
while (cur != null) {
ListNode next = cur.next;
if (next == null) {
return dummyHead.next;
}else {
// 此时链表中至少有两个节点
if (cur.val == next.val) {
// cur和next重复
// 让next一直向后移动,直到走到第一个与cur.val不相等的节点
while (next != null && cur.val == next.val) {
next = next.next;
}
// prev - 第一个重复节点的前驱
// next - 最后一个重复节点的后继
// prev - next全是重复元素,需要删除
prev.next = next;
// prev不动,cur走到当前next继续判断后面是否还有重复元素
cur = next;
}else {
// cur与next不重复
prev = prev.next;
cur = cur.next;
}
}
}
return dummyHead.next;
}
}
总结
看完上面的内容你就可以在力扣上完成至少6道题,双指针、虚拟头结点都是链表题里面很常用的技巧,链表题一定要多画图!!!