本文是【常用算法思路分析系列】的第四篇,总结链表相关的高频题目和解题思路。本文分析如下几个问题: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;
}