链表专题

参考了力扣加加对与链表专题的讲解,刷了些 leetcode 题,在此做一些记录,不然没几天就没印象了

出处:力扣加加-链表专题

总结

leetcode 中对于链表的定义

// 定义方式1:
// 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; }
} // 定义方式2:
// Definition for singly-linked list.
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}

链表的基本操作:

// 插入: B 插入到 AC之间
temp = A.next;
A.next = B;
B.next = temp // 删除:ABC节点,删除B节点
A.next = C
// or
A.next = B.next.next // 遍历
cur = head
while(cur != null){
// 操作 ...
cur = cur.next;
}

如果是头尾节点等特殊情况的话,需要另做考虑

一个原则

画图,真的很有用

两个考点

修改指针:反转链表为典型代表,206. 反转链表

链表拼接:如,21. 合并两个有序链表

三个注意

:快慢指针判断环

边界:看题,多思考

前后序:

  • 前序遍历

    void function(ListNode head){
    if (head == null){
    return;
    }
    // 主逻辑....
    function(head.next);
    }
  • 后续遍历

    void function(ListNode head){
    if (head == null){
    return;
    }
    function(head.next);
    // 主逻辑....
    }
  • 原话摘录:如果是前序遍历,那么你可以想象前面的链表都处理好了,怎么处理的不用管如果是后序遍历,那么你可以想象后面的链表都处理好了,怎么处理的不用管

四个技巧

虚拟头

新设立一个节点,用于指向头节点,后续处理完成后,可以返回这个节点

ListNode ans = new ListNode(-1, null);
// ans 不变,后续对于head的操作都不会影响ans,ans.next还是指向最初的head节点
ans.next = head;
// ...
return ans.next;

快慢指针141. 环形链表142. 环形链表 II

穿针引线61. 旋转链表92. 反转链表 II

先穿再排后判空

  1. 先写逻辑;
  2. 再对逻辑排序;
  3. 再判断特殊条件

题目

典型题目:

索引

题目

206. 反转链表

// 正向循环
public ListNode reverseList(ListNode head) {
ListNode pre = null; // 前一个节点
ListNode cur = head; // 当前节点
ListNode next = null; // 后一个节点 while(cur != null){
// 留下联系方式
next = cur.next;
// 修改指针
cur.next = pre;
// 继续往下走
pre = cur;
cur = next;
}
// 反转后的新的头节点返回出去
return pre;
} // 递归方式
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null){
return head;
} ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}

21. 合并两个有序链表

// 方法1:遍历法
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 虚拟头节点
ListNode head = new ListNode(-1, null);
ListNode temp = head; // 开始遍历两个链表
while (l1 != null && l2 != null){
if (l1.val < l2.val){
temp.next = l1;
l1 = l1.next;
}else{
temp.next = l2;
l2 = l2.next;
}
temp = temp.next;
} // 剩下的接上
temp.next = l1 == null ? l2: l1;
return head.next;
} // 方法2:递归-后序
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null){
return l2;
}else if(l2 == null){
return l1;
}else if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}else{
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}

141. 环形链表

// 快慢指针法
public boolean hasCycle(ListNode head) { ListNode fast = head;
ListNode slow = head; while(fast != null && fast.next!=null){
fast = fast.next.next; // 快指针走两步
slow = slow.next; // 慢指针走一步
if(fast == slow){
return true;
}
}
return false;
} // hash法
public boolean hasCycle(ListNode head) { Set<ListNode> hashSet = new HashSet<>();
while(head != null){
if(!hashSet.add(head)){
return true;
}
head = head.next;
}
return false;
}

142. 环形链表 II

// hash表
public ListNode detectCycle(ListNode head) { Set<ListNode> hashSet = new HashSet<>(); while(head != null){
if(!hashSet.add(head)){
return head;
}
head = head.next;
}
return null;
} // 参考来的:快慢指针法
// 参考地址:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
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(fast == slow){
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
return null;
}

61. 旋转链表

