本文作为Java初学者练习牛客网剑指offer记录,持续更新中。
剑指offer - 牛客网链接剑指Offer_在线编程+题解_牛客题霸_牛客网
目录
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指针。
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,#}
本文提供剑指Offer中涉及链表的经典题目解析及Java实现代码,包括从尾到头打印链表、反转链表、合并两个排序链表、寻找公共节点、环形链表入口节点等。


4929

被折叠的 条评论
为什么被折叠?



