中等题 ------ 链表

文章目录

  • 1. 删除链表中的倒数第N个节点
    • (1)栈
    • (2)双指针(快慢指针)
  • 2. 交换链表中的节点
  • 3. 两数相加
  • 4. 合并两个链表
  • 5. 旋转链表
  • 6. 奇偶链表
  • 7. 两两交换
  • 8. k 个一组翻转链表
  • 9. 分割链表
  • 10. 分隔链表
  • 11. 重排链表
  • 12.设计链表
  • 13. 对链表进行插入排序
  • 14. 删除链表中的节点
  • 15.设计跳表
  • 16. 链表组件
  • 17. 链表中的下一个更大节点
  • 18 .从链表中删去总和值为零的连续节点
    • (1)暴力
    • (2)哈希表+前缀和
  • 19. 找出临界点之间的最小和最大值
  • 20.合并k个升序链表

1. 删除链表中的倒数第N个节点

在这里插入图片描述
这道题可以直接求出链表的长度,然后求出它用len - n 不就是正数的长度吗?就会把问题编程了删除正数第几个节点,那种方式就不写了,看下面的其他两种吧。

(1)栈

  • 我们将每个节点全部入栈,栈顶元素就是最后一个元素,而要对栈进行出栈,正好是倒着出,所以符合倒数第k个的性质。
  • 如果找到当前的节点位置,在栈中找其前一个节点更好找。使prev -> next = cur -> next 就好了
  • 但要注意的是,如果当前节点是头节点的话,prev 是没有的,链表进行头删就好了。
  • 我下面的栈是0号位置不存数组,所以 所删的节点公式是 top - n + 1。具体看怎么设计的栈。
    在这里插入图片描述
#define MAX_SIZE 31
struct ListNode* removeNthFromEnd(struct ListNode* head, int n)
{struct ListNode** stack = (struct ListNode**)malloc(sizeof(struct ListNode*) * MAX_SIZE);int top = 0;struct ListNode* cur = head;while(cur != NULL){//入栈stack[++top] = cur;cur = cur -> next;}int index = top - n + 1;if(index == 1){//头删head = head -> next;return head;}struct ListNode* prev = stack[index - 1];prev -> next = stack[index] -> next;free(stack[index]);return head;
}

(2)双指针(快慢指针)

  • 一开始,fast指针比slow指针快上 n步。
  • 然后slow 和 fast 一起走, 如果说 fast走完了,那么slow就是需要删除的节点。
  • 同样还需要一个prev来维护,如果fast和slow 走完了,prev还是NULL,就证明没动,要删除的节点是头节点。

在这里插入图片描述

struct ListNode* removeNthFromEnd(struct ListNode* head, int n)
{struct ListNode* fast = head;struct ListNode* slow = head;//fast 比 slow 快 n 个while(n != 0){fast = fast -> next;n--;}//当fast 走完时候,slow 就是需要删除的节点struct ListNode* prev = NULL;while(fast != NULL){prev = slow;slow = slow -> next;fast = fast -> next;}if(prev == NULL){//头删head = head -> next;return head;}prev -> next = slow -> next;free(slow);return head;
}

2. 交换链表中的节点

在这里插入图片描述
这道题是将正数第k个节点,和倒数第k个节点进行交换。

  • 在上一道题目中,可以求出倒数第k个节点是多少,那么正数第k个不是更好求吗?
  • 我们同样只需要遍历一遍链表就可以得出。
  • 采用上一道题的方式,利用快慢指针去找倒数第k个节点。
  • 而正数第k个顺带也能求出来。
  • 看下图,需要求倒数第k个节点先求fast,然而fast的前一个就是正数第k个。
    在这里插入图片描述
struct ListNode* FindEndth(struct ListNode* head, int k,struct ListNode** prev)
{struct ListNode* fast = head;struct ListNode* slow = head;while(k != 0){*prev = fast;fast = fast -> next;k--;}while(fast != NULL){slow = slow -> next;fast = fast -> next;}return slow;
}struct ListNode* swapNodes(struct ListNode* head, int k)
{struct ListNode* begin = head;struct ListNode* end = FindEndth(head,k,&begin);//交换int tmp = begin -> val;begin -> val = end -> val;end -> val = tmp;return head;
}

3. 两数相加

在这里插入图片描述
题目中给你两个数,这俩数本来就是逆序的给你的,然后将这俩数加起来的和存储到链表中去。
我没看到题目里加粗的那俩大字逆序,完了我手动逆序了一下。。。
关键是啥,前三个测试用例你看。
在这里插入图片描述
第一个是不管逆序不逆序,结果竟然都一样,而后俩,数都一样,逆序了肯定也一样。
所以前三个就这样过了,结果一提交,错了。

