题目一
反转单向链表与双向链表
要求:
如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
代码如下:
// 反转单向链表
public class ReverseList {
// 单链表
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
// 反转单向链表
public static Node reverseList(Node head){
Node pre = null;
Node next = null;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
// 反转双向链表
public class ReverseDoubleList {
// 双向链表
public static class DoubleNode{
public int value;
public DoubleNode next;
public DoubleNode last;
public DoubleNode(int value){
this.value = value;
}
}
// 反转双向链表
public static DoubleNode reverseDoubleList(DoubleNode head){
DoubleNode pre = null;
DoubleNode next = null;
while (head != null){
next = head.next;
head.next = pre;
head.last = next;
pre = head;
head = next;
}
return pre;
}
}
题目二
打印两个有序链表的公共部分
给定两个有序链表的头指针head1和head2,打印两个链表的公共部分
代码如下:
public class PrintCommonPartInList {
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
public static void printCommonPart(Node head1,Node head2){
if(head1 == null || head2 == null)
return;
Node cur1 = head1;
Node cur2 = head2;
while(cur1 != null && cur2 != null){
if(cur1.value < cur2.value){
cur1 = cur1.next;
}else if(cur2.value < cur1.value){
cur2 = cur2.next;
}else{
System.out.print(cur1.value);
head1 = head1.next;
head2 = head2.next;
}
}
}
}
题目三
给定一个链表的头节点head,判断一个链表是否为回文结构
例如:
1->2->1,返回true;
1->2->2->1,返回true;
1->2->3,返回false
要求,如果链表的长度为N,时间复杂度为O(n),额外空间复杂度尽可能优
思路1:
遍历一边链表,将链表每个节点的value压入栈中,再重头进行遍历,与栈pop的数值进行比对,如果出现不同返回false,如果栈空且无不同的数值,则说明链表的结构为回文结构。因为我们需要栈这种数据结构来盛放链表每个节点的value,所以额外空间复杂度为O(n)。
代码如下:
import java.util.Stack;
public class IsPalindromeList1 {
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
public static boolean isPalindromeList(Node head){
if(head == null || head.next == null)
return true;
Stack stack = new Stack<>();
Node cur = head;
while(cur != null){
stack.push(cur.value);
cur = cur.next;
}
cur = head;
while (cur != null){
if(stack.pop() != cur.value){
return false;
}
cur = cur.next;
}
return true;
}
}
思路二:
遍历链表使用快指针与慢指针的思想,慢指针每次走一步,快指针每次走两步;当快指针走到链表的末尾时,慢指针则指向链表中间的那个节点。然后,我们将慢指针的next及余下的部分压入到栈。再重复思路一,将指针指向头节点,依次与栈pop的数值进行比对,实际上,相当于将链表从中间对折进行比对,这样在额外空间复杂度较思路一就省去了n/2。所以思路二的额外空间复杂度为O(n/2)。
对于思路二:
快指针到边界的条件为fastP.next != null && fastP.next.next != null
比如长度为偶数的链表:
1 ->2 ->2 -> 1
遍历终止时,快指针指向第二个2,慢指针指向第一个二;慢指针的下一个节点连带后面的部分与前半部分对称
对于长度为奇数的链表:
1 ->2 ->3 ->2 -> 1
遍历终止时,快指针指向最后一个1,慢指针指向3,慢指针下一个节点连带后面的部分与除去中点3的前半部分对称
所以,链表长度是奇是偶,都可以使用此思路,代码如下:
import java.util.Stack;
public class IsPalindromeList2 {
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
public static boolean isPalindromeList(Node head){
if(head == null || head.next == null)
return true;
Stack stack = new Stack<>();
Node p1 = head; // slow
Node p2 = head; // fast
while(p2.next != null && p2.next.next != null){
p1 = p1.next;
p2 = p2.next.next;
}
p1 = p1.next;
while(p1 != null){
stack.push(p1.value);
p1 = p1.next;
}
p1 = head;
while(!stack.isEmpty()){
if(stack.pop() != p1.value){
return false;
}
p1 = p1.next;
}
return true;
}
}
思路三:
如果不使用其他的数据结构,仅凭借快慢指针,也可以做到判断链表是否为回文结构。
对于链表1 ->2 ->3 ->2 ->1:
当快慢指针遍历到边界时,如上图所示;此时,将慢指针指向的节点及后面部分进行链表的反转,再让p1,p2分别指向链表节点的头部,如下图:
然后,两个指针移动作比较,当任意一个指针指向的Node的next为null时退出循环,判断结束。当判断结束后,再将链表还原即可,因为思路三中并未出现任何的辅助数据结构,都是在链表自身上完成,所以额外空间复杂度为O(1),代码如下:
public class IspalindromeList3 {
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
public static boolean isPalindromeList(Node head){
if(head == null || head.next == null)
return true;
Node p1 = head; // slow
Node p2 = head; // fast
while(p2.next != null && p2.next.next != null){
p1 = p1.next;
p2 = p2.next.next;
}
Node p3 = null;
while(p1 != null){
p2 = p1.next;
p1.next = p3;
p3 = p1;
p1 = p2;
}
boolean res = true;
p1 = head;
p2 = p3;
while(p1 != null && p3 != null){
if(p1.value != p3.value){
res = false;
break;
}
p1 = p1.next;
p3 = p3.next;
}
p3 = null;
while(p2 != null){
p1 = p2.next;
p2.next = p3;
p3 = p2;
p2 = p1;
}
return res;
}
}
题目四
将单向链表按某值划分成左边小、中间相等、右边大的形式
给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整 数pivot
实现一个调整链表的函数:
将链表调整为左部分都是值小于 pivot 的节点,中间部分都是值等于pivot的节点,右部分都是值大于 pivot的节点
1:不要求稳定性
2:要求稳定性,且额外空间复杂度要求为O(1)
要求一的解法:
如果不要求partition之后的链表保持之前的次序性即稳定性且对额外空间复杂度无要求,我们可以使用数组这种数据结构来盛放每一个节点,然后转变为数组问题。对于数组而言,按照某值进行小于区间等于区间以及大于区间划分则等同于荷兰国旗问题,对数组进行划分好之后,再进行数组到链表的转换返回头节点即可,代码如下:
public class NodeListPartition1 {
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
public static Node nodeListPartition(Node head,int pivot){
if(head == null || head.next == null)
return head;
int len = 0;
Node cur = head;
while(cur != null){
len++;
cur = cur.next;
}
Node[] helpArr = new Node[len];
Node next = null;
int i = 0;
for(;i < len;i++){
next = head.next;
head.next = null;
helpArr[i] = head;
head = next;
}
// Arr Partition
int p1 = -1;
int p2 = len;
i = 0; // cur
while(i < p2){
if(helpArr[i].value < pivot){
swap(helpArr,++p1,i++);
}else if(helpArr[i].value > pivot){
swap(helpArr,--p2,i);
}else{
i++;
}
}
i = 1;
for(;i < len;i++){
helpArr[i - 1].next = helpArr[i];
}
return helpArr[0];
}
public static void swap(Node[] nodes,int index1,int index2){
Node tmp = nodes[index1];
nodes[index1] = nodes[index2];
nodes[index2] = tmp;
}
}
要求二的解法:
如果要求链表在partition之后还能保证稳定性,那么荷兰国旗问题则无法满足要求,而且解法一还使用了数组这种数据结构,所以额外空间复杂度为O(n)也不满足要求。
我们可以这样做:
使用三个Node变量分别指向遍历第一遍链表时第一个小于pivot的Node,第一个等于pivot的Node,第一个大于pivot的Node;在遍历的同时,如果小于pivot的就连在小于pivot的节点头的头面,等于和大于亦是如此。不过三个变量还不够,我们还需要三个变量来记录三个节点的尾部,这样在遍历一遍链表之后,才能将三个支链串在一起,这里面需要考虑是否有空链的问题,即:如果链表无小于pivot的数,无等于pivot的数,无大于pivot的数这些种种情况。代码如下:
public class NodeListPartition2 {
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
public static Node nodeListPartition(Node head,int pivot){
if(head == null || head.next == null)
return head;
Node smallH = null; // small head
Node smallT = null; // small tail
Node eqH = null; // equal head
Node eqT = null; // equal tail
Node bigH = null; // big head
Node bigT = null; // big tail
Node next = null;
while(head != null){
next = head.next;
head.next = null;
if(head.value < pivot){
if(smallH == null){
smallH = head;
smallT = head;
}else{
smallT.next = head;
smallT = head;
}
}else if(head.value == pivot){
if(eqH == null){
eqH = head;
eqT = head;
}else{
eqT.next = head;
eqT = head;
}
}else{
if(bigH == null){
bigH = head;
bigT = head;
}else{
bigT.next = head;
bigT = head;
}
}
head = next;
}
if(smallT == null){
if(eqT == null){
return bigH;
}else{
eqT.next = bigH;
return eqH;
}
}
if(eqT == null){
smallT.next = bigH;
return smallH;
}
smallT.next = eqH;
eqT.next = bigH;
return smallH;
}
}
题目五
复制含有随机指针节点的链表
一种特殊的链表描述如下:
public static class Node{
public int value;
public Node next;
public Node rand;
public Node(int data){
this.value = data;
}
}
Node类中的value是节点值,next指针和正常单链表中next指针的意义 一 样,都指向下一个节点
rand指针是Node类中新增的指针,这个指 针可 能指向链表中的任意一个节点,也可能指向null
给定一个由 Node节点类型组成的无环单链表的头节点head
请实现一个 函数完成 这个链表中所有结构的复制,并返回复制的新链表的头节点
进阶:
不使用任何数据结构
思路一:
使用hash表。hash-key存储原链表的节点,hash-value则对应存储复制的节点。通过key-value的对应关系,可以推断:
node'.next = map.get(node.next);
且有:
node'.rand = map.get(node.rand);
代码如下:
import java.util.HashMap;
public class CopyList1 {
public static class Node{
public int value;
public Node next;
public Node rand;
public Node(int data){
this.value = data;
}
}
public static Node copyListWithRand(Node head){
HashMap map = new HashMap<>();
Node cur = head;
while(cur != null){
map.put(cur,new Node(cur.value));
cur = cur.next;
}
cur = head;
while(cur != null){
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
}
思路一中使用了hash表这种数据结构,如果要求为不使用任何数据结构,那么如何解决呢?
思路二:
现有带有rand指针的链表如下,橙色为rand,蓝色为next:
先不考虑rand指针,我们将链表复制成如下结构,红色node为复制的部分:
当形成这种结构的链表时,其实也就等同于hash表了。不难看出,复制的节点的next指针与rand指针应该如何指向,代码如下:
public class CopyList2 {
public static class Node{
public int value;
public Node next;
public Node rand;
public Node(int data){
this.value = data;
}
}
public static Node copyListWithRand(Node head){
Node cur = head;
Node next = null;
while(cur != null){
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
while(cur != null){
cur.next.rand = cur.rand == null ? null : cur.rand.next;
cur = cur.next.next;
}
cur = head;
Node res = head.next;
Node curCopy = null;
while(cur != null){
next = cur.next.next;
curCopy = cur.next;
cur.next = next;
curCopy.next = curCopy.next == null ? null : curCopy.next.next;
cur = next;
}
return res;
}
}
题目六
请实现一个函数, 如果两个链表相交,请返回相交的 第一个节点;如果不相交,返回null 即可
要求:
如果链表1 的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外 空间复杂度请达到O(1)
提示:
本题中,单链表可能有环,也可能无环
分析:
1:如何判断链表有环无环,如果有环,能否返回第一个入环节点?
对于一个成环链表,存在这样的规律:
设置一个快指针与一个慢指针,从head出发,如果快指针移动中指向了null则表明这个链表无环,如果有环,则快指针和慢指针迟早会指向同一个节点,本示例中当快指针和慢指针指向的节点重合时为如下场景:
当快指针慢指针重合时,都指向了标记为橘黄色的节点;此时,快指针回到head处,改成同慢指针一样的一次走一步。快指针和慢指针再次相遇时,指向的节点即为入环的第一个节点,如下所示:
有了这样的思路,我们就解决了一个链表是否有环,以及它的入环第一个节点是什么的问题。
2:知道两个链表有环无环,以及有环情况时的入环节点后,如何确定两个链表是否相交,如果相交如何确定第一个相交的节点?
共有三种情况:
- 一个链表有环,一个链表无环
对于这种情况而言,两个链表绝对不可能相交
2.两个链表均无环
首先,对两个链表进行遍历,确定每个链表最后一个节点,如果两链表最后一个节点是同一个节点,那么必然相交,然后,我们需要对两链表的长度进行判断,长链表,则先走,走到两链表长度相同时,一直去比对当前节点是否是同一个节点,找到后返回即可。
-
两链表均有环
当两链表均有环时,共有三种情况
一:不相交
二:相交
排除了两种相交的情况,那么两个有环链表则不相交;对于相交情况一而言,我们只需要将链表一从head遍历至入环节点处依次同链表二的如何节点比对即可,对于相交情况二而言,链表一和链表二的入环节点相同,这样一来又变成了无环相交的问题,代码如下:
package blog4.NodeListReview;
public class GetFirstIntersectNode {
public static class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
// 判断链表有环无环,如果有环则返回入环的节点
public static Node getLoopNode(Node head){
if(head == null || head.next == null || head.next.next == null)
return null;
Node p1 = head.next; // slow
Node p2 = head.next.next; // fast
while(p1 != p2){
if(p2.next == null || p2.next.next == null){
return null;
}
p1 = p1.next;
p2 = p2.next.next;
}
p2 = head;
while(p1 != p2){
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
// 无环获取第一个相交的节点
public static Node noLoopGetFirstIntersectNode(Node head1,Node head2){
if(head1 == null || head2 == null)
return null;
Node cur1 = head1;
Node cur2 = head2;
int len1 = 0;
int len2 = 0;
while(cur1.next != null){
len1++;
cur1 = cur1.next;
}
while(cur2.next != null){
len2++;
cur2 = cur2.next;
}
if(cur1 != cur2)
return null;
cur1 = head1;
cur2 = head2;
int n = Math.abs(len1 - len2);
if(len1 > len2){
while (n > 0){
cur1 = cur1.next;
n--;
}
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
}else if(len1 < len2){
while (n > 0){
cur2 = cur2.next;
n--;
}
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
}else{
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
}
return cur1;
}
// 有环获取第一个相交的节点
public static Node loopGetFirstIntersectNode(Node head1,Node head2,Node loop1,Node loop2){
if(loop1 == loop2){
Node cur1 = head1;
Node cur2 = head2;
int len1 = 0;
int len2 = 0;
while(cur1.next != loop1){
len1++;
cur1 = cur1.next;
}
while(cur2.next != loop2){
len2++;
cur2 = cur2.next;
}
cur1 = head1;
cur2 = head2;
int n = Math.abs(len1 - len2);
if(len1 > len2){
while (n > 0){
cur1 = cur1.next;
n--;
}
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
}else if(len1 < len2){
while (n > 0){
cur2 = cur2.next;
n--;
}
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
}else{
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
}
return cur1;
}else{
Node cur1 = loop1.next;
while(cur1 != loop1){
if(cur1 == loop2){
return loop1; // loop2也可以
}
cur1 = cur1.next;
}
return null;
}
}
public static Node getFirstIntersectNode(Node head1,Node head2){
if(head1 == null || head2 == null)
return null;
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if(loop1 == null && loop2 == null){
return noLoopGetFirstIntersectNode(head1,head2);
}else if(loop1 != null && loop2 != null){
return loopGetFirstIntersectNode(head1,head2,loop1,loop2);
}else{
return null;
}
}
}