中等题 ------ 链表

文章目录

  • 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个节点

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

(1)栈

  • 我们将每个节点全部入栈,栈顶元素就是最后一个元素,而要对栈进行出栈,正好是倒着出,所以符合倒数第k个的性质。
  • 如果找到当前的节点位置,在栈中找其前一个节点更好找。使prev -> next = cur -> next 就好了
  • 但要注意的是,如果当前节点是头节点的话,prev 是没有的,链表进行头删就好了。
  • 我下面的栈是0号位置不存数组,所以 所删的节点公式是 top - n + 1。具体看怎么设计的栈。
    中等题 ------ 链表_第2张图片
#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,就证明没动,要删除的节点是头节点。

中等题 ------ 链表_第3张图片

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. 交换链表中的节点

中等题 ------ 链表_第4张图片
这道题是将正数第k个节点,和倒数第k个节点进行交换。

  • 在上一道题目中,可以求出倒数第k个节点是多少,那么正数第k个不是更好求吗?
  • 我们同样只需要遍历一遍链表就可以得出。
  • 采用上一道题的方式,利用快慢指针去找倒数第k个节点。
  • 而正数第k个顺带也能求出来。
  • 看下图,需要求倒数第k个节点先求fast,然而fast的前一个就是正数第k个。
    中等题 ------ 链表_第5张图片
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. 两数相加

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

  • 所以回到题目上,就和之前再数组中加法和字符串中的加法一样,不过这个是得自己开辟空间
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. 合并两个链表

中等题 ------ 链表_第8张图片
这道题是给你两个链表,然后再给你两个下标[a,b],注意是下标,将list1中[a,b]区间的值换成list2
就好了。

  • 那么我们首先去遍历list1,找到其开始和结束的区间,也就是那两个指针,
  • prev [begin,end].指针顺序是这样子的。
  • 所以将prev -> next = list2, 将list2Tail -> next = end -> next 就好了。
    中等题 ------ 链表_第9张图片
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. 旋转链表

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

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

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

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

  • 然后会形成一个像下图这样的,三个指针变量,prev,cur,tail。
    中等题 ------ 链表_第12张图片

  • 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. 奇偶链表

中等题 ------ 链表_第13张图片
这道题的是要将下标为奇数的全部放在左边,下标为偶数的全部放在右边,第一个按所以1为计算。

  • 我们可以创建4个指针来分别表示oddHead 和 oddTail, evenHead 和 evenTail。
  • 然后两个Tail去遍历链表,因为奇偶是交替出现的,所以我们让Tail -> next = Tail -> next -> next;
  • 而循环结束的条件,将会是判断evenTail,因为奇数是在偶数前面的,判断偶数的边界即可,
  • 看下图:
    中等题 ------ 链表_第14张图片
  • 所以说不管长度为奇数还是偶数,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. 两两交换

中等题 ------ 链表_第15张图片
题目中要求两两交换相邻节点的值,不让改变节点的值。

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

中等题 ------ 链表_第16张图片
我们只有先改变cur -> next = next -> next. 如果先改变了next -> next = cur,那么节点3就找不到了
中等题 ------ 链表_第17张图片
最后一步,便是next -> next = cur。
中等题 ------ 链表_第18张图片
此时 2 和 1已经交换完成,而下一次需要交换的是 3 和 4
所以此时,拿1 充当对于 3 和 4的头节点 ,也是 prev.

中等题 ------ 链表_第19张图片
这样子就会使其一直走下去,除非遇到下面这种情况是无法再继续了。
中等题 ------ 链表_第20张图片

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 个一组翻转链表

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

  • 我们只需要将每k个链表进行翻转,将一个大链表,切断成每个小链表就好了,比如下图
    中等题 ------ 链表_第22张图片
  • 既然中间的翻转链表都会了,所以直接看如何切断,和链接就好了。
  • 找到begin和end节点,然后先记录end以后的链表,然后从begin开是翻转链表。
  • 翻转链表之后,将prev -> next = end , begin -> next = tmp;
    中等题 ------ 链表_第23张图片
  • 所以说有了这些东西,可以发现,新增加一个头节点来充当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将变成结束,连上刚才所断开的tmp
        begin->next = tmp;

        prev = begin;
        i += k;
    }

    return newHead -> next;
}

9. 分割链表

中等题 ------ 链表_第24张图片
题目要求我们将链表进行分割,把所有小于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. 分隔链表

中等题 ------ 链表_第25张图片
这道题是给一个链表,然后将链表分成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, 直接按照一个一个分,不够的补NULL
        int 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. 重排链表

中等题 ------ 链表_第26张图片
这道题要求将链表重新排列一种形式如下:

 - 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.设计链表

中等题 ------ 链表_第27张图片
这道题设计一个链表,在数据结构中咱们都学过,我这里直接设计一个双向循环链表
淦淦!!!

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. 对链表进行插入排序

中等题 ------ 链表_第28张图片

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

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

  • 然后我们要去找,不是增序的那个节点,比如下图:
    中等题 ------ 链表_第29张图片

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

  • 然后对先对tmp进行比遍历,找到第一个大于cur-> val的节点就停下来,现在tmp在1的位置
    中等题 ------ 链表_第30张图片

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

  • 下面的三个步骤是在合适的位置进行交换插入,而最后一下就是使cur继续迭代下去。
    中等题 ------ 链表_第31张图片

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

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. 删除链表中的节点

中等题 ------ 链表_第32张图片
中等题 ------ 链表_第33张图片
这道题,说实话,有点意思,哈哈哈。
look picture。

中等题 ------ 链表_第34张图片

  • 有了这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.设计跳表

中等题 ------ 链表_第35张图片
这道题是设计一个跳表,在前一篇章节中有讲述C语言实现跳表(附源码)

#define MAX_LEVEL 5

typedef 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_LEVEL
    return 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. 链表组件

中等题 ------ 链表_第36张图片这道题通俗易懂的来说就是要我们统计出一个链表中,连续节点的区间个数,这个区间里的每个数都必须是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. 链表中的下一个更大节点

中等题 ------ 链表_第37张图片
这道题的是让我们拿一个和链表一样大的数组来记录每个元素相对应的下一个更大的元素。
我记得有一次看别人题解是这样说的 "看到下一个更。。"一般来说都用单调栈。

  • 首先我们应该创建一个栈出来,每一层栈中有两个元素,一个是节点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 .从链表中删去总和值为零的连续节点

中等题 ------ 链表_第38张图片
这道题要求删除题目所给链表中的连续节点并且它的和是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.

  • 看下图就可以发现。
    中等题 ------ 链表_第39张图片
    中等题 ------ 链表_第40张图片

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

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

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

  • 那是不是用不用都行?
    中等题 ------ 链表_第41张图片

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
#define MAX_SIZE 1001


typedef 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. 找出临界点之间的最小和最大值

中等题 ------ 链表_第42张图片
这道题是计算出极值,与极值之间的最大差距和最小差距。

  • 我们用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个升序链表

中等题 ------ 链表_第43张图片
这道是说给你一个链表数组里面都是升序排序的,然后对其进行排序,最后返回。
最直观的方式就是说可以先将所有的链表挨个拼接起来,然后对整个链表进行排序。
就像下面这个代码,可以用上面第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);
}

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

你可能感兴趣的:(链表,数据结构,leetcode,C语言)