关于链表的一些题目
关于链表的一些题目
1、双链表的交点
- 使用双指针,假设 A 链表的长度是 lenA ,B的长度是 lenB,根据lenA+lenB = lenB+lenA,让A指针遍历完A之后去便利B,B指针同理。即可在相遇时获得交点。即使没有交点,两指针在遍历完两个链表之后会同时指向 null
- 如果其中一个链表为空,则没有焦点。
- 注意三元运算符的写法,之前先判空,再赋值的写法错了。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if(headB==null || headA==null) return null; ListNode ptr1 = headA; ListNode ptr2 = headB; while(ptr2 != ptr1){ ptr1 = ptr1==null ? headB : ptr1.next; ptr2 = ptr2==null ? headA : ptr2.next; } return ptr1; }
2、链表是否有环
-
使用快慢指针,快指针一次两个,慢指针一次一个。入果快慢指针能相遇的话,就说明链表存在环。
-
这个相遇点在哪里呢?假设起点到环入口的距离是 a ,入口到相遇点的距离是 b ,相遇点顺时针再到入口的距离是c
则有:2(a+b)= a + nc + (n+1)b -> a = c + (n-1)(b+c) 意思是:起点到入口点的距离是 (n-1 ) 倍的环长加上 c,c 就是相遇点顺时针到入口的举例。于是继续让slow从相遇点走,另一个ptr 从起点走,最终必定在入口相遇。
public ListNode detectCycle(ListNode head) { if(head==null ||head.next==null ||head.next.next==null) return null; ListNode slow = head; ListNode fast = head; while(fast != null){ slow = slow.next; if(fast.next != null) fast = fast.next.next; else return null; if(slow == fast){ ListNode ptr = head; while(ptr != slow){ ptr = ptr.next; slow = slow.next; } return ptr; } } return null; }
3、删去倒数第 n 个节点
-
使用双指针,先让快指针走 n 步,再让两者一起走,那么当快指针走到 null 的时候,慢指针刚好走了 len - n 步,倒数第 n 个节点刚好是慢指针所在的节点。但是要删除的是这个节点,所以在慢指针开始的时候,让慢指针指向一个新建的temp节点,这个节点的next指向head,有可能刚好就头节点一个节点,要删除这个节点,那么返回的是 temp.next 而不是 head
public ListNode removeNthFromEnd(ListNode head, int n) { if(head == null) return null; ListNode temp = new ListNode(0,head); ListNode slow = temp; ListNode fast = head; while(n>0){ fast = fast.next; n--; } while(fast != null){ fast = fast.next; slow = slow.next; } slow.next = slow.next.next; return temp.next; } 4、反转链表
-
重点是将后面节点的指针指向前面节点的时候要保存后面一个节点原来的 next 指针,只需要多加一个temp来保存就好
-
易错点是头节点的指针要指向 null,否则反转之后会造成 head 和 head.next 之间的环
-
我的解答:
public ListNode reverseList(ListNode head) { if(head==null || head.next==null) return head; ListNode left = head; ListNode mid = left.next; ListNode right = mid.next; head.next = null; while(mid != null){ mid.next = left; left = mid; mid = right; if(right != null) right = right.next; } return left; } - 更好的:使用两个指针,和一个临时交换指针保存suc 的next 指针就好,我做的麻烦了。
public ListNode reverseList(ListNode head) { ListNode pre = null; ListNode suc = head; while (suc != null) { ListNode temp = suc.next; suc.next = pre; pre = suc; suc = temp; } System.gc(); return pre; }
5、链表相加
-
利用栈先进后出的特点,先将数据弹出,再从低位相加。
-
进位用一个临时数据存储,注意的是当两个链表不为空或者进位不为0时,都应该继续相加
-
我的:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) { Stack<Integer> s1 = new Stack<>(); Stack<Integer> s2 = new Stack<>(); Stack<Integer> s = new Stack<>(); ListNode res = new ListNode(); while(l1!=null){ s1.push(l1.val); l1 = l1.next; } while(l2!=null){ s2.push(l2.val); l2 = l2.next; } int temp = 0; while(!s1.isEmpty() || !s2.isEmpty() ||temp!=0){ int a = s1.isEmpty()?0:s1.pop(); int b = s2.isEmpty()?0:s2.pop(); int sum = a+b+temp; s.push(sum%10); temp = sum/10; } ListNode head = res; while(!s.empty()){ ListNode newNode = new ListNode(s.pop()); res.next = newNode; res = newNode; } return head.next; } -
更好的:直接反转后再相加,不用使用栈保存数据,同时也不需要保存最后的结果。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode list1 = reversal(l1); ListNode list2 = reversal(l2); ListNode head = new ListNode(0); ListNode sumNode=head; int temp=0; while( list1 != null || list2 != null){ int val = (list1 == null ? 0 : list1.val) + (list2 == null ? 0 : list2.val) + temp; temp = val >= 10 ? 1 : 0; val = val >= 10 ? val-10 : val; ListNode newNode =new ListNode(val); sumNode.next = newNode; sumNode = sumNode.next; list1 = list1 == null ? null : list1.next; list2 = list2 == null ? null : list2.next; } if(temp > 0){ sumNode.next = new ListNode(temp); } return reversal(head.next); } private ListNode reversal(ListNode head){ ListNode temp = null; ListNode cur = head; while( cur != null){ ListNode val = cur.next; cur.next = temp; temp = cur; cur = val; } return temp; }
-
6、重排链表
-
要求:使第 i 个链表元素的下一个指向第 len-i 个数据
-
思路:先使用快慢指针找到中点,将链表从中间分开,0-mid是前半部分。再将后半部分反转。最后合并前后部分,这里要注意的是:要将 mid 的指针指向 null ,将前后部分断开。
public void reorderList(ListNode head) { if(head==null || head.next==null) return; ListNode mid = findMid(head); ListNode l2 = mid.next; l2 = reserveList(l2); mid.next = null; ListNode l1 = head; merge(l1,l2); } public ListNode findMid(ListNode head){ ListNode slow = head; ListNode fast = head; while(fast.next!=null && fast.next.next!=null){ slow = slow.next; fast = fast.next.next; } return slow; } public ListNode reserveList(ListNode head){ //这里pre从 head 前面开始,cur从head开始,直接将 head 的next 指向 null //这里的 head 是 mid+1 的位置。不指向null会造成 mid+1 和 mid+2 之间的环 ListNode pre = null; ListNode cur = head; while(cur != null){ ListNode temp = cur.next; cur.next = pre; pre = cur; cur = temp; } return pre; } public void merge(ListNode l1,ListNode l2){ //保留 l1 和 l2 的 next 指针 ListNode l11; ListNode l22; while(l1!=null && l2!=null){ l11 = l1.next; l22 = l2.next; l1.next = l2; l1 = l11; l2.next = l1; l2 = l22; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)