【常用算法思路分析系列】链表相关高频题集
本文是【常用算法思路分析系列】的第四篇,总结链表相关的高频题目和解题思路。本文分析如下几个问题:1、环形链表的差值问题;2、只能访问单个结点的删除问题;3、链表的分化;4、打印两个链表的公共部分;5、把链表的每k个结点逆序;6、删除链表中指定结点;7、判断链表是否为回文结构;8、复杂链表的复制;9、判断链表是否有环;10、判断两个无环链表是否相交;11、判断两个有环链表是否相交;12、判断两个链表(状态未定)是否相交。
本系列前三篇导航:
【常用算法思路分析系列】排序高频题集
【常用算法思路分析系列】字符串高频题集
1、环形链表插值问题
有一个整数val,如何在节点值有序的环形链表中插入一个节点值为val的节点,并且保证这个环形单链表依然有序。
给定链表的信息,及元素的值A及对应的nxt指向的元素编号同时给定val,请构造出这个环形链表,并插入该值。
[1,3,4,5,7],[1,2,3,4,0],2
返回:{1,2,3,4,5,7}
-
public
class InsertValue {
-
/**
-
* @param args
-
*/
-
public static void main(String[] args) {
-
int[] a = {
1,
3,
4,
5,
7};
-
int[] b = {
1,
2,
3,
4,
0};
-
// insert(a,b,2);
-
System.out.println(
"dd");
-
}
-
-
public
static
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public static ListNode insert(int[] A, int[] nxt, int val) {
-
ListNode head =
null;
-
if(A ==
null || A.length ==
0){
-
head =
new ListNode(val);
-
head.next =
null;
-
}
else{
-
//构造环形链表
-
ListNode curNode =
new ListNode(A[
0]);
-
head = curNode;
-
for(
int i =
0; i < nxt.length -
1; i++){
-
ListNode node =
new ListNode(A[nxt[i]]);
-
curNode.next = node;
-
curNode = node;
-
}
-
curNode.next = head;
-
ListNode pre = head;
-
curNode = head.next;
-
boolean flag =
false;
-
while(curNode != head){
-
if(val <= curNode.val){
-
ListNode node =
new ListNode(val);
-
node.next = curNode;
-
pre.next = node;
-
flag =
true;
-
break;
-
}
else{
-
pre = curNode;
-
curNode = curNode.next;
-
}
-
}
-
if(!flag){
//表示没有找到合适的位置,需要插入到头结点的前面(可能是val大于所有值或小于所有节点值)
-
ListNode node =
new ListNode(val);
-
node.next = head;
-
pre.next = node;
-
if(val <= head.val){
-
head = node;
-
}
-
}
-
}
-
return head;
-
}
-
}
2、只能访问单个节点的删除
实现一个算法,删除单向链表中间的某个结点,没有给定头结点,你只能访问该结点。
给定带删除的节点,请执行删除操作,若该节点为尾节点,返回false,否则返回true
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public boolean removeNode(ListNode pNode) {
-
if(pNode ==
null || pNode.next ==
null)
-
return
false;
-
ListNode nextNode = pNode.next;
-
pNode.val = nextNode.val;
-
pNode.next = nextNode.next;
-
return
true;
-
}
3、链表的分化
对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。
给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。
{1,4,2,5},3
{1,2,4,5}思路:
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public ListNode listDivide(ListNode head, int val) {
-
if(head ==
null)
-
return
null;
-
ListNode minList =
null,maxList =
null;
-
ListNode minListHead =
null,maxListHead =
null;
-
ListNode next =
null;
-
while(head !=
null){
-
next = head.next;
-
head.next =
null;
-
if(head.val <= val){
//如果当前结点值<=val,将当前结点放入到小于阀值的链表
-
if(minList ==
null){
-
minList = head;
-
minListHead = head;
-
}
else{
-
minList.next = head;
-
minList = head;
-
}
-
}
else{
//如果当前结点值>val,将当前结点放入到大于阀值的链表
-
if(maxList ==
null){
-
maxList = head;
-
maxListHead = head;
-
}
else{
-
maxList.next = head;
-
maxList = head;
-
}
-
}
-
head = next;
-
}
-
-
if(minList !=
null){
-
minList.next = maxListHead;
-
}
-
return minListHead !=
null ? minListHead : maxListHead;
-
}
4、打印两个链表的公共值部分
现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。
给定两个链表的头指针headA和headB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值
{1,2,3,4,5,6,7},{2,4,6,8,10}
返回:[2.4.6]思路:
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public
int[] findCommonParts(ListNode headA, ListNode headB) {
-
if(headA ==
null || headB ==
null){
-
return
null;
-
}
-
List<Integer> list =
new ArrayList<Integer>();
-
while(headA !=
null && headB !=
null){
-
if(headA.val < headB.val){
-
headA = headA.next;
-
}
else
if(headA.val > headB.val){
-
headB = headB.next;
-
}
else{
//相等,打印值
-
list.add(headA.val);
-
headA = headA.next;
-
headB = headB.next;
-
}
-
}
-
int[] res =
new
int[list.size()];
-
for(
int i =
0; i < list.size(); i++){
-
res[i] = list.get(i);
-
}
-
return res;
-
}
5、把链表的每k个元素逆序
有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。
给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public ListNode inverse(ListNode head, int k) {
-
if(head ==
null || head.next ==
null || k <
2){
-
return head;
-
}
-
Stack<ListNode> stack =
new Stack<ListNode>();
-
ListNode next =
null;
-
ListNode resHead =
null;
//反转后的链表的头结点
-
ListNode header =
null, tail =
null;
//记录每次反转过程中的头结点和尾结点
-
ListNode lastTail =
null;
//上一次反转过程中的尾结点
-
while(head !=
null){
-
next = head.next;
-
head.next =
null;
-
stack.push(head);
-
if(stack.size() == k){
-
//先退栈反转这k个元素,找到这k个元素的头和尾部
-
header = tail = stack.pop();
-
while(!stack.isEmpty()){
-
tail.next = stack.pop();
-
tail = tail.next;
-
}
-
if(resHead ==
null){
//是否是第一次反转前k个元素
-
resHead = header;
-
lastTail = tail;
-
}
else{
-
lastTail.next = header;
-
lastTail = tail;
-
}
-
}
-
head = next;
-
}
-
if(!stack.isEmpty()){
//栈中还有元素,表示最后几个元素没有满足k个数量,保持原有顺序
-
header = stack.pop();
-
header.next =
null;
-
ListNode temp ;
-
while(!stack.isEmpty()){
//头插法,维持原来顺序
-
temp = stack.pop();
-
temp.next = header;
-
header = temp;
-
}
-
lastTail.next = header;
-
}
-
return resHead;
-
}
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public ListNode inverse(ListNode head, int k) {
-
if(head ==
null || head.next ==
null || k <
2){
-
return head;
-
}
-
ListNode next =
null;
-
ListNode resHead =
null;
//反转后的链表的头结点
-
ListNode header =
null, tail =
null;
//记录每次截取k个元素过程中的头结点和尾结点
-
ListNode lastTail =
null;
//上一次反转过程中的尾结点
-
boolean flag =
true;
-
while(flag){
-
header =
null;
-
tail =
null;
-
//每次拿出k个元素
-
for(
int i =
0; i < k; i++){
-
if(head ==
null){
//表示最后一组不满足k个元素
-
flag =
false;
-
break;
-
}
else{
-
next = head.next;
-
head.next =
null;
-
if(i ==
0){
-
header = head;
-
tail = head;
-
}
else{
-
tail.next = head;
-
tail = head;
-
}
-
head = next;
-
}
-
}
-
if(flag){
//反转这k个元素
-
ListNode tempListHeader = header;
-
ListNode temp = tempListHeader;
-
tempListHeader = tempListHeader.next;
-
temp.next =
null;
//摘取出来
-
while(tempListHeader !=
null){
-
next = tempListHeader.next;
-
tempListHeader.next = temp;
-
temp = tempListHeader;
-
tempListHeader = next;
-
}
-
if(resHead ==
null){
-
resHead = tail;
-
lastTail = header;
-
}
else{
-
lastTail.next = tail;
-
lastTail = header;
-
}
-
}
else{
//最后一组不满足k个元素,直接放到反转后的链表后面
-
lastTail.next = header;
-
}
-
}
-
return resHead;
-
}
6、删除链表中指定的元素
现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。
给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。
{1,2,3,4,3,2,1},2
{1,3,4,3,1}思路一:
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public ListNode clear(ListNode head, int val) {
-
if(head ==
null)
-
return
null;
-
ListNode first =
null,tail =
null,next =
null;
-
while(head !=
null){
-
next = head.next;
-
head.next =
null;
-
if(head.val != val){
-
if(first ==
null){
-
first = head;
-
tail = head;
-
}
else{
-
tail.next = head;
-
tail = head;
-
}
-
}
-
head = next;
-
}
-
return first;
-
}
-
public ListNode clear(ListNode head, intnum) {
-
while(head !=
null) {
-
if(head.val != num) {
-
break;
-
}
-
head = head.next;
-
}
-
ListNode pre = head;
-
ListNode cur = head;
-
while(cur !=
null) {
-
if(cur.val == num) {
-
pre.next = cur.next;
-
}
else{
-
pre = cur;
-
}
-
cur = cur.next;
-
}
-
return head;
-
}
7、判断链表是否为回文结构
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public boolean isPalindrome(ListNode pHead) {
-
if(pHead ==
null){
-
return
false;
-
}
-
Stack<ListNode> stack =
new Stack<ListNode>();
-
ListNode first = pHead;
-
while(first !=
null){
//链表中全部结点先入栈
-
stack.push(first);
-
first = first.next;
-
}
-
ListNode temp;
-
while(pHead !=
null && !stack.isEmpty()){
//再遍历一遍链表
-
temp = stack.pop();
-
if(temp.val != pHead.val){
-
return
false;
-
}
else{
-
pHead = pHead.next;
-
}
-
}
-
return
true;
-
}
-
public boolean isPalindrome(ListNode pHead) {
-
if(pHead ==
null){
-
return
false;
-
}
-
Stack<ListNode> stack =
new Stack<ListNode>();
-
ListNode fast = pHead;
-
ListNode slow = pHead;
-
while(fast !=
null){
-
if(fast.next ==
null){
//表示到快指针达尾部,说明链表结点个数为奇数,此时慢指针到达中部,且此时慢指针指向的结点不需要入栈
-
slow = slow.next;
//链表结点数为奇数时,跳过中间结点
-
break;
-
}
-
stack.push(slow);
-
slow = slow.next;
-
fast = fast.next.next;
-
}
-
ListNode temp;
-
while(slow !=
null && !stack.isEmpty()){
//后面慢指针和栈顶结点值依次比较
-
temp = stack.pop();
-
if(temp.val != slow.val){
-
return
false;
-
}
else{
-
slow = slow.next;
-
}
-
}
-
return
true;
-
}

