707. 设计链表
这道题的解题思路其实就是让我们模拟一个链表的实现:
首先我们先要创建一个内部类作为链表的结点,这个内部类要包含两个元素,一个是val值,一个是指向下一个节点的指针
在构造方法这里我们要初始化一个虚拟头街点和一个链表长度,使用虚拟头节点的好处在于可以统一处理各个节点,不用再单独处理头节点
然后在get方法这里,我们要使用一个cur指针从虚拟头节点开始出发,不断向后遍历直到我们需找到目标的节点,要注意的是,我们的下标值是要比我们cur要走过的步数少一位的,所以我们需要在step这个变量这里加上一位来确保我们能够走到目标的位置上
在处理头插法和尾插法这两个方法时候我们可以直接调用addAtIndex这个方法统一处理
在处理addAtIndex这个方法的时候,我们需要注意的是,我们要找到目标节点的前一个节点进行插入操作,所以根据刚才提到的我们的下标值是要比我们cur要走过的步数少一位的,我们直接让cur走index步,就可以到达目标节点的前一个节点!
同理处理deleteAtIndex这个方法的时候,我们的处理流程适合addAtIndex这个方法一样
具体代码如下
class MyLinkedList {
class Node{
int val;
Node next;
Node(int val){
this.val = val;
}
}
Node Dhead;
int size;
public MyLinkedList() {
Dhead = new Node(-1);
size = 0;
}
public int get(int index) {
if(index < 0 || index >= size){
return -1;
}
Node cur = Dhead;
int step = index + 1;
while(step-- > 0){
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if(index < 0 || index > size){
return;
}
Node node = new Node(val);
Node cur = Dhead;
int step = index;
while(step-- > 0){
cur = cur.next;
}
node.next = cur.next;
cur.next = node;
++size;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size){
return;
}
Node cur = Dhead;
int step = index;
while(step-- > 0){
cur = cur.next;
}
cur.next = cur.next.next;
size--;
}
}
206. 反转链表
这道题的思路其实就是从前往后一步一步的修改指向,以给的示例为例子
我们定义两个指针一个pred指向虚拟头节点,一个cur指向pred指向节点的后一个节点
然后使用while循环不断地两个指针向后移动,在移动的过程中cur修改指向让cur.next = pred
在修改完毕后,由于我们的cur的后一个节点已经是pred了,所以我们在一开始就使用curNext来记录cur的后一个节点,在修改完毕后,让后pred走到cur的位置,cur走到curNext的位置上,这样就真正的让两个节点向后移动了
需要注意的时候在最后我们要将head(实际的头节点)的指向修改成空!因为虚拟头节点是我们自己定义的并不属于官方给的例子中
最后贴上代码
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
ListNode Dhead = new ListNode();
Dhead.next = head;
ListNode pred = Dhead;
ListNode cur = head;
while(cur != null){
ListNode curNext = cur.next;
cur.next = pred;
pred = cur;
cur = curNext;
}
head.next = null;
return pred;
}
}
24. 两两交换链表中的节点
本题的关键就在于本题的题目“如何两两交换链表中的节点”,所以我们的思路就在于交换链表中的相邻的两个节点应该怎么样的交换
我们通过画图的形式来展现我们解题的思路
设置两个指针来记录我们要交换的节点,在我们两两交换完毕以后,需要对后面的链表进行操作,所以我们也需要一个指针来记录后面链表的头节点的位置,以方便我们进行修改指向
我们首先将头节点指向第二个节点,然后将第二个节点的指向修改成指向第一个节点,这样我们的交换就完成了,为了操作后面的链表,我们需要将第一个节点的指向修改成指向后面链表头节点的位置
public ListNode swapPairs(ListNode head) {
ListNode Dhead = new ListNode();
Dhead.next = head;
ListNode cur = Dhead;
while(cur.next != null && cur.next.next != null){
ListNode first = cur.next;
ListNode second = cur.next.next;
ListNode third = cur.next.next.next;
cur.next = second;
second.next = first;
first.next = third;
cur = first;
}
return Dhead.next;
}
这里需要注意的是while里面的终止条件,由于我们不知道链表的长度是奇数还是偶数,这个就需要注意cur是否会操作空指针,由于我们要交换的链表节点有两个,所以我们要保证两个节点都不为空,所以这里的终止条件就是
cur.next != null && cur.next.next != null
还有一点我们要注意的是,在交换完以后,我们要让cur走到两两交换节点第一个节点的前一个节点上,所以我们要让 cur = first !
19. 删除链表的倒数第 N 个结点
思路:怎么样才能删除倒数第N个节点呢?我一开始的思路是让cur指针指向头节点后一直向后走,走到最后一个节点,然后再向前走n步就能进行删除了。但是要注意的是我们这里是单向链表,不能向前走,所以我们只能考虑向后走的方法
假设链表长度是size,那么倒数第1个数就是正数第size个数,倒数第2个数就是正数第(size - 1)个数,以此类推,倒数第n个数就是正数第(size -(n-1))个数。明确这个思路就可以进行删除操作了。
我们首先要统计链表的长度是多少,所以需要一个fast指针来遍历链表,记录链表的长度。然后我们再让另一个指针slow走到n-1个节点的位置上,根据上面推导出来的公式,倒数第n个数就是正数第(size - n )个数。想要从cur节点走到第(size - n )个节点的话,那么步数step = size - n - 1。
明确上面的难点以后代码就好写了,具体代码如下
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head == null){
return null;
}
ListNode Dhead = new ListNode();
Dhead.next = head;
ListNode fast = Dhead;
ListNode slow = Dhead;
int len = 0;
while(fast != null){
fast = fast.next;
len++;
}
int step = len - n - 1;
while(step-- > 0){
slow = slow.next;
}
slow.next = slow.next.next;
return Dhead.next;
}
面试题 02.07. 链表相交
本题的思路也是使用双指针的思路,假如说我有两个链表(此时不相交),使用两个指针同时指向两个链表的头节点,那么一起走n步,两个指针指向的下标是一样的。
顺着这个思路,假如说两个链表相交,那么我只要让两个指针指向的下标与相交节点的距离相同,然后一起向后走,那么当两个指针指向同一个节点的时候,就能完成本题的要求了。
通过不同的示例我们可以发现,两个链表相交前的长度是不一样的,那么我们为了让他们的起始下标一致,可以让长的链表走n步,其中这个n步就对应着两个链表长度的差值,这样就可以让两个指针指向的下标与相交节点的距离相同了!
最后贴上代码
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int stepA = 0;
int stepB = 0;
while(curA != null){
curA = curA.next;
++stepA;
}
curA = headA;
while(curB != null){
curB = curB.next;
++stepB;
}
curB = headB;
int step = Math.abs(stepA - stepB);
if(stepA > stepB){
while(step-- != 0){
curA = curA.next;
}
}else if(stepA < stepB){
while(step-- != 0){
curB = curB.next;
}
}
while(curA != null && curB != null && curA != curB){
curA = curA.next;
curB = curB.next;
}
return curA;
}
}
142. 环形链表 II
这道题的解题思路分两个步骤,首先确认链表是否有环,其次确认环的入口节点在哪
我们首先解决第一个问题“确认链表是否有环”
对于这个问题,我们使用双指针的方式来解决这个问题,我们让慢指针每次都走一步来遍历链表,让快指针每次走两步来遍历链表。如果链表有环,那么两者一定能相遇,因为如果有环,当慢指针刚刚进入环内,快指针已经转了n(n >= 1)圈了。以慢指针为参考系,慢指针不动,快指针相对自己在圈内每次前进一步,那么最后一定是可以相遇的!
为什么不能让快指针每次走三步,四步或者更多步?还是回到参考系这个问题上,以慢指针为参考系,慢指针不动,快指针相对自己在圈内每次前进一步,那么最后一定是可以相遇的!但是快指针相对自己在圈内每次前进两步,三步,那么很有可能快指针每次移动都越过慢指针,为了确保一定能相遇我们使用快指针相对前进一步的方式进行移动
回到第二个问题。确认了链表内有环,该如何确认入口的位置呢?
我们首先要知道的是,在慢指针走过的第一圈里面就可以遇到快指针
我们可以画一个图来证明
我们推到快指针和慢指针相遇的路程的时候可以有以下假设:
头节点到入口节点的位置距离为 x
入口节点到两个节点相遇的位置距离为 y
两个节点相遇的位置到入口的位置距离为 z
那么开始下面的数学推导
快指针走过的距离 s1 = x + y + n*(y + z)
慢指针走过的距离 s2 = x + y
时间相同,快指针的速度是慢指针的两倍
所以得到 v1 = s1 / t = 2*v2 = s2 / t
即 x = (n-1)*(y+z) + z
也就说当我们设置两个指针,一个指向头节点,一个指向相遇处,那么两者是一定可以相遇,相遇的地方就是我们的入口处!
结束推导
开始写代码
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null){
return null;
}
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast){
slow = head;
while(slow != fast){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
if(fast == null || fast.next == null){
fast = null;
}
return fast;
}
}