剑指offer习题 - 01数据结构 - 链表

本文提供剑指Offer中涉及链表的经典题目解析及Java实现代码,包括从尾到头打印链表、反转链表、合并两个排序链表、寻找公共节点、环形链表入口节点等。

本文作为Java初学者练习牛客网剑指offer记录,持续更新中

剑指offer - 牛客网链接剑指Offer_在线编程+题解_牛客题霸_牛客网

目录

JZ6从尾到头打印链表

JZ24 反转链表

JZ25 合并两个排序的链表

JZ52 两个链表的第一个公共结点

JZ23 链表中环的入口结点

JZ22 链表中倒数最后k个结点

JZ35 复杂链表的复制



JZ6从尾到头打印链表

从尾到头打印链表_牛客题霸_牛客网

描述

输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。

如输入{1,2,3}的链表如下图:

返回一个数组为[3,2,1]

0 <= 链表长度 <= 10000

示例1

输入:{1,2,3}

复制返回值:[3,2,1]

示例2

输入:{67,0,24,58}

复制返回值:[58,24,0,67]

解法

1.反转链表

  • 自己建一个数组,将链表反转后放入数组输出。
反转链表

 具体Java代码如下:

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        //链表反转
        ArrayList<Integer> vals = new ArrayList<>();
        ListNode prev = null;
        ListNode cur = listNode;
        ListNode curNext = null;
        //反转
        while (cur != null) {
            curNext = cur.next;
            cur.next = prev;
            prev = cur;
            cur = curNext;
        }
        //添加到vals
        cur = prev;
        while (cur != null) {
            vals.add(cur.val);
            System.out.println(cur.val);
            cur = cur.next;
        }
        return vals;
    }
}

JZ24 反转链表

反转链表_牛客题霸_牛客网

描述

输入一个长度为n链表,反转链表后,输出新链表的表头。

数据范围: n\leq1000n≤1000

要求:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n) 。

如当输入链表{1,2,3}时,

经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。

以上转换过程如下图所示:

示例1

输入:{1,2,3}

返回值:{3,2,1}

示例2

输入:{}

返回值:{}

说明:空链表则输出空

 解法

1.不带傀儡节点(与上题反转链表解法一致)

反转链表(不带傀儡节点)

具体Java代码如下:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        ListNode prev = null;
        ListNode cur = head;
        ListNode curNext = null;
        while (cur != null) {
            curNext = cur.next;
            cur.next = prev;
            prev = cur;
            cur = curNext;
        }
        return prev;
    }
}

2.带傀儡节点

反转链表(带傀儡节点)

 具体代码如下:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode newHead = null;
        while (head != null) {
            ListNode tmp = head.next;
            head.next = newHead;
            newHead = head;
            head = tmp;
        }
        return newHead;
    }
}

JZ25 合并两个排序的链表

描述

输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。

数据范围: 0 \le n \le 10000≤n≤1000,-1000 \le 节点值 \le 1000−1000≤节点值≤1000
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6},转换过程如下图所示:

或输入{-1,2,4},{1,3,4}时,合并后的链表为{-1,1,2,3,4,4},所以对应的输出为{-1,1,2,3,4,4},转换过程如下图所示:

示例1

输入:{1,3,5},{2,4,6}

返回值:{1,2,3,4,5,6}

示例2

输入:{},{}

返回值:{}

示例3

输入:{-1,2,4},{1,3,4}

返回值:{-1,1,2,3,4,4}

 解法

  • 利用傀儡节点,比较两个有序链表的头节点大小。
合并两个有序链表

具体代码如下:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        //判空
        if (list1 == null || list2 == null) {
            return list1 == null ? list2 : list1;
        }
        ListNode newHead = new ListNode(-1);
        ListNode cur = newHead;
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                cur.next = list1;
                list1 = list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        cur.next = list1 == null ? list2 : list1;
        return newHead.next;
    }
}

JZ52 两个链表的第一个公共结点

两个链表的第一个公共结点_牛客题霸_牛客网

描述

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

数据范围: n \le 1000n≤1000
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

示例1

输入:{1,2,3},{4,5},{6,7}

返回值:{6,7}

说明:第一个参数{1,2,3}代表是第一个链表非公共部分,第二个参数{4,5}代表是第二个链表非公共部分,最后的{6,7}表示的是2个链表的公共部分 这3个参数最后在后台会组装成为2个两个无环的单链表,且是有公共节点的

示例2

输入:{1},{2,3},{}

返回值:{}

说明:2个链表没有公共节点 ,返回null,后台打印{}

