数据结构: 链表问题总结
刷了几个链表的题目,做一下总结.
题目来自 <程序员代码面试指南>
链表本身操作灵活,代码量少.很适合作为面试题考查.
链表的基本操作,如增删改查.
本文用到链表用的是牛客网上的结构.如下所示:
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :val(x), next(NULL) {
}
};
链表的题目中遇到的:
-
快慢指针
用来找节点,比如中间的节点.倒数第几个节点.
判断链表是否有环,找环的入口.
-
头结点(哨兵节点)
:拼接链表时可以使用.比如将合并两个有序链表.
-
翻转链表的操作
翻转链表,注意保存
next
指针域. 且一个节点一个节点做. -
尾插法
和头插法
用于生成目标的链表. 将划分的链表接起来. 注意:拼成的链表末尾指向NULL.
-
还有其他一些待补充.
题目: 打印两个链表的公共部分
/*
题目:
打印两个有序单链表的公共部分.
公共部分指的是相等的值
*/
vector<int> printCommonPart(ListNode* head1, ListNode* head2) {
vector<int> res;
if (head1 == NULL || head2 == NULL)
return res;
ListNode* p1 = head1;
ListNode* p2 = head2;
while (p1 != NULL&&p2 != NULL) {
if (p1->val == p2->val) {
res.push_back(p1->val);
p1 = p1->next;
p2 = p2->next;
}
else if (p1->val < p2->val) {
p1 = p1->next;
}
else if (p1->val > p2->val) {
p2 = p2->next;
}
}
return res;
}
题目: 在单链表和双链表中删除倒数第K个节点.
- 通过计数,找到倒数第K个节点的前驱
- 删除操作,单链表和双链表有区别
// 在单链表中删除节点,需要知道前驱
/*
有三种情况: 链表长度N,倒数第K个
1) N<K.则返回空,
2) N==K,则需要返回第一个节点
3) N>K,需要两次遍历,1) 遍历完,每次k--
再一次从头遍历,每次k++,知道k==0.即找到顺数N-K个节点
*/
ListNode* RemoveLastKthNode(ListNode* &head, int lastKth) {
if (head == NULL)
return NULL;
int k = lastKth;
ListNode* cur = head;
// 第一遍遍历
while (cur != NULL) {
cur = cur->next;
k--;
}
if (k == 0) {
return head->next;
}
else if (k > 0) {
return NULL;
}
else { // k<0
cur = head;
// 使用++k找到的是倒数Kth的前驱.
// 若使用k++,则找到的是倒数Kth节点
while (++k != 0) {
cur = cur->next;
}
ListNode* tobeDelete = cur->next;
cur->next = tobeDelete->next;
delete tobeDelete;
tobeDelete = NULL;
return head;
}
}
DoubleLinkNode* RemoveLastKthNode(DoubleLinkNode* head, int lastKth) {
if (head==NULL) {
return NULL;
}
DoubleLinkNode* cur = head;
int k = lastKth;
while (cur != NULL) {
cur = cur->next;
k--;
}
if (k == 0) {
return head->next;
}
else if (k > 0) {
return NULL;
}
else {
cur = head;
// 使用++k找到的是倒数Kth的前驱.
// 若使用k++,则找到的是倒数Kth节点
while (++k != 0) {
cur = cur->next;
}
// 找到了前驱
DoubleLinkNode* tobedeleted = cur->next;
DoubleLinkNode* next = tobedeleted->next;
cur->next = next;
next->last = cur;
delete tobedeleted;
tobedeleted = NULL;
return head;
}
}
题目: 翻转单链表中的一部分
- 找到 m的前驱,找到n的后继.
- 翻转 [m…n] 这个区间的链表
- 注意边界情况.
ListNode* reverseBetween(ListNode* head, int m, int n) {
if (head == NULL || n <= m) {
return head;
}
int len = 0;
ListNode* cur = head;
ListNode* beforeM = NULL;
ListNode* M = NULL;
ListNode* N = NULL;
ListNode* afterN = NULL;
while (cur != NULL) {
len++;
beforeM = len == m - 1 ? cur : beforeM;
M = len == m ? cur : M;
N = len == n ? cur : N;
afterN = len == n + 1 ? cur : afterN;
cur = cur->next;
}
ListNode* res = head; // 返回的节点
// reverse
cur = M;
ListNode* pre = beforeM;
while (cur != afterN&&cur != NULL) {
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
M->next = afterN;
if (beforeM == NULL) {
res = N;
} else {
beforeM->next = N;
}
return res;
}
题目: 将单链表按某值划分成为左边小中间相等右边大的形式
方法1: 将单链表放进去数组中,进行 类似于快排的 partition
操作,最后呢,将排序后的节点连接起来.
/*
方法一:
1. 链表的值放进数组中,
2. 对数组进行partition
3. 将数组中的链表重接
时间复杂度O(N)
空间复杂度O(N)
且不能保证数据保持和输入链表一样的顺序
*/
void ArrPartition(vector<ListNode*> &v, int pivot) {
int size = v.size();
if (size == 0) {
return;
}
int small = -1;
int big = v.size();
int index = 0;
while (index < v.size()) {
if (v[index]->val < pivot) {
v[++small] = v[index++];
}
else if (v[index]->val > pivot) {
swap(v[--big], v[index]);
}
else {
// 当前这个值和pivot相等
index++;
}
}
}
ListNode* LinkListPartition1(ListNode* head,int pivot) {
if (head == NULL)
return NULL;
vector<ListNode*> v;
// 把链表放进数组
ListNode* cur = head;
while (cur != NULL) {
v.push_back(cur);
cur = cur->next;
}
// 对数组进行partition
cur = head;
ArrPartition(v, pivot);
for (int i = 1; i < v.size(); i++) {
v[i - 1]->next = v[i];
}
v[v.size() - 1]->next = NULL;
return v[0];
}
方法2: 创建三个链表,分别是 小于,等于,大于最后将三个链表进行连接
/*
方法2:
使用类似建立链表的方法,
创建三个链表,分别是 小于,等于,大于
最后将三个链表进行连接
时间复杂度O(N)
空间复杂度O(1)
能保持顺序性
*/
ListNode* LinkListPartition2(ListNode* head, int pivot) {
if (head == NULL)
return NULL;
ListNode* smallStart = NULL;
ListNode* smallEnd = NULL;
ListNode* equalStart = NULL;
ListNode* equalEnd = NULL;
ListNode* bigStart = NULL;
ListNode* bigEnd = NULL;
ListNode* cur = head;
ListNode* next = NULL;
while (cur!=NULL) {
next = cur->next;
cur->next = NULL; // 将每个节点都断开.就会保证每个单独的链表最后是指向空的
// 尾插法
if (cur->val < pivot) {
if (smallStart == NULL) {
smallStart = cur;
smallEnd = cur;
}
else {
smallEnd->next = cur;
smallEnd = cur;
}
}
else if (cur->val == pivot) {
if (equalStart == NULL) {
equalStart = cur;
equalEnd = cur;
}
else {
equalEnd->next = cur;
equalEnd = cur;
}
}
else {
if (bigStart == NULL) {
bigStart = cur;
bigEnd = cur;
} else {
bigEnd->next = cur;
bigEnd = cur;
}
}
cur = next;
}
// 把小于和等于连接
if (smallStart != NULL) {
smallEnd->next = equalStart;
// 如果等于部分为空,则将等于部分的
equalEnd = equalEnd == NULL ? smallEnd : equalEnd;
}
// 将全部连接
if (equalEnd != NULL) {
equalEnd->next = bigStart;
}
return smallStart != NULL ? smallStart : equalStart != NULL ? equalStart : bigStart;
}
题目: 将搜索二叉树转换成为双向链表
方法1: 利用栈进行中序排序,最后将结果存储是队列中,再遍历队列,进行拼接.
/*
将搜索二叉树转换为双向链表
*/
// 利用栈实现中序遍历,存储在队列中,逐一节点进行连接.注意头结点和末尾节点特殊处理
TreeNode* convert1(TreeNode* head) {
TreeNode* res = NULL;
TreeNode* end = NULL;
queue<TreeNode*> q;
TreeNode* cur = head;
stack<TreeNode*> s;// 利用一个栈实现中序遍历
while (!s.empty() || !cur != NULL) {
while (cur != NULL) {
s.push(cur);
cur = cur->left;
}
if (!s.empty()) {
cur = s.top();
s.pop();
q.push(cur);
cur = cur->right;
}
}
// 中序遍历的结果都存在队列中了
while (!q.empty()) {
cur = q.front();
q.pop();
if (res == NULL) {
res = cur;
res->left = NULL;
end = cur;
}
else {
cur->left = end;
end->right = cur;
end = cur;
}
}
if(end!=NULL)
end->right = NULL;
return res;
}
方法2:利用递归函数.
/*
利用递归函数,
将整个二叉进行转换,
返回都是双向链表的尾节点,且,尾节点的right指向头结点.
*/
TreeNode* convertProcess(TreeNode* head) {
if (head == NULL) {
return NULL;
}
TreeNode* leftTreeLast = convertProcess(head->left);
TreeNode* rightTreeLast = convertProcess(head->right);
TreeNode* leftTreeFirst = leftTreeLast != NULL ? leftTreeLast->right : NULL;
TreeNode* rightTreeFirst = rightTreeLast != NULL ? rightTreeLast->right : NULL;
if (leftTreeLast != NULL&&rightTreeLast != NULL) {
leftTreeLast->right = head;
head->left = leftTreeLast;
head->right = rightTreeFirst;
rightTreeFirst->left = head;
rightTreeLast->right = leftTreeFirst;
return rightTreeLast;
}
else if(leftTreeLast!=NULL){
leftTreeLast->right = head;
head->right = leftTreeFirst;
head->left = leftTreeLast;
return head;
}
else if (rightTreeLast != NULL) {
head->right = rightTreeFirst;
rightTreeFirst->left = head;
rightTreeLast->right = head;
return rightTreeLast;
}
else {
head->right = head;
return head;
}
}
TreeNode* convert2(TreeNode* bst) {
if (bst == NULL) {
return NULL;
}
TreeNode* last = convertProcess(bst);
// 最后把尾节点之指向头结点的指针取消掉了
TreeNode* first = last->right;
last->right = NULL;
return first;
}
题目: 在O(1)时间删除链表节点
/*
只给一个单链表的节点Node,让删除这个节点
*/
void DeleteNode(ListNode* node) {
if (node == NULL) {
return ;
}
ListNode* next = node->next;
if (next == NULL) {
throw "ERROR";
}
node->val = next->val;
node->next = next->next;
return ;
}