算法入门 - 链表
3.1 逆序打印链表
利用栈反向输出
class Solution {
public:
//正向遍历链表,并依次将元素值放入数组中,再逆置数组即相当于逆置打印链表
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> retV;
while(head != nullptr) {
retV.push_back(head->val);
head = head->next;
}
reverse(retV.begin(), retV.end());
return retV;
}
};
可以用数组,当然逆序的问题,交给 stack 结构更合适。
利用递归反向输出
class Solution {
public:
//单路递归,递归到空指针处递归结束,回溯时向数组插入当前节点的值。
//不断回溯不断插入,相当于向前遍历反向递归。
void _PrintList(ListNode* cur, vector<int>& v) {
if (cur == nullptr) {
//结束条件
return;
}
_PrintList(cur->next, v); // 向下单路递归
v.push_back(cur->val); //回溯时插入
}
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> retV;
_PrintList(head, retV);
return retV;
}
};
单路递归,递归到空指针处递归结束,回溯时向数组插入当前节点的值。
3.2 链表倒数结点
头尾指针
class Solution {
public:
//头尾指针,头指针先走k步,尾指针再开始并一同遍历,直到头指针走到空(左闭右开),尾指针则为倒数第k个节点。
//[f, r) [3, n) 相当于是个等距的左闭右开的区间。
// 7 6 5 4 3 2 1 n -- k=3
// f r
// f r
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode* rear = pListHead;
ListNode* front = pListHead;
//rear先走k步
while (k--) {
if (rear == nullptr) {
// 将k的合法性的判断融入先走k步的步骤中
return nullptr;
}
rear = rear->next;
}
//front和rear一同遍历
while (rear != nullptr) {
front = front->next;
rear = rear->next;
}
return front;
}
};
统计长度再次遍历
class Solution {
public:
//通过倒数位置计算正序位置,再正向遍历链表到相应位置。
ListNode* FindKthToTail(ListNode* pListHead, size_t k) {
size_t sz = 0;
ListNode* cur = pListHead;
while (cur) {
//统计链表长度
sz++;
cur = cur->next;
}
if (k < 1 || k > sz) {
//排除不合法数值
return nullptr;
}
size_t i = sz - k + 1; //计算正序位置
cur = pListHead;
while (--i) {
//正向遍历到正确位置
cur = cur->next;
}
return cur;
}
};
3.3 逆置链表
栈结构模拟
class Solution {
public:
//利用栈结构先进后出的特性,倒序存储链表节点的值,再正序放入链表中
ListNode* ReverseList(ListNode* pHead) {
if (pHead == nullptr)
return nullptr;
stack<int> st;
ListNode* cur = pHead;
while (cur != nullptr) {
st.push(cur->val);
cur = cur->next;
}
cur = pHead;
while (!st.empty()) {
cur->val = st.top();
st.pop();
cur = cur->next;
}
return pHead;
}
};
迭代修改链接关系
class Solution {
public:
//正向遍历链表,提前保存当前节点的next指针,并修改next指向上一个节点,直至遍历结束
ListNode* ReverseList(ListNode* pHead) {
if (pHead == nullptr) {
return nullptr;
}
if (pHead->next == nullptr) {
return pHead;
}
int count = 2;
ListNode* prev = pHead;
ListNode* next = pHead;
ListNode* cur = pHead->next;
while (cur) {
count++;
next = cur->next; //提前保存下一个节点
cur->next = prev; //反向链接
prev = cur; //prev进位
cur = next; //cur 进位
}
cur = prev;
while (count--) {
cur = cur->next;
}
cur->next = nullptr; //逆置之后的最后节点的next指针没有置空
return prev;
}
};
3.4 合并有序链表
迭代
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
ListNode* head = new ListNode(0);
ListNode* cur = head;
while (pHead1 && pHead2) {
if (pHead1->val < pHead2->val) {
cur->next = pHead1;
pHead1 = pHead1->next; //cur1进位
}
else {
cur->next = pHead2;
pHead2 = pHead2->next; //cur2进位
}
cur = cur->next; // cur 进位
}
//补全剩余
if (pHead1 != nullptr) {
cur->next = pHead1;
}
if (pHead2 != nullptr) {
cur->next = pHead2;
}
return head->next;
}
};
递归
class Solution {
public:
//递归
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
if (!pHead1 || !pHead2) {
return pHead1 == nullptr ? pHead2 : pHead1;
}
ListNode* head = nullptr; //无头
if (pHead1->val < pHead2->val) {
head = pHead1;
pHead1 = pHead1->next;
}
else {
head = pHead2;
pHead2 = pHead2->next;
}
//pHead1,pHead2已进位,问题规模缩小。
head->next = Merge(pHead1, pHead2);
return head;
}
};
3.5 删除重复结点
明确出符合条件的左开右闭区间,才方便单链表进行操作。
class Solution {
public:
//带头指向首位元素,左指针指向头节点,右指针指向首节点。有个头节点能方便很多
//满足 r->val != r->next->val,让左右指针向后遍历,致使左指针停在相等元素区间的前一位。右指针指向相等元素区间的首位。
//满足 r->val == r->next->val,只让右指针向后遍历,直至右指针到相等元素区间的最后一位。
//这样就构成了(l,r]为相等元素的左开右闭的区间,直接让l->next = r->next 就可以删去该区间。
//删除后右指针复位到左指针的下一位,即让其进位。以继续判断之后是否该有相等元素区间,直至r指向null。
ListNode* deleteDuplication(ListNode* pHead) {
if (pHead == nullptr || pHead->next == nullptr) {
//排除不合法情况
return pHead;
}
ListNode* head = new ListNode(0); //建立头节点
head->next = pHead;
ListNode* l = head; //左指针指向头节点
ListNode* r = head->next; //右指针指向左指针的下一位
while (r != nullptr) {
//1. 将l确定在重复区域的起始位置 (,]
while (r->next != nullptr && r->val != r->next->val) {
//不相等则指针向后遍历
l = l->next;
r = r->next;
}
//2. 将r确定在重复区域的末尾位置 (,]
while (r->next != nullptr && r->val == r->next->val) {
//相等则单独遍历右指针
r = r->next;
}
if (l->next != r) {
//有重复节点
l->next = r->next; //左指针链接区间尾部
}
r = r->next; //进位
}
return head->next;
}
};
3.6 链表公共节点
class Solution {
public:
//利用第一遍循环,判断两个链表是否相交,并统计链表的长度。
//如果最后一个节点不相等则无相交节点,如果有,就让指针从距尾节点相同的位置起始向后遍历直到节点相同。
ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
int length1 = 0, length2 = 0;
ListNode* cur1 = pHead1, *cur2 = pHead2;
while (cur1 != nullptr || cur2 != nullptr) {
if (cur1) {
++length1;
cur1 = cur1->next;
}
if (cur2) {
++length2;
cur2 = cur2->next;
}
}
ListNode* more = pHead1, *less = pHead2;
if (length1 < length2) {
more = pHead2, less = pHead1;
}
int step = abs(length1 - length2); //差距步
while (step--) {
more = more->next; // 移动链表指针至对应位置
}
//同时判定,一并向后遍历
while (more != nullptr && less != nullptr) {
if (more == less) {
return more;
}
more = more->next;
less = less->next;
}
return nullptr;
}
};