解法

  • 走到公共节点前,两个列表长度不一致,那就让长的那个先走,等长的走到与短的一样多时,两个一起走,最终到达公共节点。

具体代码如下:

便于理解版本:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        //判空
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        //定义两个指针,假设pHead1为长链表头节点pl(plong),pHead2为短链表头节点ps(pshort)
        ListNode pl = pHead1;
        ListNode ps = pHead2;
        
        int len1 = 0;
        while (pl != null) {
            len1++;
            pl = pl.next;
        }
        
        int len2 = 0;
        while (ps != null) {
            len2++;
            ps = ps.next;
        }
        //此时pl == ps == null,需要重置
        pl = pHead1;
        ps = pHead2;
        
        //二者相差len步
        //让pl永远指向那个最长的链表,ps指向最短的那个链表
        int len = len1 - len2;
        if (len < 0) {
            pl = pHead2;
            ps = pHead1;
            len = len2 - len1;
        }
        
        //pl先走len步
        while (len != 0) {
            pl = pl.next;
            len--;
        }
        //pl、ps一起走
        while (pl != null && ps != null && pl != ps) {
            pl = pl.next;
            ps = ps.next;
        }
        if (pl == ps && pl != null) {
            return pl;
        }
        return null;
    }
}

精简版本

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        //判空
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        
        ListNode pl = pHead1;
        ListNode ps = pHead2;
        
        while (pl != ps) {
            pl = pl == null ? pHead2 : pl.next;
            ps = ps == null ? pHead1 : ps.next;
        }
        return pl;
    }
}

 JZ23 链表中环的入口结点

描述

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

数据范围: n\le10000n≤10000

节点值范围:[1,10000]

要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:

可以看到环的入口结点的结点值为3,所以返回结点值为3的结点。

输入描述:

输入分为2段,第一段是入环前的链表部分,第二段是链表环的部分,后台将这2个会组装成一个有环或者无环单链表

返回值描述:

返回链表的环的入口结点即可。而我们后台程序会打印这个节点

示例1

输入:{1,2},{3,4,5}

返回值:3

说明:2返回环形链表入口节点,我们后台会打印该环形链表入口节点,即3

示例2

输入:{1},{}

返回值:"null"

说明:没有环,返回null,后台打印"null"

示例3

输入:{},{2}

返回值:2

说明:只有环形链表节点2,返回节点2,后台打印2

解法

  • 单链表快慢指针
链表中环的入口结点

 具体代码如下:

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode fast = pHead;
        ListNode slow = pHead;
        
        //制造fast与slow相遇点
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                break;
            }
        }
        //判空,看看是环不
        if (fast == null || fast.next == null) {
            return null;
        }
        //此时fast与slow都在相遇点,需要把slow放到头节点
        slow = pHead;
        //fast与slow一起出发
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

JZ22 链表中倒数最后k个结点

描述

输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

如果该链表长度小于k,请返回一个长度为 0 的链表。

数据范围:0 \leq n \leq 10^50≤n≤105,0 \leq a_i \leq 10^90≤ai​≤109,0 \leq k \leq 10^90≤k≤109

要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

示例1

输入:{1,2,3,4,5},2

返回值:{4,5}

说明:返回倒数第2个节点4,系统会打印后面所有的节点来比较

示例2

输入:{2},8

返回值:{}

解法

  • 双指针:fast指针先移动k步,slow指针再从头开始,然后两个指针同时移动,当fast指针到链表的末尾的时候,返回slow指针。
链表中倒数第k个节点

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        //判空
        if (pHead == null) {
            return null;
        }
        //定义快慢指针
        ListNode fast = pHead;
        ListNode slow = pHead;
        //fast先走k步
        while (fast != null && k != 0) {
            fast = fast.next;
            k--;
        }
        //k越界时置空
        if (k > 0) {
            return null;
        }
        //fast与slow一起走
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

JZ35 复杂链表的复制

描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。

示例:

输入:{1,2,3,4,5,3,5,#,2,#}

输出:{1,2,3,4,5,3,5,#,2,#}

解析:我们将链表分为两段,前半部分{1,2,3,4,5}为ListNode,后半部分{3,5,#,2,#}是随机指针域表示。

以上示例前半部分可以表示链表为的ListNode:1->2->3->4->5

后半部分,3,5,#,2,#分别的表示为

1的位置指向3,2的位置指向5,3的位置指向null,4的位置指向2,5的位置指向null

如下图:

示例1

输入:{1,2,3,4,5,3,5,#,2,#}

返回值:{1,2,3,4,5,3,5,#,2,#}

解法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值