#算法#链表#环形链表
今天依然练习链表的操作,做了几道题之后,感觉数组和链表各有自己的优势吧。当然,站在面试或者说单纯做题的角度,两个也都有各自需要注意的点,数组就是删除的话需要考虑后面元素的向前移动,链表的话就是遍历以及指针移动需要考虑清楚。
24. 两两交换链表中的节点
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
这道题乍一看像反转链表,但是实际上跟反转链表有区别,即使是对每两个节点的处理上,也是不一样。解决思路:创建虚拟节点dummyNode,其next指向链表的head结点,主要是做以下三步:
1. dummy指向第二个节点
2. 第二个节点指向第一个节点
3. 第一个节点指向第二个的next节点。
这三步循环执行。
如下图(图片来自代码随想录)
执行完这三步,就可以像1和2节点交换位置了:
具体代码:
public ListNode swapPairs(ListNode head) {
if(null == head) {
return head;
}
ListNode dummy = new ListNode();
dummy.next = head;
ListNode temp = null;
ListNode cur = dummy;
ListNode first = head;
ListNode second = first.next;
while(null != first && null != first.next) {
temp = second.next;
cur.next = second;
second.next = first;
first.next = temp;
cur = first;
first = temp;
if(null != first) {
second = first.next;
}
}
return dummy.next;
}
这里我可能和其他人的略有不同,但是大体逻辑是一样的。
19.删除链表的倒数第N个节点
题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
这道题有两种思路。
第一种就是马上可以想到的,先遍历计算链表的总长度size,然后倒数第N个也就是第size - N +1个,这样的话再进行一次遍历,就可以找到对应的节点然后删除。
第二种就是经典的双指针法。通过双指针找到删除的节点,只进行一次遍历。创建两个指针fast和slow,fast先遍历到第N个节点,然后slow从头结点开始和fast一起遍历,fast遍历完整个链表,则slow对应的下个节点是要删除的节点。
具体代码:
public ListNode removeNthFromEnd(ListNode head, int n) {
if(null == head) {
return head;
}
//解法一: 遍历两次,先拿到size, 倒数第N个就是第 size - N个
// int size = 0;
// ListNode count = head; //注意,这里不能直接用head,否则原链表head就发生变化了
// while(null != count) {
// size++;
// count = count.next;
// }
// int target = size - n;
// ListNode dummy = new ListNode();
// dummy.next = head;
// ListNode cur = dummy;
// int i = 0;
// while(null != cur.next && i < target) {
// cur = cur.next;
// i++;
// }
// if(null != cur && null != cur.next) {
// cur.next = cur.next.next;
// }
//解法二 双指针法,通过双指针,一次遍历找到删除的位置
ListNode dummy = new ListNode();
dummy.next = head;
ListNode fast = dummy.next;
int i = 0;
while(null != fast && i < n) {
fast = fast.next;
i++;
}
ListNode slow = dummy;
while(null != fast) {
fast = fast.next;
slow = slow.next;
}
if(null != slow && null != slow.next) {
slow.next = slow.next.next;
}
return dummy.next;
}
两种解法写在一起了,我是两次都在leetcode上进行了提交,都可以通过,分开的话怕复制粘贴搞得有问题,容易出错。
02.07. 链表相交
题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
这个我是想了一会,首先就是思考如何确认有相交,因为我们这种是单链表,所以类似数组的查找公共元素,所以我的想法是两个循环,遍历链表,找到两个链表都有的节点,那就是相交节点,返回对应节点即可,如果没有那就是没相交。
具体代码:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(null == headA || null == headB) {
return null;
}
//两个循环遍历(但是不符合题目要求:你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?)
ListNode curA = headA;
while(null != curA) {
ListNode curB = headB; //这里需要注意,每次A链表更新节点时,B链表都要从第一个节点开始
while(null != curB) {
if(curA.equals(curB)) {
return curA;
}
curB = curB.next;
}
curA = curA.next;
}
return null;
}
142.环形链表II
题目链接:142. 环形链表 II - 力扣(LeetCode)给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
这道题首先是判断是否有环,有环再去找对应的节点,没有环则直接返回null。
如何判断是否有环,这里用到了快慢指针,fast每次移动两个节点,slow移动一个,遍历之后,如果有环,那么两个指针一定会相遇,如果没有环,那fast会先遍历为null。这个其实是一个固定的结论,看到有些人说这个是小学算术题,我也是虎躯一震,我小学怎么完全不知道这个。
如何找入环的节点,这里需要经过计算,具体的计算经过可以参考代码随想录,讲的比较详细,最终结论:两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口。
具体代码:
public ListNode detectCycle(ListNode head) {
if(null == head || null == head.next || null == head.next.next) {
return null;
}
//check if there has a cycle
ListNode fast = head;
ListNode slow = head;
while(null != fast && null != fast.next) {
fast = fast.next.next;
slow = slow.next;
if(slow.equals(fast)) {
//has a cycle
ListNode index1 = fast;
ListNode index2 = head;
//find the target node
while(!index1.equals(index2)) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
这道题就是判断是否有环可以多思考下,找入环的第一个节点,我觉得可以学习后记一下结论,每次都算一遍还是比较耗时。
这次的题就全部完成了,下次继续加油!(有疑问的朋友可以留言,相互交流,思想碰撞才能一起成长,加油!)