public ListNode rotateRight(ListNode head, int k) {

    // 判断
if (head == null || head.next == null || k == 0){
return head;
} // 记录头节点
ListNode ans = new ListNode(-1);
ans.next = head;
ListNode last = null; // 获取链表的长度
int length = 1;
while (head.next != null){
head = head.next;
length ++;
}
last = head; // 当k为length时,直接返回链表即可
if (k % length == 0){
return ans.next;
} // 处理k大于length的情况
int new_k = k % length; // 找到循环后的头节点
int headIndex = length - new_k;
ListNode pre = null;
ListNode cur = ans.next;
while(headIndex != 0){
pre = cur;
cur = cur.next;
headIndex-=1;
} // 改变指针方向
last.next = ans.next;
pre.next = null; return cur;
}

92. 反转链表 II

public ListNode reverseBetween(ListNode head, int m, int n) {

    // 虚拟头
ListNode ans = new ListNode(-1);
ans.next = head; ListNode pre = null;
ListNode next = null; ListNode m_before = null;
ListNode m_node = null; int count = 0;
while(head != null){
count++; if(count+1 == m){
// 找到m位置的前一个节点
m_before = head;
}else if(count == m){
// 找到m节点
m_node = head;
} if (count >= m && count <= n){
// 开始反转m节点之后的内容
next = head.next;
head.next = pre;
pre = head;
head = next;
if (count == n){
// 反转到n结点后结束
m_node.next = head;
if (m_before != null){
m_before.next = pre;
pre = ans.next;
}
break;
}
continue;
} head = head.next;
}
return pre;
}

其他题目:

索引

题目

25. K 个一组翻转链表

public ListNode reverseKGroup(ListNode head, int k) {
if(head == null){
return head;
} ListNode a = head;
ListNode b = head; for(int i=0; i<k; i++){
if(b == null){
return head;
}
b = b.next;
} // 反转前 k 个元素
ListNode newHead = reverse(a, b);
a.next = reverseKGroup(b, k); return newHead; } // 反转 [a, b) 之间的元素
ListNode reverse(ListNode a, ListNode b){ ListNode pre = null;
ListNode cur = a;
ListNode next = null; while(cur != b){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}

82. 删除排序链表中的重复元素 II

public ListNode deleteDuplicates(ListNode head) {

    // 虚拟头
ListNode ans = new ListNode(-1);
ans.next = head; // set中存了有重复的值
Set<Integer> set = new HashSet<>(); while ( head != null){
// 把存在重复的值都放入set中
if (head.next != null && head.val == head.next.val){
set.add(head.val);
}
head = head.next;
} // set不为空,说明有重复元素存在
if (!set.isEmpty()){
head = ans.next;
ans.next = null;
ListNode pre;
pre = ans;
while (head != null){
if(!set.contains(head.val)){
pre.next = new ListNode(head.val);
pre = pre.next;
}
head = head.next;
}
}
return tmp.next;
} // 参考解法:
public ListNode deleteDuplicates(ListNode head) {
// 创建虚拟头
ListNode ans = new ListNode(-1);
ans.next = head;
ListNode cur = ans; while(cur.next != null && cur.next.next != null){
if(cur.next.val == cur.next.next.val){
ListNode tmp = cur.next;
while(tmp.next != null && tmp.val == tmp.next.val){
tmp = tmp.next;
}
cur.next = tmp.next;
}else{
cur = cur.next;
}
} return ans.next;
}

83. 删除排序链表中的重复元素

// 遍历思路
public ListNode deleteDuplicates(ListNode head) {
ListNode ans = new ListNode(-1);
ans.next = head; while (head != null && head.next != null){
if (head.val == head.next.val){
ListNode tmp = head.next;
while(tmp.next != null && tmp.val == tmp.next.val){
tmp = tmp.next;
}
head.next = tmp.next;
}
head = head.next;
}
return ans.next;
} // 官方解法
public ListNode deleteDuplicates(ListNode head) { ListNode cur = head; while(cur != null && cur.next!=null){
if (cur.val == cur.next.val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
} return head;
} // 递归解法:
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null){
return head;
} if(head.val == head.next.val){
return deleteDuplicates(head.next);
}else{
head.next = deleteDuplicates(head.next);
return head;
}
}