-
public boolean isPalindrome(ListNode pHead) {
-
if(pHead ==
null){
-
return
false;
-
}
-
ListNode fast = pHead;
-
ListNode slow = pHead;
-
ListNode lastHead =
null;
-
while(fast.next !=
null && fast.next.next !=
null){
//寻找到慢指针指向的中点
-
slow = slow.next;
-
fast = fast.next.next;
-
}
-
if(fast.next !=
null){
//表示链表节点数量为偶数
-
lastHead = slow.next;
-
}
else{
-
lastHead = slow;
-
}
-
ListNode head = lastHead;
//指向反转后的头结点
-
lastHead = lastHead.next;
-
ListNode next =
null;
-
head.next =
null;
-
while(lastHead !=
null){
//反转链表
-
next = lastHead.next;
-
lastHead.next = head;
-
head = lastHead;
-
lastHead = next;
-
}
-
boolean res =
true;
-
ListNode tempList = head;
-
while(tempList !=
null && pHead !=
null){
-
if(tempList.val != pHead.val){
-
res =
false;
-
break;
-
}
-
tempList = tempList.next;
-
pHead = pHead.next;
-
}
-
//再把后面反转的链表反转回来
-
ListNode head2 = head;
-
head = head.next;
-
head2.next =
null;
-
while(head !=
null){
-
next = head.next;
-
head.next = head2;
-
head2 = head;
-
head = next;
-
}
-
slow.next = head2;
-
return res;
-
}
8、复杂链表的复制

