编程题分类——链表
前言
经验
正文
1. 两数相加
题目
code
答案
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* head1, ListNode* head2) {
//这题的条件是属于相对苛刻的,两个节点相加的值居然可以加到后面的那个节点上去。
//这题是借助于大数相加的原理
if(head1==nullptr||head2==nullptr)
return nullptr;
ListNode* node1 = head1;
ListNode* node2 = head2;
int len1 = 1;
int len2 = 1;
//1. 先计算出当前有多少个节点
while(node1->next!=NULL)//这里错第二次了,老是写成node!=NULL
{
++len1;
node1 = node1->next;
}
while(node2->next!=NULL)
{
++len2;
node2 = node2->next;
}
//2. 创建一个虚拟节点
ListNode *head = new ListNode(-1);
ListNode *node = head;
//3. 若知道哪条链表比较短,则将较短链表补的跟长链表一样长
while(node1&&len1<len2)
{
++len1;
node1->next = new ListNode(0);
node1 = node1->next;
}
while(node2&&len1>len2)
{
++len2;
node2->next = new ListNode(0);
node2 = node2->next;
}
node1 = head1;
node2 = head2;
bool flag = false;
int sum=0;
//4. 开始从头加到尾
while(node1&&node2)
{
sum = flag+node1->val+node2->val;//先对值进行相加+flag
flag = sum>=10?true:false;//计算flag
node->next = new ListNode(sum%10);//将值存入链表中
node = node->next;//开始搞下一个节点
node1 = node1->next;
node2 = node2->next;
}
if(flag&&node)//注意最后,如果还有一位是进位的,要将进位也放入链表中
{
node->next = new ListNode(flag);
node = node->next;
}
return head->next;
}
};
2. 判断链表中是否有环 ----- 有关单链表中环的问题
其实最重要的就是要明白的一个关系:
就是头节点到入口点的距离 = 相遇点到入口点的距离。
求相遇点就用双指针就可以了。
2.1 判断单链表中是否有环
快慢指针,若两个指针相遇,则证明有环。
code
答案
//如何判断是否有环
typedef struct node{
char data;
node *next;
};
bool exitLoop(Node *head)
{
Node *fast,*slow;//声明快慢指针
slow = fast = head;
//这样就可以判断有没有环了,如果没环,第二个节点肯定很早就到达末尾了,不会有slow = fast的这种情况。
while(slow!=NULL&&fast->next!=NUL)
{
slow = slow->next;
fast = fast->next->next;
if(slow==fast)
return true;
}
return false;
}
2.2 判断单链表的入口点的地址
相遇点到入口点的距离=头节点到入口点的距离
code
答案
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* head) {
//这题第一眼看到也是没有多少想法,https://www.cnblogs.com/yorkyang/p/10876604.html
ListNode *fast,*slow;
slow = fast = head;
//1. 首先,先找到相遇点
while(slow!=NULL&&fast->next!=NULL)
{
slow = slow->next;
fast = fast->next->next;
if(slow==fast)//这时找到入口点就停下来
break;
}
//2. 如果跳出循环的,已经到最后了,那就是没有环了
if(slow==NULL||fast->next==NULL)//你这里肯定判断有没有环了,若有环才继续下一步
return NULL;
//3. 到这里就基本意味着是有环的
ListNode *ptr1 = head;
ListNode *ptr2 = slow;
//4. 接下来就按照公式法了,头->入口点=相遇点->入口点的距离
while(ptr1!=ptr2)//有一个相等关系:头->入口点 = 两个指针的相遇点->入口点/。这两段距离是相等的
{
ptr1 = ptr1->next;
ptr2 = ptr2->next;
}
return ptr1;
}
};
2.3 如果存在环,求出环上各点的个数。
思路1:记录下相遇节点存入临时变量tempPtr,然后让slow(或者fast,都一样)继续向前走slow = slow -> next;一直到slow == tempPtr; 此时经过的步数就是环上节点的个数;
思路2: 从相遇点开始slow和fast继续按照原来的方式向前走slow = slow -> next; fast = fast -> next -> next;直到二者再次项目,此时经过的步数就是环上节点的个数 。
3.旋转链表
题目
思路:先收尾相连,然后在最后面的那个指针向前移动k个位置,移到那个位置后,断开当前链表。**注意一点点是若k的大小大于这个链表的长度的话,则哟啊进行取余的操作。
code
答案
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
//题目要求,向右旋转k个节点
ListNode* rotateRight(ListNode* head, int k) {
//1. 搞掉一些特殊情况
if(k==0||head==nullptr||head->next==nullptr)
return head;
int n = 1;
ListNode* node = head;
//2. 计算出当前有多少元素
while(node->next!=nullptr)
{
node = node->next;
n++;
}
//3. 判断需要右移的个数
int add = n-k%n;
//4. 若需要右移的元素刚好是n的整数倍
if(add==n)
return head;
//5. 先进行收尾相连
node->next = head;
//6. 根据这个,移到要断开的那个位置
while(add--)
{
node = node->next;
}
//7. 断开的方式就是就是把下一个节点赋给一个临时变量。然后,将其指向nullptr
ListNode *ret = node->next;//这里的ret其实就已经是头节点了
node->next = nullptr;
return ret;
}
};
题解
这题有几个点:
- 首先,将链表首尾相连
- 得出链表有多少个元素。
- 注意移动的元素的数量有可能大于n.所以要取余。
- 注意,这里的这个add,移动的个数是n-k。
4. 链表中将所有的偶数移到奇数后面不改变原来的相对位置?
code
答案
ListNode* oddEvenList(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
return head;
ListNode* q = NULL;
ListNode* p = head;
while (p)
{
if (p->val & 1)//if若成立,则表示p->val是奇数
{
if (q == NULL)
{
swap(head->val, p->val);
q = head;
}
else
{
q = q->next;//相当于q所指的位置,就是所有奇数的最后一位
swap(q->val, p->val);
}
}
p = p->next;
}
return head;
}
5. 判断回文链表
题目
code
总结:
答案
class Solution {
public:
bool isPalindrome(ListNode* head)
{
//1. 空指针也是回文链表
if (head == nullptr)
{
return true;
}
// 2. 找到前半部分链表的尾节点并反转后半部分链表,先使用快慢指针找到中间节点
ListNode* firstHalfEnd = endOfFirstHalf(head);
// 3. 将该节点后面的所有节点进行翻转,注意传入的节点为Next节点
// 4. 这样就得到了后面那部分链表的最后一个节点
ListNode* secondHalfStart = reverseList(firstHalfEnd->next);
// 5. 判断是否回文
ListNode* p1 = head;
ListNode* p2 = secondHalfStart;
bool result = true;
// 6. 直接加上那个值
while (result && p2 != nullptr)
{
if (p1->val != p2->val)
{
result = false;
}
p1 = p1->next;
p2 = p2->next;
}
// 7. 最好还原链表并返回结果
firstHalfEnd->next = reverseList(secondHalfStart);
return result;
}
ListNode* reverseList(ListNode* head)
{
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr)
{
ListNode* nextTemp = curr->next;//先储存下这个next节点
curr->next = prev;//将指针进行反转
prev = curr;//把cur变成pre,把next变成cur
curr = nextTemp;
}
return prev;//error1:注意返回的pre,这个才是最后的根节点
}
ListNode* endOfFirstHalf(ListNode* head)
{//返回的是链表中间节点的指针
ListNode* fast = head;
ListNode* slow = head;
while (fast->next != nullptr && fast->next->next != nullptr)
{
fast = fast->next->next;//一跳跳到第3个节点
slow = slow->next;//一步一步的走
}
return slow;
}
};
6. 删除链表中重复的结点
方法一:递归的方法。 就是比如头节点的几个是相同的,然后的时候,就换一个节点跟前面的节点的值不一样的作为头结点进行返回。
答案
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
//采用递归的方式进行处理
ListNode* deleteDuplication(ListNode* head) {
//1. 返回节点为空,或只剩一个节点的情况
if(head==nullptr||head->next==nullptr)//error1:忘记只有一个节点也要返回
return head;
//2. 若当前节点的值和下一个节点的值是相等的
if(head->val==head->next->val)
{
//3. 暂存第3个节点的指针
ListNode *p = head->next->next;
//4. 若第3个节点的值和第二个节点的值是相等的,则p往下走
while(p!=NULL&&p->val==head->next->val)
{
p = p->next;
}
//5. 直到走到p不和前面的元素相等
head = p;
return deleteDuplication(head);
}
head->next = deleteDuplication(head->next);//如果不相等,就一直链接下去
return head;
}
};
总结:
- 作为空节点与单个节点的返回。
- 如果第一个节点和第二个节点相同,就继续比较第三个节点和第2个节点的值。用p往后面移。等到没有和前面的节点相同的情况下,作为head往下面传。
- 特殊情况处理完后,就类似于链表的连接,把head->next不断的往下传。
方法二:非递归法,从前往后走。注意,while循环里,一般先进行正常情况的判断,然后再else。并且,注意让你删除链表中的某些元素。一定要delete这个元素。最好再置个空。然后就是指针的前面一定要进行判断。
code
答案
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* head) {
//非递归版,从前往后走。跳过一些元素就好了
if(head==nullptr||head->next==nullptr)
return head;
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode *next = node->next;
while(next)
{
//1. 先进行正常不相等的情况,不断的往下走,修改变量的值
if(cur->val!=next->val)//error1:未先进行正常情况的判断
{
pre = cur;
cur = next;
next = next->next;
}
else
{
//2. 将next指针走到与前面的指针不相等的情况
while(cur->val==next->val)
{
next = next->next;
}
//3. 不断删除那些与前面值相等的指针
while(cur!=next)
{
ListNode* tmp = cur->next;
delete cur;
cur = tmp;
}
//4. 如果pre==nullptr的话,就意味着前面几个元素都是相同的
if(pre==nullptr)
head = cur;
else//5. 否则,就把pre->next指向cur
{
pre->next = cur;
}
//6. 如果还有下一个指针,就接着往下走
if(next)//2. error2:忘记判断,就直接想next
next = next->next;
}
}
return head;
}
};
7. 删除链表的倒数第 N 个结点
要点:1. 尽量弄个虚拟节点,这样会好弄很多。2. 最后最好删掉那个虚拟节点。
code
答案
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//基本就是新头节点+前后指针+删除慢指针的下一个节点
if(head==nullptr||n==0)
return head;
ListNode* newhead = new ListNode(0);
newhead->next = head;
ListNode *slow = newhead;
ListNode *fast = newhead;
while(n)//3 2 1
{
fast = fast->next;
n--;
}
while(fast->next)
{
fast = fast->next;
slow = slow->next;
}
ListNode *delNode = slow->next;
slow->next = delNode->next;
delete delNode;
delNode = nullptr;
ListNode *res = newhead->next;
return res;
}
};
8. 单向链表中如何高效删除一个结点(只给定头指针和指向当前结点的指针)
思路:如果按照正常的方法去遍历删除,时间复杂度肯定是O(N)。
- 所以,平常的那种方法是不行的,所以,我们换种思路:我们是不是一定要删除这个节点,其实是不是的。我们要删除的是这个节点的数据。所以,我们可以把这个节点的数据和下一个节点的数据进行交换。然后删除下一个节点,从而我们这样的时间复杂度就是O(1)了。
- 但同时,你也要想到,若要删除的节点是最后一个节点,则遍历到最后一个节点,这个是避免不了的。这种方法的时间复杂度会是O(N). 但时间复杂度在这边的计算是,在n-1的情况是O(1),只有在一种情况下是O(N). 所以(n-1O(1) +1O(N))/n = O(1)
9. 反转链表
题目
1. 递归
code
答案
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* head) {
//1. 只有一个节点,和包括空节点的时候,就直接返回就可以了
if(head==NULL||head->next==NULL)
return head;
//2. ans指向的都是head->next已经反转好的链表
ListNode* ans = ReverseList(head->next);
//3. 这里就做基本的反转操作,把head->next的next 指向head
head->next->next = head;
//4. 最后一个反转的节点,就直接指向空即可,也就是原来的头结点,直接将其指向空
head->next = NULL;
return ans;
}
};
2. 迭代
code
答案
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* head) {
if(head==nullptr)
return nullptr;
//1. 标准的前,中,下 三个指针的声明
ListNode* pre = NULL;
ListNode* node = head;
ListNode* next = NULL;
ListNode* reverseHead = NULL;
while(node)
{
//2. 首先,搞出next指针
next = node->next;
//3. 当next指针为空时,证明已经到最后一个节点了,直接将当前节点赋成新的head即可。
if(next==NULL)
reverseHead = node;
//4. 经典的反转三段论
node->next = pre;
pre = node;
node = next;
}
//5. 返回就返回新头节点即可。
return reverseHead;
}
};
3. 测试代码
code
答案
#include <stdio.h>
#include <iostream>
#include <vector>
using namespace std;
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
ListNode* ReverseList(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
cout << "反转前" << head->next->val<<endl;
ListNode* ans = ReverseList(head->next);
head->next->next = head;
head->next = NULL;
return ans;
}
void createList(ListNode* head)
{
ListNode* p = head;
for (int i = 2; i < 10; ++i)
{
ListNode* node = new ListNode(i);
node->next = NULL;
p->next = node;
p = node;
}
}
int main()
{
ListNode* head = new ListNode(1);
createList(head);
ListNode* node = head;
//while (node)
//{
// cout << "node"<<node->val << endl;
// node = node->next;
//}
ListNode* reverHead = ReverseList(head);
ListNode* newNode = reverHead;
//while (newNode)
//{
// cout <<"reverNode"<< newNode->val<<endl;
// newNode = newNode->next;
//}
return 0;
}
10. 合并K个升序链表
题目
code
答案
### 解题思路
这道题的合并,主要是利用了递归+归并这两种方法解决了k个的问题。
分治法的思想借网上的说法就是:
1.分解:将原问题分解成为性质一样的若干子问题。
2.解决:解决子问题,并递归求解子问题的问题。问题足够小的时候可以直接求解。
3.合并:合并子问题的解.
其实两个链表的合并大家应该都是会的,但需要在这个的基础上再加上归并的思路可能就提升了整个题目的难度了。
### 代码
```cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
//分治法
if(lists.size()<=0)
return nullptr;
return merge(lists,0,lists.size()-1);
}
private:
ListNode* merge(vector<ListNode*> &lists,int left,int right)
{
if(left>right)
return nullptr;
if(left==right)
return lists[left];
int mid = (left+right)/2;
//1. 最后利用归并思想,会只细分到只剩一个链表和另一个链表进行合并
return mergeTwoList(merge(lists,left,mid),merge(lists,mid+1,right));
}
ListNode* mergeTwoList(ListNode* list1,ListNode* list2)
{
//1. 如果有一个链表为空的话,就直接返回即可
if(!list1||!list2)
return list1?list1:list2;
ListNode* head,*cur;
//2. 先连接上头部的第一个元素
if(list1->val>list2->val)
{
head = list2;
list2 = list2->next;
}
else
{
head = list1;
list1 = list1->next;
}
//3. 记录一下head的这个节点,接下来用cur去接
cur = head;
//4. 接下来,再一个节点一个节点的往下接
while(list1!=nullptr&&list2!=nullptr)
{
if(list1->val>list2->val)
{
cur->next = list2;//error1:cur->next
list2 = list2->next;
}
else
{
cur->next = list1;
list1 = list1->next;
}
cur = cur->next;
}
//5. 到这里已经是出现一个链表为空的情况了,所以,接下来,就把不为空的全部链接上即可
if(list1!=nullptr)
cur->next = list1;//error2:cur->next
if(list2!=nullptr)
cur->next = list2;
return head;
}
};
11. 二叉树的直径
11->13都是类似的题目
题目
code
答案
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxValue = 0;
//1. 直径问题,有可能是深度很长,也有可能是宽度很长,都要进行考虑
int diameterOfBinaryTree(TreeNode* root) {
if(root==nullptr)
return 0;
dfs(root);
return maxValue;
}
int dfs(TreeNode* root)
{
//2. 只有一个节点的时候,就直接返回0 了
if(root->left==nullptr&&root->right==nullptr)
return 0;
//3. 算出左节点有多深
int left = root->left==nullptr?0:dfs(root->left)+1;
//4. 算出右节点有多深
int right = root->right ==nullptr?0:dfs(root->right)+1;
//5. 算出maxValue,和左+右之间哪个比较大
maxValue = max(maxValue,left+right);
//6. 返回左边或右边,哪个值比较大
return max(left,right);
}
};
12. 二叉树中的最大路径和
题目
code
答案
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxValue = INT_MIN;
int maxPathSum(TreeNode* root) {
dfs(root);
return maxValue;
}
int dfs(TreeNode* root)
{
if(root==nullptr)
return 0;
//1. 求出左边最大值
int left =max(0,dfs(root->left));
//2. 求出右边最大值
int right =max(0,dfs(root->right));
//3. 求出两者加上当前值的最大值
maxValue = max(maxValue,left+right+root->val);
return max(left,right)+root->val;
}
};
13. 最长同值路径
题目
code
答案
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxValue = 0;
int longestUnivaluePath(TreeNode* root) {
if(root==nullptr)
return 0;
dfs(root);
return maxValue;
}
int dfs(TreeNode* root)
{
if(root->left==nullptr&&root->right==nullptr)
return 0;
//想象成只有3个节点就可以了
int left = root->left==nullptr?0:dfs(root->left)+1;
int right = root->right ==nullptr?0:dfs(root->right)+1;
if(left>0&&root->left->val!=root->val)//如果左子树不等于根节点,那就left=0.
left = 0;
if(right>0&&root->right->val!=root->val)
right = 0;
maxValue = max(maxValue,left+right);//比较的数据就是maxValue与left+right进行相比。
return max(left,right);//这里的返回值,就是让root选择走哪一条路
}
};
参考
当你在凝视深渊时,深渊也在凝视你。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具