86. 分隔链表

public ListNode partition(ListNode head, int x) {

    // 虚拟头
ListNode ans = new ListNode(-1);
ListNode tmp = ans; // 串联比x小的节点 ListNode ans2 = new ListNode(-2);
ListNode tmp2 = ans2; // 串联比x大的节点 while (head != null){
if (head.val < x){
tmp.next = head;
tmp = tmp.next;
}else{
tmp2.next = head;
tmp2 = tmp2.next;
}
head = head.next;
} tmp2.next = null;
tmp.next = ans2.next; return ans.next;
}

138. 复制带随机指针的链表

public Node copyRandomList(Node head) {

    Node ans = new Node(-1);
ans.next = head; Node tmp;
// node1: 原节点 node2:新节点
HashMap<Node, Node> hashMap = new HashMap<>(); while(head != null){
tmp = new Node(head.val);
hashMap.put(head, tmp);
head = head.next;
} head = ans.next;
while(head != null){ tmp = hashMap.get(head);
tmp.next = hashMap.get(head.next);
tmp.random = hashMap.get(head.random); head = head.next;
} return hashMap.get(ans.next);
}

143. 重排链表

// 效率较低
public void reorderList(ListNode head) {
ListNode ans = new ListNode(-1);
ans.next = head; // 记录链表长度
int count = 0;
// 记录每个节点
HashMap<Integer, ListNode> hashMap = new HashMap<>(); while(head != null){
hashMap.put(count++, head);
head = head.next;
} ListNode tmp = null;
for(int i=0; i < count/2; i++){
ListNode first = hashMap.get(i);
ListNode last = hashMap.get(count-i-1);
tmp = first.next; if(tmp == last){
last.next = null;
}else{
first.next = last;
last.next = tmp;
}
} if (tmp!=null && tmp.next != null){
tmp.next = null;
}
} // 官方解法,与我逻辑上相同,但是编程更加简洁
public void reorderList(ListNode head) {
if(head == null){
return;
} List<ListNode> list = new ArrayList<>();
ListNode node = head;
while(node != null){
list.add(node);
node = node.next;
} int i = 0, j = list.size() - 1;
while(i < j){
list.get(i).next = list.get(j);
i++;
if(i==j){
break;
}
list.get(j).next = list.get(i);
j--;
}
list.get(i).next = null;
} /***********************************************/
// 官方解法:寻找链表中点 + 链表逆序 + 合并链表
public void reorderList(ListNode head) { if (head == null){
return;
}
// 找到链表中点
ListNode mid = middleNode(head);
ListNode l1 = head; // 左边的链表头
ListNode l2 = mid.next; // 右边的链表头
mid.next = null; // 链表逆序
l2 = reverseList(l2); // 合并链表
mergeList(l1, l2);
} // 找到链表中点: 调整:相同返回前面的
public ListNode middleNode(ListNode head){
ListNode fast = head;
ListNode slow = head; while(fast.next != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
} return slow;
} // 链表逆序
public ListNode reverseList(ListNode head){
ListNode pre = null;
ListNode cur = head;
ListNode next = null; while(cur != null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
} return pre;
} // 合并链表
public void mergeList(ListNode l1, ListNode l2){ ListNode tmp1;
ListNode tmp2; while(l1 != null && l2 != null){
tmp1 = l1.next;
tmp2 = l2.next; l1.next = l2;
l2.next = tmp1; l1 = tmp1;
l2 = tmp2;
}
}
/***********************************************/

148. 排序链表