-
public
class RandomListNode {
-
int label;
-
RandomListNode next =
null;
-
RandomListNode random =
null;
-
-
RandomListNode(
int label) {
-
this.label = label;
-
}
-
}
-
-
public RandomListNode Clone(RandomListNode pHead){
-
if(pHead ==
null){
-
return
null;
-
}
-
RandomListNode list = pHead;
-
//第一遍遍历结点,每遍历到一个结点,复制一个相同的结点,并加入到当前结点的下一个位置;
-
while(list !=
null){
-
RandomListNode node =
new RandomListNode(list.label);
-
node.next = list.next;
-
list.next = node;
-
list = list.next.next;
-
}
-
//第二遍遍历结点,设置新结点的random指针
-
list = pHead;
-
while(list !=
null){
-
if(list.random !=
null){
-
list.next.random = list.random.next;
-
}
else{
-
list.next.random =
null;
-
}
-
list = list.next.next;
-
}
-
list = pHead;
-
RandomListNode newList =
null;
-
RandomListNode temp =
null;
-
RandomListNode res =
null;
-
//第三遍遍历结点,还原原来链表,取出所有的新结点;
-
while(list !=
null){
-
temp = list.next;
-
list.next = list.next.next;
-
temp.next =
null;
-
list = list.next;
-
if(newList ==
null){
-
newList = temp;
-
res = temp;
-
}
else{
-
newList.next = temp;
-
newList = temp;
-
}
-
}
-
return res;
-
}
9、判断链表是否有环
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
public int chkLoop(ListNode head) {
-
if(head ==
null){
-
return -
1;
-
}
-
int res = -
1;
-
Map<ListNode,Integer> map =
new HashMap<ListNode,Integer>();
-
while(head !=
null){
-
if(map.containsKey(head)){
-
return head.val;
-
}
-
map.put(head,
1);
-
head = head.next;
-
}
-
return res;
-
}
-
public int chkLoop(ListNode head) {
-
if(head ==
null || head.next ==
null){
//链表为空或者只有一个结点
-
return -
1;
-
}
-
ListNode fast = head.next.next;
//我的判断条件中是fast!=slow,因此这里不能设置fast=slow=head;
-
ListNode slow = head.next;
-
while(fast !=
null && fast.next !=
null && fast != slow){
//如果无环,则fast或fast.next会指向null,如果有环,某一时刻fast==slow
-
fast = fast.next.next;
-
slow = slow.next;
-
}
-
if(fast == slow){
//表示有环
-
fast = head;
-
while(fast != slow){
-
fast = fast.next;
-
slow = slow.next;
-
}
-
return fast.val;
-
}
-
return -
1;
-
}
10、判断两个无环链表是否相交
现在有两个无环单链表,若两个链表的长度分别为m和n,请设计一个时间复杂度为O(n + m),额外空间复杂度为O(1)的算法,判断这两个链表是否相交。
给定两个链表的头结点headA和headB,请返回一个bool值,代表这两个链表是否相交。
思路一:
-
public boolean chkIntersect(ListNode headA, ListNode headB) {
-
// write code here
-
ListNode alast=headA,blast=headB;
-
while(alast.next!=
null){
-
alast=alast.next;
-
}
-
while(blast.next!=
null){
-
blast=blast.next;
-
}
-
if(alast==blast)
-
returntrue;
-
else
-
returnfalse;
-
}
-
public boolean chkIntersect(ListNode headA, ListNode headB) {
-
if(headA ==
null || headB ==
null){
-
return
false;
-
}
-
ListNode listA = headA;
-
ListNode listB = headB;
-
int m =
0;
//链表A的长度
-
int n =
0;
//链表B的长度
-
int dst =
0;
//两个链表的长度差值
-
while(listA !=
null){
-
m++;
-
listA = listA.next;
-
}
-
while(listB !=
null){
-
n++;
-
listB = listB.next;
-
}
-
listA = headA;
-
listB = headB;
-
if(m >= n){
-
dst = m - n;
-
for(
int i =
0; i < dst; i++){
-
listA = listA.next;
-
}
-
}
else{
-
dst = n - m;
-
for(
int i =
0; i < dst; i++){
-
listB = listB.next;
-
}
-
}
-
while(listA !=
null && listB !=
null && listA != listB){
-
listA = listA.next;
-
listB = listB.next;
-
}
-
if(listA !=
null && listB !=
null && listA == listB){
-
return
true;
-
}
-
return
false;
-
}
11、有环单链表相交判断


