旋转链表 (2)

本文详细解析了如何使用迭代法反转链表中指定区间的节点,以示例12345678和反转区间2到6为例,通过创建哑节点dummy辅助操作,避免丢失反转部分的节点,并确保正确连接。文章强调了关键步骤,包括保存节点的下一位、断开和重新连接节点关系,以及返回dummy->next。

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

这题写了好多次,老是记不住关键点。
比如输入的是 1,2,3,4,5,6,7,8。
2,6。也就是要反转为1,6,5,4,3,2,1,7,8.
大致思路就是,先找到要移动的第一个节点的前一位(叫它pre吧),然后用一个指针(叫它cur吧)指向第一个要移动的结点开始循环,每次循环都会把cur的下一位移动到pre的下一位,期间cur一直指向一个结点。大致过程是

1 2 3 4 5 6 7 8//cur指向2,cur的下一位是3,把3移动到pre的下一位,也就是1的下一位。
1 3 2 4 5 6 7 8//cur还是指向2,cur的下一位是4,把4移动到pre的下一位
1 4 3 2 5 6 7 8//cur还是指向2,下一位是5......
1 5 4 3 2 6 7 8
1 6 5 4 3 2 7 8

代码如下

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *dummy = new ListNode();//很关键,首先方便了以后找left的前一位,而且如果left等于1(也就是从第一个点开始反转,直接return head会少丢掉被反转的元素),最后return dummy->next是不会错的
        dummy->next = head;
        ListNode *pre = dummy;//pre要指向要反转的元素的前一个元素
        int i;
        for(i = 0; i < left - 1; i++)//移动pre到left的前一位
        {
            pre = pre->next; 
        }
        ListNode* cur = pre->next;//cur指向当前需要反转的结点
        for(i = left; i < right; i++)
        {
            ListNode* p = cur->next;//p来保存cur的下一位的地址
            cur->next = p->next;//断开cur和p的连接,让cur指向p的下一位
            p->next = pre->next;//断开p和下一位的连接,让p指向pre的下一位(千万别写成p->next = cur,cur的位置是一直在变的,而pre->next一直都是那个地方)
            pre->next = p;//断开pre和cur的连接,让pre的下一位为p
        }
        return dummy->next;
}

最关键的就是

p->next = pre->next;
//不要写成  p->next = cur;

还有最后返回的是dummy->next而不是head

### 解题思路 解决 LeetCode 61 题“旋转链表”的核心思想是找到旋转后的新节点并重新连接链表。为了高效处理,通常采用以下步骤: - **计算链表长度**:遍历整个链表以确定其长度 `length`。 - **特殊情况判断**:如果 `k == 0` 或者链表为空,则直接返回原节点。 - **取模运算优化旋转次数**:由于链表旋转 `length` 次后会恢复原状,因此只需要执行 `k % length` 次旋转。 - **快慢指针或前后指针法**:使用双指针方法快速定位新节点尾节点。 - **链表拼接**:将链表尾部连接到部,并在合适的位置断开形成新的链表结构。 --- ### 方法一:快慢指针 + 取模优化 该方法通过快慢指针来寻找新节点与旧尾部的连接点。 ```java class Solution { public ListNode rotateRight(ListNode head, int k) { if (head == null || k <= 0) return head; ListNode front = head, behind = head; int len = 0; // 找出链表长度,并调整front指针位置 while (k-- > 0) { front = front.next; len++; if (front == null) { k %= len; front = head; } } // 如果不需要旋转,直接返回原节点 if (front == head) return head; // 同时移动frontbehind,直到front指向最后一个节点 while (front.next != null) { front = front.next; behind = behind.next; } // 调整链表连接关系 ListNode newHead = behind.next; front.next = head; behind.next = null; return newHead; } } ``` --- ### 方法二:整体长度计算 + 切分点查找 此方法首先计算链表总长度,再根据 `k % length` 确定切分点,然后进行节点重连。 ```java class Solution { public ListNode rotateRight(ListNode head, int k) { if (head == null || k == 0) return head; ListNode node = head; int length = 0; // 计算链表长度 while (node.next != null) { node = node.next; length++; } length++; // 取模优化旋转次数 k = k % length; if (k == 0) return head; // 将链表首尾相连 node.next = head; // 移动指针到断开位置 for (int i = 0; i < length - k; i++) { node = node.next; } // 新节点为当前节点的下一个节点 ListNode newHead = node.next; node.next = null; return newHead; } } ``` --- ### 方法三:快慢指针 + 明确切分点 该方法明确计算出切分点位置,并通过快慢指针分别定位到关键节点。 ```java class Solution { public ListNode rotateRight(ListNode head, int k) { if (head == null) return head; int length = 1; ListNode oldTail = head; // 获取链表长度 while (oldTail.next != null) { oldTail = oldTail.next; length++; } // 取模优化 k = k % length; if (k == 0) return head; // 找到新的尾节点 ListNode newTail = head; for (int i = 0; i < length - k - 1; i++) { newTail = newTail.next; } // 新节点尾节点的连接 ListNode newHead = newTail.next; newTail.next = null; oldTail.next = head; return newHead; } } ``` --- ### 时间复杂度分析 - **时间复杂度**:`O(n)`,其中 `n` 是链表长度。需要两次遍历链表(一次计算长度,一次调整指针)。 - **空间复杂度**:`O(1)`,仅使用常数级额外空间。 --- ### 总结 以上三种解法均基于快慢指针或前后指针的思想,结合取模操作优化旋转次数,从而避免不必要的重复操作。不同实现方式各有侧重,但核心逻辑相似,适用于不同场景下的链表旋转问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值