// 解法1:通过list保存所有节点+对list进行排序实现
public ListNode sortList(ListNode head) { if (head == null){
return null;
} List<ListNode> list = new ArrayList<>(); while(head != null){
list.add(head);
head = head.next;
} list.sort(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
}); for(int i=0; i < list.size()-1; i++){
list.get(i).next = list.get(i+1);
} list.get(list.size() - 1).next = null; return list.get(0);
} // 解法2:归并排序:递归实现
// 时间复杂度:O(nlogn)
// 空间复杂度:O(n),递归过程中要开辟和原链表同样长度的节点数目
public ListNode sortList(ListNode head) { if(head == null || head.next == null){
return head;
} // 切分:把链表切分成左右两部分
ListNode fast = head, slow = head;
while(fast.next != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
}
// 右边链表的起始点
ListNode tmp = slow.next;
// 左右链表之间的连接
slow.next = null;
// 递归继续切分:直到切分至只有一个节点为止,进行后续的合并操作
ListNode left = sortList(head);
ListNode right = sortList(tmp); // 开始进行合并操作
// 保存left和right合并后的头节点,用于后续的合并
ListNode ans = new ListNode(-1);
ListNode tmp_ans = ans;
while(left != null && right != null){
if(left.val < right.val){
tmp_ans.next = left;
left = left.next;
}else{
tmp_ans.next = right;
right = right.next;
}
tmp_ans = tmp_ans.next;
}
tmp_ans.next = left != null ? left : right;
return ans.next;
}

234. 回文链表

// YES:快慢指针找到中间节点+反转后续链表+再进行比较+最好恢复一下链表(没做)
public boolean isPalindrome(ListNode head) { if (head == null || head.next == null){
return true;
} // 快慢指针找到中间节点
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next !=null){
fast = fast.next.next;
slow = slow.next;
} // 反转链表: show指针后的链表将其进行反转
ListNode pre = null;
ListNode cur = slow;
ListNode next = null;
while(cur != null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
} // 开始比较了
ListNode cmp_head = pre; while(cmp_head != null){
if (head.val != cmp_head.val){
return false;
}
head = head.next;
cmp_head = cmp_head.next;
}
return true;
} // 好家伙!直接通过数组:快速解决,但是效率不高
public boolean isPalindrome(ListNode head) { if (head == null || head.next == null){
return true;
} List<Integer> list = new ArrayList<Integer>(); while(head != null){
list.add(head.val);
head = head.next;
} // 使用双指针判断是否回文
int front = 0;
int back = list.size()-1; while(front < back){
if (!list.get(front).equals(list.get(back))){
return false;
}
front++;
back--;
}
return true;
}

876. 链表的中间结点

// 快慢指针解题
public ListNode middleNode(ListNode head) { ListNode fast = head;
ListNode slow = head; while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
} return slow;
}

面试题 02.02. 返回倒数第 k 个节点

// 方法1:暴力法
public int kthToLast(ListNode head, int k) { ListNode ans = new ListNode(-1);
ans.next = head; if(head == null){
return 0;
} int size = 0;
while(head != null){
size ++;
head = head.next;
} head = ans.next;
for(int i=0; i<size; i++){
if(i == (size-k)){
return head.val;
}
head = head.next;
} return 0;
} // 方法2:双指针,指针1先走k,然后指针1,2一起走,指针1走到末尾后,返回指针2,指针2的距离即为:size-k
public int kthToLast(ListNode head, int k) { if(head == null){
return 0;
} ListNode node1 = head;
ListNode node2 = head;
while(k-- != 0){
node1 = node1.next;
} while(node1 != null){
node2 = node2.next;
node1 = node1.next;
} return node2.val;
}

面试题 02.03. 删除中间节点

public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}

