算法-代码随想录-链表

移除链表元素#

移除链表元素

迭代法

注意:

  1. head可能为null,也可能是要删除的val;因此要设置一个虚拟头结点指向头结点,再通过迭代一个一个删除。
  2. 删除方式为:循环,判断下一节点是否是目标节点,是则指向目标节点的下一节点(这里不要移动指针,因为删除后的下一节点仍然有可能是目标节点)。不是则将指针后移。

代码:#

Copy Highlighter-hljs
class Solution { public ListNode removeElements(ListNode head, int val) { ListNode remo = new ListNode(0); remo.next = head; ListNode tmp = remo; while(tmp.next != null){ if(tmp.next.val == val){ tmp.next = tmp.next.next; }else{ tmp = tmp.next; } } // 这里不能直接返回head结点 return remo.next; } }

设计链表#

设计链表

单链表#

  • 自定义链表节点
  • 设计链表中,包含链表大小及虚拟头结点
  • MyLinkedList() 初始化链表,设置初始值
  • void addAtIndex(int index, int val) 此处index与其他添加方法不同,index可以等于size,这种情况就是插入尾节点。
Copy Highlighter-hljs
public class design { //测试主方法 public static void main(String[] args) { MyLinkedList obj = new MyLinkedList(); obj.addAtHead(7); obj.addAtHead(2); obj.addAtHead(1); obj.addAtIndex(3, 0); obj.deleteAtIndex(2); obj.addAtHead(6); obj.addAtTail(4); int a = obj.get(4); System.out.println(a);//4 obj.addAtHead(4); obj.addAtIndex(5, 0); obj.addAtHead(6); } } //自定义节点类 class LinkedNode{ int val; LinkedNode next; LinkedNode(){} LinkedNode(int val){ this.val = val; } } //设计链表 class MyLinkedList{ int size;//链表大小 LinkedNode head;//虚拟头结点 //初始化链表 public MyLinkedList(){ size = 0; head = new LinkedNode(0); } //获取指定下标元素 public int get(int index){ if(index < 0 || index > size - 1){ return -1; } LinkedNode aHead = head; for(int i = 0; i <= index; i++){ aHead = aHead.next; } return aHead.val; } //在头部插入节点 public void addAtHead(int val){ LinkedNode node = new LinkedNode(val); LinkedNode aHead = head; node.next = aHead.next; aHead.next = node; size++; } //在尾部插入节点 public void addAtTail(int val) { LinkedNode aHead = head; LinkedNode node = new LinkedNode(val); while(aHead.next != null){ aHead = aHead.next; } aHead.next = node; size++; } //在指定索引插入元素 public void addAtIndex(int index, int val) { //index可以等于size if(index < 0 || index > size){ return; } int i = 0; LinkedNode aHead = head; LinkedNode node = new LinkedNode(val); while(i < index){ aHead = aHead.next; i++; } node.next = aHead.next; aHead.next = node; size++; } //删除索引处元素 public void deleteAtIndex(int index) { if(index < 0 || index > size - 1){ return; } int i = 0; LinkedNode aHead = head; while(i < index){ i++; aHead = aHead.next; } aHead.next = aHead.next.next; size--; } }

双链表#

