4.<tag-链表的反转>lt.206-反转链表 + lt.92-反转链表 II 1
文章目录
lt.206-反转链表
[案例需求]
1, 头插法;
2. 栈;
3. 原地修改
4. 迭代法
[迭代法第一种, 头插法]
- 绝对的面试高频题型, 需要同时掌握迭代法和递归法
- 迭代法, 遍历原链表, 把遍历到的每个节点使用头插法插入到新的链表头结点的后面.
[代码实现]
class Solution {
public ListNode reverseList(ListNode head) {
//迭代法, 遍历链表, 把遍历到的结点以头插法的方式插入到一个新链表的头结点中
//遍历原有链表为空时, 返回新链表的头结点即可
ListNode dummyNode = new ListNode(-1);
ListNode temp = head;
ListNode temp_cur = temp;
while(true){
if(temp == null)break;
temp_cur = temp.next; // temp指代的某个节点要被插入到新链表中了, 我们需要使用一个变量存储此时temp的下一个结点
temp.next = dummyNode.next;
dummyNode.next = temp;
temp = temp_cur; //temp指针回到原链表中, 并指向原来指向结点的下一个结点
}
return dummyNode.next;
}
}
[迭代法第二种, 原地反转]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//链表原地反转
ListNode curr = head; //当前结点
ListNode prev = null; //当前节点的前一个结点
//目的: 将 1->2->3->4->5 变为
// 1 <- 2 <- 3 <- 4 <- 5 <-prev , 返回prev就够了
while(curr != null){
ListNode next = curr.next; //保存当前节点的下一个节点
curr.next = prev; // 当前节点链表反转
prev = curr; //后移, 继续指示着下一个curr的前一个结点
curr = next; //当前节点后移
}
return prev;
}
}
[迭代法第三种, 栈]
//2. 栈
/**
* 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) {
//1. 遍历, 头插法
//2. 节点放入栈中
//3. 翻转两个节点之间的next域
//4. 递归
Deque<ListNode> stack = new LinkedList<>();
ListNode temp = head;
while(true){
if(temp == null)break;
stack.push(temp); // 还没断开
temp = temp.next;
}
//取出, 连接;
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while(!stack.isEmpty()){
cur.next = stack.pop();
cur = cur.next;
}
cur.next = null; //断开head的next域
return dummy.next;
}
}
[递归法第一种, 递归法]
- 递归法,
- 确定函数的参数列表和功能, 参数是原有链表中的每一个节点, 返回值是
- 确定递归出口, 遇到结点为空时, 退出递归
- 确定单层递归逻辑,
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;
}
}
lt.92-反转链表 II
[案例需求]
[思路分析一, 模拟]
思路:切断left到right的子链,然后反转,最后再反向连接
复杂度:时间复杂度O(n),空间复杂度O(1)
pre ---->rightNodeleftNode—>post
转为: pre---->leftNoderightNode---->post
[代码实现]
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
//把链表要反转的部分断开,反转后再接回来
//1. 遍历找到位置
// pre 最终位置为 leftNode的前一个节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode; /
// pre --> leftNode(head1) --> rightNode --> rear
for(int i = 0; i < left - 1; i++){
pre = pre.next;
}
//2. 赋值
ListNode rightNode = pre;
//3. 遍历查找
// rightNode 最终位置为待反转序列的最后一个节点
for(int i = 0; i < right - left + 1; i++){
rightNode = rightNode.next;
}
//4.保留左右位置
ListNode leftNode = pre.next;
ListNode rear = rightNode.next;
//5. 断开链表
pre.next = null;
rightNode.next = null;
//6. 反转链表
reverseList(leftNode);
//7. 接回去
pre.next = rightNode;
leftNode.next = rear;
return dummyNode.next;
}
//利用头插法反转链表
public void reverseList(ListNode head){
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode temp = head;
while(temp != null){
ListNode cur = temp.next;
temp.next = dummyNode.next;
dummyNode.next = temp;
temp = cur;
}
}
改进做法, 找到left的前一个位置后, 用 pre指针标记
//然后把left-->right的元素使用头插法插入到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 reverseBetween(ListNode head, int left, int right) {
//遍历到left前一个节点, 断开与left的链接,
//单独对left->right进行翻转
// pre-->rightNode ---> leftNode ---> post
// pre--->leftNOde ---> rightNode--->post
ListNode dummy = new ListNode(-1);
dummy.next = head;
//用cur遍历链表,记录反转链表的前一个节点pre,
//pre后面的第一个节点是rightNode, 作为被翻转链表的末尾节点
//用cur继续遍历right-left次, 来到被反转链表的最后一个节点leftNode;
//leftNode后面的第一个节点是post, 代表着反转链表后面的节点
ListNode cur = dummy;
for(int i = 0; i < left - 1; i++){
cur = cur.next;
}
//pre->rightNode
ListNode pre = cur;
ListNode rightNode = cur.next;
pre.next = null; //断开pre
//重新建立temp, 遍历到反转链表段的最后一个节点
ListNode temp = rightNode;
//temp遍历了right-left次, 来到被反转链表段的最后一个节点, 我们叫leftNode
for(int i = 0; i < right - left; i++){
temp = temp.next;
}
//post是反转链表段后面的一个节点
ListNode post = temp.next;
temp.next = null;//断开反转链表段的右侧连接
//rightNode----leftNode 链表段的反转
ListNode leftNode = reverse(rightNode);
pre.next = leftNode; // pre--->leftNode=====rightNode--->post
rightNode.next = post;
return dummy.next;
}
public ListNode reverse(ListNode head){
ListNode pre = null;
ListNode temp = head;
while(true){
if(temp == null)return pre;
ListNode temp_next = temp.next;
temp.next = pre;
pre = temp;
temp = temp_next;
}
}
}
[思路分析二, 结合头插法]
[代码实现]
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
// 定义一个dummyHead, 方便处理
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
// 初始化指针
ListNode g = dummyHead;
ListNode p = dummyHead.next;
// 将指针移到相应的位置
for(int step = 0; step < m - 1; step++) {
g = g.next; p = p.next;
}
// 头插法插入节点
for (int i = 0; i < n - m; i++) {
ListNode removed = p.next;
p.next = p.next.next;
removed.next = g.next;
g.next = removed;
}
return dummyHead.next;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)