链表
移除链表元素
对于链表来说,删除头节点和中间节点具体操作不一样是因为想要删除一个中间节点,必须要知道该节点的前一个节点,而头节点没有前一个节点。
使用虚拟头节点,统一节点的删除操作,用一个虚拟头节点的next指向head,这个链表中的每个元素都会有前一个节点,从而对所有节点都可以统一使用前一个节点进行删除。
最后是返回原虚拟头节点的下一个节点,才是新的修改过的链表,而不能直接返回原先的head。
找到原因:如果删除的是头节点,head指向的节点并不会被删除,更新的是p指向的链表,所有综合起来直接返回虚拟头节点的下一个节点
public ListNode removeElements1(ListNode head, int val) {
ListNode p=new ListNode();
ListNode q=new ListNode();//是虚拟头节点
p.next=head;//临时指针 遍历链表(包括虚拟头节点)
q=p;
while (p.next!=null){
if (p.next.val==val){
p.next=p.next.next;
}else {
p=p.next;
}
}
return q.next; //不能直接返回head,调试后发现p指向的链表会改变,但head所在的链表并没有随之改
//变,只有再添加一个node节点让它等于p
}
设计链表
对于链表的操作需要在节点的基础上增加一个虚拟头节点dummy node,它的next指向链表的头节点,再创建一个当前节点curr=dummy node,使用当前节点对链表进行操作,增删之后再使得head=dunmmy node.next。
为什么增删后要执行head=dunmmy node.next?因为对于链表的增删操作很有可能会删除头节点或者将头节点后移,此时光靠头节点是没办法得到最新的链表的,但虚拟头节点的位置是不会变的,所以要根据虚拟头节点找到head。
查看代码
public class MyLinkedList {
int length;
listNode head;
public MyLinkedList() {
}
public MyLinkedList(int length, listNode head) {
this.length = length;
this.head = head;
}
public int get(int index){
listNode p=head;
if (index<length){
for (int i = 0; i < index; i++) {
p=p.next;
}
return p.val;
}else {
return -1;
}
}
public void addAtHead(int val){
listNode newNode=new listNode();
newNode.val=val;
newNode.next=head;
head=newNode;//这里直接用了head
length=length+1;
}
public void addAtTail(int val){
listNode newNode=new listNode();
newNode.val=val;
listNode p=new listNode();
p.next=head;
listNode q=new listNode();
q=p;
while (p.next!=null){//找到插入节点的前一个节点
p=p.next;
}
p.next=newNode;
head=q.next;//如果此时链表为空,加在末尾的也是头节点,但此时head经过上面的操作并没有发生改变,因此要更新
length+=1;
}
public void addAtIndex(int index,int val){
listNode newNode=new listNode(val);
listNode p=new listNode();
p.next=head;
listNode q=p;
if (index<=length){
for (int i = 0; i < index; i++) {
p=p.next;//得到前一个
}
newNode.next=p.next;
p.next=newNode;
head=q.next;//必须要加上这句,如果此时插入的是头节点,head节点会放到新插入节点的后面,根据head并不能返回最新的链表 但如果插入的是中间节点,则直接用head即可
length=length+1;
}
}
public void deleteAtIndex(int index){
listNode p=new listNode();
p.next=head;
listNode q=new listNode();
q=p;
if (index<length){
for (int i = 0; i < index; i++) {
p=p.next;
}
p.next=p.next.next;
head=p.next;//同理 如果删除的是头节点的话 必须要同步更新
length-=1;
}
}
}
翻转链表
使用双指针思想,curr指向头节点,pre为null,再用一个临时节点temp保存curr.next.
查看代码
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode curr=head;
ListNode temp=new ListNode();
while (curr!=null){
temp=curr.next;
curr.next=pre;
pre=curr;
curr=temp;
}
return pre;
}
两两交换链表中的节点
问题:没有将虚拟头节点的指针重新指向排序后的头节点,造成排序后第一个节点消失。
查看代码
public ListNode swapPairs(ListNode head) {
ListNode dummyNode=new ListNode();
dummyNode.next=head;
ListNode pre=dummyNode;
ListNode curr1=pre.next;
ListNode curr2=null;
if (curr1!=null &&curr1.next!=null){
curr2=curr1.next;
}
ListNode temp;
while (curr1!=null && curr2!=null){
temp=curr2.next;
pre.next=curr2;
curr2.next=curr1;
curr1.next=temp;
pre=curr1;
curr1=temp;
if (temp!=null){
curr2=temp.next;
}
}
return dummyNode.next;
}
删除链表的倒数第N个节点
思路:使用前后指针,中间间隔节点数为倒数节点个数-1。
查看代码
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode=new ListNode();
dummyNode.next=head;
ListNode preN=dummyNode;
ListNode curr=dummyNode;
while (n-->0){
curr=curr.next;
}
while(curr.next!=null){
preN=preN.next;
curr=curr.next;
}
preN.next= preN.next.next;
return dummyNode.next;
}
其中在对链表节点进行增加时,不能直接新建节点来接收更新的list.head值,否则list中的head是没有更新的
MyLinkedList list=new MyLinkedList();
int[] arr=new int[]{1,2,3,4,5};
for (int i = 0; i < arr.length; i++) {
list.head = list.addLinkedlist(arr[i]);//不能新建一个ListNode为head 而是把这个链表的head值进行更新
}
// list.printLinkedlist(list.head);
list.head = removeNthFromEnd(list.head, 1);
list.printLinkedlist(list.head);
链表相交
该题的初始化无法使用简单的增加链表节点,如果让输入的两个数组独立进行链表初始化,即使值相同但并不能指向同一节点。
思路:将两个链表进行等长操作,具体为求出两个链表的长度,然后将长链表的当前指向节点往后移动直到和短链表的头节点位置相同,然后开始顺序比较每个节点是否相同(注意:不是节点的值,而是节点!!)
查看代码
int lengthA=0;
int lengthB=0;
ListNode currA=headA;
ListNode currB=headB;
while (currA!=null || currB!=null){
if (currA!=null){
lengthA+=1;
currA=currA.next;
}
if (currB!=null){
lengthB+=1;
currB=currB.next;
}
}
currA=headA;
currB=headB;
int gap=Math.abs(lengthA-lengthB);
while (gap-->0){
if (lengthA>lengthB){
currA=currA.next;
}else {
currB=currB.next;
}
}
while (currA!=null && currB!=null){
if (currA==currB){
return currA;
}
currA=currA.next;
currB=currB.next;
}
return null;
环形链表II
要掌握它们之间的数学关系,才能解此题,两个关键点:第一是通过快慢指针,当相遇时一定在环内,得到相遇节点;第二是根据数学公式(x+y)*2=x+y+n(y+z),得到x=(n-1)(y+z)+z,即当指针从头节点和相遇节点同时出发时,一定会再次相遇,再次相遇的节点为环形入口节点。
查看代码
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
ListNode index1=fast;
ListNode index2=head;
while(index1!=index2){
index1=index1.next;
index2=index2.next;
}
return index1;
}
}
return null;