链表题–经典解法:1、哑巴结点,也叫虚拟结点 2、栈 3、双指针,比如经典的快慢指针 4、递归
链接地址:LeetCode19.删除链表的倒数第N个结点
想到利用双指针找规律
由于头指针没有前驱节点,使用一个虚拟节点dummy指向头节点,将left初始指向dummy,right初始指向头结点。首先right走n步,使得left与right两个指针相隔n个结点,然后循环两个节点同时走(next),可以发现当right指向null时,left的下一个结点就是题目所要求的
coding如下:
/**
* 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 dummy = new ListNode(0,head);
ListNode left = dummy;
ListNode right = head;
for(int i = 0;i < n;i++){
right = right.next;
}
while(right != null){
left = left.next;
right = right.next;
}
left.next = left.next.next;
return dummy.next;
}
}
思考:使用栈
链接地址:LeetCode148.排序链表
注意:题目要求时间空间复杂度分别为 O(nlogn)和 O(1)
想到时间复杂度的排序算法有快速排序(Quick Sort)、归并排序(Merge Sort)、堆排序(Heap Sort)、希尔排序(Shell Sort)
很明显 这道题要采用分治的思想 需要采用归并排序
对链表自顶向下归并排序的过程如下
1.找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
2.对两个子链表分别排序。
3.将两个排序后的子链表合并,得到完整的排序后的链表。可以使用21.合并两个有序链表的做法,将两个有序的子链表进行合并
也可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 1,即当链表为空或者链表只包含 1个节点时,不需要对链表进行拆分和排序。
coding实现如下
class Solution {
public ListNode sortList(ListNode head) {
return sortList(head, null);
}
public ListNode sortList(ListNode head, ListNode tail) {
if (head == null) {
return head;
}
if (head.next == tail) {
head.next = null;
return head;
}
ListNode slow = head, fast = head;
while (fast != tail) {
slow = slow.next;
fast = fast.next;
if (fast != tail) {
fast = fast.next;
}
}
ListNode mid = slow;
ListNode list1 = sortList(head, mid);
ListNode list2 = sortList(mid, tail);
ListNode sorted = merge(list1, list2);
return sorted;
}
//合并链表
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummyHead = new ListNode(0);
ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
while (temp1 != null && temp2 != null) {
if (temp1.val <= temp2.val) {
temp.next = temp1;
temp1 = temp1.next;
} else {
temp.next = temp2;
temp2 = temp2.next;
}
temp = temp.next;
}
if (temp1 != null) {
temp.next = temp1;
} else if (temp2 != null) {
temp.next = temp2;
}
return dummyHead.next;
}
}
如果相交,链表A: a+c, 链表B : b+c. a+c+b+c = b+c+a+c 。则会在公共处c起点相遇。若不相交,a +b = b+a 。因此相遇处是NULL
即数学原理:当两个链表相交时,两个指针分别遍历完自己的链表再遍历对方的链表,这样使得它们遍历的总长度相等
coding如下:
//设A的长度为a+c,B的长度为b+c;其中c为A、B的公共部分;
// 拼接AB、BA:A+B=a+c+b+c B+A=b+c+a+c;由于a+c+b=b+c+a,因此二者必定在c的起始点处相遇
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
链接地址:LeetCode206.反转链表
在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用
/**
* 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) {
if(head == null){
return null;
}
//用于保存当前节点的前一个节点
ListNode pre = null;
ListNode cur = head;
while(cur != null){
//保存当前节点的后续节点
ListNode next = cur.next;
//将pre赋值给当前节点的下一个节点
cur.next = pre;
//更新两个节点
pre = cur;
cur = next;
}
return pre;
}
}
方法二:递归
主要是node.next.next=node的使用 即当前节点的下一个节点的下一个节点指向自己 即可实现当前节点的反转
coding实现如下:
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}