剑指Offer-LinkedList题目合集

276 阅读7分钟

LinkedList简介

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个结点的地址,分为单向链表和双向链表。

实例解析

剑指Offer_06_从尾到头打印链表

题目描述

输入一个链表的头结点,从尾到头反过来返回每个节点的值(用数组返回)

示例:

输入: head = [1,3,2]

输出: [2,3,1]

限制:

0 <= 链表长度 <= 10000

方案一

借助于栈先进后出的特性,将链表的数据依次压入栈,ListNode == null时,执行出栈操作,将栈数据存储在数组中,实现链表反转操作

public int[] reversePrint(ListNode head) {
    Stack<ListNode> stack = new Stack<>();
    ListNode temp = head;
    while (temp != null) {
        stack.push(temp);
        temp = temp.next;
    }
    int size = stack.size();
    int[] result = new int[size];
    for (int i = 0; i < size; i++) {
        result[i] = stack.pop().val;
    }
    return result;
}

剑指Offer_22_链表中倒数第k个结点

题目描述

输入一个链表,输出该链表中倒数第k个节点,从1开始计数,即链表的尾结点是倒数第1个节点。例如,一个链表有6个节点,从头结点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例:

给定一个链表:1 -> 2 -> 3 -> 4 -> 5,和 k = 2

返回链表 4 -> 5

方案一

执行顺序遍历获取链表长度 count,然后再执行二次遍历,count 累减,当遍历到 count == k时,返回链表结点 head

public ListNode getKthFromEnd(ListNode head,int k){
    int count = 0;
    ListNode temp = head;
    while(temp!=null){
        count++;
        temp = temp.next;
    }
    while(head!=null){
        if (count==k){
            return head;
        }
        head = head.next;
        count--;
    }
    return head;
}

方案二

借助于快慢指针,将fast和slow同时指向链表头head,先将fast向后移动k,指向第k+1个节点,然后再继续将fast与slow向后移动,直到fast==null,返回结点slow

    public ListNode getKthFromEnd(ListNode head, int k) {
            ListNode fast = head;
            ListNode slow = head;
            while (fast != null && k > 0) {
                fast = fast.next;
                k--;
            }
            while (fast != null) {
                fast = fast.next;
                slow = slow.next;
            }
            return slow;
    }

剑指Offer_24_反转链表

题目描述

定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点

示例

输入: 1 -> 2 -> 3 -> 4 -> 5 -> NULL 输出:5 -> 4 -> 3 -> 2 -> 1 -> NULL

限制

0 <= 结点个数 <= 5000

方案一

将一个链表反转的话,可以采用迭代的方法,首先new一个ListNode pre作为预指针,之后再new一个cur指向head表示当前节点的位置,next指针表示下一个指向的位置,依次遍历,当前ListNode!=null时,将cur.next赋值给next,并将当前结点的下一节点指向pre,pre = cur依次右移,并将next赋值给cur,最终跳出循环,返回结点pre。

public ListNode reverseList(ListNode head) {
    ListNode pre = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}

剑指Offer_21_合并两个有序链表

题目描述

将两个升序链表合并为一个升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成的

示例1

① -> ② -> ③

① -> ③ -> ④

① -> ① -> ② -> ③ -> ④ -> ④

输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]

示例2

输入:l1 = [],l2 = [] 输出:[]

示例3

输入:l1 = [],l2 = [0] 输出:[0]

提示

  • 两个链表的节点数目范围是[0,50]
  • -100 <= Node.val <= 100
  • l1和l2均按非递减顺序排列

方案一

合并两个链表的话,首先判断链表是否为空,链表l1为空则直接return链表l2,相反直接return链表l1,然后再判断两个链表的头结点,将val最小的作为头结点,然后可以采用递归的思维,将链表l1与l2合并分解为l1.next与l2合并的问题,依次递归

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    if (list1 == null) {
        return list2;
    } else if (list2 == null) {
        return list1;
    } else if (list1.val < list2.val) {
        list1.next = mergeTwoLists(list1.next, list2);
        return list1;
    } else {
        list2.next = mergeTwoLists(list1, list2.next);
        return list2;
    }
}

剑指Offer_23_合并k个升序链表

给你一个链表数组,每个链表都已经按升序排列,请你将所有链表合并到一个升序链表中,返回合并后的链表 示例1

输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6]

示例2

输入:lists = [] 输出:[]

示例3

输入:lists = [[]] 输出:[]

提示

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <=500
  • -10^4 <= lists[i][j] <=10^4
  • lists[i].length的总和不超过10^4

方案一

合并k个有序链表的话,可以采用暴力破解的方式,将所有链表的数据填充在一个数组中,然后将数组排序,依次赋值到一个链表