  • 所以回到题目上,就和之前再数组中加法和字符串中的加法一样,不过这个是得自己开辟空间
struct ListNode* BuyNode(int* sum)
{struct ListNode* nweNode = (struct ListNode*)malloc(sizeof(struct ListNode));nweNode -> val = (*sum % 10);*sum /= 10;return nweNode;
}struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2)
{struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* cur = head;struct ListNode* cur1 = l1;struct ListNode* cur2 = l2;int sum = 0;while(cur1 != NULL || cur2 != NULL || sum != 0){if(cur1 != NULL){sum += cur1 -> val;cur1 = cur1 -> next;}if(cur2 != NULL){sum += cur2 -> val;cur2 = cur2 -> next;}//指向下一个节点cur -> next = BuyNode(&sum);cur = cur -> next;}cur -> next = NULL;return head -> next;
}

4. 合并两个链表

在这里插入图片描述
这道题是给你两个链表,然后再给你两个下标[a,b],注意是下标,将list1中[a,b]区间的值换成list2
就好了。

  • 那么我们首先去遍历list1,找到其开始和结束的区间,也就是那两个指针,
  • prev [begin,end].指针顺序是这样子的。
  • 所以将prev -> next = list2, 将list2Tail -> next = end -> next 就好了。
    在这里插入图片描述
struct ListNode* mergeInBetween(struct ListNode* list1, int a, int b, struct ListNode* list2)
{struct ListNode* prev = NULL,*begin = list1, *end = list1 -> next;int i = 0, j = 1;//确定删除区间while(i != a || j != b){if(i != a){i++;prev = begin;begin = begin -> next;}   if(j != b){j++;end = end -> next;}}//找到list2的尾struct ListNode* tail = list2;while(tail -> next != NULL){tail = tail -> next;}prev -> next = list2;tail -> next = end -> next;return list1;
}

5. 旋转链表

在这里插入图片描述
题目要求给我们一个链表,然后再给一个k,k决定你旋转几次链表。
在这里插入图片描述
看上图可以发现,就是说如果k % 链表长度为0的话,那么就是原地,不进行旋转。
相应的,roate k,就是从倒数第k的位置进行截断,将其变成新的头节点。
没错,我又用到了倒数第k个节点这个函数🤣

  • 有了以上规律我们就好做了。

  • 首先遍历一遍原来的链表,对其进行求长度的同时,得到链表最后一个节点。

  • 然后判断k%len 决定k是进行旋转多少次,取倒数第几个。

  • 然后会形成一个像下图这样的,三个指针变量,prev,cur,tail。
    在这里插入图片描述

  • prev -> next = NULL, tail -> next = head;

  • return cur;

 //找出倒数第n个节点,并且还得拿prev记录前一个
struct ListNode* FindNthEnd(struct ListNode* head,struct ListNode** prev, int n)
{struct ListNode* slow = head, *fast = head;while(n != 0){fast = fast -> next;n--;}while(fast != NULL){*prev = slow;slow = slow -> next;fast = fast -> next;}return slow;
}struct ListNode* rotateRight(struct ListNode* head, int k)
{if(head == NULL){return head;}int len = 1;struct ListNode* tail = head;//找尾,同时记录出长度while(tail -> next != NULL){len++;tail = tail -> next;}k %= len;if(k == 0){return head;}struct ListNode* prev = NULL;struct ListNode* cur = FindNthEnd(head,&prev,k);prev -> next = NULL;tail -> next = head;return cur;    
}

6. 奇偶链表

在这里插入图片描述
这道题的是要将下标为奇数的全部放在左边,下标为偶数的全部放在右边,第一个按所以1为计算。

  • 我们可以创建4个指针来分别表示oddHead 和 oddTail, evenHead 和 evenTail。
  • 然后两个Tail去遍历链表,因为奇偶是交替出现的,所以我们让Tail -> next = Tail -> next -> next;
  • 而循环结束的条件,将会是判断evenTail,因为奇数是在偶数前面的,判断偶数的边界即可,
  • 看下图:
    在这里插入图片描述
  • 所以说不管长度为奇数还是偶数,oddTail 永远都指向evenHead就好了.
struct ListNode* oddEvenList(struct ListNode* head)
{if(head == NULL || head -> next == NULL){return head;}struct ListNode* oddHead = head, *evenHead = head -> next;struct ListNode* oddTail = head, *evenTail = head -> next;while(evenTail != NULL && evenTail -> next != NULL){oddTail -> next = oddTail -> next -> next;oddTail = oddTail -> next;evenTail -> next = evenTail -> next -> next;evenTail = evenTail -> next;}oddTail -> next = evenHead;return oddHead;
}

7. 两两交换

在这里插入图片描述
题目中要求两两交换相邻节点的值,不让改变节点的值。

  • 我们首先需要一个头节点,这样子可以更加的方便。

在这里插入图片描述
我们只有先改变cur -> next = next -> next. 如果先改变了next -> next = cur,那么节点3就找不到了
在这里插入图片描述
最后一步,便是next -> next = cur。
在这里插入图片描述
此时 2 和 1已经交换完成,而下一次需要交换的是 3 和 4
所以此时,拿1 充当对于 3 和 4的头节点 ,也是 prev.

在这里插入图片描述
这样子就会使其一直走下去,除非遇到下面这种情况是无法再继续了。
在这里插入图片描述

struct ListNode* swapPairs(struct ListNode* head)
{   if(head == NULL || head -> next == NULL){return head;}struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));newHead -> next = head;struct ListNode* prev = newHead;struct ListNode* cur = NULL;struct ListNode* next = NULL;while(prev -> next != NULL && prev -> next -> next != NULL){cur = prev -> next;next = cur -> next;prev -> next = next;cur -> next = next -> next;next -> next = cur;prev = cur; }return newHead -> next;
}

8. k 个一组翻转链表

在这里插入图片描述
这道题使要求我们,每k个节点然后对其进行翻转链表。
首先我们得学会如何翻转链表,在以前的简单题目中是有刷过的,这里就不重复在写一遍翻转链表的思想。

  • 我们只需要将每k个链表进行翻转,将一个大链表,切断成每个小链表就好了,比如下图
    在这里插入图片描述
  • 既然中间的翻转链表都会了,所以直接看如何切断,和链接就好了。
  • 找到begin和end节点,然后先记录end以后的链表,然后从begin开是翻转链表。
  • 翻转链表之后,将prev -> next = end , begin -> next = tmp;
    在这里插入图片描述
  • 所以说有了这些东西,可以发现,新增加一个头节点来充当prev,
  • 然后下次再逆序的时候使 prev = begin就好了。

