LeetCode:链表专题
链表专题
参考了力扣加加对与链表专题的讲解,刷了些 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
先穿再排后判空:
- 先写逻辑;
- 再对逻辑排序;
- 再判断特殊条件
题目
典型题目:
索引
题目
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:链表专题的更多相关文章
- LeetCode链表专题
链表 套路总结 1.多个指针 移动 2.虚假链表头:凡是有可能删除头节点的都创建一个虚拟头节点,代码可以少一些判断(需要用到首部前一个元素的时候就加虚拟头指针) 3.快慢指针 如leetcode160 ...
- LeetCode 单链表专题 (一)
目录 LeetCode 单链表专题 <c++> \([2]\) Add Two Numbers \([92]\) Reverse Linked List II \([86]\) Parti ...
- LeetCode刷题 链表专题
链表专题 链表题目的一般做法 单链表的结构类型 删除节点 方法一 方法二 增加节点 LeedCode实战 LC19.删除链表的倒数第N个结点 解法思路 LC24.两两交换链表中的节点 解法思路 LC6 ...
- LeetCode 字符串专题(一)
目录 LeetCode 字符串专题 <c++> \([5]\) Longest Palindromic Substring \([28]\) Implement strStr() [\(4 ...
- [LeetCode] [链表] 相关题目总结
刷完了LeetCode链表相关的经典题目,总结一下用到的技巧: 技巧 哑节点--哑节点可以将很多特殊case(比如:NULL或者单节点问题)转化为一般case进行统一处理,这样代码实现更加简洁,优雅 ...
- LeetCode树专题
LeetCode树专题 98. 验证二叉搜索树 二叉搜索树,每个结点的值都有一个范围 /** * Definition for a binary tree node. * struct TreeNod ...
- Leetcode链表
Leetcode链表 一.闲聊 边学边刷的--慢慢写慢慢更 二.题目 1.移除链表元素 题干: 思路: 删除链表节点,就多了一个判断等值. 由于是单向链表,所以要删除节点时要找到目标节点的上一个节点, ...
- [LeetCode 总结帖]: 链表专题
链表在笔试面试中都是出镜率极高的一种数据结构. 由于链表具有结构简单,代码量较少,变化多,可以较为全面的考察应聘者的逻辑思考能力以及应变能力的特点,而备受面试官青睐. 在本节中,我将Leetcode中 ...
- [LeetCode] 链表反转相关题目
暂时接触到LeetCode上与链表反转相关的题目一共有3道,在这篇博文里面总结一下.首先要讲一下我一开始思考的误区:链表的反转,不是改变节点的位置,而是改变每一个节点next指针的指向. 下面直接看看 ...
随机推荐
- NumPy的基本操作
1 简介 NumPy 是用于处理数组的 python 库,部分用 Python 编写,但是大多数需要快速计算的部分都是用 C 或 C ++ 编写的.它还拥有在线性代数.傅立叶变换和矩阵领域中工作的函数 ...
- Python - 面向对象编程 - __del__() 析构方法
del 语句 Python 提供了 del 语句用于删除不再使用的变量 语法 del 表达式 删除变量的栗子 var = "hello" del var print(var) # ...
- Fastjson 1.2.22-24 反序列化漏洞分析(1)
Fastjson 1.2.22-24 反序列化漏洞分析(1) 前言 FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转 ...
- Map集和
目录 Map 特点 继承树 常用方法 entrySet 方法 HashMap 特点 HashMap 的重要常量 存储结构 jdk1.8 总结 面试题 HashMap存储自定义类型键值 LinkedHa ...
- Java学习笔记--注解和反射
注解和反射 1. 注解 注解作用: 对程序做出解释 被其他程序读取 注解格式: @注释名,还可以添加一些参数值,例如@SuppressWarnings(value="unchecked&qu ...
- 谈谈raft fig8 —— 迷惑的提交条件和选举条件
谈谈raft fig8 -- 迷惑的提交条件和选举条件 前言 这篇文章的思路其实在两个月前就已经成型了,但由于实习太累了,一直没来得及写出来.大概一个月前在群里和群友争论fig8的一些问题时,发现很多 ...
- 343 day08File类、递归
day08[File类.递归] 主要内容 File类 递归 教学目标 [ ] 能够说出File对象的创建方式 [ ] 能够说出File类获取名称的方法名称 [ ] 能够说出File类获取绝对路径的方法 ...
- Linux没有/var/log/messages日志文件
1.新安装的CentOS8没有/var/log/messages日志文件: 安装rsyslog: dnf install -y rsyslog 或 yum install -y rsys ...
- Python+Selenium:初步使用Chrome谷歌浏览器
·············环境结合··············· 我的环境:window10 64位 Python 3.7 32-bit selenium 3.141.0 Goo ...
- Linux系列(2) - 命令提示符
命令提示符 起始符 [root@localhost ~]# root:当前登录用户 localhost:主机名 ~:当前所在目录(家目录);管理员为 /root ,user用户为 /home/user ...