  • 节点包含next、prev双向指针
  • 创建链表时,创建虚拟头结点和虚拟尾节点
Copy Highlighter-hljs
class LinkedNode{ int val; LinkedNode next, prev; LinkedNode(){} LinkedNode(int val){this.val = val;} } class MyLinkedList{ int size; LinkedNode head, tail; MyLinkedList(){ //初始化 size = 0; head = new LinkedNode(0); tail = new LinkedNode(0); head.next = tail; tail.prev = head; } public int get(int index){ if (index < 0 || index >= size){ return -1; } LinkedNode aHead = head; LinkedNode aTail = tail; if(index < size / 2){ for (int i = 0; i <= index; i++) { aHead = aHead.next; } return aHead.val; } else{//4, 3 for (int i = size; i > index; i--) { aTail = aTail.prev; } return aTail.val; } } public void addAtIndex(int index, int val){ if(index > size){ return; } LinkedNode node = new LinkedNode(val); LinkedNode aHead = head; //找到前驱 for (int i = 0; i < index; i++) { aHead = aHead.next; } node.next = aHead.next; aHead.next.prev = node; node.prev = aHead; aHead.next = node; size++; } public void addAtHead(int val) { //等价于在第0个元素前添加 addAtIndex(0,val); } public void addAtTail(int val) { //等价于在最后一个元素(null)前添加 addAtIndex(size,val); } public void deleteAtIndex(int index) { //判断索引是否有效 if(index<0 || index>=size){ return; } //删除操作 size--; LinkedNode pre = this.head; for(int i=0; i<index; i++){ pre = pre.next; } pre.next.next.prev = pre; pre.next = pre.next.next; } }

反转链表#

双指针法#

cur指针指向head结点,pre指针指向null。将cur.next 指向null。再将两个指针向右平移。

img

Copy Highlighter-hljs
import java.util.Scanner; public class reverse { public static void main(String[] args) { Scanner scan = new Scanner(System.in); ListNode head = new ListNode(); //fprint(head); ListNode aHead = head; String str = scan.nextLine(); String[] s = str.split(" "); for (int i = 0; i < s.length; i++) { int num = Integer.valueOf(s[i]); ListNode newNode = new ListNode(num); aHead.next = newNode; aHead = aHead.next; } fprint(head.next); head = reverseList(head); fprint(head); } //打印链表方法 static void fprint(ListNode head){ while(head != null){ System.out.print(head.val + " "); head = head.next; } } //反转链表*** static ListNode reverseList(ListNode head) { ListNode tmp;//临时记录cur.next ListNode cur = head.next;//双指针-前指针 ListNode pre = null;//双指针-后指针 while(cur != null){ tmp = cur.next; cur.next = pre; //先后顺序不能变 pre = cur; cur = tmp; } return pre; } } //节点定义 class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } }

递归法#

思路与双指针法类似,写法不同。

Copy Highlighter-hljs
static ListNode reverseList(ListNode head) { return reverse(null, head); } static ListNode reverse(ListNode pre, ListNode cur){ if (cur == null) return pre; ListNode tmp = cur.next; cur.next = pre; return reverse(cur, tmp); }

两两交换链表中的节点#

题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)

思路:#

设置虚拟头结点,交换后两个节点,再将虚拟头节点向后移动两位继续做交换。

24.两两交换链表中的节点3

代码:#

Copy Highlighter-hljs
class Solution { public ListNode swapPairs(ListNode head) { ListNode cur = new ListNode(); cur.next = head; ListNode aHead = cur; while(aHead.next != null && aHead.next.next != null){ //临时记录 ListNode tmp = aHead.next; ListNode tmp1 = aHead.next.next.next; aHead.next = aHead.next.next;//步骤一 aHead.next.next = tmp;//步骤二 aHead.next.next.next = tmp1;//步骤三 aHead = aHead.next.next; } ListNode result = cur.next; return result; } }

删除链表倒数第n个节点#

思路:#

  1. 虚拟头结点,方便删除
  2. 双结点,设置fast和slow两个结点,要找到倒数第n个数,即与最后一个结点的后一位空结点相差n个距离。那么在最开始,先让fast移动n个位置,使得fast与slow相差n个结点,再让fast与slow一起移动,当fast移动到空结点,slow就是需要删除的结点。

img

  • 移动n+1个单位,是为了让slow指向目标结点的前一结点,方便后续删除

img

img

img

代码:#

Copy Highlighter-hljs
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode val = new ListNode(0); val.next = head; ListNode fast = val; ListNode slow = val; //移动n+1个单位 for(int i = 0; i <= n; i++){ fast = fast.next; } //fast移动到空结点以前,fast与slow一起移动 while(fast != null){ fast = fast.next; slow = slow.next; } //删除目标结点 slow.next = slow.next.next; //返回头结点 return val.next; } }

链表相交#

思路:#

  1. 如果链表相交,则链表A与链表B的相交部分必然在尾部,且长度相等。
  2. 将两个链表的尾部对齐,将长链表头部多出来的部分直接跳过,再依次判断链表A与链表B的结点是否相等,相等则返回结点,如果没有符合的结点,则返回null。

代码:#

Copy Highlighter-hljs
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode curA = headA; ListNode curB = headB; int lenA = 0; int lenB = 0; //A链表长度 while(curA != null){ curA = curA.next; lenA++; } //B链表长度 while(curB != null){ curB = curB.next; lenB++; } curA = headA; curB = headB; //如果B长度>A长度 if(lenB > lenA){ //交换len int tmpLen = lenB; lenB = lenA; lenA = tmpLen; //交换cur结点 ListNode tmpNode = curB; curB = curA; curA = tmpNode; } //gap是A与B相差的长度,使A与B对齐 int gap = lenA - lenB; while(gap != 0){ curA = curA.next; gap--; } while(curA != null){ if(curA == curB){ return curA; } curA = curA.next; curB = curB.next; } return null; } }

环形链表#

思路:#

  • 判断是否有环,找到环的入口
  • 如何判断是否有环:设置快慢双指针,快指针每次移动两个结点,而慢指针每次移动一个结点,如果有环的话,快慢指针终会在环中的某个位置相遇,说明有环。
  • 如何找到环的入口

img

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

Copy Highlighter-hljs
(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

142.环形链表II(求入口)

代码:#

Copy Highlighter-hljs
public class Solution { public ListNode detectCycle(ListNode head) { ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(slow == fast){//有环 ListNode index1 = head; ListNode index2 = fast; while(index1 != index2){ index1 = index1.next; index2 = index2.next; } return index1; } } return null; } }

posted @   forest-pan  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示
CONTENTS