代码如下:

//逆序链表
struct ListNode* ReverseList(struct ListNode* head)
{if(head == NULL){return NULL;}struct ListNode* newhead = NULL;struct ListNode* cur = head;struct ListNode* next = head -> next;while(cur != NULL){cur -> next = newhead;newhead = cur;cur = next;if(next != NULL)next = next -> next;}return newhead;
}struct ListNode* reverseKGroup(struct ListNode* head, int k)
{int len = 0;struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));newHead->next = head;struct ListNode* begin = head;//计算出链表的长度while (begin != NULL){len++;begin = begin->next;}int i = 0;struct ListNode* prev = newHead;while (len - i >= k){begin = prev->next;struct ListNode* end = begin;//找到当前k的结尾int j = 1;while (j != k){j++;end = end->next;}//将end->next = NULL,拿临时指针存储end之后的链表struct ListNode* tmp = end->next;end->next = NULL;//逆序单个子链表end = ReverseList(begin);//end将变成开始prev->next = end;//而begin将变成结束,连上刚才所断开的tmpbegin->next = tmp;prev = begin;i += k;}return newHead -> next;
}

9. 分割链表

在这里插入图片描述
题目要求我们将链表进行分割,把所有小于x的放在链表左侧,大于或者等于x的放到链表右侧,从测试用例可以看出,不需要有序也行。

  • 我们创建出两个链表,一个表示小的数,一个表示大的数。
  • 然后去遍历整个链表的同时构建这两个链表,最后大的因为是要放到后面的,所以将bigTail -> next = NULL;
  • 而将 smallTail -> next = bigList 就好了。
struct ListNode* partition(struct ListNode* head, int x)
{struct ListNode* bigList = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* bigTail = bigList;struct ListNode* samllList = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* smallTail = samllList;struct ListNode* cur = head;while(cur != NULL){if(cur -> val < x){smallTail -> next = cur;smallTail = smallTail -> next;}else{bigTail -> next = cur;bigTail = bigTail -> next;}cur = cur -> next;}bigTail -> next = NULL;smallTail -> next = bigList -> next;return samllList -> next;
}

10. 分隔链表

在这里插入图片描述
这道题是给一个链表,然后将链表分成k份,各个链表长度不能相差不能超过1,然后前面的链表长度得比后面的大。

  • 我们可以得出一个关系公式。
  • len / k 得到的是每一段存放多少个。
  • len % k 得到的是前几段里面需要放 len / k + 1个节点。
int GetListLen(struct ListNode* head)
{struct ListNode* cur = head;int len = 0;while(cur != NULL){cur = cur -> next;len++;}return len;
}
struct ListNode** splitListToParts(struct ListNode* head, int k, int* returnSize)
{struct ListNode** ans = (struct ListNode**)malloc(sizeof(struct ListNode*) * k);*returnSize = k;struct ListNode* cur = head;int len = GetListLen(head);if(len <= k){//len <= k, 直接按照一个一个分,不够的补NULLint i;for (i = 0; i < k; i++){if(cur != NULL){ans[i] = cur;cur = cur -> next;ans[i] -> next = NULL;}else{ans[i] = NULL;}}}else{//len % k 得到的是前几个多放一个。//len / k 决定每一段放几个。int tmp = len % k, num = len / k;int i;struct ListNode* begin = cur; //将前几个里面多放一个for (i = 0; i < tmp; i++){int j = 1;struct ListNode* end = begin;while(j != num + 1){end = end -> next;j++;}ans[i] = begin;begin = end -> next;end -> next = NULL;}//后面的按照num放就好了for (i; i < k; i++){int j = 1;struct ListNode* end = begin;while(j != num){end = end -> next;j++;}ans[i] = begin;begin = end -> next;end -> next = NULL;}}return ans;
}

11. 重排链表

在这里插入图片描述
这道题要求将链表重新排列一种形式如下:

 - 0 , n , 1 , n-1, 2 , n - 2 ……………… mid。
  • 可以将该题分成三个步骤。
  • 首先找到链表的中间节点。
  • 然后将后半段翻转。
  • 最后将前半段和后半段拼接在一起就好了。
//返回链表的中间节点,然后记录前一个出现的节点
struct ListNode* GetListMidNode(struct ListNode* head, struct ListNode** prev)
{struct ListNode* slow =  head;struct ListNode* fast =  head;while(fast != NULL && fast -> next != NULL){*prev = slow;slow = slow -> next;fast = fast -> next -> next;}return slow;
}//逆序链表
struct ListNode* ReverseList(struct ListNode* head)
{if(head == NULL || head -> next == NULL){return head;}struct ListNode* newhead = NULL;struct ListNode* next = head -> next;while(head != NULL){head -> next = newhead;newhead = head;head = next;if(next != NULL){next = next -> next;}}return newhead;
}void reorderList(struct ListNode* head)
{if(head == NULL || head -> next == NULL || head -> next -> next == NULL){return;}//获取中间节点struct ListNode* prev = NULL;struct ListNode* mid = GetListMidNode(head,&prev);prev -> next = NULL;//先将逆序之前的头存储下来struct ListNode* rearTail = mid -> next;struct ListNode* rear = ReverseList(mid -> next);mid -> next = NULL;rearTail -> next = mid;//至此前半段和后半段链表依然构成。struct ListNode* cur1 = head;struct ListNode* cur2 = rear;struct ListNode* next1 = head -> next;struct ListNode* next2 = rear -> next;while(cur1 != NULL && cur2 != NULL){cur1 -> next = cur2;if(next1 != NULL){cur2 -> next = next1;}cur1 = next1;cur2 = next2;if(next1 != NULL){next1 = next1 -> next;}if(next2 != NULL){next2 = next2 -> next;}}}

12.设计链表

在这里插入图片描述
这道题设计一个链表,在数据结构中咱们都学过,我这里直接设计一个双向循环链表
淦淦!!!

typedef struct Node
{int val;struct Node* prev;struct Node* next;
} MyLinkedList,ListNode;MyLinkedList* BuyNode(int val)
{MyLinkedList* newNode = (MyLinkedList*)malloc(sizeof(ListNode));newNode -> prev = NULL;newNode -> next = NULL;newNode -> val = val;return newNode;
}MyLinkedList* myLinkedListCreate()
{//头节点,无意义。MyLinkedList* obj = BuyNode(-1);obj -> next = obj;obj -> prev = obj;return obj;
}int myLinkedListGet(MyLinkedList* obj, int index)
{int i = 0;ListNode* cur = obj -> next;while(i != index){cur = cur -> next;if(cur == obj){return -1;}i++;}return cur -> val;
}void myLinkedListAddAtHead(MyLinkedList* obj, int val)
{ListNode* newNode = BuyNode(val);if (obj->next != obj){obj->next->prev = newNode;}newNode -> next = obj -> next;newNode -> prev = obj;obj -> next = newNode;if(obj -> prev == obj){//没有尾,第一个也是尾obj -> prev = obj -> next;}}void myLinkedListAddAtTail(MyLinkedList* obj, int val)
{if (obj->prev == obj){//无节点时候,尾插相等于头插myLinkedListAddAtHead(obj, val);}else{ListNode* newNode = BuyNode(val);ListNode* tail = obj->prev;newNode->prev = tail;tail->next = newNode;newNode->next = obj;obj->prev = newNode;}
}void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val)
{int i = 0;ListNode* cur = obj -> next;while(i != index){   if(cur == obj){return;}cur = cur->next;i++;if (cur == obj && i == index){//等于链表长度进行尾插myLinkedListAddAtTail(obj, val);return;}}   ListNode* newNode = BuyNode(val);newNode -> prev = cur -> prev;newNode -> next = cur;cur -> prev -> next = newNode;cur -> prev = newNode;}void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index)
{  int i = 0;ListNode* cur = obj -> next;while(i != index){cur = cur -> next;if(cur == obj){//索引大于链表长度,啥也不干return;}i++;}cur -> prev -> next = cur -> next;cur -> next -> prev = cur -> prev;free(cur);
}void myLinkedListFree(MyLinkedList* obj)
{ListNode* cur = obj -> next;ListNode* prev = cur;while(cur != obj){prev = cur;cur = cur -> next;free(prev);}obj -> prev = obj;obj -> next = obj;
}

13. 对链表进行插入排序

在这里插入图片描述

这道题目的意思是要我们对链表进行一个插入排序。
当你也可以,转数组,qsort,转链表。三步大功告成,那这样子确实很无趣。
如果对前面的排序不是很了解,在我前面的文章中也有排序。

  • 做链表这种题目,做的多了就会发现用一个头节点来协助我们会方便许多,还是新建一个头节点

  • 然后我们要去找,不是增序的那个节点,比如下图:
    在这里插入图片描述

  • 拿 prev 和 cur去比较,发现不是大小顺序不对,然后对其进行操作,先将tmp成为head.

  • 然后对先对tmp进行比遍历,找到第一个大于cur-> val的节点就停下来,现在tmp在1的位置
    在这里插入图片描述

  • 然后就行对其进行一个链表的插入了,也可以说是交换吧。

  • 下面的三个步骤是在合适的位置进行交换插入,而最后一下就是使cur继续迭代下去。
    在这里插入图片描述

  • 注意步骤的顺序不能变。

struct ListNode* insertionSortList(struct ListNode* head)
{if(head == NULL || head -> next == NULL){return head;}struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));newHead -> next = head;struct ListNode* cur = head -> next, *prev = head, *tmp = NULL;while(cur != NULL){if(cur -> val >= prev -> val){//去找当前节点比前一个小的节点。cur = cur -> next;prev = prev -> next;}else{//比前面小的节点。tmp = newHead;//找合适的位置while(tmp -> next -> val < cur -> val){tmp = tmp -> next;}prev -> next = cur -> next;cur -> next = tmp -> next;tmp -> next = cur;  //cur 继续迭代下去cur = prev -> next; }}return newHead -> next;
}

14. 删除链表中的节点

在这里插入图片描述
在这里插入图片描述
这道题,说实话,有点意思,哈哈哈。
look picture。

在这里插入图片描述

  • 有了这prev cur next 这三个指针,链表里就无敌了好吧。
void deleteNode(struct ListNode* node)
{struct ListNode* prev = node;struct ListNode* cur = prev -> next;struct ListNode* next = cur -> next;prev -> val = cur -> val;prev -> next = next;free(cur);
}

15.设计跳表

在这里插入图片描述
这道题是设计一个跳表,在前一篇章节中有讲述C语言实现跳表(附源码)

#define MAX_LEVEL 5typedef struct SkipNode
{int val;int maxLeve;struct SkipNode** next;
}SkipNode;typedef struct
{int level;SkipNode* head;
} Skiplist;SkipNode* BuyNode(int level, int val)
{SkipNode* newNode = (SkipNode*)malloc(sizeof(SkipNode));newNode -> val = val;newNode -> maxLeve = level;newNode->next = (SkipNode**)malloc(sizeof(SkipNode*) * level);int i;for (i = 0; i < level; i++){newNode -> next[i] = NULL;}return newNode;
}int GetRandomLevel()
{int level = 1;while(rand() % 2){level++;}//最大不能超过MAX_LEVELreturn level > MAX_LEVEL ? MAX_LEVEL : level;
}Skiplist* skiplistCreate()
{Skiplist* obj = (Skiplist*)malloc(sizeof(Skiplist));obj -> head = BuyNode(MAX_LEVEL,-1);obj -> level = 0;int i;SkipNode* newHead = BuyNode(MAX_LEVEL,-1);for (i = 0; i < MAX_LEVEL; i++){obj -> head -> next[i] = newHead;}return obj;
}bool skiplistSearch(Skiplist* obj, int target)
{if(obj == NULL){return false;}SkipNode* cur = NULL;if(obj -> level > 0){cur = obj -> head -> next[obj -> level - 1];}int i;for (i = obj -> level - 1; i >= 0; i--){while(cur -> next[i] != NULL && cur -> next[i] -> val < target){cur = cur -> next[i];}//跳过该层if(cur -> next[i] == NULL){continue;}//找到了if(cur -> next[i] -> val == target){return true;}}  return false;
}void skiplistAdd(Skiplist* obj, int num)
{SkipNode** prevNodes = (SkipNode**)malloc(sizeof(SkipNode*) * MAX_LEVEL);SkipNode* cur = NULL;if(obj -> level > 0){cur = obj -> head -> next[obj -> level - 1];}int i;for (i = obj -> level - 1; i >= 0; i--){while(cur -> next[i] != NULL && cur -> next[i] -> val < num){cur = cur -> next[i];}prevNodes[i] = cur;}int suitLevel = GetRandomLevel();if(suitLevel > obj -> level){for (i = obj -> level; i < suitLevel; i++){prevNodes[i] = obj -> head -> next[i];}//更新层数obj -> level = suitLevel;}SkipNode* newNode = BuyNode(suitLevel,num);//新节点与前面的节点进行链接for (i = 0; i < suitLevel; i++){newNode -> next[i] = prevNodes[i] -> next[i];prevNodes[i] -> next[i] = newNode;}    }bool skiplistErase(Skiplist* obj, int num)
{if(!skiplistSearch(obj,num)){//没有目标节点return false;}SkipNode** prevNodes = (SkipNode**)malloc(sizeof(SkipNode*) * MAX_LEVEL);SkipNode* cur = NULL;if(obj -> level > 0){cur = obj -> head -> next[obj -> level - 1];}int i;for (i = obj -> level - 1; i >= 0; i--){while(cur -> next[i] != NULL && cur -> next[i] -> val < num){cur = cur -> next[i];}prevNodes[i] = cur;}cur = cur -> next[0];for (i = 0; i < cur -> maxLeve; i++){prevNodes[i] -> next[i] = cur -> next[i];}//判断是否删除最高层for (i = obj -> level -1; i >= 0; i--){if(obj -> head -> next[i] -> next[i] != NULL){break;}obj -> level--;}free(cur);return true;
}void skiplistFree(Skiplist* obj)
{if(obj == NULL || obj -> head -> next[0] -> next[0] == NULL){return;}SkipNode* cur = obj -> head -> next[0];SkipNode* tmp = cur -> next[0];while(cur != NULL){tmp = tmp -> next[0];free(cur);cur = tmp;}free(obj);obj = NULL;
}/*** Your Skiplist struct will be instantiated and called as such:* Skiplist* obj = skiplistCreate();* bool param_1 = skiplistSearch(obj, target);* skiplistAdd(obj, num);* bool param_3 = skiplistErase(obj, num);* skiplistFree(obj);
*/

16. 链表组件

在这里插入图片描述这道题通俗易懂的来说就是要我们统计出一个链表中,连续节点的区间个数,这个区间里的每个数都必须是nums中的。

  • 看到关于计数的这种题,首先就应该想到是哈希表。
  • 拿哈希表记录出每个数是否出现。
  • 然后对链表进行遍历,如果当前节点在哈希表中出现,那么就记录一下,并且如果是连续的区间的话,后面也会在哈希表中出现,对后面的也全部遍历完。
int numComponents(struct ListNode* head, int* nums, int numsSize)
{int* map = (int*)calloc(100000,sizeof(int));int i,ans = 0;//统计是否出现for (i = 0; i < numsSize; i++){map[nums[i]] = 1;}struct ListNode* cur = head;while(cur != NULL){if(map[cur -> val] == 1){while(cur != NULL && map[cur -> val] == 1){cur = cur -> next;}ans++;   }if(cur == NULL){break;}cur = cur -> next;}return ans;
}
  • 当然还可以拿一个falg来表示,当前是否进入连续区间,
  • 如果进入连续区间flag = true 否则 flag = false;

代码如下:

int numComponents(struct ListNode* head, int* nums, int numsSize)
{int* map = (int*)calloc(100000,sizeof(int));int i,ans = 0;bool flag = false;//统计是否出现for (i = 0; i < numsSize; i++){map[nums[i]] = 1;}struct ListNode* cur = head;while(cur != NULL){if(map[cur -> val] == 1){if(!flag){ans++;flag = true;}}else{flag = false;}cur = cur -> next;}return ans;
}

17. 链表中的下一个更大节点

在这里插入图片描述
这道题的是让我们拿一个和链表一样大的数组来记录每个元素相对应的下一个更大的元素。
我记得有一次看别人题解是这样说的 "看到下一个更。。"一般来说都用单调栈。

  • 首先我们应该创建一个栈出来,每一层栈中有两个元素,一个是节点node,另一个是索引index
  • 因为最后返回的ans是按照节点所相对应构成的数组。
  • 首先呢将链表中的第一个元素入栈,然后呢对链表进行遍历。
  • 如果发现当前节点元素小于或者等于栈顶的元素,那么就其入栈,因为找的是下一个更大的。
  • 反之呢,对栈进行出栈,出栈的同时,将其对应的索引位置放入当前更大节点的值。
  • 反复对栈进行一个出栈的操作,知道栈顶元素不再小于当前的元素。
  • 那么此时就将当前元素入栈即可。
  • 一直反复,只到遍历完链表 。
int GetListLenth(struct ListNode* head)
{if(head == NULL){return 0;}int len = 0;while(head != NULL){len++;head = head -> next;}return len;
}typedef struct 
{struct ListNode* node;int index;
}Stack;int* nextLargerNodes(struct ListNode* head, int* returnSize)
{int len = GetListLenth(head);int* ans = (int*)malloc(sizeof(int) * len);Stack* s = (Stack*)malloc(sizeof(Stack) * (len + 1));*returnSize = len;int top = 0,pos = 0,i;memset(ans,0,sizeof(int) * len);struct ListNode* cur = head;//讲第一个节点入栈s[++top].node = cur;s[top].index = pos++;cur = cur -> next;//遍历链表while(cur != NULL){//获取栈顶元素if(cur -> val <= s[top].node -> val){//当前的元素不大于栈顶元素的话,入栈s[++top].node = cur;s[top].index = pos;}else{while(top != 0 && cur -> val > s[top].node -> val){ans[s[top--].index] = cur -> val;}//入栈s[++top].node = cur;s[top].index = pos;}pos++;cur = cur -> next;}return ans;
}

18 .从链表中删去总和值为零的连续节点

在这里插入图片描述
这道题要求删除题目所给链表中的连续节点并且它的和是0。

(1)暴力

  • 首先呢既然知道题目中必须只能删除连续的,那么我们就对链表进行一个双层for循环
  • 使每一个节点都当一次开始区间,然后去找和为0的结束节点。如果找到了,删除它就好了。
  • 因为需要删除的可能使第一个节点,所以我们开辟一个头节点出来。
struct ListNode* removeZeroSumSublists(struct ListNode* head)
{struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));newHead -> next = head;struct ListNode* prev = newHead;for (prev; prev != NULL; prev = prev -> next){struct ListNode* end = prev -> next;int sum = 0;for(end; end != NULL; end = end -> next){sum += end -> val;if(sum == 0){prev -> next = end -> next;}}}return newHead -> next;
}

(2)哈希表+前缀和

这个思路就很巧妙,但是用C语言来做就很🤢,你懂的。

  • 首先我们建立一个哈希表,拿前缀和去当key去用,拿node当val。

  • 在遍历第一遍的时候,将前缀和与相对的节点存到哈希表中去,如果前缀和多次出现,只存它最后一次出现的位置。

  • 然后再第二次遍历的时候,同样也用前缀和去遍历,如果发现当前节点的前缀和与哈希表中的前缀和不一致,那么他们中间的节点和就是0.

  • 看下图就可以发现。
    在这里插入图片描述
    在这里插入图片描述

  • 从上面两张图就能够发现,既然两种情况都可以。

  • 第一个是利用了头节点,假设头节点的前缀和是0.

  • 第二个假如我们不用头节点。它不为前缀和不为0的情况,就会走到节点2上去。

  • 那是不是用不用都行?
    在这里插入图片描述

  • 上图中右边的跳了等于白跳,所以应该利用头节点,将其前缀和设置成0.

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
#define MAX_SIZE 1001typedef struct 
{struct ListNode* node;int prefix;    
}Hash;//如果存在返回位置,不存在则返回合适的位置
bool HashSearch(Hash* map, int size, int target, int* index)
{int left = 0, right = size - 1;while(left <= right){int mid = (left + right) / 2;if(map[mid].prefix < target){left = mid + 1; }else if(map[mid].prefix > target){right = mid - 1;}else{*index = mid;return true;}}*index = left;return false;
}//前缀和是key,node是val
void HahsAdd(Hash* map,int* size, int key, struct ListNode* node)
{if(*size + 1 >= MAX_SIZE){printf("哈希空间不足!\n");exit(-1);}int index = 0;if(HashSearch(map,*size,key,&index)){//数据有直接覆盖map[index].node = node;}else{//没有数据,需要新插入,挪动数据for (int i = *size; i > index; i--){map[i] = map[i-1];}map[index].prefix = key;map[index].node = node;(*size)++;}}struct ListNode* removeZeroSumSublists(struct ListNode* head)
{Hash* map = (Hash*)malloc(sizeof(Hash) * MAX_SIZE);int mapSize = 0;struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));newHead -> val = 0;newHead -> next = head;struct ListNode* cur = head;int prefixSum = 0;while(cur != NULL){prefixSum += cur -> val;//将前缀和为key,node为val入哈希HahsAdd(map,&mapSize,prefixSum,cur);cur = cur -> next;}// for (int k = 0; k < mapSize; k++)// {//     printf("key: %d val: %d \n",map[k].prefix,map[k].node->val);// }//二次遍历cur = newHead;prefixSum = 0;while (cur != NULL){prefixSum += cur->val;int index = 0;if (HashSearch(map, mapSize, prefixSum, &index)){struct ListNode* node = map[index].node;if (node != cur){cur->next = node -> next;}}cur = cur->next;}return newHead->next;
}

19. 找出临界点之间的最小和最大值

在这里插入图片描述
这道题是计算出极值,与极值之间的最大差距和最小差距。

  • 我们用begin mid end 的三个变量来代表到了哪一位了。
  • 要想使距离最大,最前面的极值和最后面的极值,没有啥比他俩更远了吧。
  • 而最小的距离,则需要再遍历中去比较
  • 我们在比较的时候,只需找到第一个出现的极值用begin记录,后续比较mid和end求出最小值。
  • 而最大的就是end - begin了
int* nodesBetweenCriticalPoints(struct ListNode* head, int* returnSize)
{int* ans = (int*)malloc(sizeof(int) * 2);*returnSize = 2;ans[0] = -1;ans[1] = -1;if(head == NULL || head -> next == NULL || head -> next -> next == NULL){return ans;}struct ListNode* prev = head,*cur = prev -> next, *next = cur -> next;int min = INT_MAX,max = INT_MIN;int begin, mid, end,n = 2;  //n表示第几位bool flag = false;  //用于标记第一个的开始while(next != NULL){if((cur -> val > prev -> val && cur -> val > next -> val) || (cur -> val < prev -> val && cur -> val < next -> val)){if(flag == false){//begin记录第一个之后就不动了begin = mid = end = n;flag = true;}else{//拿end和mid去比较就能求出最短距离end = n;if(end - mid < min){min = end - mid;}//mid记得移动mid = end;}}n++;prev = cur;cur = next;next = next -> next;}if(flag == false || begin == end){//说明没有极值或者只有一个极值return ans;}ans[0] = min;ans[1] = end - begin;return ans;
}

20.合并k个升序链表

在这里插入图片描述
这道是说给你一个链表数组里面都是升序排序的,然后对其进行排序,最后返回。
最直观的方式就是说可以先将所有的链表挨个拼接起来,然后对整个链表进行排序。
就像下面这个代码,可以用上面第13题中的插入排序也能解答。

struct ListNode* insertionSortList(struct ListNode* head)
{if(head == NULL || head -> next == NULL){return head;}struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));newHead -> next = head;struct ListNode* cur = head -> next, *prev = head, *tmp = NULL;while(cur != NULL){if(cur -> val >= prev -> val){//去找当前节点比前一个小的节点。cur = cur -> next;prev = prev -> next;}else{//比前面小的节点。tmp = newHead;//找合适的位置while(tmp -> next -> val < cur -> val){tmp = tmp -> next;}prev -> next = cur -> next;cur -> next = tmp -> next;tmp -> next = cur;  //cur 继续迭代下去cur = prev -> next; }}return newHead -> next;
}struct ListNode* mergeKLists(struct ListNode** lists, int listsSize)
{struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));head -> next = NULL;struct ListNode* cur = head;int i;for (i = 0; i < listsSize; i++){struct ListNode* node = lists[i];while(node != NULL){cur -> next = node;cur = cur -> next;node = node -> next;}}head -> next = insertionSortList(head -> next);return head -> next;
}

当然也可以用归并排序来解答。

 //合并两个有序链表
struct ListNode* mergeTwoLists(struct ListNode* a, struct ListNode* b)
{if(a == NULL || b == NULL){return a == NULL ? b : a;}struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));dummy -> next = NULL;struct ListNode* cur = dummy;struct ListNode* cur1 = a, *cur2 = b;while(cur1 != NULL && cur2 != NULL){if(cur1 -> val < cur2 -> val){cur -> next = cur1;cur1 = cur1 -> next;}else{cur -> next = cur2;cur2 = cur2 -> next;}cur = cur -> next;}cur -> next = cur1 == NULL ? cur2 : cur1;return dummy -> next;
}struct ListNode* Merge(struct ListNode** lists, int left, int right)
{if(left == right){return lists[left];}if(left > right){return NULL;}int mid = (left + right) / 2;struct ListNode* leftList = Merge(lists,left, mid);struct ListNode* rightList = Merge(lists,mid+1, right);return mergeTwoLists(leftList,rightList);
}struct ListNode* mergeKLists(struct ListNode** lists, int listsSize)
{return Merge(lists,0,listsSize - 1);
}

感觉这道题更像再考排序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/667267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

19.HarmonyOS App(JAVA)依赖布局DependentLayout使用方法

layout/ability_main.xml 显示位置不对&#xff1a;检查布局文件ohos:lef_of "id:tuzi",比如显示在兔子的左侧&#xff0c;这里就会显示不对。 需要id前没有$符号。改为&#xff1a; ohos:lef_of "$id:tuzi" <?xml version"1.0" encodi…

第六讲:文件操作

第六讲:文件操作 文件夹创建文件夹移动文件夹复制文件夹删除文件夹文件操作文件读取文件写入文件文件夹 创建文件夹 定义创建文件夹函数:chmk_path()定义一个函数 chmk_path(),这个函数的功能是创建文件夹。 首先需要导入操作系统接口模块——os 模块,这个模块中包含某些函…

前端小案例——滚动文本区域(HTML+CSS, 附源码)

一、前言 实现功能: 这个案例实现了一个具有滚动功能的文本区域&#xff0c;用于显示长文本内容&#xff0c;并且可以通过滚动条来查看完整的文本内容。 实现逻辑&#xff1a; 内容布局&#xff1a;在<body>中&#xff0c;使用<div>容器创建了一个类名为listen_t…

5.0 HDFS 集群服务建立教程

HDFS 集群是建立在 Hadoop 集群之上的&#xff0c;由于 HDFS 是 Hadoop 最主要的守护进程&#xff0c;所以 HDFS 集群的配置过程是 Hadoop 集群配置过程的代表。 使用 Docker 可以更加方便地、高效地构建出一个集群环境。 每台计算机中的配置 Hadoop 如何配置集群、不同的计…

【实战系列----消息队列 数据缓存】rabbitmq 消息队列 搭建和应用

线上运行图&#xff0c;更新不算最新版&#xff0c;但可以使用修改线程等补丁功能&#xff0c;建议使用新版本。 远程服务器配置图: 这个可以更具体情况&#xff0c;因为是缓存队列理所当然 内存越大越好&#xff0c;至于核心4核以上足够使用。4核心一样跑 这里主要是需要配置服…

2024美赛数学建模E题思路+代码

文章目录 1 赛题思路2 美赛比赛日期和时间3 赛题类型4 美赛常见数模问题5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 美赛比赛日期和时间 比赛开始时间&#xff1a;北京时间2024年2月2日&#xff08;周五&#xff…

断路精灵:探秘Sentinel熔断策略的神奇效果

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 断路精灵&#xff1a;探秘Sentinel熔断策略的神奇效果 前言熔断策略基础&#xff1a;数字断路精灵的初见熔断策略的基本原理&#xff1a;简单示例演示熔断策略的基本用法&#xff1a; 慢调用比例熔断策…

计算机网络-差错控制(奇偶校验码 CRC循环冗余码)

文章目录 差错从何而来从传感器层面提高信道比来减少线路本身的随机噪声的一个例子热噪声和冲击噪声 数据链路层的差错控制检错编码-奇偶校验码检错编码-CRC循环冗余码例子注意 差错从何而来 噪声通常指的是任何未预期的、随机的信号干扰&#xff0c;这些干扰可能源自多种物理…

linux ln命令-linux软链接、硬链接-linux软、硬链接的区别(一):硬链接

0、序 1、硬链接 1.1、创建硬链接的注意事项 (1)、硬链接不能指向目录&#xff08;不能对目录文件做硬链接&#xff09;。 (2)、硬链接只能在同一个文件系统中创建&#xff0c;不能在不同的文件系统之间做硬链接。就是说&#xff0c;链接文件和被链接文件必须位于同一个文件…

java设计模式:观察者模式

在平常的开发工作中&#xff0c;经常会使用到设计模式。合理的使用设计模式&#xff0c;可以提高开发效率、提高代码质量、提高代码的可拓展性和维护性。今天来聊聊观察者模式。 观察者模式是一种行为型设计模式&#xff0c;用于对象之间一对多的依赖关系&#xff0c;当被观察对…

vue3+threejs+koa可视化项目——模型文件上传(第四步)

文章目录 ⭐前言&#x1f496;往期node系列文章&#x1f496;threejs系列相关文章&#x1f496;vue3threejs系列 ⭐koa后端文件上传(koa-body)&#x1f496;自动创建目录&#x1f496;自定义目录上传&#x1f496;apifox自测上传接口 ⭐vue3前端上传模型文件&#x1f496; axio…

LeetCode --116

116. 填充每个节点的下一个右侧节点指针 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针&#xff0c;让…

代码编辑器1.9.0

多线程&#xff01;&#xff01;&#xff01; #include <winsock2.h> #include <windows.h> #include <iostream> #include <stdlib.h> #include <string.h> #include <fstream> #include <conio.h> #include <stdio.h> #incl…

说说RDB和AOF

简介&#xff1a; 众所周知&#xff0c;redis是一个内存数据库&#xff0c;当机器重启后&#xff0c;内存中数据都会丢失。所以redis提供了两种持久化方式&#xff0c;即&#xff1a;RDB(保存一个时间点前的数据)和AOF(保存redis服务器端执行的每一条命令)。 RDB: RDB有两种…

【算法与数据结构】300、674、LeetCode最长递增子序列 最长连续递增序列

文章目录 一、300、最长递增子序列二、674、最长连续递增序列三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、300、最长递增子序列 思路分析&#xff1a; 第一步&#xff0c;动态数组的含义。 d p [ i ] dp[i] dp[i…

程序执行内存区域,堆栈使用及区别

文章目录 一、程序执行内存区域二、堆区详解三、栈内存与堆内存的区别总结 一、程序执行内存区域 **代码区&#xff1a;**当我们的程序被执行时&#xff0c;它会有一个加载准备的过程。其中函数及内部的流程结构代码指令会被放到代码区中&#xff0c;等待着被调用执行。 **常数…

【JS】基于React的Next.js环境配置与示例

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍基于React的Next.js环境配置与示例。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下…

【乳腺肿瘤诊断分类及预测】基于自适应SPREAD-PNN概率神经网络

课题名称&#xff1a;基于自适应SPREAD-PNN的乳腺肿瘤诊断分类及预测 版本日期&#xff1a;2023-06-15 运行方式: 直接运行PNN0501.m 文件即可 代码获取方式&#xff1a;私信博主或QQ&#xff1a;491052175 模型描述&#xff1a; 威斯康辛大学医学院经过多年的收集和整理&…

笔记---求组合数

总&#xff1a; ①10w次询问——1 < b < a < 2000——递推求—— N N N2 ②1w次询问——1 < b < a < 105——公式求—— N l o g N NlogN NlogN ③20次询问——1 < b < a < 1018——Lucas定理 ④1次询问——1 < b < a < 5000——拆分质因子…

【数据结构与算法】(7)基础数据结构之双端队列的链表实现、环形数组实现示例讲解

目录 2.6 双端队列1) 概述2) 链表实现3) 数组实现习题E01. 二叉树 Z 字层序遍历-Leetcode 103 2.6 双端队列 1) 概述 双端队列、队列、栈对比 定义特点队列一端删除&#xff08;头&#xff09;另一端添加&#xff08;尾&#xff09;First In First Out栈一端删除和添加&…