每题代码汇总:登录 - Gitee.com
关于使用VS调试OJ代码,见:使用VS调试OJ代码-CSDN博客
基础单链表知识,见:数据结构:单链表(详解)-CSDN博客
1.移除链表元素
首先理解题意:

以及力扣已经提供了单链表的节点结构和待实现的函数。

思路一:
遍历链表,查找值为val的节点,并返回节点,删除指定位置的节点,再return,时间复杂度为O(n^2)
这样的代码直观但效率不高。
大致的逻辑:
while(遍历)
{
//SLTNode* SLTFind(x);
//SLTErase(pos);
}
代码:
typedef struct ListNode ListNode;//自定义,方便后续引用
//查找
ListNode* SLTFind(ListNode* phead, int val)
{
ListNode* pcur = phead;
while(pcur)
{
if(pcur->val == val)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//删除
void SLTErase(ListNode** pphead,ListNode* pos)
{
assert(pphead && pos);
//pos为头节点
if(pos == *pphead)
{
*pphead = pos->next;
free(pos);
return;
}
else{
ListNode* prev = *pphead;
while(prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode* pos;
while((pos = SLTFind(head,val))!= NULL)
{
SLTErase(&head,pos);
}
return head;
}
最终通过测试。
思路二:
创建新链表,将原链表中不为val的节点拿下来尾插。时间复杂度:O(N)。
newHead为新链表头指针,newTail为新链表尾指针,pcur为遍历原链表的指针。

基本逻辑:
while(遍历)
{
//尾插
}
代码:
typedef struct ListNode ListNode;//自定义,方便后续引用
struct ListNode* removeElements(struct ListNode* head, int val) {
//创建新链表
ListNode* newHead,*newtail;
newHead = newtail = NULL;
ListNode* pcur = head;
while(pcur)
{
//判断
if(pcur->val != val)
{
//尾插
if(newHead == NULL)
{
//链表为空
newHead = newtail = pcur;
}
else{
//链表非空
newtail->next = pcur;
newtail = newtail->next;
}
}
pcur = pcur->next;
}
if(newtail)
newtail->next = NULL;//避免残留原链表的指针
return newHead;
}
最终通过测试。
2.反转链表
理解题意:

思路一:
创建新链表,遍历原链表将节点头插到新链表中,时间复杂度:O(N)。

代码见下:
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
ListNode* newHead = NULL;
while(head)
{
ListNode* temp = head->next;
head->next = newHead;
newHead = head;
head = temp;
}
return newHead;
}
最终通过测试。
思路二:
创建n1,n2,n3三个指针,改变指针的指向。
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL)
{
return head;
}
//创建三个指针
ListNode*n1,*n2,*n3;
n1 = NULL, n2 = head, n3 = n2->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3)
n3 = n3->next;
}
return n1;
}
最终通过测试。
3.链表的中间结点
理解题意:

思路一:
求得链表总长度,根据下标,总长度/2就是中间节点,再遍历找到中间节点及新链表。时间复杂度:O(N)。
代码如下:
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
int len = 0;
ListNode* curr = head;
while(curr)
{
len++;
curr = curr->next;
}
int mid = len / 2;
curr = head;
for(int i = 0;i < mid; i++)
{
curr = curr->next;
}
return curr;
}
最终测试通过。
思路二:
使用快慢指针法,慢指针每次走一步,快指针每次走两步。

代码:
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
//创建指针
ListNode* slow = head;
ListNode* fast = head;
while(fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
注意:在while循环中,不能将条件改为fast->next != NULL && fast != NULL,否则会导致空指针解引用错误(具体错误:当fast为NULL时,会先尝试访问fast->next,导致崩溃),在涉及指针的条件表达式时,应该先检查指针本身是否为空,再访问其他成员,这样才能写出健壮,无崩溃的代码。
4.合并两个有序链表
之前有过合并两个有序数组的文章:数据结构:顺序表-CSDN博客,除过我文章中的,还可以通过这篇文章c语言内存函数以及数据在内存中的存储_函数在内存里是如何存储的-CSDN博客中的memcpy解决。
先理解题意:

思路一:
创建新链表,遍历并比较原链表中结点的值,小的插入到新链表。

编写代码方式一:
代码:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
//创建空链表
ListNode* newHead, *newTail;
newHead = newTail = NULL;
ListNode* l1 = list1;
ListNode* l2 = list2;
if(l1 == NULL)
{
return l2;
}
if(l2 == NULL)
{
return l1;
}
//遍历
while(l1 != NULL && l2 != NULL)
{
if(l1->val < l2->val)
{
//l1尾插
//空链表
if(newHead == NULL)
{
newHead = newTail = l1;
}
else{
//非空
newTail->next = l1;
newTail = newTail->next;
}
l1 = l1->next;
}
else{
//l2尾插
//空链表
if(newHead == NULL)
{
newHead = newTail = l2;
}
else{
//非空
newTail->next = l2;
newTail = newTail->next;
}
l2 = l2->next;
}
}
//遍历下去,l1为空或l2为空时
if(l1)
{
newTail->next = l1;
}
if(l2)
{
newTail->next = l2;
}
return newHead;
}
最终测试通过。
编写代码方式二:

