LeetCode.链表相关

88 阅读3分钟

前言

在leetCode中关于链表的题目有很多,今天分享几个比较常见,也比较简单的链表初级入门题目,分别是相交链表、回文链表、环形链表和反转链表。

现在假设现在我们有一个链表ListNode,结构如下代码所示

public class ListNode {
      int val;
      ListNode next;
      ListNode(int x) {
          val = x;
          next = null;
      }
  }

相交链表

给定两个链表A,B,判断该链表是否相交。

示例1:

image.png

A:[1,9,1,2,4]

B:[3,2,4]

输出:true(相交于2)

示例2:

image.png

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所处节点是否存在,所以我们可以使用两个指针pointApointB分别从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:

image.png

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开始逐步反转。

回文链表