反转链表Ⅰ
单向链表是一种动态存储结构,只有两个成员,一个是data,一个是指向后继节点的指针
题目:
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
/*
* 单向链表只有两个成员,一个是值,一个是指向后继节点的指针
* 要起到反转链表的作用,只需要改变指针的指向,每个节点都指向它的前驱节点,即可实现反转的效果
* 示例:
* (1) 1 → 2 → 3 → 4 → 5 → NULL
* (2) NULL ← 1 2 → 3 → 4 → 5 → NULL
* (3) NULL ← 1 ← 2 3 → 4 → 5 → NULL
* (4) NULL ← 1 ← 2 ← 3 4 → 5 → NULL
* (5) NULL ← 1 ← 2 ← 3 ← 4 5 → NULL
* (6) NULL ← 1 ← 2 ← 3 ← 4 ← 5
*
* 对于改变节点指针指向的方法:
* 顺序遍历链表,遍历过程中,记录前驱节点,当前节点,后继节点
* 记录后继节点:防止链表后续段丢失
* 将当前节点的指向后继节点的指针赋值为前驱节点
*/
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
head = swapNotePt(nullptr, head, head->next);
return head;
}
/*
* 前驱节点:prec
* 当前节点:node
* 后继节点:succor
*/
ListNode* swapNotePt(ListNode* prec, ListNode* node, ListNode* succ)
{
// 递归基
if (succ == nullptr) // 当后继节点为空时,说明当前节点已经是最后一个非空节点,
{ // 将其链表指针指向前驱节点,即完成递归
node->next = prec;
return node; // 返回表头
}
node->next = prec; // 当前节点指向前驱节点
prec = node; // 进行递归,前驱节点赋值为当前节点
node = succ; // 当前节点赋值为其后继节点
return swapNotePt(prec, node, node->next);
}
};
复杂度分析:
对于递归函数
时间复杂度:每一次递归,进行当前节点的指向,由指向后继,变为指向前驱,时间复杂度为O(n),交换指向的时间复杂度为O(1)
故,总时间复杂度为O(n)
空间复杂度:没有使用额外的空间
递归进行下去,肯定会运行到后继节点为空的非空节点,会通过递归基结束递归
反转链表Ⅱ
题目:
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL,m = 2, n = 4
输出: 1->4->3->2->5->NULL
/*
* 反转步骤可以演示为以下步骤
* (1) 1 → 2 → 3 → 4 → 5 → NULL
* (2) 1 → 2 | NULL ← 2 3 → 4 → 5 → NULL
* (3) 1 → 2 | NULL ← 2 ← 3 4 → 5 → NULL
* (4) 1 → 2 | NULL ← 2 ← 3 ← 4 5 → NULL
* ↑ ↑ ↑ ↑
* m-1 m n n+1
* (5) 将2(节点m)的next指向5(节点n+1),将1(节点m-1)的next指向4(节点n)
* (6) 1 → 4 → 3 → 2 → 5 → NULL
*/
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode* node = head; // 当前节点:node
ListNode* leader = nullptr; // 节点 m-1
for (int i = 1; i <= m - 1; ++i)
{
leader = node;
node = node->next;
}
ListNode* tempNode = node; // 记录节点 m
ListNode* prec = nullptr; // 前驱节点:prec
ListNode* succ = node->next;// 后继节点:succ
for (int i = m; i < n; ++i)
{
node->next = prec; // 当前节点指向前驱节点
prec = node; // 进行递归,前驱节点赋值为当前节点
node = succ; // 初始化当前节点为当前节点的后继节点
succ = node->next; // 初始化后继节点为当前节点的后继节点的后继节点
}
node->next = prec; // 实现 m ~ n 反转
// 此时node为节点 n,succ为节点 n+1
// 对于第(5)点m.next → n+1,(m - 1).next → n
tempNode->next = succ; // tempNode为节点m,进行 m.next → n+1 步骤
if (leader != nullptr)
leader->next = node; // leader为节点m - 1,进行 (m - 1).next → n
if (m == 1)
head = node; // 如果head包含在反转范围内,则返回新head
return head;
}
};
复杂度分析:
时间复杂度:需要一直遍历到节点n,时间复杂度与给定参数n有关,为O(n),交换指向的时间复杂度为O(1)
故,总时间复杂度为O(n)
空间复杂度:定义了常数个指针,空间复杂度为O(1)
对反转链表Ⅱ总结:
需要考虑一些边界:
- 当链表头包含在反转范围内时,链表头会变化为反转范围内的最后一个节点。