public ListNode mergeKLists(ListNode[] lists) {
    int[] data = new int[10000];
    int length = 0;
    for (int i = 0; i < lists.length; i++) {
        ListNode temp = lists[i];
        while (temp != null) {
            data[length] = temp.val;
            temp = temp.next;
            length++;
        }
    }
    if (length == 0) {
        return null;
    }
    Arrays.sort(data, 0, length);
    ListNode res = new ListNode(-1);
    ListNode get = res;
    for (int i = 0; i < length - 1; i++) {
        get.val = data[i];
        get.next = new ListNode();
        get = get.next;
    }
    get.val = data[length - 1];
    return res;
}

方案二

剑指Offer_35_复杂链表的复制

题目描述

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例1

image.png

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]

输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例2

image.png

输入: head = [[1,1],[2,1]]

输出: [[1,1],[2,1]]

示例3

image.png

输入: head = [[3,null],[3,0],[3,null]]

输出: [[3,null],[3,0],[3,null]]

示例4

输入: head = [ ]

输出: [ ]

解释: 给定的链表为空(空指针),因此返回 null。

提示

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000

方案一

剑指Offer_52_两个链表的第一个公共结点

题目描述:

输入两个链表,找出它们的第一个公共结点。如下面的两个链表

image.png 在节点c1开始相交

示例

image.png

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3

输出:Reference of the node with value = 8

输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

注意

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

方案一

找出两个链表的第一个公共节点,可以借助于哈希集合,即用HashSet存储其中某个链表的所有节点,然后对另一个链表进行遍历,当已存储的set集合中contain遍历的节点时,则证明存在公共节点,返回该结点即可,否则不存在公共节点,返回null

ListNode getIntersectionNode(ListNode headA,ListNode headB){
    Set set = new HashSet();
    ListNode l1 = headA;
    ListNode res = null;
    while (l1!=null){
        set.add(l1);
        l1 = l1.next;
    }
    ListNode l2 = headB;
    while (l2!=null){
        if (set.contains(l2)){
            res = l2;
            break;
        }
        l2 = l2.next;
    }
    return res;
}

方案二

首先判断链表是否为null,链表为null则一定不存在公共节点,两个链表均不为空时,借助两个指针指向链表的头结点,分别依次遍历,例如pA执行headA,pB指向headB,当pA!=pB时,如果pA为null,将headB赋值给pA,else pA = pA.next,如果pB为null,将headA赋值给pB,else pB = pB.next,如果两个链表存在公共节点,将会跳出循环,如果两个链表不存在公共节点,pA,pB会将两段链表都进行遍历,最后同时指向null,跳出循环。

    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            if (pA == null) {
                pA = headB;
            } else {
                pA = pA.next;
            }
            if (pB == null) {
                pB = headA;
            } else {
                pB = pB.next;
            }
        }
        return pA;
    }
}

剑指OfferII_055_链表中环的入口结点

题目描述

给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

实例1:

image.png

输入: head = [3,2,0,-4], pos = 1
输出: 返回索引为 1 的链表节点
解释: 链表中有一个环,其尾部连接到第二个节点。

实例2:

image.png

输入: head = [1,2], pos = 0
输出: 返回索引为 0 的链表节点
解释: 链表中有一个环,其尾部连接到第一个节点。

实例3:

image.png

输入: head = [1], pos = -1
输出: 返回 null
解释: 链表中没有环。

提示:

链表中节点的数目范围在范围 [0, 104] 内 -105 <= Node.val <= 105 pos 的值为 -1 或者链表中的一个有效索引  

进阶:是否可以使用 O(1) 空间解决此题?

方案一

可以借助于哈希表,对链表进行依次遍历,并进行记录,一旦遇到此前遍历过的节点,则可判定链表中有环,否则返回null

public ListNode detectCycle(ListNode head) {
    ListNode l = head;
    Set set = new HashSet();
    while (l != null) {
        if (set.add(l)) {
            l = l.next;
        } else {
            return l;
        }
    }
    return null;
}

方案二

快慢指针

剑指Offer_018_删除链表的结点

题目描述:

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

注意: 此题对比原题有改动

示例1:

输入: head = [4,5,1,9], val = 5

输出: [4,1,9]

解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例2:

输入: head = [4,5,1,9], val = 1

输出: [4,5,9]

解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

说明:

  • 题目保证链表中节点的值互不相同
  • 若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

方案一

可以直接顺序遍历,new 一个 temp指向链表 head,将temp.next!=null作为循环条件,依次进行遍历,如果temp.next.val == 目标值,则舍弃temp.next,将temp.next.next赋值给temp.next,此时也可直接跳出循环。此时,不要忘记一种情况,即如果第一个节点就需要被删除,则可以直接返回head.next。

public ListNode deleteNode(ListNode head, int val) {
    if (head.val == val) {
        return head;
    }
    ListNode temp = head;
    while (temp.next != null) {
        if (temp.next.val == val) {
            temp.next = temp.next.next;
            break;
        } else {
            temp = temp.next;
        }
    }
    return head;
}

方案二