单链表
链表技巧总结
// 对于快慢指针,定位中间元素
// 若为偶数,取中间元素时为:n/2+1 ,例如为4,则3号元素在中间 这种方式对于偶数多循环一次
// while (fast != null && fast.next != null) {
// 若为偶数,取中间元素时为:n/2 ,例如为4,则2号元素在中间
// while (fast.next != null && fast.next.next != null) {
链表实体定义
@ToString
@AllArgsConstructor
public class Node {
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
// 初始值
Node node5 = new Node(5);
Node node4 = new Node(4, node5);
Node node3 = new Node(3, node4);
Node node2 = new Node(2, node3);
Node head = new Node(1, node2);
链表头节点插值
Node head = new Node(1);
Node node1 = new Node(2);
// 这部分不能抽取为方法 链表循环反转时有使用到它
node1.next = head;
head = node1;
System.out.println(head);
链表取中
Node getMid(Node head) {
// 思路,设计一个快指针(两步),一个慢指针(一步)
Node slow = head;
Node fast = head;
// 若为偶数,取中间元素时为:n/2+1 ,例如为4,则3号元素在中间
// while (slow != null && fast != null && fast.next != null) {
// 若为偶数,取中间元素时为:n/2 ,例如为4,则2号元素在中间
while (slow != null && fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
删除单链表的中间节点
给定链表的头节点head,实现删除链表的中间节点的函数
例如:
1->2,删除节点1;
1->2->3,删除节点2;
1->2->3->4,删除节点2;
1->2->3->4-5,删除节点3
Node deleteMid(Node head) {
if (head == null || head.next == null) {
return head;
}
// 慢指针
Node slow = head;s
// 快指针 这样赋值少循环一次,让slow为待删除元素前一个值
Node fast = head.next.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 这样是为了便于GC回收
// Node deleteNode = slow.next;
// slow.next = slow.next.next;
// deleteNode.next = null;
// 可以直接这样来删除后一个元素,但按照可达性分析法,GC不能回收待删除元素
slow.next = slow.next.next;
return head;
}
删除链表中 a/b 处的节点
给定链表的头节点 head、整数 a 和 b,实现删除位于 a/b 处节点的函数
例如:
链表:1->2->3->4->5,假设 a/b 的值为 r。
如果 r = 0,不删除任何节点
如果 r 在区间 (0,1/5] 上,删除节点 1;
如果 r 在区间 (3/5,4/5] 上,删除节点 4;
如果 r 在区间 (4/5,1] 上,删除节点 5;
如果 r 大于 1,不删除任何节点。
// 这道题可以转换为删除第 K = (a * n / b)个节点。其中n表示链表节点的个数,但由于(a * n / b)有可能出现小数,所以我们取 K的上限。
// 所谓上限就是大于等于K的最小整数
Node deleteByRadio(Node head, int a, int b) {
if (a < 1 || a > b)
return head;
int n = 0;
Node cur = head;
//统计一共有多少个节点
while (cur != null) {
n++;
cur = cur.next;
}
// 问题转换为删除第K个节点,取(a * n / b)的整数上限
int K = (int) Math.ceil((double) (a * n) / (double) b);
if (K == 1)
return head.next;
if (K > 1) {
cur = head;
//定位到第K个节点的前驱
while (--K != 1) {
cur = cur.next;
}
cur.next = cur.next.next;
}
return head;
}
删除单链表的第 K个节点
在单链表中删除倒数第 K 个节点
删除的时候会出现三种情况:
- 不存在倒数第 K 个节点,此时不用删除
- 倒数第 K 个节点就是第一个节点
- 倒数第 K 个节点在第一个节点之后
所以我们可以用一个变量 sum 记录链表一共有多少个节点:
- 如果 num < K,则属于第一种情况
- 如果 num == K,则属于第二种情况
- 如果 num > K, 则属于第三种情况,此时删除倒数第 K 个节点等价于删除第 (num - k + 1) 个节点
Node removeLastKthNode(Node head, int K) {
if (head == null || K < 1)
return head;
Node temp = head;
int num = 0;
while (temp != null) {
num++;
temp = temp.next;
}
if (num == K) {
return head.next;
}
if (num > K) {
temp = head;
// 删除第(num-k+1)个节点
// 定位到这个点的前驱
while (num - K != 0) {
temp = temp.next;
num--;
}
temp.next = temp.next.next;
}
return head;
}
链表反转
反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1
循环
当我们在反转一个节点的时候,把一个节点的后驱改为指向它前驱就可以了
注意:当你把当前节点的后驱指向前驱的时候,这个时候链表会被截断,也就是说后面的节点和当前节点分开了,所以我们需要一个变量来保存当前节点的后驱,以访丢失
// 反转链表,时间复杂度:0(n),空间复杂度0(1)
Node reverseList2(Node head) {
// 指向当前节点的前驱 已完成反转的链表头节点
Node pre = null;
// 指向当前节点的后驱 还未完成反转的链表头节点
Node next = null;
while (head != null) {
// 获取当前节点的后驱,表示尚未完成反转的链表
next = head.next;
// 断开当前节点与原来节点的关系,pre保存着新链表的首节点
head.next = pre;
pre = head;
// 把当前节点的下一个节点赋值给当前节点,此时它为未反转节点的首节点
head = next;
}
return pre;
}
递归法
递归头:当链表只有一个节点,或者是空表
递归体:这个的等价关系不像 n 是个数值那样,比较容易寻找。但对于链表来说,还是链表的节点个数不断在变小。例如链表1->2->3->4,首先将2->3->4当作一个整体2与1交换。交换规则为:把整体 2 的 next 指向 1,然后把 1 的 next 指向 null。所以定义函数功能为:改变节点1、2的指向
Node reverseList(Node head) {
// 递归头:当链表只有一个节点,或者是空表
if (head == null || head.next == null) {
return head;
}
// 递归体:递归反转 子链表
Node newList = reverseList(head.next);
// 改变1,2节点的指向 将节点2指向节点1,节点1后置节点置空
head.next.next = head;
// 通过head.next获取节点2
// Node temp = head.next;
// 让2的next指向1
// temp.next = head;
// 让1的next置为null
head.next = null;
// 将调整之后的链表返回
return newList;
}
分析:第一次进入第10行时,是head=Node(val=4, next=Node(val=5, next=null)),newlist为Node(val=5, next=null)
反转部分链表节点
题目:给定一个单向链表的头结点head,以及两个整数from和to,在单项链表上把第from个节点和第to个节点这一部分进行反转
列如: 1->2->3->4->5->null,from=2、to=4 结果:1->4->3->2->5->null
列如:1->2->3->null,from=1、to=3 结果为3->2->1->null
【要求】
- 如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
- 如果不满足1<=from<=to<=N,则不调整
Node reversePart(Node head, int from, int to) {
// 在头节点之前定义一个新的节点
Node preHead = new Node(0);
preHead.next = head;
Node cur = preHead;
for (int i = 1; i < from; i++)
cur = cur.next;
// curPre定位到 from-1 处节点
Node curPre = cur;
// cur定位到 from 处节点
cur = cur.next;
// 从from处节点到to处节点,需要将to-from+1个节点进行翻转,那就是将to-from个节点插在from处节点之前,因此需要to-from次操作
// 进入循环之前需要定义一个next
Node next = null;
for (int i = 0; i < to - from; i++) {
// for(int i = from; i < to; i++) {
next = cur.next;
cur.next = next.next;
next.next = curPre.next;
curPre.next = next;
}
// 结束后,输出preHead.next即可
return preHead.next;
}
两两交换链表
1->2->3->4 2->1->4->3
此次图例参考反转from->to链表元素
Node swapPairs(Node head) {
Node preHead = new Node(0, head);
Node cur = head;
Node pre = preHead;
Node next = null;
while (cur != null && cur.next != null) {
next = cur.next;
cur.next = next.next;
next.next = cur;
pre.next = next;
// 指向下一段的当前节点与下一个节点
cur = cur.next;
pre = preHead.next.next;
}
return preHead.next;
}
1->2->3->4->5 1->3->2->5->4
Node node1 = reverse(swapPairs(reverse(head)));
将单链表的每K个节点之间逆序
给定一个单链表的头节点head,实现一个调整单链表的函数,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点
例如:
链表:1->2->3->4->5->6->7->8->null, K = 3。调整后:3->2->1->6->5->4->7->8->null。其中 7,8不调整,因为不够一组
可以用递归来实现,假设方法reverseKNode()的功能是将单链表的每K个节点逆序(从头部开始组起的哦);reverse()方法的功能是将一个单链表逆序
那么对于下面的这个单链表,其中 K = 3
把前K个节点与后面的节点分割出来:
temp指向的剩余的链表,可以说是原问题的一个子问题。可以调用reverseKNode()方法将temp指向的链表每K个节点进行逆序。再调用reverse()方法把head指向的那3个节点进行逆序,结果如下:
接着,我们只需要把这两部分给连接起来就可以了。最后的结果如下:
Node reverseKNodes(Node head, int k) {
if (head == null || head.next == null) {
return head;
}
Node cur = head;
for (int i = 1; cur != null && i < k; i++) {
cur = cur.next;
}
// 判断节点的数量是否能够凑成一组
if (cur == null) {
return head;
}
// temp指向剩余的链表
Node temp = cur.next;
cur.next = null;
//把k个节点进行反转
Node newHead = reverse(head);
//把之后的部分链表进行每K个节点逆转
Node newTemp = reverseKNodes(temp, k);
//把两部分节点连接起来
head.next = newTemp;
return newHead;
}
// 逆序单链表
private static Node reverse(Node head) {
if (head == null || head.next == null)
return head;
Node result = reverse(head.next);
// 改变1,2节点的指向 将节点2指向节点1,节点1后置节点置空
head.next.next = head;
head.next = null;
return result;
}
反转逆序
给定一个单链表的头节点 head,实现一个调整单链表的函数,使得每K个节点为一组进行逆序,并且从链表的尾部开始组起,头部剩余节点数量不够一组的不需要逆序(不能使用队列或者栈作为辅助)
例如:链表:1->2->3->4->5->6->7->8->null, K = 3。那么 6->7->8,3->4->5,1->2各位一组。调整后:1->2->5->4->3->8->7->6->null。其中 1,2不调整,因为不够一组
这道题的难点在于,是从链表的尾部开始组起的,而不是从链表的头部,如果是头部的话,那我们还是比较容易做的,因为你可以遍历链表,每遍历 k 个就拆分为一组来逆序。但是从尾部的话就不一样了,因为是单链表,不能往后遍历组起
其实这道题只需要先把单链表进行一次逆序,逆序之后就能转化为从头部开始组起了,然后按照我上面的解法,处理完之后,把结果再次逆序即搞定。两次逆序相当于没逆序
例如对于链表(其中 K = 3)
我们把它从尾部开始组起,每 K 个节点为一组进行逆序。步骤如下:
-
先进行逆序
逆序之后就可以把问题转化为从头部开始组起,每 K 个节点为一组进行逆序
-
处理后的结果如下:
-
接着在把结果逆序一次,结果如下:
Node solve(Node head, int k) {
// 调用逆序函数
head = reverse(head);
// 调用每 k 个为一组的逆序函数(从头部开始组起)
head = reverseKNodes(head, k);
// 在逆序一次
head = reverse(head);
return head;
}
约瑟夫环
Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
提示:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束
【要求】
输入:一个环形单向链表的头节点 head 和报数 m.
返回:最后生存下来的节点,且这个节点自己组成环形单向链表,其他节点都删除掉
这道题如果不考虑时间复杂度的话还是挺简单的,就遍历环形链表,每遍历 m 个节点就删除一个节点,直到链表只剩下一个节点就可以了
Node createCycle(int n) {
if (n < 1) {
return null;
}
Node head = null;
// 在形成环形链表时需要一个辅助指针,指向环形链表的最后一个节点
Node temp = null;
for (int no = 1; no < n + 1; no++) {
Node node = new Node(no);
if (no == 1) {
head = node;
// 形成一个环形的链表
head.next = head;
} else {
temp.next = node;
node.next = head;
}
temp = node;
}
return head;
}
要注意领会,首节点被遍历到最后一个节点才开始计数
// 时间复杂度为O(n*m)的解决方法
Node josephusJump(Node head, int m) {
if (head == null || m < 1) {
return head;
}
// 辅助节点,用于定位head到最后一个节点
Node temp = head;
while (head.next != temp) {
head = head.next;
}
int count = 0;
while (head.next != head) {
if (++count == m) {
head.next = head.next.next;
count = 0;
} else {
head = head.next;
}
}
return head;
}
合并两个有序链表
两个有序链表默认都是按照从小到大排列的
Node mergeTwoList2(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return head1 != null ? head1 : head2;
}
Node head = head1.val > head2.val ? head2 : head1;
// 指向头结点较小的链表
Node small = head == head1 ? head1 : head2;
// 指向头结点较大的链表
Node big = head == head1 ? head2 : head1;
// 将big中的值放到small中
// small前一个元素
Node pre = null;
// big后一个元素
Node next = null;
while (small != null && big != null) {
if (small.val <= big.val) {
pre = small;
small = small.next;
} else {
// 把big的元素合并到small中
next = big.next;
pre.next = big;
big.next = small;
pre = big;
big = next;
}
}
pre.next = small == null ? big : small;
return head;
}
Node mergeTwoList(Node head1, Node head2) {
Node head = null;
if (head1 == null || head2 == null) {
return head1 != null ? head1 : head2;
} else {
if (head1.val > head2.val) {
head = head2;
head.next = mergeTwoList(head1, head2.next);
} else {
head = head1;
head.next = mergeTwoList(head1.next, head2);
}
}
return head;
}
奇数位升序,偶数位降序,对该链表排序
1、8、3、6、5、4、7、2、9
Node reverse(Node head) {
Node pre = null;
Node next = null;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
Node mergeTwoList3(Node head1, Node head2) {
Node head = null;
if (head1 == null || head2 == null) {
return head1 == null ? head2 : head1;
} else {
if (head1.val > head2.val) {
head = head2;
head.next = mergeTwoList3(head1, head2.next);
} else {
head = head1;
head.next = mergeTwoList3(head1.next, head2);
}
}
return head;
}
/*思路:可以分为三步:
* 1、按照奇数位和偶数位拆分为两个链表
* 2、对偶数位进行反转
* 3、将两个有序链表进行合并*/
public static Node[] getLists(Node head) {
Node head1 = null;
Node head2 = null;
Node cur1 = null;
Node cur2 = null;
// 用来计数
int count = 1;
while (head != null) {
if (count % 2 == 1) {
if (cur1 != null) {
cur1.next = head;
cur1 = cur1.next;
} else {
cur1 = head;
head1 = cur1;
}
} else {
if (cur2 != null) {
cur2.next = head;
cur2 = cur2.next;
} else {
cur2 = head;
head2 = cur2;
}
}
head = head.next;
count++;
}
cur1.next = null;
cur2.next = null;
Node[] nodes = new Node[]{head1, head2};
return nodes;
}
Node node9 = new Node(9);
Node node2 = new Node(2, node9);
Node node7 = new Node(7, node2);
Node node4 = new Node(4, node7);
Node node5 = new Node(5, node4);
Node node6 = new Node(6, node5);
Node node3 = new Node(3, node6);
Node node8 = new Node(8, node3);
Node head = new Node(1, node8);
Node[] lists = getLists(head);
Node head1 = lists[0];
Node head2 = lists[1];
head2 = reverse(head2);
Node node = mergeTwoList3(head1, head2);
System.out.println(node);
判断回文链表
可以让链表的后半部分入栈,然后把栈中的元素与链表的前半部分对比,例如 1->2->3->2->2。然后逐个出栈,与链表的前半部分(1->2)对比。这样做的话空间复杂度会减少一半
boolean f(Node head) {
if (head == null || head.next == null) {
return true;
}
Node slow = head;
Node fast = head;
// slow最终指向中间节点 5指向3,4指向2
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
Stack<Node> stack = new Stack<>();
while (slow != null) {
stack.push(slow);
slow = slow.next;
}
// 进行判断
while (!stack.empty()) {
if (head.val != stack.pop().val) {
return false;
}
head = head.next;
}
return true;
}
优化
可以把链表的后半部分进行反转,然后再用后半部分与前半部分进行比较就可以了。这种做法额外空间复杂度只需要 O(1),时间复杂度为 O(n)
boolean f2(Node head) {
if (head == null || head.next == null) {
return true;
}
Node slow = head;
Node fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 这里注意,4时slow为2,将cur指向3号位置
Node cur = slow.next;
// 链表采用循环方式反转,需要2个辅助参数,一个用来保存反转后的头结点,一个用来保存待保存的头结点
Node next;
Node pre = null;
while (cur != null) {
// next保存反转后的头结点
next = cur.next;
// 下面两步操作,相当于在头结点pre前面加一个节点,在将头节点赋值给pre
cur.next = pre;
pre = cur;
cur = next;
}
while (pre != null) {
if (pre.val != head.val) {
return false;
}
pre = pre.next;
head = head.next;
}
return true;
}
复制带随机指针的链表
2两数相加
将两个链表看成是相同长度的进行遍历,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry;其中,链表相应位置的数字为(n1+n2+carry)%10,而新的进位值为 (n1+n2+carry)/10。如果一个链表较短则在前面补0,比如 987 + 23 = 987 + 023 = 1010
每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值
如果两个链表全部遍历完毕后,进位值为1,则在新链表最前方添加节点 1
小技巧:对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 dummy,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果
ListNode addTwoNumbers(ListNode p, ListNode q) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
int carry = 0;
while (p != null || q != null) {
int x = p == null ? 0 : p.val;
int y = q == null ? 0 : q.val;
int sum = x + y + carry;
carry = sum > 9 ? 1 : 0; // carry = sum / 10;
sum = sum % 10;
// cur始终指向最尾端结点
cur.next = new ListNode(sum);
cur = cur.next;
if (p != null)
p = p.next;
if (q != null)
q = q.next;
}
if (carry == 1) {
cur.next = new ListNode(carry);
}
return dummy.next;
}
判断链表中是否有环
给定一个单链表,判断其中是否有环,已经是一个比较老同时也是比较经典的问题,在网上搜集了一些资料,然后总结一下大概可以涉及到的问题,以及相应的解法
首先,关于单链表中的环,一般涉及到以下问题:
- 给一个单链表,判断其中是否存在环
- 如果存在环,找出环的入口
- 如果存在环,求出环上节点个数
- 如果存在环,求出链表长度
- 如果存在环,求出环上距离任意一个节点最远的点(对面节点)
- (扩展)如何判断两个无环链表是否相交
- (扩展)如果相交,求出第一个相交的节点
判断是否有环(链表头指针为head)
对于这个问题可以采用“快慢指针”的方法。就是有两个指针fast和slow,开始时候两个指针都指向链表头head,然后在每一步操作中,slow向前走一步:slow = slow->next,而fast每一步向前两步:fast = fast->next->next
由于fast要比slow移动的快,如果有环,fast一定会先进入环,而slow后进入环。当两个指针都进入环之后,经过一定步的操作之后,二者一定能够在环上相遇,并且此时slow还没有绕环一圈,也就是说一定是在slow走完第一圈之前相遇。证明可以看下图:
当slow刚进入环时每个指针可能处于上面的情况,接下来slow和fast分别向前走即:
if (slow != null && fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
也就是说,slow每次向前走一步,fast向前追了两步,因此每走一步后fast到slow的距离缩短1,这样继续下去就会使得两者之间的距离逐渐缩小:…、5、4、3、2、1、0(相遇)。又因为在同一个环中fast和slow之间的距离不会大于换的长度,因此到二者相遇的时候slow一定还没有走完一周(或者正好走完,这种情况出现在开始的时候fast和slow都在环的入口处)
boolean existLoop(Node head) {
Node slow = head;
Node fast = head;
while (slow != null && fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
// 代表不存在环
return false;
}
找出环的入口点
从上面可知,当fast和slow相遇时,slow还没有走完链表,假设fast已经在环内循环了n(1<= n)圈。slow走了s步,则fast走了2s步,又由于fast走过的步数 = s + n*r(s + 在环上多走的n圈),则有下面的等式:
2*s = s + n * r ;
=> s = n * r
如果假设整个链表的长度是L,入口和相遇点的距离是x(如上图所示),起点到入口点的距离是a(如上图所示),则有:
a + x = s = n * r;
// 由环的长度 = 链表总长度 - 起点到入口点的距离求出
a + x = s = (n - 1) * r + r = (n - 1) * r + (L - a)
a = (n - 1) * r + (L - a - x) // 相遇点到环入口节点的距离
结论:
- (L - a - x)为相遇点到环入口节点的距离,即从相遇点开始向前移动(L - a - x)步后,会两次到达环入口节点
- 即:从链表的头节点到环入口节点的距离=(n-1)*环内循环+相遇点到环入口点的距离
- 于是从链表头与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点(环内的多循环了n-1圈,才等到头节点的相遇)
因此就可以分别用一个指针(headNode, meetingNode),同时从head与相遇点出发,每一次操作走一步,直到headNode == meetingNode,此时的位置也就是入口点
Node findLoopStart(Node head) {
if (head == null || head.next == null) {
return head;
}
Node slow = head;
Node fast = head;
// 最后一个判断节点是存在环的情况
do {
slow = slow.next;
fast = fast.next.next;
} while(slow!= null && fast != null && fast.next != null && slow != fast);
// 没有环,返回null
// if (slow == null || fast.next == null)
if (slow != fast) {
return null;
}
// 链表开始结点
Node headNode = head;
// 环入口点
Node meetingNode = fast;
while (headNode != meetingNode) {
headNode = headNode.next;
meetingNode = meetingNode.next;
}
// 返回环的入口节点
return meetingNode;
}
存在环,求出环上节点个数
对于这个问题,有两个思路(肯定还有其它跟好的办法):
思路1:相遇之后让slow(或者fast都一样)继续向前走slow = slow.next;一直到slow == fast; 此时经过的步数就是环上节点的个数
思路2: 从相遇点开始slow和fast继续按照原来的方式向前走slow = slow -> next; fast = fast -> next -> next;直到二者再次项目,此时经过的步数就是环上节点的个数
int calculateLoopLength(Node head) {
if (head == null) {
return 0;
}
if (head.next == null) {
return 1;
}
Node slow = head.next;
Node fast = head.next.next;
while (slow != null && fast != null && fast.next != null && slow != fast){
slow = slow.next;
fast = fast.next.next;
}
// 没有环,返回0
if (slow != fast) {
return 0;
}
int step = 0;
do {
slow = slow.next;
step++;
} while (slow != fast);
return step;
}
存在环,求出链表的长度
到这里,问题已经简单的多了,因为有问题1、2、3中已经做得足够的”准备工作“。可以这样求出整个链表的长度:
链表长度L = 起点到入口点的距离 + 环的长度r
已经知道了起点和入口点的位置,那么两者之间的距离很好求了吧!环的长度也已经知道了,因此该问题也就迎刃而解了
int getLinkLength(Node head) {
int step = 0;
Node loopStart = findLoopStart(head);
if (loopStart == null) {
return 0;
}
if (loopStart == head) {
return 1;
}
// 计算head到环入口的长度
do {
head = head.next;
step++;
} while (head != loopStart);
// 计算环长度
do {
head = head.next;
step++;
} while (head != loopStart);
return step;
}
存在环,求出环上距离任意一个节点最远的点
def getLongNode(head: SingleNode, LoopNode: SingleNode): SingleNode = {
// 需要判断 LoopNode 是否是属于环上节点
var (slow, fast) = (LoopNode, LoopNode)
do {
slow = slow.next
fast = fast.next.next
} while (fast == LoopNode)
slow
}
如何判断两个无环链表是否相交
假设有两个链表listA和listB,如果两个链表都无环,并且有交点,那么可以让其中一个链表(不妨设是listA)的首尾节点相连,这样在listB中就一定会出现一个环
如果相交,求出第一个相交的节点
3中已经做得足够的”准备工作“。可以这样求出整个链表的长度:
链表长度L = 起点到入口点的距离 + 环的长度r
已经知道了起点和入口点的位置,那么两者之间的距离很好求了吧!环的长度也已经知道了,因此该问题也就迎刃而解了
int getLinkLength(Node head) {
int step = 0;
Node loopStart = findLoopStart(head);
if (loopStart == null) {
return 0;
}
if (loopStart == head) {
return 1;
}
// 计算head到环入口的长度
do {
head = head.next;
step++;
} while (head != loopStart);
// 计算环长度
do {
head = head.next;
step++;
} while (head != loopStart);
return step;
}
存在环,求出环上距离任意一个节点最远的点
def getLongNode(head: SingleNode, LoopNode: SingleNode): SingleNode = {
// 需要判断 LoopNode 是否是属于环上节点
var (slow, fast) = (LoopNode, LoopNode)
do {
slow = slow.next
fast = fast.next.next
} while (fast == LoopNode)
slow
}
如何判断两个无环链表是否相交
假设有两个链表listA和listB,如果两个链表都无环,并且有交点,那么可以让其中一个链表(不妨设是listA)的首尾节点相连,这样在listB中就一定会出现一个环