0 数据结构
链表结构体及构造函数
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
1 合并两个有序序列
【链接】
https://www.nowcoder.com/questionTerminal/d8b6b4358f774294a89de2a6ac4d9337
【题目描述】
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
【注意点】
- Node为NULL,不要取next,会异常出错
- 构造函数初始化
- 非递归版本速度快于递归版本
1.1 非递归版本
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* pHead = new ListNode(-1);
ListNode* pNode = pHead;
while(pHead1 != NULL && pHead2 != NULL){
if(pHead1->val < pHead2->val){
pNode->next = pHead1;
pHead1 = pHead1->next;
}
else{
pNode->next = pHead2;
pHead2 = pHead2->next;
}
pNode = pNode->next;
}
if(pHead1 == NULL)
pNode->next = pHead2;
if(pHead2 == NULL)
pNode->next = pHead1;
return pHead->next;
}
};
1.2 递归版本
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* pHead = NULL;
if(pHead1 == NULL)
return pHead2;
if(pHead2 == NULL)
return pHead1;
if(pHead1->val < pHead2->val){
pHead = new ListNode(pHead1->val);
pHead1 = pHead1->next;
}
else{
pHead = new ListNode(pHead2->val);
pHead2 = pHead2->next;
}
pHead->next = Merge(pHead1, pHead2);
return pHead;
}
};
2 翻转链表
单链表反转,包括头插法和递归法
2.1 全翻转
【链接】
https://www.nowcoder.com/questionTerminal/75e878df47f24fdc9dc3e400ec6058ca
【题目描述】
输入一个链表,反转链表后,输出新链表的表头。
【整体思路】
详见方案2.2,全翻转是指定下标区间翻转的特例
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* dummy = new ListNode(-1);
dummy->next = pHead;
ListNode* pPre = dummy;
ListNode* pNode = pHead;
ListNode* pNext = NULL;
while(pNode){
pNext = pNode->next;
if(pNext){
pNode->next = pNext->next;
pNext->next = pPre->next;
pPre->next = pNext;
}
else
break;
}
return dummy->next;
}
};
2.2 指定下标区间翻转
【链接】
https://www.nowcoder.com/questionTerminal/b58434e200a648c589ca2063f1faf58c
【题目描述】
将一个链表m位置到n位置之间的区间反转,要求使用原地算法,并且在一次扫描之内完成反转。
例如:
给出的链表为1->2->3->4->5->NULL, m = 2 ,n = 4,
返回1->4->3->2->5->NULL.
注意:
给出的m,n满足以下条件:
1 ≤ m ≤ n ≤ 链表长度
【解析】
对于reverse部分有点迷糊。网上看到的解释,也许更能帮助理解.https://yq.aliyun.com/articles/3867
不妨拿出四本书,摞成一摞(自上而下为 A B C D),要让这四本书的位置完全颠倒过来(即自上而下为 D C B A):
盯住书A,每次操作把A下面的那本书放到最上面
初始位置:自上而下为 A B C D
第一次操作后:自上而下为 B A C D
第二次操作后:自上而下为 C B A D
第三次操作后:自上而下为 D C B A
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
//头插法,考虑翻转头节点情况
ListNode* pHead = new ListNode(-1);
pHead->next = head;
ListNode* pPre = pHead;
ListNode* pNode = head;
ListNode* pNext = NULL;
int cur = 1; //下标从1开始
while(pNode){
pNext = pNode->next;
if(cur < m){
pPre = pNode;
pNode = pNext;
}
else if(cur >= m && cur < n){
//翻转
pNode->next = pNext->next; //n位置的必然存在,所以pNext->next不会越界
pNext->next = pPre->next;
pPre->next = pNext;
}
else{
pNode = pNext;
}
cur += 1;
}
return pHead->next;
}
};
2.3 k个值一组翻转
【链接】
https://www.nowcoder.com/questionTerminal/b49c3dc907814e9bbfa8437c251b028e
[特例] k=2时,https://www.nowcoder.com/profile/351750300/codeBookDetail?submissionId=81128930
【题目描述】
将给出的链表中的节点每k个一组翻转,返回翻转后的链表
如果链表中的节点数不是k的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
只允许使用常数级的空间
例如:
给定的链表是1->2->3->4->5
对于 k = 2, 你应该返回 2->1->4->3->5
对于 k = 3, y你应该返回 3->2->1->4->5
2.3.1 递归版本
【注意】
- 一轮翻转后,如何保留表头及其下一个信息
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head == NULL || k < 2)
return head;
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* pNode = head;
//统计链表长度
int len = 0;
while(pNode){
len++;
pNode = pNode->next;
}
//无需翻转
if(len < k)
return dummy->next;
//需要进行翻转
ListNode* pPre = dummy;
pNode = head;
ListNode* pNext = NULL;
int cur = 1;
while(pNode){
pNext = pNode->next;
if(pNext){
pNode->next = pNext->next;
pNext->next = pPre->next;
pPre->next = pNext;
cur += 1;
if(cur == k)
break;
}
//else //这条路径因为上面判断所以不会走到
// break;
}
if(pNode->next == NULL) //恰巧走到结尾
return dummy->next;
else{
pNode->next = reverseKGroup(pNode->next, k);
return dummy->next;
}
}
};
3 两个链表的第一个公共节点
【链接】
https://www.nowcoder.com/questionTerminal/6ab1d9a29e88450685099d45c9e31e46
【题目描述】
输入两个链表,找出它们的第一个公共结点
【题意理解】
从链表的定义可以看出,这两个链表是单链表,如果两个链表有公共节点,那么这两个链表从某一节点开始,它们的m_pNext都指向同一个节点,之后它们所有的节点都是重合的,不可能再出现分叉。所以拓扑形状看起来是Y型。
一个简单的方法是:首先遍历两个链表得到它们的长度,就能知道哪个链表比较长,以及长的链表比短的链表多几个节点。在第二次遍历的时候,先在较长的节点上走若干步,接着同时在两个链表上遍历,找到的第一个相同的节点就是它们的公共的节点。
class Solution {
public:
ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2){
ListNode *p1 = pHead1;
ListNode *p2 = pHead2;
int len1 = 0, len2 = 0, diff = 0;
while(p1 != NULL){
p1=p1->next;
len1++;
}
while(p2 != NULL){
p2=p2->next;
len2++;
}
//p1指向较长的串,p2指向较短的串
if(len1 > len2){
diff = len1 - len2;
p1 = pHead1;
p2 = pHead2;
}
else{
diff = len2 - len1;
p1 = pHead2;
p2 = pHead1;
}
//较长的串先移动到与较短的串相同长度的位置
for(int i=0;i<diff;i++){
p1 = p1->next;
}
while(p1 != NULL && p2 != NULL){
if(p1 == p2)
break;
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
};
4 删除链表中倒数第n个节点(快慢指针)
【链接】
https://www.nowcoder.com/questionTerminal/f95dcdafbde44b22a6d741baf71653f6
【题目描述】
给定一个链表,删除链表的倒数第n个节点并返回链表的头指针
例如,
给出的链表为:1->2->3->4->5, n= 2.↵↵ 删除了链表的倒数第n个节点之后,链表变为1->2->3->5.
备注:
题目保证n一定是合法的
请尝试只用一步操作完成该功能
【题解】
问题关键在于找到倒数第N个节点,并且尽量只使用一次循环。
采用两个指针,对前指针,使其先走出N步,随后两个指针同时前进,当前指针到达链表尾部时,后指针到达倒数第N个节点的位置。
【注意点】
- 删除时注意待删除节点为头结点时的情况
- 删除倒数第n个节点,先找到倒数第n+1个节点(链表删除结点的特殊性)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//定义快、慢指针
ListNode *pFast = head;
ListNode *pLow = head;
//快指针先走n步
for(int i = 0; i < n; i++)
pFast = pFast->next;
//考虑删除头结点的情况
if(pFast == NULL)
return head->next;
//当快指针走到结尾时停下来
//因为要删除倒数第n个节点,所以要找到倒数第n+1个节点
while(pFast->next != NULL){
pFast = pFast->next;
pLow = pLow->next;
}
//删除pLow->next结点,与上面pFast->next对应
ListNode *tmp = pLow->next;
pLow->next = pLow->next->next;
delete tmp;
return head;
}
};
5 在链表中删除指定值的节点(输入&输出)
【链接】
https://www.nowcoder.com/questionTerminal/1a5fd679e31f4145a10d46bb8fd3d211
【题目描述】
给出一个链表和一个整数 num,输出删除链表中节点值等于 num 的节点之后的链表。
输入描述:
第一行一个整数 n,n 表示单链表的节点数量。
第二行 n 个整数表示单链表的各个节点的值。
第三行一个整数 num。
输出描述:
在给定的函数中返回指定链表的头指针。
示例1
输入
4
1 2 3 4
3
输出
1 2 4
【题解】
头插法即可,考虑删除结点为头结点
#include <iostream>
using namespace std;
struct ListNode{
int val;
struct ListNode* next;
ListNode(int x): val(x), next(NULL){
}
};
int main()
{
//根据输入构建链表
int len;
cin >> len;
ListNode* pHead = new ListNode(-1);
ListNode* pNode = pHead;
int x;
for(int i = 0; i < len; i++){
cin >> x;
pNode->next = new ListNode(x);
pNode = pNode->next;
}
int num; //指定删除的值
cin >> num;
//删除列表中指定值
pNode = pHead->next;
ListNode* pPre = pHead;
while(pNode){
ListNode* pNext = pNode->next;
if(pNode->val == num){ //需要进行删除
ListNode* tmp = pNode;
pPre->next = pNext;
delete tmp;
}
else{
pPre = pNode;
}
pNode = pNext;
}
//输出链表
pNode = pHead->next;
while(pNode){
cout << pNode->val << " ";
pNode = pNode->next;
}
cout << endl;
return 0;
};
6 删除重复元素
6.1 去重
【链接】
https://www.nowcoder.com/questionTerminal/c087914fae584da886a0091e877f2c79
【题目描述】
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
例如:
给出的链表为1->1->2,返回1->2.
给出的链表为1->1->2->3->3,返回1->2->3.
Given a sorted linked list, delete all duplicates such that each element appear only once.
For example,
Given1->1->2, return1->2.
Given1->1->2->3->3, return1->2->3.
示例1
输入
复制
{1,1,2}
输出
复制
{1,2}
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* pNode = head;
while(pNode != NULL && pNode->next != NULL){
if(pNode->val == pNode->next->val){
ListNode* tmp = pNode->next;
pNode->next = pNode->next->next;
delete tmp;
}
else
pNode = pNode->next;
}
return head;
}
};
6.2 只出现一次
【链接】
https://www.nowcoder.com/questionTerminal/71cef9f8b5564579bf7ed93fbe0b2024
【题目描述】
给出一个排好序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
例如:
给出的链表为1->2->3->3->4->4->5, 返回1->2->5.
给出的链表为1->1->1->2->3, 返回2->3.
示例1
输入
{1,2,2}
输出
{1}
【注意点】
- 注意边界,空节点,只有1或2个节点
- 一些测试case
- 删除头节点、尾节点
- 连续删除多个值
6.2.1 非递归版本(思路值得学习)
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL)
return NULL;
//这样定义两个变量,不会出现边界问题
ListNode* pPre = NULL;
ListNode* pNode = head;
while(pNode){
ListNode* pNext = pNode->next; //妙
if(pNext && pNode->val == pNext->val){
int val = pNode->val; //秒
while(pNode && pNode->val == val){
pNext = pNode->next;
delete pNode;
pNode = pNext;
}
if(pPre == NULL) //妙
head = pNode;
else
pPre->next = pNode;
}
else{ //当前节点与下一个节点不相等
pPre = pNode;
pNode = pNext;
}
}
return head;
}
};
6.2.2 递归版本
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL || head->next == NULL)
return head;
ListNode* pNext = head->next;
if (head->val == pNext->val){
while (pNext && head->val == pNext->val){
ListNode* tmp = pNext;
pNext = pNext->next;
delete tmp;
}
delete head;
return deleteDuplicates(pNext);
//将上面两行替换为注释的这两行,即可解决drop duplicates I
//node->next = drop_duplicates(p);
//return node;
}
else{
head->next = deleteDuplicates(head->next);
return head;
}
}
};
7 划分链表
【链接】
https://www.nowcoder.com/questionTerminal/1dc1036be38f45f19000e48abe00b12f
【题目描述】
给出一个链表和一个值x,以x为参照将链表划分成两部分,使所有小于x的节点都位于大于或等于x的节点之前。
两个部分之内的节点之间要保持的原始相对顺序。
例如:
给出1->4->3->2->5->2和x = 3,
返回1->2->2->4->3->5.
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode* pSmallHead = new ListNode(-1);
ListNode* pSmall = pSmallHead;
ListNode* pBigHead = new ListNode(-1);
ListNode* pBig = pBigHead;
while(head){
if(head->val < x){
pSmall->next = new ListNode(head->val);
pSmall = pSmall->next;
}
else{
pBig->next = new ListNode(head->val);
pBig = pBig->next;
}
head = head->next;
}
pSmall->next = pBigHead->next;
return pSmallHead->next;
}
};
8 找到环的入口节点(数学证明)
【链接】https://www.nowcoder.com/questionTerminal/6e630519bf86480296d0f1c868d425ad
【题目描述】
对于一个给定的链表,返回环的入口节点,如果没有环,返回null
【解析】
思路:
1)同linked-list-cycle-i一题,使用快慢指针方法,判定是否存在环,并记录两指针相遇位置(Z);
2)将两指针分别放在链表头(X)和相遇位置(Z),并改为相同速度推进,则两指针在环开始位置相遇(Y)。
证明如下:
如下图所示,X,Y,Z分别为链表起始位置、环开始位置和两指针相遇位置
相遇Z时,慢针走了(a+b)步,快针是其二倍2(a+b);同时,快针走了a+b之后又绕了n圈
如何存在环,走得快的总会和走得慢的指针相遇
得到2*(a + b) = a + b + n * (b + c);【1】
即a = (n - 1) * b + n * c = (n - 1)(b + c) +c; 【2】
注意到b+c恰好为环的长度,故可以推出,如将此时两指针分别放在起始位置和相遇位置,并以相同速度前进,当一个指针走完距离a时,另一个指针恰好走出 绕环n-1圈加上c的距离。
故两指针会在环开始位置相遇。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == NULL)
return NULL;
ListNode* slow = head;
ListNode* fast = head;
//快慢指针找到相遇位置
while(fast != NULL && fast->next != NULL){
slow = slow->next; //走一步
fast = fast->next->next; //走两步
if(slow == fast)
break;
}
if(fast == NULL || fast->next == NULL)
return NULL;
//慢指针从头开始,快指针从相遇点开始,当再次相遇时,为环开始位置(图中Y)
slow = head;
while(slow != fast){
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
9 合并K个有序序列
【链接】
https://www.nowcoder.com/questionTerminal/65cfde9e5b9b4cf2b6bafa5f3ef33fa6
【题目描述】
合并k个已排序的链表并将其作为一个已排序的链表返回。分析并描述其复杂度
【题解】
不断从k条序列中,取出第1和第2条排序并push_back,直至只剩一条序列
class Solution {
public:
ListNode* merge_two_sorted_lists(ListNode* pHead1, ListNode* pHead2){
ListNode* pHead = new ListNode(-1);
ListNode* pNode = pHead;
while(pHead1 != NULL && pHead2 != NULL){
if(pHead1->val < pHead2->val){
pNode->next = pHead1;
pHead1 = pHead1->next;
}
else{
pNode->next = pHead2;
pHead2 = pHead2->next;
}
pNode = pNode->next;
}
if(pHead1 == NULL)
pNode->next = pHead2;
if(pHead2 == NULL)
pNode->next = pHead1;
return pHead->next;
}
ListNode *mergeKLists(vector<ListNode *> &lists) {
if(lists.empty())
return NULL;
while(lists.size() > 1)
{
lists.push_back(merge_two_sorted_lists(lists[0], lists[1]));
lists.erase(lists.begin());
lists.erase(lists.begin());
}
return lists[0];
}
};
10 两个数(链表)求和
【链接】
https://www.nowcoder.com/questionTerminal/56f8d422eae04f129c8e5a05299ae275
【题目描述】
给定两个代表非负数的链表,数字在链表中是反向存储的(链表头结点处的数字是个位数,第二个结点上的数字是十位数…),求这个两个数的和,结果也用链表表示。
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 0 -> 8
【注意点&技巧】
- 进位计算
- 不断用NULL/0填充,使两条链表长度相同,精简代码
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* pHead = new ListNode(-1);
ListNode* pNode = pHead;
int carry = 0; //10进位
while(l1 || l2){
int val_l1 = l1 == NULL ? 0 : l1->val;
int val_l2 = l2 == NULL ? 0 : l2->val;
int val = val_l1 + val_l2 + carry;
pNode->next = new ListNode(val % 10);
carry = val / 10;
pNode = pNode->next;
l1 = l1 == NULL ? NULL : l1->next; //这样写可以避免next越界
l2 = l2 == NULL ? NULL : l2->next;
}
if(carry > 0)
pNode->next = new ListNode(carry);
return pHead->next;
}
};