1.两两交换链表中的节点:代码随想录
力扣链接:. - 力扣(LeetCode)
这个题按照链表节点个数分为两种情况:
偶数个:两两交换没有剩余
奇数个:最后一个不交换
模拟一下交换过程就行了,具体看代码随想录链接中的思路部分。
贴一下代码,自己写了不少注释方便理解和复习。
package LuStudy;
/*
* 两两交换链表中的节点
*
* https://leetcode.cn/problems/swap-nodes-in-pairs/
*
* 问题理解:链表中节点为偶数则都需要两两交换,奇数个则不交换最后一个
* 情况分类:链表空、链表一个节点、链表奇数个节点(节点数>=1)链表偶数个节点
*
* 此题代码随想录有三个解法:递归解法、模拟交换、模拟交换简洁版
*/
/*
* 自己定义下Listnode
*/
class ListNode {
int val;
ListNode next;
// 无参构造方法
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public class _4_1TwoTwoExchageLinkListNode { // 模拟交换法
// 建议先在本子上自己模拟下过程
private static ListNode swapPairs(ListNode head) { // 传参为头节点
// 循环外定义2个变量:虚拟头节点、控制循环的cur
// 用一个链表的例子: 1 -> 2 -> 3 -> 4
ListNode dumyHead = new ListNode(-1, head);
ListNode cur = dumyHead;
while (cur.next != null && cur.next.next != null) {
ListNode node1 = cur.next; // 节点1
ListNode node2 = cur.next.next.next; // 节点3这个也能在下面定义,但是像这样好理解
// 先让虚拟头节点连上第二个节点
// 目前虚拟头节点指向2节点,1也指向2,下一步要改变1指向2的链接,改为2指向1
// 目前cur.nex指向2了已经,即 cur.next -> 2;cur -> dumyHead
cur.next = cur.next.next;
// 将2的线指向1,再将1的线指向3
cur.next.next = node1;
node1.next = node2;
// 这两个节点交换结束,关注cur的指向,敢这么移动是因为在while里有条件可以移动
cur = cur.next.next;
}
return dumyHead.next; // 返回的是头节点
/*
* cur.next = cur.next.next;
* cur.next.next = node1;
* node1.next = node2;
*
* 实际上这三条语句可以翻译一下,以第一次交换为例,cur第一次指向dumyHead
* cur的next是2,2=cur.next.next
* 2的next是1(原来第一个节点),1=node1,node1存储过了
* 1的next是3(原来第三个节点),3=node2,node2存储过了,其实这边叫node3比较形象
*/
}
public static void main(String[] args) {
// 初始化链表
ListNode head = new ListNode(1);
// 添加元素
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
head.next.next.next.next.next = new ListNode(6);
head.next.next.next.next.next.next = new ListNode(7);
// 测试两两交换操作
head = swapPairs(head);
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
}
}
虚拟头节点出现场景:有增删的地方,定义这个头节点就可以统一操作,不用按情况分类,即考虑头节点的情况。
头节点定义这边,个人这样理解:
ListNode dumyHead = new ListNode(-1, head);
里面的-1不重要,注意后面的head,是上面的函数 ListNode swapPairs(ListNode head) 传过来的,传过来了链表的头节点。
看ListNode的构造函数:
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
这里就是 this.next = head;即这个dumyHead.next = head;、
完成虚拟头节点的定义。
定义的cur指针指向虚拟头节点dumyHead,定义好虚拟头节点后,整个流程其实就三步(链表例子:1->2->3->...)
1.让cur.next指向2节点,所以要写cur.next = cur.next.next;
2.让2节点指向1节点,所以要写cur.next.next = node1;这个node1就是为了备份1不让1走丢的
3.让1节点指向3节点,所以要写node1.next = node2;node2就是为了指明1的方向,让1知道下一个是谁。
最后再移动下cur,cur永远指向要调换的两个节点的第一个节点的前一个。
return下面的注释:加强理解
可以结合代码随想录网站的思路、代码中的注释、return下面的注释一起理解,应该很快就知道什么意思了。main中是为了本地测试随便写的例子。
2.删除链表的倒数第N个节点:代码随想录 (programmercarl.com)
力扣链接:. - 力扣(LeetCode)
一定要注意是倒数第N个,注意看题,别傻呵呵写一堆发现是解决第N个节点,这跟设计链表那道题就没啥区别了。
这个题有点绕,双指针写法确实比较巧妙,建议多看几遍代码随想录的视频和文章。双指针的细节写在注释。快指针先行的原因来自代码随想录B站视频教程评论区。
package LuStudy;
/*
* 删除链表的倒数第N个节点
*
* https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
*/
//先定义一个链表结构
class ListNode {
int val;
ListNode next;
public ListNode() {
};
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public class _4_2DeleteLastNLinkListNodeByDoublePoint { // 双指针
// 定义删除链表的倒数第N个节点作用的函数
/*
* 为啥要让快指针先行?(摘抄评论区:https://www.bilibili.com/video/BV1vW4y1U7Gf/?vd_source=
* ab65c9e0700638016fc5e2fafb93d68e)
* 我认为更好懂的一种解释:快指针先行n步,这样快慢指针之间形成了一段长度为n的窗口,之后快慢指针同步向前相当于保持窗口长度不变。
* 这样当快指针到达了末尾指向NULL,另一端的慢指针距离末尾的长度是n,自然就是指向倒数第n个位置了。
*/
private static ListNode deleteLastNNode(ListNode head, int target) {
ListNode dummyHead = new ListNode(0, head);
ListNode fastNode = dummyHead;
ListNode slowNode = dummyHead;
for (int i = 0; i <= target; i++) {
fastNode = fastNode.next;
}
while (fastNode != null) {
fastNode = fastNode.next;
slowNode = slowNode.next;
}
if (slowNode.next != null) {
slowNode.next = slowNode.next.next;
}
return dummyHead.next;
}
public static void main(String[] args) {
// 初始化链表
ListNode head = new ListNode(1);
// 添加元素
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
head.next.next.next.next.next = new ListNode(6);
head.next.next.next.next.next.next = new ListNode(7);
head = deleteLastNNode(head, 1);
ListNode cur = head;
if (cur == null) {
System.out.print("[]");
}
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
}
}
3.链表相交:代码随想录 (programmercarl.com)
力扣链接:. - 力扣(LeetCode)
一定注意:值相同的不一定是同一个节点,其实这个题就是求地址相同的节点。
这块困扰好久,发现是被例子误导了,一定注意是地址相同,所以最好别管值是啥。
建议多看文章、多看这个题的力扣评论区,你会发现这题写的真是太优雅了。反正我想不出来。
两个版本,一个代码多点,来自随想录的思路;另一个是优雅解法,多看力扣讨论区。思路有点巧妙,今天没空记录了,先粘一下代码。有空再review。
版本1:
package LuStudy;
/*
* 链表相交
*
* https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/
*/
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
next = null;
}
}
public class _4_3IntersectionLinkedList {
private 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为最长链表的头,lenA为其长度
if (lenA < lenB) {
int tempLen = lenA;
lenA = lenB;
lenB = tempLen;
ListNode tempNode = curB;
curA = curB;
curB = tempNode;
}
// 求长度差
int gap = lenA - lenB;
while (gap-- > 0) {
curA = curA.next;
}
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
public static void main(String[] args) {
// 创建链表A: 1 -> 2 -> 3 -> 4 -> 5
ListNode headA = new ListNode(1);
// 添加元素
headA.next = new ListNode(2);
headA.next.next = new ListNode(3);
headA.next.next.next = new ListNode(4);
headA.next.next.next.next = new ListNode(5);
// 创建链表B: 3 -> 4 -> 5
ListNode headB = new ListNode(3);
// 添加元素
headB.next = new ListNode(4);
headB.next.next = new ListNode(5);
// 假设链表 B 从1节点val=3开始与链表 A 相交,为了测试函数能不能找到,这样才可打印出结果
ListNode bIntersection = headB;
bIntersection.next = headA.next.next;
_4_3IntersectionLinkedList listNode = new _4_3IntersectionLinkedList();
ListNode res = listNode.getIntersectionNode(headA, headB);
if (res != null) {
System.out.println(res.val);
} else {
System.out.println("No intersection node found.");
}
}
}
版本2(优雅写法):
package LuStudy;
/*
* 链表相交
*
* https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/
*/
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
next = null;
}
}
public class _4_3IntersectionLinkedListByDoublePoint {
private ListNode getIntersectionNodeByDP(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
while (curA != curB) {
if (curA != null) {
curA = curA.next;
} else {
curA = headB;
}
if (curB != null) {
curB = curB.next;
} else {
curB = headA;
}
}
return curA;
}
public static void main(String[] args) {
// 创建链表A: 1 -> 2 -> 3 -> 4 -> 5
ListNode headA = new ListNode(1);
// 添加元素
headA.next = new ListNode(2);
headA.next.next = new ListNode(3);
headA.next.next.next = new ListNode(4);
headA.next.next.next.next = new ListNode(5);
// 创建链表B: 3 -> 4 -> 5
ListNode headB = new ListNode(3);
// 添加元素
headB.next = new ListNode(4);
headB.next.next = new ListNode(5);
// 假设链表 B 从1节点val=3开始与链表 A 相交,为了测试函数能不能找到
ListNode bIntersection = headB;
bIntersection.next = headA.next.next;
_4_3IntersectionLinkedListByDoublePoint listNode = new _4_3IntersectionLinkedListByDoublePoint();
ListNode res = listNode.getIntersectionNodeByDP(headA, headB);
if (res != null) {
System.out.println(res.val);
} else {
System.out.println("No intersection node found.");
}
}
}
版本3(超优雅写法):力扣评论区排名前几个,都是大神
4.环形链表:代码随想录 (programmercarl.com)
力扣链接:. - 力扣(LeetCode)
没看懂,mark一下。贴个自己的代码(哈哈哈笑话下自己)
package LuStudy;
/*
* 环形链表II
*
* https://leetcode.cn/problems/linked-list-cycle-ii/
*/
public class _4_4CircularLinkedList {
public static void main(String[] args) {
}
}