一、链表的中间结点 --- 找到中间的节点并返回
** **思路1:先计算链表的长度,然后定义一个变量k没走一步k++,当k>= count / 2时循环结束,返 回cur
struct ListNode* middleNode(struct ListNode* head) {
int count = 0;
struct ListNode* cur = head;
while(cur != NULL)
{
cur = cur->next;
count++;
}
int k = 0;
cur = head;
while(k < count / 2)
{
cur = cur->next;
k++;
}
return cur;
}
思路2:快慢指针
快指针一次走两步,慢指针一次走一步,因为当指针走到结尾的时候,慢指针正好就到一半的位置
struct ListNode* middleNode(struct ListNode* head) {
int count = 0;
struct ListNode* fast = head;
struct ListNode* slow = head;
while (fast != NULL)
{
count++;
if (count % 2 == 0)
{
slow = slow->next;
}
fast = fast->next;
}
}
** ** 思路三
同上,只是优化了一下
struct ListNode* middleNode(struct ListNode* head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
二、链表中的倒数第k个结点 --- 找到倒数第k个结点返回该指针的地址
思路:创建两个结构体指针,一个是快指针,一个是慢指针,跟上面这题找中间结点很像,不过快指针就不是一次走两步了,而是走k步,快指针先走k步后,再和慢指针一起走,快指针和慢指针的距离就会差距k,如果是倒数第2个结点,那么当快指针走到空时,慢指针正好在倒数第二个,同理倒数第3、第4也是类似
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k)
{
struct ListNode* fast, * slow;
fast = slow = pListHead;
while (k--)
{
if (fast == NULL)
{
return NULL;
}
fast = fast->next;
}
while (fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
三、合并两个有序链表 --- 将两个有序的链表合并在一起形成一个新的有序链表
思路:创建四个指针,p1和p2是分别指向两个有序链表,newlist是用来记录合并成有序链表的头结点,最后需要返回合并后的新头结点地址,tmp是指向新链表的指针,p1和p2一起遍历,p1和p2指向的结点的值进行比较,小的那一个结点的地址赋给tmp->next,然后小的那一个指针往后走一步,当其中一个指针走向空该循环结束,循环结束后若p1为空则将tmp->next指向篇,若p2为空则将tmp->next指向p1,这样就把剩余没有链接的结点全链接起来了,最后返回newlist即可
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
// 首先判断边界情况(输入有可能为空)
if (list1 == NULL) return list2;
if (list2 == NULL) return list1;
struct ListNode* p1 = list1, * p2 = list2;
struct ListNode* newlist = NULL;
struct ListNode* tmp = newlist;
// 当两个链表均不为空时,使用两个指针逐个判断大小
while (p1 != NULL && p2 != NULL) {
// 新链表赋值。这里要特别注意一下,如果新链表为空时,
// 需要先赋值才能继续操作
if (newlist == NULL) {
newlist = p1->val > p2->val ? p2 : p1;
tmp = newlist;
}
else {
tmp->next = p1->val > p2->val ? p2 : p1;
tmp = tmp->next;
}
// 更新遍历旧链表的指针
if (p1->val > p2->val)
p2 = p2->next;
else
p1 = p1->next;
}
// 当指向其中一个链表的指针已为空,剩下的操作就只需要把新链表结尾的
// 指针指向另一个链表的剩余部分
if (p1 == NULL) {
tmp->next = p2;
}
else if (p1 != NULL) {
tmp->next = p1;
}
return newlist;
}
四、链表分割 --- 把大于x的值放后面,把小于x的节点放在前面,不改变原来的数据顺序,其实就是原链表数据的先后顺序是不会变的,但是整体变了
思路:将原链表小于x的节点连到一个新链表上 --- lessHead,lessTail,将原链表大于x的节点连到另一个新链表上 --- greaterHead,greaterTail,定义一个用来遍历原链表的节点 --- cur,lessHead和greaterHead属于开的两个哨兵位,遍历原链表,将原链表的每个结点的值分别与x进行比较,小于x的链接到lessTail->next,大于x的链接到greaterTail->next,循环结束后将两个链表链接到一起,再将greaterTail->next置为空,防止greaterTail->next链接到less这个链表中的一个结点,形成一个死循环
ListNode* partition(ListNode* phead, int x)
{
//将原链表小于x的节点连到一个新链表上 --- lessHead,lessTail
//将原链表大于x的节点连到另一个新链表上 --- greaterHead,greaterTail
//定义一个用来遍历原链表的节点 --- cur
ListNode* lessHead, * lessTail;
ListNode* greaterHead, greaterTail;
//还要开一个哨兵位
lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
lessTail->next = NULL;
greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
greaterTail->next = NULL;
ListNode* cur = phead;
while(cur)
{
if (cur->val < x)
{
lessTail->next = cur;
lessTail = lessTail->next;
}
else
{
greaterTail->next = cur;
greaterTail = greaterTail->next;
}
cur = cur->next;
}
lessTail->next = greaterHead->next;
// 2 5 10 15 4
//最后切到15的时候,15还链接着4,就会导致4的next链接greaterHead->next,
//然后15->next链接到4,形成一个死循环
greaterTail->next = NULL;
ListNode* next = lessHead->next;
free(lessHead);
free(greaterHead);
lessHead = NULL;
greaterHead = NULL;
return next;
}
五、链表的回文结构 --- 要求时间复杂度为O(N),空间复杂度为O(1)
链表的回文结构:说白了就是对称结构,左边翻折过来和右边的重合即是回文结构
思路:找到中间节点后逆置后半部分,然后定义一前一后的两个指针,p1指向头结点,p2指向中 间结点,两个同时走,每次比较两个指针指向的值若不相等,说明不是回文,若两个指针同时走 到空时都是相等的,那么这个链表就是回文的,就拿上面的偶数那个例子来说1->2->2->1逆置之后为 1->2 1->2,1和1相等,2和2相等,然后此时前一个2还链接着后半部分逆置之后的2所以两个链表会同时走向空,奇数个也是如此,不需要考虑其中一个指针会先走到空
bool chkPalindrome(ListNode* A)
{
ListNode* slow, * fast;
slow = fast = A;
//找到中间节点
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
ListNode* prev = slow;
ListNode* next = slow->next;
slow->next = NULL;
slow = next;
//在后半部分逆置
while (slow)
{
next = slow->next;
slow->next = prev;
prev = slow;
slow = next;
}
//判断是不是回文结构
ListNode* p1, * p2;
p1 = A;
p2 = prev;
while (p1 && p2)
{
if (p1->val != p2->val)
{
return false;
}
p1 = p1->next;
p2 = p2->next;
}
return true;
}
六、相交链表
思路1:依次取A链表中的每个节点跟B链表中的所有节点比较,如果有地址相同的节点,就是相交,第一个相同的交点,这种方法时间复杂度最大,不可取,虽然可以输出正确的结果,但是代码运行效率太低了,跑不过测试的
思路2:时间复杂度:O(N+M),空间复杂度:O(1)
情况一:两个链表相交
pA走了 n + m + c(这个c是两个指针同步走的次数)
pB走了m + n + c
走的都是相同次数,会到达同一个点,因为一个是从A链表遍历到链表B,一个是从B链表 遍历到链表A
情况二:两个链表不相交
如果 m = n,那么两个同时走到null1,返回null
如果m不等于n,那么走完两个指针走完 m + n 次会同时走到null,返回null
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL) {
return NULL;
}
struct ListNode* pA = headA, * pB = headB;
while (pA != pB) {
pA = pA == NULL ? headB : pA->next;
pB = pB == NULL ? headA : pB->next;
}
return pA;
}
思路3:时间复杂度为O(N),空间复杂度为O(1)
分别计算两个链表的长度countA和countB,然后用k来存储countA和countB的差值,定义两个 指针分别指向上边和下边,让长的那一边先走k步,再让两个指针一起走,这样两个指针会同时走 到相交点,返回相交点即可,若不相交,两个指针会同时走向空,返回null
struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB) {
int countA = 1;
int countB = 1;
struct ListNode* curA, * curB;
curA = headA;
curB = headB;
while (curA->next)
{
curA = curA->next;
countA++;
}
while (curB->next)
{
curB = curB->next;
countB++;
}
if (curA != curB)
{
return NULL;
}
int k = countA > countB ? countA - countB : countB - countA;
int n = 0;
curA = headA;
curB = headB;
while (curA && curB)
{
if (n >= k)
{
if (curA == curB)
{
return curA;
}
curA = curA->next;
curB = curB->next;
}
else
{
if (countA > countB)
{
curA = curA->next;
}
else
{
curB = curB->next;
}
}
n++;
}
return NULL;
}
七、环形链表
一、什么是环形链表?
环形链表是一种特殊类型的链表数据结构,其最后一个节点的"下一个"指针指向链表中的某个节点,形成一个闭环。换句话说,链表的最后一个节点连接到了链表中的某个中间节点,而不是通常情况下连接到空指针(null)。如图:
二、判断是否为环形链表
思路:快慢指针。快指针一次走两步,慢指针一次走一步,
其实本质就是追击问题,如果没有环,那快指针会先走到尾,返回false,如果有环,那么两个 指针在同一个地方循环走,快指针会在环里面追上慢指针
bool hasCycle(struct ListNode* head) {
struct ListNode* fast, * slow;
fast = slow = head;
while (fast && fast->next)
{
fast = fast->next->next;
if (fast == slow)
{
return true;
}
slow = slow->next;
}
return false;
}
三、找到入环点
slow走一步,fast走两步,一定会相遇,如何求环的入口点?
结论:一个指针从相遇点开始走,一个指针从链表头开始走,他们会在环的入口点相遇。
证明:假设head指针距离环入口点距离为L, meetNode也就是相遇的点距离环的入口点距离为 x,环的长度为C,追上相遇的过程中:慢指针走的距离:L+X,快指针走的距离:L+C*N+X,
N是慢指针和快指针相遇之前,fast在环里面走的圈数(N >= 1)
快指针走的路程是慢指针的2倍,也就是 L + C * N + X = 2 *( L + X) -> L + X = C * N -> L = N * C - X -> L = (N-1) * C + C - X,
(N-1) * C指的是从meetNode走又回到meetNode,就相当于没走
C - X就是从meetNode到入环点的距离
这个公式的意思就是如果L不长的情况下,N为1,那么meetNode到入环点的距离等于L,也就 是会在入环点相遇,如果L很长,C很小,那么 L的距离会等于meetNode转很多圈之后的距离 加上meetNode到入环点的距离C-X,那么前面说了无论转多少圈meetNode这个指针就相当于 没走,无论转多少圈,始终会转到这个meetNode这个点,那么我们可以把这段距离等化成0, 那么这个公式就变成L = C - X也就是跟上面那个一样了,因为无论怎么走meetNode距离入环 点的距离始终是C - X,也就是L 跟 C - X相等
所以一个从相遇点开始走和从头开始走,走到入口点都是一样的距离,两个同步走,最后相遇 的那点就是入环点。
思路:由上证明了快指针一次走两步和慢指针一次走一步一定会相遇,且相遇点距离入环点的 距离跟头指针距离入环点的距离相等。那么我们可以先用快指针和慢指针找到相遇点,再让相 遇点和头结点一起走,最后两者相遇即为入环点。
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* fast, * slow;
fast = slow = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (fast == slow)
{
struct ListNode* meet = slow;
while (meet != head)
{
meet = meet->next;
head = head->next;
}
return head;
}
}
return NULL;
}
八、随机链表的复制
什么是随机链表?
随机链表也就是带随机指针random的节点![](https://img-blog.csdnimg.cn/direct/301a37af62384d968260710cdccec7e6.png)
struct Node* copyRandomList(struct Node* head) {
//思路:建立两个链表的链接,通过原链表的random,也就是利用两个链表的连接关系,
//找到复制后链表的random。比如复制后的13这个节点的random是原链表13的random的next那个节
//点,也就是复制之后的7节点
if (head == NULL)
{
return NULL;
}
struct Node* node = head;
//建立两个链表的链接
while (node)
{
struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));
newnode->val = node->val;
newnode->next = node->next;
node->next = newnode;
node = node->next->next;
}
//处理复制后的random
node = head;
while (node)
{
struct Node* copynode = node->next;
copynode->random = (node->random != NULL) ? node->random->next : NULL;
node = node->next->next;
}
//把复制后的链表拆下来,将原链表恢复
node = head;
struct Node* newhead = head->next;
while (node)
{
struct Node* copynode = node->next;
node->next = node->next->next;
copynode->next = (copynode->next != NULL) ? copynode->next->next : NULL;
node = node->next;
}
return newhead;
}
总结
以上就是关于链表的一些OJ题的解法,希望本文会对你有所帮助,如果有什么问题,可在下方留言沟通。
版权归原作者 Ccat. 所有, 如有侵权,请联系我们删除。