-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public boolean chkInter(ListNode head1, ListNode head2) {
-
if(head1 ==
null || head2 ==
null){
-
return
false;
-
}
-
ListNode loopNode1 = getLoopStartNode(head1);
//拿到链表head1的入环结点
-
ListNode loopNode2 = getLoopStartNode(head2);
//拿到链表head2的入环结点
-
if(loopNode1 ==
null || loopNode2 ==
null)
-
return
false;
-
if(loopNode1 == loopNode2){
//表示两个链表的入环结点相同,则两个链表肯定相交
-
return
true;
-
}
else{
-
ListNode temp = loopNode1.next;
-
while(temp != loopNode1){
-
if(temp == loopNode2){
//表示环中有相交结点
-
return
true;
-
}
-
temp = temp.next;
-
}
-
}
-
return
false;
-
}
-
-
public ListNode getLoopStartNode(ListNode head){
-
ListNode fast = head.next.next;
-
ListNode slow = head.next;
-
while(fast !=
null && fast.next !=
null && fast != slow){
-
fast = fast.next.next;
-
slow = slow.next;
-
}
-
if(fast == slow){
-
fast = head;
-
while(fast != slow){
-
fast = fast.next;
-
slow = slow.next;
-
}
-
return fast;
-
}
-
return
null;
-
}
12、判断两个链表(状态未知)是否相交
-
public
class ListNode {
-
int val;
-
ListNode next =
null;
-
ListNode(
int val) {
-
this.val = val;
-
}
-
}
-
-
public boolean chkInter(ListNode head1, ListNode head2, int adjust0, int adjust1) {
-
if(head1 ==
null || head2 ==
null){
-
return
false;
-
}
-
ListNode loopNode1 = getLoopStartNode(head1);
//拿到链表head1的入环结点
-
ListNode loopNode2 = getLoopStartNode(head2);
//拿到链表head2的入环结点
-
//如果一个链表有环,一个链表无环,那么这两个链表不可能相交
-
if((loopNode1 !=
null && loopNode2 ==
null) || (loopNode2 !=
null && loopNode1 ==
null)){
-
return
false;
-
}
-
//如果两个链表都没有环,则问题回到两个无环链表是否相交的判断上
-
if(loopNode1 ==
null && loopNode2 ==
null){
-
return chkNoLoopList(head1,head2);
-
}
-
//如果两个入环结点都不为空,即问题回到两个有环链表是否相交的判断上
-
if(loopNode1 !=
null && loopNode2 !=
null){
-
return chkHasLoopList(loopNode1,loopNode2);
-
}
-
return
false;
-
}
-
-
/**
-
* 判断两个有环链表是否相交
-
* @param loopNode1
-
* @param loopNode2
-
* @return
-
*/
-
public boolean chkHasLoopList(ListNode loopNode1, ListNode loopNode2) {
-
if(loopNode1 == loopNode2){
//表示两个链表的入环结点相同,则两个链表肯定相交
-
return
true;
-
}
else{
-
ListNode temp = loopNode1.next;
-
while(temp != loopNode1){
-
if(temp == loopNode2){
//表示环中有相交结点
-
return
true;
-
}
-
temp = temp.next;
-
}
-
}
-
return
false;
-
}
-
-
/**
-
* 判断两个无环链表是否相交
-
* @param headA
-
* @param headB
-
* @return
-
*/
-
public boolean chkNoLoopList(ListNode headA, ListNode headB) {
-
if(headA ==
null || headB ==
null){
-
return
false;
-
}
-
ListNode listA = headA;
-
ListNode listB = headB;
-
int m =
0;
//链表A的长度
-
int n =
0;
//链表B的长度
-
int dst =
0;
//两个链表的长度差值
-
while(listA !=
null){
-
m++;
-
listA = listA.next;
-
}
-
while(listB !=
null){
-
n++;
-
listB = listB.next;
-
}
-
listA = headA;
-
listB = headB;
-
if(m >= n){
-
dst = m - n;
-
for(
int i =
0; i < dst; i++){
-
listA = listA.next;
-
}
-
}
else{
-
dst = n - m;
-
for(
int i =
0; i < dst; i++){
-
listB = listB.next;
-
}
-
}
-
while(listA !=
null && listB !=
null && listA != listB){
-
listA = listA.next;
-
listB = listB.next;
-
}
-
if(listA !=
null && listB !=
null && listA == listB){
-
return
true;
-
}
-
return
false;
-
}
-
-
/**
-
* 获取链表的入环结点
-
* @param head
-
* @return
-
*/
-
public ListNode getLoopStartNode(ListNode head){
-
if(head ==
null || head.next ==
null){
//链表为null或者只有一个结点,此时没有入环结点
-
return
null;
-
}
-
ListNode fast = head.next.next;
-
ListNode slow = head.next;
-
while(fast !=
null && fast.next !=
null && fast != slow){
-
fast = fast.next.next;
-
slow = slow.next;
-
}
-
if(fast == slow){
-
fast = head;
-
while(fast != slow){
-
fast = fast.next;
-
slow = slow.next;
-
}
-
return fast;
-
}
-
return
null;
-
}