前言
紧接上期博客,我们已经介绍了单链表的相关知识点以及实现了单链表的增删改查,那么本期博客就让我们走进单链表的典型OJ题目当中,感受一下单链表独特的魅力吧!
单链表算法题
1.移除链表元素
2.反转链表
3.链表的中间结点
4合并两个有序链表
5.链表分割
6.链表的回⽂结构
7.相交链表
题型总结
1. 反转(转置或者说逆置)单链表
2.寻找单链表的中间节点
3.输出倒数第k个单链表中的值
4.合并两个单链表
答案解析:
下边便是上述单链表OJ题的答案和思路,需要的可以参考一下:
#define _CRT_SECURE_NO_WARNINGS
//作业题一:
//1.删除链表中等于给定值 val 的所有节点
/*
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
//typedef struct ListNode ListNode;
//struct ListNode* removeElements(struct ListNode* head, int val) {
// //创建新链表
// ListNode* newNode, * newTail;
// newNode = newTail = NULL;
// //遍历新链表
ListNode* pcur = head;
while (pcur)
{
//当原链表中的值和val不同的时候,将原链表中的值尾插到新链表中去
if (pcur->val != val)
{
//当新链表为空的时候
if (newNode == NULL)
{
newNode = newTail = pcur;
}
else
{
//当新链表不为空的时候
newTail->next = pcur;
newTail = newTail->next;
}
}
pcur = pcur->next;
}
//防止出现旧链表为同一个数,然后val也为这个数,导致新链表中没有数可尾插,使得新链表仍然为空,那么就让新链表的头结点置为空即可。
if (newTail)
newTail->next = NULL;
return newNode;
}
//作业题二:
//2.反转一个单链表。
// 思路一:创建一个新的链表,将旧链表遍历,然后从头头插到新链表中去
//思路二:创建三个指针实现一个单链表的反转,也叫逆置这个单链表,这样可以不用创建一个新的链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
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;
//由于n3会提前走到空,所以要对它进行单独的判断
if (n3)
n3 = n3->next;
}
//此时n1就是旧链表反转之后的新的头结点
return n1;
}
//作业三:
/*3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点
作业内容
3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。*/
//思路一;分为两次循环,第一次循环:求链表的总长度,计算中间节点的位置,
// 第二次循环,根据中间节点的位置走到中间节点
//思路二:这个题目涉及快慢指针,慢指针每次走一步,快指针一次走两步
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
ListNode* slow = head, * fast = head;
//慢指针一次走一步
//快指针一次走两步
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
//此时的slow指的就是中间结点
return slow;
}
//作业四:
//4.输入一个链表,输出该链表中倒数第k个结点。
//思路一://先逆置原链表,然后再根据k的值查找链表中对应的值
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k) {
//先逆置原链表,然后再根据k的值查找链表中对应的值
ListNode* n1, * n2, * n3;
n1 = NULL, n2 = head, n3 = n2->next;
while (n2)
{
n2->next = n1;
n1 = n2;
//n3先到达为空,所以要对n3进行处理。
if (n3)
n3 = n3->next;
}
ListNode* newHead = n1;
while (k--)
{
newHead = newHead->next;
}
return newHead->val;
}
//思路二:双指针法
//首先创建两个双指针,先让一个指针走k步,然后两个双指针再同时一步一步的走。
// 之后,当先走的指针为空的时候,后走的指针刚好到达倒数第k个结点,也就知道倒数第k个结点的值了
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k) {
// 双指针 src, dst 都指向头节点 head
ListNode* src = head, * dst = head;
// dst 先走 k 步
for (int i = 0; i < k; i++)
dst = dst->next;
// 当 dst 走过尾节点时跳出
while (dst != NULL)
{
// src, dst 都向前走 1 步
src = src->next;
dst = dst->next;
}
return src->val;
}
//作业五:
//5.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
//方案一:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
// 先处理链表为空的情况
if (list1 == NULL) {
return list2;
}
if (list2 == NULL) {
return list1;
}
// 创建一个新链表
ListNode* newHead = NULL, * newTail = NULL;
// 创建两个新指针指向链表的头结点
ListNode* l1 = list1;
ListNode* l2 = list2;
while (l1 && l2) // 注意这里是且,不是或
{
if (l1->val < l2->val) {
// l1尾插到新的链表中去,但是,新链表也有可能为空,所以还要在判断一下
if (newHead == NULL) {
newHead = newTail = l1;
}
else {
newTail->next = l1;
newTail = newTail->next;
}
l1 = l1->next;
}
else {
if (newHead == NULL) {
newHead = newTail = l2;
}
else {
newTail->next = l2;
newTail = newTail->next;
}
l2 = l2->next;
}
}
// 跳出循环的条件,无非就是l1为空(l2不为空)或者l2为空(l1不为空)
if (l1)
newTail->next = l1;
if (l2)
newTail->next = l2;
return newHead;
}
//方案二:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
// 先处理链表为空的情况
if (list1 == NULL) {
return list2;
}
if (list2 == NULL) {
return list1;
}
// 创建一个新的非空链表
ListNode* newHead, * newTail;
newHead = newTail = (ListNode*)malloc(sizeof(ListNode));
// 创建两个新指针指向链表的头结点
ListNode* l1 = list1;
ListNode* l2 = list2;
while (l1 && l2) // 注意这里是且,不是或
{
if (l1->val < l2->val) {
// l1尾插到新的链表中去,此时,新链表一定不为空,所以直接尾插即可
newTail->next = l1;
newTail = newTail->next;
l1 = l1->next;
}
else {
newTail->next = l2;
newTail = newTail->next;
l2 = l2->next;
}
}
// 跳出循环的条件,无非就是l1为空(l2不为空)或者l2为空(l1不为空)
if (l1)
newTail->next = l1;
if (l2)
newTail->next = l2;
ListNode* ret = newHead->next;
free(newHead);
newHead = NULL;
return ret;
}
//作业题六
/*现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,
且不能改变原来的数据顺序,返回重新排列后的链表的头指针。*/
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstdlib>
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
// write code here
//创建两个不为空的链表
//分别为小链表和大链表。小链表存储比x小的数据,大链表存储,比x大的数据
ListNode* lessHead, * lessTail;
lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
ListNode* greaterHead, * greaterTail;
greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
//遍历原链表,将比x小的尾插到小链表中去,把比x大的数据尾插大链表中去
ListNode* pcur = pHead;
while (pcur) { //相当于是pcur!=NULL
if (pcur->val < x) {
//尾插到小链表中去
lessTail->next = pcur;
lessTail = lessTail->next;
}
else {
//尾插到大链表中去
greaterTail->next = pcur;
greaterTail = greaterTail->next;
}
pcur = pcur->next;
}
//大链表的最后一个节点的指向为空
greaterTail->next = NULL;
//大小链表相连
lessTail->next = greaterHead->next;
ListNode* ret = lessHead->next;
free(lessHead);
free(greaterHead);
lessHead = greaterTail = NULL;
return ret;
}
};
//作业题七:
/*对于一个链表,请设计一个时间复杂度为O(n), 额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
1->2->2->1
返回:true*/
//方法一,遍历链表,将链表中的数据存入一个数组中,然后在数组中判断是否为回文数/*
//struct ListNode {
// int val;
// struct ListNode* next;
// ListNode(int x) : val(x), next(NULL) {}
//}; */
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
// write code here
int arr[1000];
ListNode* pucr = A;
int i = 0;
//遍历链表,将链表中每一个结点中的数值拿到数组中去
while (pucr)
{
arr[i++] = pucr->val;
pucr = pucr->next;
}
//i即为结点的个数
//找中间结点,判断是否为回文数
int left = 0;
int right = i - 1;
while (left < right)
{
if (arr[left] != arr[right])
{
return false;
}
left++;
right--;
}
//是回文结构
return true;
}
};
//方法二:
//反转链表
//1.通过快慢指针找到中间结点
//2.找到中间结点之后,对结点之后的链表进行反转
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
ListNode* FindMidNode(ListNode* phead) {
ListNode* slow = phead;
ListNode* fast = phead;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* reverseList(ListNode* phesd) {
ListNode* n1, * n2, * n3;
n1 = NULL, n2 = phesd,
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
//1.找中间节点
ListNode* mid = FindMidNode(A);
//2.根据中间节点反转后边的列表
ListNode* right = reverseList(mid);
//3.从原链表和反转之后的链表比较节点的值
ListNode* left = A;
while (right) {
if (left->val != right->val) {
return false;
}
left = left->next;
right = right->next;
}
return true;
}
};
总结
以上便是部分单链表的OJ题目,还有双向链表和环形链表没有给出具体的解析和代码,环形链表的题目博主放在下边了(大家可以尝试写一写):
环形链表I
提示:(使用快慢指针)
快慢指针快慢指针,即慢指针⼀次⾛⼀步,快指针⼀次⾛两步,两个指针从链表起始位置开始运⾏,如果链表带环则⼀定会在环中相遇,否则快指针率先⾛到链表的未尾
环形链表II
随机链表的复制
更多链表算法刷题⼊⼝:
关于双向链表,那就是另外一个专题了,在以后的时间,博主会抽时间写出这个专题的
本篇博客到此结束,谢谢阅读!我们下次栈和队列再见!