跳到主要内容
数据结构初阶:顺序表与链表精选 15 道 OJ 练习 | 极客日志
C++ 算法
数据结构初阶:顺序表与链表精选 15 道 OJ 练习 数据结构中顺序表和链表的 15 道经典 OJ 练习题。涵盖删除有序数组重复项、移除元素、合并有序数组等顺序表操作,以及返回倒数第 K 个节点、回文结构、相交链表、环形链表、随机链表复制、移除链表元素、反转链表、中间结点、合并有序链表、约瑟夫问题、分割链表等链表操作。重点讲解快慢指针、双指针、头插尾插、哨兵位等核心算法技巧,帮助读者巩固基础并提升解题能力。
孤勇者 发布于 2026/3/29 更新于 2026/5/27 24 浏览数据结构初阶:顺序表与链表精选 15 道 OJ 练习
顺序表 OJ 练习
26. 删除有序数组中的重复项
题目介绍
方法一
int removeDuplicates (int * nums, int numsSize) {
int fast = 0 , slow = 0 ;
for (; fast < numsSize; fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1 ;
}
27. 移除元素
题目介绍
方法一
int removeElement (int * nums, int numsSize, int val) {
int fast = 0 , slow = 0 ;
for (; fast < numsSize; fast++) {
(nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
slow;
}
if
return
88. 合并两个有序数组
题目介绍
方法一 void merge (int * nums1, int nums1Size, int m, int * nums2, int nums2Size, int n) {
int tmp[n + m];
int p1 = 0 , p2 = 0 , pi = 0 ;
for (; p1 < m && p2 < n; pi++) {
if (nums1[p1] <= nums2[p2]) tmp[pi] = nums1[p1++];
else tmp[pi] = nums2[p2++];
}
while (p1 < m) tmp[pi++] = nums1[p1++];
while (p2 < n) tmp[pi++] = nums2[p2++];
for (int i = 0 ; i < m + n; i++) nums1[i] = tmp[i];
return ;
}
方法二 void merge (int * nums1, int nums1Size, int m, int * nums2, int nums2Size, int n) {
int l1 = m - 1 , l2 = n - 1 ;
int l3 = m + n - 1 ;
while (l1 >= 0 && l2 >= 0 ) {
if (nums1[l1] < nums2[l2]) nums1[l3--] = nums2[l2--];
else nums1[l3--] = nums1[l1--];
}
while (l2 >= 0 ) {
nums1[l3--] = nums2[l2--];
}
return ;
}
链表 OJ 练习
面试题 02.02. 返回倒数第 k 个节点
题目介绍
方法一
class Solution {
public :
int kthToLast (ListNode* head, int k) {
ListNode *first = head, *last = head;
while (k--) first = first->next;
for (; first != nullptr ;) {
first = first->next;
last = last->next;
}
return last->val;
}
};
OR36 链表的回文结构
题目介绍
方法一
判断链表是否为回文链表 = 反转单向链表 反转单向链表 = 寻找单向链表的中间节点
实现:寻找链表的中间节点
ListNode* middleNode (ListNode* head) {
ListNode* slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
代码片段解释: while (fast && fast->next)
这个循环条件是快慢指针法寻找链表中间节点的核心安全保证 ,其精妙之处在于同时防范了两种可能的越界情况:
第一种情况:开始的情况
输入情况 条件表现 结果正确性 空链表 fast 初始为 null → 不进入循环 返回 null 单节点链表 fast->next 为 null → 不进入循环 返回 head
第二种情况:结束的情况
检查条件 防护场景 示例说明 fast != nullptr防止访问 nullptr->next 的非法操作 当链表节点数为奇数时,快指针最后停在最后一个节点(fast->next == nullptr) fast->next != nullptr确保能安全执行 fast->next->next 当链表节点数为偶数时,快指针最后会走到空指针(fast = nullptr)
实现:反转单向链表
ListNode* reverseList (ListNode*& head)
{
ListNode* next = nullptr ;
ListNode* newHead = nullptr ;
ListNode* curr = head;
while (curr != nullptr ) {
next = curr->next;
curr->next = newHead;
newHead = curr;
curr = next;
}
head = newHead;
return head;
}
bool chkPalindrome (ListNode* A) {
struct ListNode * mid = middleNode (A);
struct ListNode * rmid = reverseList (mid);
while (rmid && A) {
if (rmid->val != A->val) return false ;
rmid = rmid->next;
A = A->next;
}
return true ;
}
综合:判断链表是否为回文链表
class PalindromeList {
public :
ListNode* middleNode (ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* reverseList (ListNode* head) {
ListNode* next = nullptr ;
ListNode* newHead = nullptr ;
ListNode* curr = head;
while (curr != nullptr ) {
next = curr->next;
curr->next = newHead;
newHead = curr;
curr = next;
}
head = newHead;
return head;
}
bool chkPalindrome (ListNode* A) {
ListNode* mid = middleNode (A);
ListNode* rmid = reverseList (mid);
while (rmid) {
if (A->val != rmid->val) return false ;
A = A->next;
rmid = rmid->next;
}
return true ;
}
};
160. 相交链表
题目介绍
方法一
class Solution {
public :
ListNode *getIntersectionNode (ListNode *headA, ListNode *headB) {
int lenA = 1 , lenB = 1 ;
while (tmpA->next) { tmpA = tmpA->next; lenA++; }
while (tmpB->next) { tmpB = tmpB->next; lenB++; }
if (tmpA != tmpB) return nullptr ;
int gap = abs (lenA - lenB);
ListNode *longList = headA, *shortList = headB;
if (lenB > lenA) {
longList = headB;
shortList = headA;
}
while (gap--) {
longList = longList->next;
}
while (longList != nullptr ) {
if (longList == shortList) return longList;
longList = longList->next;
shortList = shortList->next;
}
return nullptr ;
}
};
141. 环形链表
题目介绍
方法一
class Solution {
public :
bool hasCycle (ListNode *head) {
ListNode *fast = head, *slow = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (fast == slow) return true ;
}
return false ;
}
};
思考与探究
142. 环形链表 II
题目介绍
方法一
class Solution {
public :
ListNode *detectCycle (ListNode *head) {
ListNode *fast = head, *slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (fast == slow) {
ListNode* meet = fast;
while (head != meet) {
head = head->next;
meet = meet->next;
}
return meet;
}
}
return nullptr ;
}
};
思考与探究
138. 随机链表的复制
题目介绍
方法一
class Solution {
public :
Node* copyRandomList (Node* head) {
Node* curr = head;
while (curr != nullptr ) {
Node* copy = new Node (curr->val);
copy->next = curr->next;
curr->next = copy;
curr = copy->next;
}
curr = head;
while (curr != nullptr ) {
Node* copy = curr->next;
if (curr->random == nullptr ) copy->random = nullptr ;
else copy->random = curr->random->next;
curr = copy->next;
}
Node *copyhead = nullptr , *copytail = nullptr ;
curr = head;
while (curr != nullptr ) {
Node* copy = curr->next;
Node* next = copy->next;
if (copyhead == nullptr ) copyhead = copytail = copy;
else {
copytail->next = copy;
copytail = copy;
}
curr->next = copy->next;
curr = next;
}
return copyhead;
}
};
程序执行流程
203. 移除链表元素
题目介绍
方法一
struct ListNode * removeElements (struct ListNode* head, int val) {
while (head != NULL && head->val == val) {
struct ListNode * del = head;
head = head->next;
free (del);
}
if (head == NULL ) return NULL ;
struct ListNode * prev = head;
while (prev->next != NULL ) {
if (prev->next->val == val) {
struct ListNode * del = prev->next;
prev->next = prev->next->next;
free (del);
}
else {
prev = prev->next;
}
}
return head;
}
代码片段解释
疑问:为什么要将情况 2 写在情况 1 之前,这样你还有多此一举在 while 循环写一个:head!=NULL&&?
回答:大家提出的这个问题是真的不错啊!!!
但是你心中的这个疑问可以使用下面的这个测试样例来解答
**当链表的所有节点都等于 val 时(例如 [6,6,6],val=6)**你的 while (head->val == val) 循环会一直删除头节点,直到 head 变为 NULL但 while (prev->next != NULL) 循环仍然会执行 ,而 prev 已经是 NULL,导致 访问 prev->next 时出现空指针解引用(Segmentation Fault)
所以 :在 while (head->val == val) 循环结束后,必须检查 head 是否为 NULL ,如果是,直接返回 NULL,避免后续操作。在 while (head->val == val) 循环中增加 head != NULL 检查,确保在 head 变为 NULL 时停止循环。
方法二
struct ListNode * removeElements (struct ListNode* head, int val) {
struct ListNode * pcur = head;
struct ListNode * phead = NULL , *ptail = NULL ;
while (pcur != NULL ) {
if (pcur->val != val) {
if (phead == NULL ) {
phead = ptail = pcur;
}
else {
ptail->next = pcur;
ptail = ptail->next;
}
}
pcur = pcur->next;
}
if (ptail != NULL ) ptail->next = NULL ;
return phead;
}
206. 反转链表
题目介绍
方法一
struct ListNode * reverseList (struct ListNode* head) {
struct ListNode * pcur = head;
struct ListNode * phead = NULL ;
while (pcur != NULL ) {
struct ListNode * next = pcur->next;
pcur->next = phead;
phead = pcur;
pcur = next;
}
return phead;
}
876. 链表的中间结点
题目介绍
方法一
struct ListNode * middleNode (struct ListNode* head) {
struct ListNode * fast = head;
struct ListNode * slow = head;
while (fast != NULL && fast->next != NULL ) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
21. 合并两个有序链表
题目介绍
方法一
struct ListNode * mergeTwoLists (struct ListNode* list1, struct ListNode* list2) {
if (list1 == NULL && list2 == NULL ) return NULL ;
if (list1 == NULL ) return list2;
if (list2 == NULL ) return list1;
struct ListNode * pcur1 = list1;
struct ListNode * pcur2 = list2;
struct ListNode * phead = NULL ;
struct ListNode * ptail = NULL ;
while (pcur1 != NULL && pcur2 != NULL )
{
if (pcur1->val <= pcur2->val) {
if (phead == NULL ) {
phead = ptail = pcur1;
}
else {
ptail->next = pcur1;
ptail = ptail->next;
}
pcur1 = pcur1->next;
} else {
if (phead == NULL ) {
phead = ptail = pcur2;
}
else {
ptail->next = pcur2;
ptail = ptail->next;
}
pcur2 = pcur2->next;
}
}
if (pcur1 == NULL ) {
ptail->next = pcur2;
} else {
ptail->next = pcur1;
}
return phead;
}
方法二:使用哨兵位进行优化
struct ListNode * mergeTwoLists (struct ListNode* list1, struct ListNode* list2) {
if (list1 == NULL && list2 == NULL ) return NULL ;
if (list1 == NULL ) return list2;
if (list2 == NULL ) return list1;
struct ListNode * pcur1 = list1;
struct ListNode * pcur2 = list2;
struct ListNode * phead = NULL ;
struct ListNode * ptail = NULL ;
phead = ptail = (struct ListNode*)malloc (sizeof (struct ListNode));
while (pcur1 != NULL && pcur2 != NULL )
{
if (pcur1->val <= pcur2->val) {
ptail->next = pcur1;
ptail = ptail->next;
pcur1 = pcur1->next;
} else {
ptail->next = pcur2;
ptail = ptail->next;
pcur2 = pcur2->next;
}
}
if (pcur1 == NULL ) {
ptail->next = pcur2;
} else {
ptail->next = pcur1;
}
struct ListNode * next = phead->next;
free (phead);
phead = next;
return phead;
}
环形链表的约瑟夫问题
题目介绍
方法一
struct ListNode * CreateNode (int x) {
struct ListNode * newNode = (struct ListNode*)malloc (sizeof (struct ListNode));
if (newNode == NULL ) {
exit (1 );
}
newNode->val = x;
newNode->next = NULL ;
return newNode;
}
struct ListNode * CreateCircle (int n) {
struct ListNode * phead = CreateNode (1 );
struct ListNode * ptail = phead;
for (int i = 2 ; i <= n; i++) {
ptail->next = CreateNode (i);
ptail = ptail->next;
}
ptail->next = phead;
}
int ysf (int n, int m) {
struct ListNode * prev = CreateCircle (n);
struct ListNode * pcur = prev->next;
int count = 1 ;
while (pcur->next != pcur) {
if (count == m) {
prev->next = pcur->next;
free (pcur);
pcur = prev->next;
count = 1 ;
} else {
prev = prev->next;
pcur = pcur->next;
count++;
}
}
return pcur->val;
}
面试题 02.04. 分割链表
题目介绍
方法一
struct ListNode * partition (struct ListNode* head, int x) {
if (head == NULL || head->next == NULL ) {
return head;
}
struct ListNode * ptail = head;
struct ListNode * newHead = head, *newTail = head;
struct ListNode * pcur = head;
struct ListNode * prev = NULL ;
while (ptail->next != NULL ) {
ptail = ptail->next;
}
newTail = ptail;
while (pcur != ptail) {
if (pcur->val >= x) {
struct ListNode * next = pcur->next;
if (prev == NULL ) {
}
else {
}
pcur = next;
} else {
prev = pcur;
pcur = pcur->next;
}
}
return newHead;
}
*疑问:上面的代码中为什么需要注释那些代码?*关于释放节点(释) :原始代码中注释掉了释放节点的操作,因为:我们不是要真正删除节点,而是将它移动到链表尾部如果调用 free(pcur),节点就被销毁了,无法再使用它进行尾插 关于移动指针(移) :原始代码中 pcur=pcur->next 和 prev=prev->next 被注释,因为:我们需要先完成当前节点的尾插操作,才能移动指针在第二阶段(尾插部分)统一处理指针移动更安全
方法二
struct ListNode * partition (struct ListNode* head, int x) {
if (head == NULL || head->next == NULL ) return head;
struct ListNode * lessHead, *lessTail;
struct ListNode * greaterHead, *greaterTail;
lessHead = lessTail = (struct ListNode*)malloc (sizeof (struct ListNode));
lessHead->next = NULL ;
greaterHead = greaterTail = (struct ListNode*)malloc (sizeof (struct ListNode));
greaterHead->next = NULL ;
struct ListNode * pcur = head;
while (pcur != NULL ) {
if (pcur->val < x) {
lessTail->next = pcur;
lessTail = lessTail->next;
}
else {
greaterTail->next = pcur;
greaterTail = greaterTail->next;
}
pcur = pcur->next;
}
lessTail->next = greaterHead->next;
greaterTail->next = NULL ;
struct ListNode * ret = lessHead->next;
free (lessHead);
lessHead = NULL ;
free (greaterHead);
greaterHead = NULL ;
return ret;
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online