声明:题目来源:1. 力扣(LeetCode)2. 牛客网(newcoder)。
题目目录:
- 删除链表中等于给定值 val 的所有节点。
- 反转一个单链表。
- 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
- 输入一个链表,输出该链表中倒数第k个结点。
- 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
- 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
1. 删除链表中等于给定值 val 的所有节点。
题目链接:移除链表元素
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
本题比较简单,主要考察对链表结构的理解。
解法:定义两个节点类型的指针,一个节点指针用于判断当前节点的值是否与 val 相等,另一个节点指针始终在当前节点的前面一个位置。当前节点的值若等于 val ,则删除当前节点,直到遍历完整个链表。以下代码仅供参考:
struct ListNode* removeElements(struct ListNode* head, int val){
if(head == NULL){
return head;
}
struct ListNode *pre = head;
struct ListNode *cur = head->next;
while(cur != NULL){
if(cur->val == val){
pre->next = cur->next;
free(cur);
}
else{
pre = cur;
}
cur = pre->next;
}
if(head->val == val){
return head->next;
}
return head;
}
2. 反转一个单链表。
题目链接:反转链表
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
本题介绍三种解法:
解法1: 将第一个结点指向NULL,其余每个节点的 next 值指向上一个节点,返回最后遍历到的节点。
这种解法简单、易于理解,推荐使用;
解法2: 适用于带头结点的链表,如果不带头结点,也可以创建一个头结点。方法是从第二个节点开始,对每个遍历到的节点使用头插法,最后返回头结点,就得到了一个已翻转的链表。
一次操作的结果如图:
这种方法实际上和解法1是一样的,只是逻辑不一样而已:
解法1需要另外创建一个指针变量保存下一个需要遍历的节点,并且指向当前节点、上一个节点和下一个节点的指针都要不断移动;
而解法2只需要两个指针变量,且一个始终指向头结点,一个始终指向头结点指向的节点,两个指针不发生移动,只需要不断使用头插操作即可。
解法3: 递归法,从链表的第一个节点开始,不断使用递归直到访问到最后一个节点,然后让该节点指向上一层递归到的节点,与解法1的思想类似,不过是倒过来的:
以下为解法3的参考代码:
struct ListNode* reverse(struct ListNode* head){
if(head->next == NULL){
return head;
}
struct ListNode *List = reverse(head->next);
List->next = head;
head->next = NULL;
return head;
}
struct ListNode* reverseList(struct ListNode* head){
if(head == NULL){
return head;
}
struct ListNode *later = head;
while(later->next != NULL){
later=later->next;
}
reverse(head);
return later;
}
解法2的参考代码:
struct ListNode* reverseList(struct ListNode* head){
if(head == NULL){
return head;
}
struct ListNode *plist = (struct ListNode *)malloc(sizeof(struct ListNode));
plist->next = NULL;
struct ListNode *cur = head;
struct ListNode *next = cur;
while(next != NULL){
next = next->next;
cur->next = plist->next;
plist->next = cur;
cur = next;
}
head = plist->next;
free(plist);
return head;
}
3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
题目链接: 链表的中间节点
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
本题思路很简单,设快慢两个指针同时从头结点开始,慢指针每次向后移动一个节点,快指针移动两个节点,直到快指针遍历到了最后一个节点返回慢指针。
参考代码:
struct ListNode* middleNode(struct ListNode* head){
if(head == NULL){
return NULL;
}
struct ListNode *MidNode = head;
struct ListNode *cur = head;
while(cur && cur->next != NULL){
cur = cur->next->next;
MidNode = MidNode->next;
}
return MidNode;
}
4. 输入一个链表,输出该链表中倒数第k个结点。
题目链接:输出单链表中的倒数第k个节点
输入描述:
输入说明
1 输入链表结点个数
2 输入链表的值
3 输入k的值
输出描述:
输出一个整数
解法1: 解法1的思路同上题,设快慢两指针,快指针先向后移动 k 个节点,慢指针再和快指针以相同的速度移动,直到快指针指向空返回慢指针。
解法2: 还有一种比较投机的方法就是:使用头插法创建链表,再返回该链表的第 k 个节点即可。
以下是解法1的算法代码:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef struct ListNode {
int val;
struct ListNode* next;
}ListNode;
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(k == 0){
return pListHead;
}
ListNode *end = pListHead->next;
for (unsigned int i = 1; i < k; ++i) {
end = end->next;
}
ListNode *dest = pListHead->next;
while (end->next != NULL) {
dest = dest->next;
end = end->next;
}
return dest;
}
void CreatList(ListNode* pListHead, int length) {
ListNode* pre = pListHead;
for (int i = 0; i < length; ++i) {
ListNode* cur = (ListNode *)malloc(sizeof(ListNode));
scanf("%d ", &cur->val);
pre->next = cur;
pre = pre->next;
}
pre->next = NULL;
}
int main() {
int length;
while(scanf("%d", &length) != EOF){
ListNode* pListHead;
pListHead = (ListNode *)malloc(sizeof(ListNode));
pListHead->val = 0;
CreatList(pListHead, length);
int k;
scanf("%d", &k);
printf("%d\n", FindKthToTail(pListHead, k)->val);
}
return 0;
}
5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
题目链接:合并两个有序链表
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
题目已知两个链表是有序的,因此只需要设两个指针分别指向两个链表,不断移动两个指针并比较当前位置的值,将值较小的节点优先插入新链表,直到某个链表访问完。最后再判断是否有链表未访问完,并将这个链表的后续部分插到新链表的尾部。
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode *newl;
if(l1 == NULL){
return l2;
}else if(l2 == NULL){
return l1;
}
if(l1->val > l2->val){
newl = l2;
l2 = l2->next;
}
else{
newl = l1;
l1 = l1->next;
}
struct ListNode *cur = newl;
while(l1 != NULL && l2 != NULL){
if(l1->val > l2->val){
cur->next = l2;
l2 = l2->next;
cur = cur->next;
}
else{
cur->next = l1;
l1 = l1->next;
cur = cur->next;
}
}
if(l1 == NULL){
cur->next = l2;
}
else{
cur->next = l1;
}
return newl;
}
6. 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
题目链接:删除链表中的重复元素
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
这道题目依然没啥难度:创建两个指针,一个 cur 指向当前节点,一个 tmp 指向下一个节点,若两节点相等则删除 tmp 指向的节点,直到 cur 指向的下一个节点与该节点值不相等,再向后移动 cur ······直到 tmp 为空则退出。
参考代码如下:
struct ListNode* deleteDuplicates(struct ListNode* head){
struct ListNode* cur = head;
struct ListNode* tmp;
while(cur && cur->next != NULL){
tmp = cur->next;
if(tmp->val == cur->val){
cur->next = tmp->next;
free(tmp);
continue;
}
cur = cur->next;
}
return head;
}