203. 移除链表元素
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode myHead=new ListNode(0,head);
ListNode pre=myHead;
while(head!=null){
if(head.val==val){
pre.next=head.next;
head=head.next;
}
else{
pre=head;
head=head.next;
}
}
return myHead.next;
}
}
707. 设计链表
class Node{
public int data;
public Node next;
public Node(){
;
}
public Node(int val,Node next){
this.data=val;
this.next=next;
}
}
class MyLinkedList {
public int size=0;
public Node head;
public MyLinkedList() {
head=new Node(0,null);
size=0;
}
public int get(int index) {
if(index>=size||index<0)return -1;
Node ptr=head;
for(int i=0;i<=index;i++){
ptr=ptr.next;
}
return ptr.data;
}
public void addAtHead(int val) {
Node newHead=new Node(val,head.next);
head.next=newHead;
size++;
}
public void addAtTail(int val) {
Node ptr=head;
while(ptr.next!=null){
ptr=ptr.next;
}
Node tail=new Node(val,null);
ptr.next=tail;
size++;
}
public void addAtIndex(int index, int val) {
Node pre=head;
if(index==size){
addAtTail(val);
return ;
}
if(index>size)return ;
if(index<0)index=0;
for(int i=0;i<index;i++){
pre=pre.next;
}
Node node=new Node(val,pre.next);
pre.next=node;
size++;
}
public void deleteAtIndex(int index) {
if(index>=size)return;
Node pre=head;
for(int i=0;i<index;i++){
pre=pre.next;
}
pre.next=pre.next.next;
size--;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
206.反转链表
思路一:顺序遍历原有链表,使用头插法建立新链表即为反转后的链表。时间复杂度O(n)空间复杂度也为O(n)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode newhead=new ListNode(0,null);
ListNode ptr=newhead;
while(head!=null){
ptr=head;
head=head.next;
ptr.next=newhead.next;
newhead.next=ptr;
}
return newhead.next;
}
}
思路二:原地翻转,ptr用于遍历链表,pre指向在原链表中的前驱,temp用于存储ptr的后驱,最后pre所指就是反转后的头节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode ptr=head;
ListNode temp;
while(ptr!=null){
temp=ptr.next;
ptr.next=pre;
pre=ptr;
ptr=temp;
}
return pre;
}
}
24.两两交换链表的节点
思路:递归算法,首先确定出口,当链表为空或者只有一个节点时直接返回。将前两个之后的链表作为一个新链表进行递归处理,然后将前两个节点交换。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null)return head;
ListNode next=head.next;
ListNode newNode = swapPairs(next.next);
head.next=newNode;
next.next=head;
return next;
}
}
19.删除链表的倒数第N个节点
思路:这个题的思路还是很巧妙的,利用双指针确定好距离,用距离为n的双指针同时向后遍历,当快指针指向尾节点时,slow恰好指向倒数第n个节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode myHead=new ListNode(0,head);
ListNode fast=head;
ListNode slow=head;
ListNode pre=myHead;
for(int i=1;i<n;i++)fast=fast.next;
while(fast.next!=null){
fast=fast.next;
slow=slow.next;
pre=pre.next;
}
pre.next=slow.next;
return myHead.next;
}
}
面试题 02.07 . 链表相交
思路:先计算两个链表的长度,如果链表相交的话,两个链表的尾部应该是对齐的,所以我们计算出长度的差值gap,并且将长的链表的指针指向第gap个位置,这样,两个链表同时向后遍历,如果相交,肯定会同时遍历到相交的节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int len1=0,len2=0;
ListNode ptr1=headA,ptr2=headB;
while(ptr1!=null){
len1++;
ptr1=ptr1.next;
}
while(ptr2!=null){
len2++;
ptr2=ptr2.next;
}
ptr1=headA;
ptr2=headB;
if(len1<len2){
int temp=len1;
len1=len2;
len2=temp;
ptr1=headB;
ptr2=headA;
}
int gap=len1-len2;
while(gap!=0){
ptr1=ptr1.next;
gap--;
}
while(ptr1!=null){
if(ptr1==ptr2){
return ptr1;
}
ptr1=ptr1.next;
ptr2=ptr2.next;
}
return null;
}
}
142. 环形链表
思路:核心分两步,首先是判断是否存在环。我们快慢指针,fast指针一次走两步,slow指针一次走一步,如果存在环,fast和slow必然会在环内相遇。在确定环之后下一步就是找环的入口。这里需要一步数学证明推导,参考代码随想录。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null)return head;
ListNode fast=head,slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
ListNode ptr1=head,ptr2=fast;
while(ptr1!=ptr2){
ptr1=ptr1.next;
ptr2=ptr2.next;
}
return ptr1;
}
}
return null;
}
}
总结
刷完这七道链表相关的题目,比较有收获的地方有
1. 虚拟头节点。使用虚拟头节点可以使第一个结点和其他结点的操作方法一致,不用单独处理头节点
2. 对于遍历链表,如果是已知下标,用for循环会比较好。
3. 对于插入和删除操作,需要先找到前驱节点
4. 双指针的应用非常巧妙,可以实现一次遍历解决问题
5. 相交链表和环形链表需要加强记忆