文章目录
- 一、链表
- 1.1 提纲
- 1.2 链表删除
- 1.2.1 删除排序链表中的重复元素(仅保留一个重复元素)
- 1.2.2 删除排序链表中的重复元素 II (删除所有重复的元素)
- 1.3 链表反转
- 1.3.1 反转链表
- 1.3.2 反转链表
- 1.4 合并链表
- 1.4.1 合并两个有序链表
- 1.4.2 合并K个升序链表
- 1.5 快慢指针
- 1.5.1 链表的中间结点
- 1.5.2 重排链表
- 1.5.3 回文链表
- 1.6 结构判断
- 1.6.1 环形链表
- 1.6.2 环形链表 II
- 1.7 其他
- 1.7.1 删除链表的倒数第 N 个结点
- 1.7.2 复制带随机指针的链表
一、链表
https://chienmy.gitbook.io/algorithm-pattern-java/shu-ju-jie-gou/linked_list
1.1 提纲
- null 异常处理
- dummy node 哑巴结点
- 快慢指针
- 插入一个结点到排序列表
- 从一个链表中移除一个结点
- 翻转链表
- 合并两个链表
- 找到链表的中间结点
1.2 链表删除
1.2.1 删除排序链表中的重复元素(仅保留一个重复元素)
83. 删除排序链表中的重复元素
/**
* 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 {
/**
解题思路:
1、使用辅助结点p指向第一个结点;
2、(注意:前提是已排好序)依次从当前位置的下一个结点开始判断是否与当前结点的值相同
(2-1) 不相同则将当前指针p指向下一个位置
(2-2) 相同则将当前结点的next指向下下个位置,再判断当前位置的下一个位置(即:循环作2的判断。实际是不断的删除与p临近的相同值的结点)
注:任务中需保留一个重复值的结点,我们的方法会保留重复结点的第一个结点
3、返回头指针
*/
public ListNode deleteDuplicates(ListNode head) {
ListNode p = head;
while(p!=null){
while(p.next != null && p.val == p.next.val){
p.next = p.next.next;
}
p = p.next;
}
return head;
}
}
1.2.2 删除排序链表中的重复元素 II (删除所有重复的元素)
82. 删除排序链表中的重复元素 II
/**
* 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 {
/**
思路分析:
1、定义一个头结点,其next指向首元结点(可能删除首元结点)
2、定义一个辅助结点p指向头结点,依次判断当前结点的下一个与下下一个是否重复
(2-1) 如果没有重复,则将p指向下一个结点位置
(2-2) 如果重复则以当前结点作为待删除结点的前一个结点位置,依次删除后面的重复元素结点并修改p的next值
3、返回头结点的next(首元结点)
*/
public ListNode deleteDuplicates(ListNode head) {
// 设置头结点,指向首元结点(由于首元结点可能被删除)
ListNode dummy = new ListNode(-1,head);
// p 指向需要判断是否重复元素的前一个结点位置(由于需要修改被删除结点的前一个结点的next)
ListNode p = dummy;
int n;
while(p.next!=null && p.next.next != null){
// 出现连续两个的重复的结点
if(p.next.val == p.next.next.val){
// 保存结点的重复值
n = p.next.val;
// 依次从第一个重复结点位置向后(头)删除重复元素的结点,并修改前一结点位置的next
while(p.next!=null && p.next.val == n){
p.next = p.next.next;
}
}else{
p = p.next; // 没有连续重复的结点则向后判断
}
}
return dummy.next;
}
}
1.3 链表反转
1.3.1 反转链表
206. 反转链表
/**
* 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 {
/**
思路分析:
method1: 基于迭代法
1、依次创建 指向当前结点与当前结点下一个结点的指针 curr 与 next,并创建一个反转链表的首元结点 prev
2、依次用头插法将当前的结点插入至 prev,并迭代 curr 为 next 直至 尾结点
method2:基于递归(略)
*/
public ListNode reverseList(ListNode head) {
// method1: 基于迭代的方法
// ListNode curr = head,next,prev=null;
// while(curr!=null){
// next = curr.next;
// curr.next = prev;
// prev = curr;
// curr = next;
// }
// return prev;
// method 2: 基于迭代法
return recursion(head);
}
private ListNode recursion(ListNode head){
// head == null 表示头指针为头指针为 null
// head.next == null 表示此时 head 为尾结点
if(head == null || head.next == null){
return head;
}
// newHead 指向原链表的尾结点
ListNode newHead = recursion(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
1.3.2 反转链表
92. 反转链表 II
/**
* 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 {
/**
思路分析:
1、定位至待反转链表首元结点的前一个位置(从新创建的头结点开始)
2、反转区间链表
3、重置原区间链表的左边界前一结点的next与右边界结点的next
*/
public ListNode reverseBetween(ListNode head, int left, int right) {
// 设置哑结点(可能从首元结点开始反转)
ListNode dummy = new ListNode(-1,head);
// 定位到需要反转位置的第一个结点的前一个位置(默认从头结点开始先后搜索)。由于需要在前一个结点的next中保存反转后的首元结点
ListNode p = dummy;
for(int i = 0;i < left-1;i++){
p = p.next;
}
// 从当前位置的右侧第一个结点开始直至right进行反转(基于迭代法)
ListNode curr = p.next,next,prev=null;
for(int i = left;i<=right;i++){
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
// 修改反转后的链表首元结点前一位置的next与,尾结点的next
// 此时 p.next指向反转后链表的尾结点
// curr 指向原链表区间的尾结点的后一个结点(可能为null)
p.next.next = curr;
p.next = prev;
return dummy.next;
}
}
1.4 合并链表
1.4.1 合并两个有序链表
21. 合并两个有序链表
/**
* 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 {
/**
思路分析
1、创建哑结点(头结点)用于保存两条链表上的结点
2、双指针同时遍历得到较小元素的结点并插入结果结果链表中
3、将还有结点的链表插入结果链表
*/
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 设置哑结点(头结点)
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
while(list1!=null && list2!=null){
if(list1.val < list2.val){
p.next = list1;
list1 = list1.next;
}else{
p.next = list2;
list2 = list2.next;
}
p = p.next;
}
p.next = list1!=null?list1:list2;
return dummy.next;
}
}
1.4.2 合并K个升序链表
23. 合并K个升序链表
/**
* 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 {
/**
思路分析:利用归并排序的分治思想
1、递归实现"分"
2、使用合并算法实现"治"
*/
public ListNode mergeKLists(ListNode[] lists) {
// 基于归并排序的思想
if(lists==null || lists.length<=0){
return null;
}
return mergeLists(lists,0,lists.length-1);
}
// 分治算法。先分后治
private ListNode mergeLists(ListNode[] lists,int left,int high){
if(left==high){
return lists[left];
}
int mid = (left+high) >> 1;
ListNode lNode = mergeLists(lists,left,mid);
ListNode rNode = mergeLists(lists,mid+1,high);
return merge(lNode,rNode);
}
// 合并两条链表
private ListNode merge(ListNode l1,ListNode l2){
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
while(l1!=null && l2!= null){
if(l1.val < l2.val){
p.next = l1;
l1 = l1.next;
}else{
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
p.next = l1!=null?l1:l2;
return dummy.next;
}
}
1.5 快慢指针
1.5.1 链表的中间结点
876. 链表的中间结点
/**
* 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 {
/**
思路分析:
基于快慢指针的关系:快指针走的步数是满指针的2倍
*/
public ListNode middleNode(ListNode head) {
// 定义快慢指针
// 快指针每次走2步
ListNode fast = head;
// 慢指针每次走1步
ListNode slow = head;
// situation1:对于偶数个结点的链表,中间结点定位的是相对后一个结点
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
// situation2:对于偶数个结点的链表,中间结点定位的是相对前一个结点
// while(fast.next!=null && fast.next.next!=null){
// fast = fast.next.next;
// slow = slow.next;
// }
return slow;
}
}
1.5.2 重排链表
143. 重排链表
/**
* 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 {
/**
思路分析:
1、找到中间结点(偶数个结点的链表找打相对前一个)。划分前后链表
2、反转后一链表
3、合并前后链表
*/
public void reorderList(ListNode head) {
// 一、划分链表
ListNode lLinkedList = getPreEndListNode(head);
ListNode rLinkedList = lLinkedList.next;
// 二、反转链表
ListNode rrLinkedList = reverseLinkedList(rLinkedList);
// 三、合并两条链表
lLinkedList.next = null; // 设置左链表的尾元素的next为null
ListNode p=head, rr = rrLinkedList,rNext;
while(p!=null && rr!=null){
rNext = rr.next;
rr.next = p.next;
p.next = rr;
p = rr.next;
rr = rNext;
}
}
private ListNode reverseLinkedList(ListNode head){
ListNode curr = head,next,prev=null;
while(curr!=null){
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
private ListNode getPreEndListNode(ListNode head){
// 基于快慢指针,找到中间结点
ListNode slow = head;
ListNode fast = head;
while(fast.next!=null&&fast.next.next!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
1.5.3 回文链表
234. 回文链表
/**
* 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 {
/**
思路分析:
1、基于快慢指针找到中间结点(偶数个结点的链表找到相对前一个)
2、反转后一链表
3、按位置依次比较是否相同
*/
private ListNode reverseLinkedList(ListNode head){
ListNode curr=head,next,prev=null;
while(curr!=null){
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
public boolean isPalindrome(ListNode head) {
// 一、基于快慢指针划分链表
ListNode slow = head;
ListNode fast = head;
while(fast.next!=null&& fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
}
// 二、反转后一链表
ListNode rLinkedList = reverseLinkedList(slow.next);
// 三、依次比较链表
ListNode l1 = head;
ListNode l2 = rLinkedList;
boolean result = true;
while(result && l2!=null){
if(l1.val!=l2.val){
result = false;
}
l1 = l1.next;
l2 = l2.next;
}
// 四、(可选)恢复原链表
// 由于左链表的尾指针一直指向原链表的右半链表的首元结点,因此只需要反转右链表即可
reverseLinkedList(rLinkedList);
return result;
}
}
1.6 结构判断
1.6.1 环形链表
141. 环形链表
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
/**
基于快慢指针
*/
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
return true;
}
}
return false;
}
}
1.6.2 环形链表 II
142. 环形链表 II
https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
解题思路
/**
* 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) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){ // 第一次相遇
fast = head; // 重置 fast 的位置
// 使 slow 与 fast 第二次相遇则是环的入口结点位置
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
// 没有环则返回 null
return null;
}
}
1.7 其他
1.7.1 删除链表的倒数第 N 个结点
19. 删除链表的倒数第 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 dummy = new ListNode(-1,head);
// 基于快慢指针
ListNode slow = dummy;
ListNode fast = dummy;
for(int i = 0;i<n;i++){
fast = fast.next;
if(fast==null){
return null;
}
}
while(fast.next!=null){
fast = fast.next;
slow = slow.next;
}
// 删除倒数第 n 个结点
slow.next = slow.next.next;
return dummy.next;
}
}
1.7.2 复制带随机指针的链表
138. 复制带随机指针的链表
题解:https://leetcode-cn.com/problems/copy-list-with-random-pointer/solution/fu-zhi-dai-sui-ji-zhi-zhen-de-lian-biao-c2nvs/
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
/**
思路分析:method1: 基于迭代的方法
1、依次建立一个与原节点值相同的新节点到原结点的后面
2、复制原结点的random指向的新结点的复制结点,到新节点中(p.next.random = p.random.next 需要注意p.random为null的情况)
3、修改新链表每个结点的next指针,并还原原链表的next指针
*/
public Node copyRandomList(Node head) {
// 基于迭代的方法
if(head == null){
return null;
}
Node p = head,temp;
// 依次在原节点后深复制一个值与原结点相同的结点
while(p!=null){
temp = new Node(p.val);
temp.next = p.next;
p.next = temp;
p = temp.next;
}
// 复制源节点的random指针到新链表的结点中
p = head;
while(p!=null){
p.next.random = ((temp=p.random)==null?temp:temp.next);
p = p.next.next;
}
// 依次构建新链表中结点的next指针,并还原原链表的next
p = head;
Node newHead = head.next;
boolean flag = true;
while(p!=null && flag){
if((temp = p.next.next) == null){
flag = false;
}else{
p.next.next = temp.next; // 新链表结点的next设置
}
p.next = temp; // 还原原始结点的next
p = p.next;
}
return newHead;
}
}
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
/**
思路分析:method2:基于哈希表与回溯算法
- 在基于深度优先遍历记录原结点与新节点的hash对应关系(如果已经遍历则返回hashMap中的值)
*/
private HashMap<Node,Node> hashMap = new HashMap<>();
public Node copyRandomList(Node head) {
return dfs(head);
}
private Node dfs(Node head){
if(head == null){
return head;
}
// 创建当前结点的信息到hashMap
// 若已经遍历则返回存储的结点
if(hashMap.containsKey(head)) return hashMap.get(head);
// 若没有遍历则创建结点后保存并返回
Node clone = new Node(head.val);
hashMap.put(head,clone);
clone.next = dfs(head.next);
clone.random = dfs(head.random);
return clone;
}
}