上述代码有冗余,以往有联合的方式进行整合:自定义类型:联合和枚举-CSDN博客,不过此处根本原因是:初始时链表为空,那如果我创建非空链表呢?
代码如下:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
//创建非空链表
ListNode* newHead, *newTail;
newHead = newTail = (ListNode*)malloc(sizeof(ListNode));
ListNode* l1 = list1;
ListNode* l2 = list2;
if(l1 == NULL)
{
return l2;
}
if(l2 == NULL)
{
return l1;
}
//遍历
while(l1 != NULL && l2 != NULL)
{
if(l1->val < l2->val)
{
//l1尾插
newTail->next = l1;
newTail = newTail->next;
l1 = l1->next;
}
else{
//l2尾插
newTail->next = l2;
newTail = newTail->next;
l2 = l2->next;
}
}
//遍历下去,l1为空或l2为空时
if(l1)
{
newTail->next = l1;
}
if(l2)
{
newTail->next = l2;
}
ListNode* retHead = newHead->next;
free(newHead);
newHead = NULL;
return retHead;
}
最终通过测试。
思路二:
使用递归,假设 "当前两个链表的剩余部分已合并为有序链表",只需决定当前头节点的选择,递归处理剩余部分。
代码如下:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
ListNode* l1 = list1;
ListNode* l2 = list2;
// 基准情况:如果一个链表为空,返回另一个链表
if (l1 == NULL)
return l2;
if (l2 == NULL)
return l1;
// 递归选择较小的节点作为当前节点
if (l1->val <= l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
最终通过测试。
5.链表的回文结构
本题在牛客网上,选择编程语言没有c语言,只能选c++,不过此题涉及到的也只是不需要写这串代码:typedef struct ListNode ListNode; 其余按照c语言完成即可。
理解题意:

思路一:
创建一个新链表,保存原链表所有结点,反转新链表,判断两者结点是否相同。
代码如下:
class PalindromeList {
public:
// 复制链表
ListNode* copyList(ListNode* head) {
if (head == NULL) return NULL;
ListNode* copyHead = new ListNode(head->val);
ListNode* curCopy = copyHead;
ListNode* curOrg = head->next;
while (curOrg != NULL) {
curCopy->next = new ListNode(curOrg->val);
curCopy = curCopy->next;
curOrg = curOrg->next;
}
return copyHead;
}
// 反转链表
ListNode* reverseList(ListNode* head) {
ListNode* prev = NULL;
ListNode* cur = head;
while (cur != NULL) {
ListNode* next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
bool chkPalindrome(ListNode* A) {
if (A == NULL) return true; // 空链表视为回文
ListNode* copy = copyList(A);
ListNode* reversed = reverseList(copy);
ListNode* p1 = A;
ListNode* p2 = reversed;
// 逐节点比较
while (p1 != NULL && p2 != NULL) {
if (p1->val != p2->val) {
return false;
}
p1 = p1->next;
p2 = p2->next;
}
return true;
}
};
最终测试通过。
思路二(投机取巧):
题中保证链表长度小于等于900即可,那么创建数组大小为900,遍历链表,将其依次存储在数组中,若数组为回文结构,那么链表也是回文结构。
代码如下:
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
// write code here
int arr[900] = {0};
//遍历
ListNode* curr = A;
int i = 0;
while(curr)
{
arr[i++] = curr->val;
curr = curr->next;
}
//判断
int left = 0;
int right = i - 1;
while(left < right)
{
if(arr[left] != arr[right])
{
return false;
}
left++;
right--;
}
return true;
}
};
最终测试通过。
思路三:
找到链表的中间节点,将中间节点作为新链表的头节点,反转链表,遍历原链表和反转后的链表节点是否相等。
代码如下:
class PalindromeList {
public:
//快慢指针找中间结点
ListNode* middleNode(struct ListNode* head) {
//创建指针
ListNode* slow = head;
ListNode* fast = head;
while (fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
//反转
ListNode* reverseList(struct ListNode* head) {
if (head == NULL) {
return head;
}
//创建三个指针
ListNode* n1, * n2, * n3;
n1 = NULL, n2 = head, n3 = n2->next;
while (n2) {
n2->next = n1;
n1 = n2;
n2 = n3;
if (n3)
n3 = n3->next;
}
return n1;
}
bool chkPalindrome(ListNode* A) {
// write code here
//找中间节点
ListNode* mid = middleNode(A);
//反转
ListNode* rev = reverseList(mid);
//比较
ListNode* left = A;
while(rev)
{
if(left->val != rev->val)
{
return false;
}
left = left->next;
rev = rev->next;
}
return true;
}
};
最终测试通过。
本章完。