前言
在leetCode中关于链表的题目有很多,今天分享几个比较常见,也比较简单的链表初级入门题目,分别是相交链表、回文链表、环形链表和反转链表。
现在假设现在我们有一个链表ListNode,结构如下代码所示
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
相交链表
给定两个链表A,B,判断该链表是否相交。
示例1:
A:[1,9,1,2,4]
B:[3,2,4]
输出:true(相交于2)
示例2:
A:[2,6,4]
B:[1,5]
输出:false(无相交)
Tips:这里使用数值代表一个
ListNode节点,相同数值代表一个相同的节点;但是在实际的开发中,需要比较的是两个
ListNode节点的内存地址,而非值。
分析
方案一(map预存法)
我们很容易想到通过遍历一个链表,用map存下链表中所有ListNode的地址引用信息,然后再循环另外一个链表,通过判断节点是否存在map中解决该问题。
附代码:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 存放headA原链表节点信息
Map<ListNode, Integer> map = new HashMap<>();
map.put(headA, headA.val);
while (headA != null) {
map.put(headA, headA.val);
headA = headA.next;
}
// 遍历headB链表
while (headB != null) {
if (map.get(headB) != null) {
return headB;
}
headB = headB.next;
}
return null;
}
空间复杂度O(n),map中存放数据根据head的长度变化所变化。
时间复杂度O(n+m),需要遍历headA全部节点信息 + headB中未相交节点信息
除此以外还有没有其他的更加优化的方法呢?答案是有的
方案二(双指针)
分析
我们将两个链表分别分成两段
A:a(不相交部分) + c(相交部分)
B:b(不相交部分) + c(相交部分)
其中a、b、c均可能为0
c为0代表A和B不相交
由于我们只需要判断c所处节点是否存在,所以我们可以使用两个指针pointA和pointB分别从A的头结点和B的头结点同时出发,pointA先遍历完A链表后指向链表B头结点,继续遍历B链表,pointB同理,从链表B开始遍历,遍历完成链表B后,指向A链表头节点继续遍历。
相当于不同指针走不通的链表开始,但是在遍历完自己链表后还需要遍历其他的链表,直至得到结论.
原理:
如果相交点存在,则公式恒等:
a + c + b = b + c + a如果相交点不存在,则两个指针均不可能存在相等的情况。
附代码:
public static ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode aPoint = headA, bPoint = headB;
while (aPoint != bPoint ) {
// headA指针遍历完A链表,开始遍历B链表
aPoint = aPoint.next == null ? headB :aPoint.next;
// headB指针遍历完B链表,开始遍历A链表
bPoint = bPoint.next == null ? headA :bPoint.next;
}
// 如果最后全部遍历完都没有相交的话,此时aPoint应当指向null
return aPoint;
}
空间复杂度O(1),只用了2个指针。
时间复杂度O(n+m),需要遍历headA全部节点信息 + headB中未相交节点信息
反转链表
给定一个链表,请将该链表反转 示例1:
l:[1,2,3,4,5]
输出:[5,4,3,2,1]
示例2:
l:[]
输出:[]
分析
需要反转链表,只需要我们从头结点开始遍历,有一个新的空列表放置每一个单独节点即可 附代码:
public static ListNode reverseList(ListNode head) {
ListNode tempHead = head, pre = null;
while (tempHead != null) {
// 临时保存下个节点
ListNode next = tempHead.next;
// 断开原链表节点,并设置为已经反转的链表的头结点
tempHead.next = pre;
// 已经反转的节点更新
pre = tempHead;
// 遍历节点前推
tempHead = next;
}
return pre;
}
重要的点在于理解pre的作用,pre存放的是每次反转之后的结果,默认头节点为null,从null开始逐步反转。