LeetCode:链表专题的更多相关文章

  1. LeetCode链表专题

    链表 套路总结 1.多个指针 移动 2.虚假链表头:凡是有可能删除头节点的都创建一个虚拟头节点,代码可以少一些判断(需要用到首部前一个元素的时候就加虚拟头指针) 3.快慢指针 如leetcode160 ...

  2. LeetCode 单链表专题 (一)

    目录 LeetCode 单链表专题 <c++> \([2]\) Add Two Numbers \([92]\) Reverse Linked List II \([86]\) Parti ...

  3. LeetCode刷题 链表专题

    链表专题 链表题目的一般做法 单链表的结构类型 删除节点 方法一 方法二 增加节点 LeedCode实战 LC19.删除链表的倒数第N个结点 解法思路 LC24.两两交换链表中的节点 解法思路 LC6 ...

  4. LeetCode 字符串专题(一)

    目录 LeetCode 字符串专题 <c++> \([5]\) Longest Palindromic Substring \([28]\) Implement strStr() [\(4 ...

  5. [LeetCode] [链表] 相关题目总结

    刷完了LeetCode链表相关的经典题目,总结一下用到的技巧: 技巧 哑节点--哑节点可以将很多特殊case(比如:NULL或者单节点问题)转化为一般case进行统一处理,这样代码实现更加简洁,优雅 ...

  6. LeetCode树专题

    LeetCode树专题 98. 验证二叉搜索树 二叉搜索树,每个结点的值都有一个范围 /** * Definition for a binary tree node. * struct TreeNod ...

  7. Leetcode链表

    Leetcode链表 一.闲聊 边学边刷的--慢慢写慢慢更 二.题目 1.移除链表元素 题干: 思路: 删除链表节点,就多了一个判断等值. 由于是单向链表,所以要删除节点时要找到目标节点的上一个节点, ...

  8. [LeetCode 总结帖]: 链表专题

    链表在笔试面试中都是出镜率极高的一种数据结构. 由于链表具有结构简单,代码量较少,变化多,可以较为全面的考察应聘者的逻辑思考能力以及应变能力的特点,而备受面试官青睐. 在本节中,我将Leetcode中 ...

  9. [LeetCode] 链表反转相关题目

    暂时接触到LeetCode上与链表反转相关的题目一共有3道,在这篇博文里面总结一下.首先要讲一下我一开始思考的误区:链表的反转,不是改变节点的位置,而是改变每一个节点next指针的指向. 下面直接看看 ...

随机推荐

  1. NumPy的基本操作

    1 简介 NumPy 是用于处理数组的 python 库,部分用 Python 编写,但是大多数需要快速计算的部分都是用 C 或 C ++ 编写的.它还拥有在线性代数.傅立叶变换和矩阵领域中工作的函数 ...

  2. Python - 面向对象编程 - __del__() 析构方法

    del 语句 Python 提供了 del 语句用于删除不再使用的变量 语法 del 表达式 删除变量的栗子 var = "hello" del var print(var) # ...

  3. Fastjson 1.2.22-24 反序列化漏洞分析(1)

    Fastjson 1.2.22-24 反序列化漏洞分析(1) 前言 FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转 ...

  4. Map集和

    目录 Map 特点 继承树 常用方法 entrySet 方法 HashMap 特点 HashMap 的重要常量 存储结构 jdk1.8 总结 面试题 HashMap存储自定义类型键值 LinkedHa ...

  5. Java学习笔记--注解和反射

    注解和反射 1. 注解 注解作用: 对程序做出解释 被其他程序读取 注解格式: @注释名,还可以添加一些参数值,例如@SuppressWarnings(value="unchecked&qu ...

  6. 谈谈raft fig8 —— 迷惑的提交条件和选举条件

    谈谈raft fig8 -- 迷惑的提交条件和选举条件 前言 这篇文章的思路其实在两个月前就已经成型了,但由于实习太累了,一直没来得及写出来.大概一个月前在群里和群友争论fig8的一些问题时,发现很多 ...

  7. 343 day08File类、递归

    day08[File类.递归] 主要内容 File类 递归 教学目标 [ ] 能够说出File对象的创建方式 [ ] 能够说出File类获取名称的方法名称 [ ] 能够说出File类获取绝对路径的方法 ...

  8. Linux没有/var/log/messages日志文件

    1.新安装的CentOS8没有/var/log/messages日志文件: 安装rsyslog: dnf  install   -y  rsyslog 或 yum  install  -y  rsys ...

  9. Python+Selenium:初步使用Chrome谷歌浏览器

    ·············环境结合··············· 我的环境:window10 64位 Python 3.7 32-bit selenium            3.141.0 Goo ...

  10. Linux系列(2) - 命令提示符

    命令提示符 起始符 [root@localhost ~]# root:当前登录用户 localhost:主机名 ~:当前所在目录(家目录);管理员为 /root ,user用户为 /home/user ...