代码随想录自刷2:链表篇
203.移除链表元素
思路:避免头节点就是要删除的节点之一,要用一个虚拟头节点dummy接上:dummy.next=head
返回的时候直接dummy.next即可!
- 需要有一个前节点
pre,方便接上断开的链 - 需要有一个当前节点
cur,方便遍历
public ListNode removeElements(ListNode head, int val) {
ListNode dummy=new ListNode();
ListNode cur=head;
dummy.next=cur;
ListNode pre=dummy;
while(cur!=null){
if(cur.val==val){
//接上断开的节点
pre.next=cur.next;
}else{
//pre往下移动
pre=cur;
}
//遍历下一个
cur=cur.next;
}
return dummy.next;
}
707. 设计链表
思路:设计一个ListNode,要有val和next属性。
- MyLinkedList要有
size记录链表长度(不含虚拟头节点),和dummy(虚拟头节点)
class MyLinkedList {
private class ListNode{
int val;
ListNode next;
public ListNode(){}
public ListNode(int val){
this.val=val;
}
}
ListNode dummy;
int size;
public MyLinkedList() {
dummy=new ListNode();
size=0;
}
public int get(int index) {
if(index>=size || index<0)
return -1;
ListNode cur=dummy;
//包含index是要把dummy给过滤掉
for(int i=0;i<=index;i++){
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>size)
return;
if(index<0)
index=0;
//插入一个元素所以++
size++;
ListNode cur=dummy;
//找到要插入位置的前一个元素
for(int i=0;i<index;i++){
cur=cur.next;
}
ListNode tmp=cur.next;
cur.next=new ListNode(val);
cur.next.next=tmp;
}
public void deleteAtIndex(int index) {
if(index>=size || index<0)
return;
ListNode cur=dummy;
for(int i=0;i<index;i++){
cur=cur.next;
}
size--;
cur.next=cur.next.next;
}
}
206. 反转链表
思路:链表的增删,通常都需要有虚拟头节点!但这题不需要dummy,因为反转到最后发现头节点跑到后面去了,而pre此时正好停留在头节点位置上,所以到时候直接返回pre就是反转后的头节点啦
- 主要修改节点间的next关系
- 需要有两个节点遍历原来的链表:
pre、cur - 遍历中需要记录
cur.next,因为在修改cur.next关系指向pre时,就把链表断开了,cur要想移动到下一个节点就需要通过这个记录来移动
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode tmp=cur.next;
cur.next=pre;
pre=cur;
cur=tmp; //移动
}
return pre;
}
24. 两两交换链表中的节点
思路:需要用到的节点比较多,最好画图辅助理解交换的过程。注意cur当前指向的是dummy!!
参考代码随想录的图:
public ListNode swapPairs(ListNode head) {
ListNode dummy=new ListNode();
dummy.next=head;
ListNode cur=dummy;
ListNode tmp; // 临时节点,保存两个节点后面的节点
ListNode first; // 临时节点,保存两个节点之中的第一个节点
ListNode second; // 临时节点,保存两个节点之中的第二个节点
while(cur.next!=null && cur.next.next!=null){
first=cur.next;
second=cur.next.next;
tmp=cur.next.next.next;
cur.next=second;
second.next=first;
first.next=tmp;
//移动
cur=first;
}
return dummy.next;
}
19. 删除链表的倒数第 N 个结点
思路:使用快慢指针,涉及到删除所以用到dummy,注意这里快慢指针都先指向dummy。
- 根据删除倒数第
n个节点,快指针先走n+1步(因为一开始指向dummy所以+1) - 快慢指针再一起遍历,直到快指针走完链表
- 此时慢指针来到要删除的节点前一个位置
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy=new ListNode();
ListNode fast=dummy;
ListNode slow=dummy;
dummy.next=head;
while(n>=0){
fast=fast.next;
n--;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummy.next;
}
面试题 02.07. 链表相交
思路:
- 两个链表先各自遍历计算长度,自己规定
链表A永远代表较长的链表(方便后续计算),所以计算完后如果当前链表A较短要做对调操作。 - 两个长度相减得到差值,让
链表A先走这么多步,这样两个链表的起点位置就相同了 - 开始遍历链表找出相同节点就是相交处
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode nodeA=headA;
ListNode nodeB=headB;
int lA=0;
int lB=0;
while(nodeA!=null){
nodeA=nodeA.next;
lA++;
}
while(nodeB!=null){
nodeB=nodeB.next;
lB++;
}
nodeA=headA;
nodeB=headB;
if(lA<lB){
int tmp=lA;
lA=lB;
lB=tmp;
nodeA=headB;
nodeB=headA;
}
int num=lA-lB;
while(num>0){
nodeA=nodeA.next;
num--;
}
while(nodeA!=null){
if(nodeA==nodeB){
return nodeA;
}
nodeA=nodeA.next;
nodeB=nodeB.next;
}
return null;
}
142. 环形链表 II
思路:主要用快慢指针,快指针永远走2步,慢指针走1步。然后分两部分判断:一是否有循环、二循环口在哪里
- 如果快指针和慢指针相等了,就表示走进循环中
- (此时选定慢指针,让其从当前点继续每次走1步)
- 一个指针从头开始(每次走1步)如果和慢指针相等了就表示当前点就是循环口!
- 没有相等表示没有循环!
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(slow==fast){
ListNode cur=head;
while(cur!=fast){
cur=cur.next;
fast=fast.next;
}
return cur;
}
}
return null;
}