双指针法
在数组的时候使用过双指针法,在对链表进行操作的时候,双指针法依旧可以使用在对链表遍历的操作。
反转链表
题目描述:
力扣链接🔗

题目分析:
如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示:

之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改变next指针的方向。
步骤:
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
代码:
解法一:
/*** 双指针法* @param head* @return*/public ListNode reverseList(ListNode head) {ListNode pre = null;ListNode cur = head;ListNode tmp = null; // 临时节点保存cur下一个节点,防止指针指向反转后找不到下一个节点while (cur != null) {tmp = cur.next;cur.next = pre;// 向后移动pre = cur;cur = tmp;}return pre;}
解法二:递归法(详细见递归)
两两交换链表中的节点
题目描述:
力扣链接🔗

题目分析:
建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。

代码:
解法一:虚拟头结点 + 双指针法
/*** 虚拟头节点法* @param head* @return*/public ListNode swapPairs(ListNode head) {// 虚拟头节点ListNode dummyNode = new ListNode(0);dummyNode.next = head;ListNode pre = dummyNode;ListNode tmp = null; // 临时节点// 注意判断只有一个节点的情况,所以head.next也需要判断// 也可以使用pre.next != null && pre.next.next != null判断while (head != null && head.next != null) {tmp = head.next.next;pre.next = head.next;head.next.next = head;head.next = tmp;// 向后移动pre = head;head = tmp;}return dummyNode.next;}
解法二:递归法(详细见递归)
链表相交
题目描述:
力扣链接🔗
题目分析:
简单来说,就是求两个链表交点节点的指针。 这里同学们要注意,交点不是数值相等,而是指针相等。
为了方便举例,假设节点元素数值相等,则节点指针相等。
看如下两个链表,目前curA指向链表A的头结点,curB指向链表B的头结点:
我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针。
代码:
解法一:
public class Solution {/*** 优化方法** @param headA* @param headB* @return*/public ListNode getIntersectionNode(ListNode headA, ListNode headB) {ListNode curA = headA;ListNode curB = headB;int lenA = 0, lenB = 0;// 分別计算两个链表的长度while (curA != null) {curA = curA.next;lenA++;}while (curB != null) {curB = curB.next;lenB++;}curA = headA;curB = headB;// 让curA为最长链表的头if (lenA < lenB) {int tmpLen = lenA;lenA = lenB;lenB = tmpLen;// 交换指针ListNode tmpNode = curA;curA = curB;curB = tmpNode;}// 将长的链表移动到和短的链表尾部平齐int count = lenA - lenB;while (count-- > 0) {curA = curA.next;}// 进行比较,相等则返回,此时长度相同while (curA != null) {if (curA == curB) {return curA;}curA = curA.next;curB = curB.next;}return null;}}
解法二:
public class Solution {/*** 循环遍历(时间复杂度为O(n的平方))* @param headA* @param headB* @return*/public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA == null || headB == null) {return null;}ListNode curA = headA;ListNode curB = headB;// 循环判断while (curA != null) {while (curB != null) {if (curA == curB)return curA;curB = curB.next;}curA = curA.next;curB = headB;}return null;}}
解法三:
// 双指针法public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA == null || headB ==null) return null;ListNode nodeA = headA, nodeB = headB;while (nodeA != nodeB) {// 移动到尾部之后,在移动到另一个节点的头节点点nodeA = nodeA == null ? headB : nodeA.next;nodeB = nodeB == null ? headA : nodeB.next;}return nodeA;}
环形链表Ⅱ
题目描述:
力扣链接🔗
题目解析:
详细解析🔗
代码:
解法一:在查重的情况下可以考虑hash表。
public class Solution {/*** 哈希表** @param head* @return*/public ListNode detectCycle(ListNode head) {HashSet<ListNode> set = new HashSet<ListNode>();ListNode cur = head;while (cur != null) {if (set.contains(cur)) {return cur;}set.add(cur);cur = cur.next;}return null;}}
解法二:
public class Solution {/*** 快慢指针法** @param head* @return*/public ListNode detectCycle(ListNode head) {ListNode fast = head;ListNode slow = head;// 判断为null或者一个节点时while (fast != null && fast.next != null) {// fast移动两步,slow移动一步slow = slow.next;fast = fast.next.next;// 在环中相交时if (slow == fast) {ListNode index1 = slow;ListNode index2 = head;while (index1 != index2) {index1 = index1.next;index2 = index2.next;}return index1;}}return null;}}
删除链表的倒数第N个节点
题目描述
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例 1:
输入:head = [1,2,3,4,5], n = 2输出:[1,2,3,5]示例 2:
输入:head = [1], n = 1输出:[]示例 3:
输入:head = [1,2], n = 1输出:[1]
思路
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
思路是这样的,但要注意一些细节。
分为如下几步:
- 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑。
 - 定义fast指针和slow指针,初始值为虚拟头结点,如图:
 

- fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:

 - fast和slow同时移动,直到fast指向末尾,如题:

 - 
代码:
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode dummy = new ListNode(-1);dummy.next = head;ListNode slow = dummy;ListNode fast = dummy;while (n-- > 0) {fast = fast.next;}// 记住 待删除节点slow 的上一节点ListNode prev = null;while (fast != null) {prev = slow;slow = slow.next;fast = fast.next;}// 上一节点的next指针绕过 待删除节点slow 直接指向slow的下一节点prev.next = slow.next;// 释放 待删除节点slow 的next指针, 这句删掉也能ACslow.next = null;return dummy.next;}}
 
