Leetcode链表题目总结

  最近刷了一些链表相关的题目,总结了下比较经典的题目。在leetcode中,官方给出的说明是malloc的内存不需要释放,所以代码中都没有free。但是在实际的编程中,我们要将申请的内存释放掉,同时把指针指向NULL,这是一种良好的习惯。

876.链表的中间结点

题目描述

  给定一个带有头结点 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,我们返回第二个结点。
提示:给定链表的结点数介于 1 和 100 之间。

解题思路

  这是一个典型的双指针问题,定义一个快指针,一个慢指针。快指针一次走两步,慢指针一次走一步。当快指针走到结尾时,慢指针指向的就是中间节点。这里要注意一个细节:题目要求:「两个中间结点的时候,返回第二个中间结点」。此时可以在草稿纸上写写画画,就拿自己的左右手的两根指头同步移动,可以得出:快指针可以前进的条件是:当前快指针和当前快指针的下一个结点都非空。如果题目要求「在两个中间结点的时候,返回第一个中间结点」,此时快指针可以前进的条件是:当前快指针的下一个结点和当前快指针的下下一个结点都非空。注意体会以上二者的不同之处。

代码实现

struct ListNode* middleNode(struct ListNode* head){
    struct ListNode* slower = head;
    struct ListNode* faster = head;
    //注意边界条件
  while((faster!=NULL)&&(faster->next!=NULL))
{

    faster=faster->next->next;
    slower=slower->next;
}
return slower;

}

328. 奇偶链表

题目描述

  给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
  请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入:2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
应当保持奇数节点和偶数节点的相对顺序。 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

解题思路

  这个题目可以用双指针来解决。定义OddNode和EvenNode代表奇偶链表,分别用来遍历链表。其中OddNode = head,EvenNode = head->next。两个指针同时开始遍历,依次连接遍历的元素。到最后时,分别将奇偶链表连接起来即可。

代码实现

struct ListNode* oddEvenList(struct ListNode* head){
    if(head == NULL)
        return NULL;
    
    struct ListNode* OddHead = head;
    struct ListNode* OddNode = head;
    struct ListNode* EvenHead = head->next;
    struct ListNode* EvenNode = head->next;

    while((EvenNode!=NULL)&&(EvenNode->next!=NULL))
    {
        OddNode->next = OddNode->next->next;
        EvenNode->next = EvenNode->next->next;
        OddNode=OddNode->next;
        EvenNode=EvenNode->next;
    }
     //跳出循环时OddNode指在链表的前一个节点,EvenNode指向OddNode的前一个 拼接链表, 循环结束时 oddNode 指向奇数链表最后一个元素
    OddNode -> next = EvenHead; 
    return OddHead;
}

面试题 02.06. 回文链表

题目描述

  编写一个函数,检查输入的链表是否是回文的。

示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

扫描二维码关注公众号,回复: 11548868 查看本文章

解题思路

  利用双指针找到链表的中间位置,然后将后半段反转,将后半段依次与前半段比较。

代码实现

bool isPalindrome(struct ListNode* head){
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    struct ListNode* p = head;
    while((fast!=NULL) &&(fast->next!=NULL))
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    struct ListNode *cur = slow;
    struct ListNode *pre = NULL;
     //将链表的后半段反转
    while(cur!=NULL){
        //保存下一个的值
        struct ListNode* temp = cur->next;
        //重新连接当前节点
        cur->next = pre;
        //更新pre
        pre = cur;
        //cur指向下一个
        cur = temp;
    }
   // 将后半段反转的链表与前半段依次比较
    while(pre&&head){
        if(pre->val!=head->val)
            return false;
        pre=pre->next;head=head->next;
    }
    return true;

}

1290.二进制链表转整数

题目描述

  给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。

示例 1:

在这里插入图片描述

输入:head = [1,0,1]
输出:5
解释:二进制数(101) 转化为十进制数 (5)

解题思路

  将二进制数转换为10进制数的公式为:2n-1 + 2n-2+ …+2n-0(其中n为链表长度)。比如,将1001011转换为10进制数为:1 * 26+0 * 25+0 * 24+1 * 23+0 * 22+1 * 21+1 * 20 = 75。所以,我们只要遍历链表,编程实现这个公式就可以。

代码实现

int getDecimalValue(struct ListNode* head){
    int cnt=0;
    int data = 0;
    struct ListNode* p =head;
    //计算链表长度
    while(p)
    {
        p = p->next;
        cnt++;
    }
    //注意长度要减一
     cnt =  cnt -1;
    //从高位开始计算
    for(;cnt>=0;cnt--)
    {
        data += head->val*pow(2,cnt);
        head = head->next;
    }
    return data;
}

24.反转链表

题目描述

  定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

  示例:
  输入: 1->2->3->4->5->NULL
  输出: 5->4->3->2->1->NULL   
  限制:0 <= 节点个数 <= 5000

解题思路

  反转链表实际就是重新指向结构体中的next指针,我们需要修改下一个节点的next指针指向前一个节点。所以,在遍历链表时我们要逐个修改链表的指针指向。这个题目也可以用递归来做,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作head .
  此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。同时让当前结点的 next 指针指向 NULL ,从而实现从链表尾部开始的局部反转。当递归函数全部出栈后,链表反转完成。

代码实现

  非递归

struct ListNode* reverseList(struct ListNode* head){

    //判断节点长度
    if(head == NULL || head->next == NULL)
         return head;
    else
        {
              struct ListNode* former = NULL;
              struct ListNode* mid = head;
              struct ListNode* latter = NULL;
              while(mid != NULL)
       {
              //保存下一个节点
              latter = mid->next;
              //让该节点指向上一个节点
              mid->next = former;
              //上一个节点走到当前节点
              former = mid;
             //当前节点走到下一个节点
             mid = latter;
       }
            return former;
         }

  递归版本

struct ListNode* reverseList(struct ListNode* head){

		//递归版本
        if(head == NULL || head->next == NULL){
                return head;
        }else{
        		//保存头节点
                struct ListNode* mid = head;
                //保存下一个节点
                struct ListNode* latter = mid->next;
                //递归到最后一个几点,返回转置后链表的地址
                head = reverseList(latter);
                //让后面的节点指向前一个节点
                latter->next = mid;
                //每次递归返回都赋值为空,最后一次返回将转置后的节点的next赋值为空
                mid->next = NULL;
                return head;
        }
         }

面试题 02.07. 链表相交

题目描述

  给定两个(单向)链表,判定它们是否相交并返回交点。请注意相交的定义基于节点的引用,而不是基于节点的值。换句话说,如果一个链表的第k个节点与另一个链表的第j个节点是同一节点(引用完全相同),则这两个链表相交。

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA
= 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为
[5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3,
skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2
(注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB
= 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 解释:这两个链表不相交,因此返回 null。

注意:

如果两个链表没有交点,返回 null 。 在返回结果后,两个链表仍须保持原有的结构。 可假定整个链表结构中没有循环。 程序尽量满足 O(n)
时间复杂度,且仅用 O(1) 内存。

解题思路

  根据题意,两个链表相交的点是指: 两个指针指向的内容相同,则说明该结点记在A链表上又在B链表上,进而说明A和B是相交的
而对于相交的情况,两条链表一定是这种结构:
在这里插入图片描述
  为什么呢?
  因为如果链表A和链表B相交于D的话,那么说明D结点即在A上又在B上,而D之后的元素自然也就均在A和B上了,因为他们是通过next指针相连的.
  如果有相交的结点D的话,每条链的头结点先走完自己的链表长度,然后回头走另外的一条链表,那么两结点一定为相交于D点,因为这时每个头结点走的距离是一样的,都是 AD + BD + DC,而他们每次又都是前进1,所以距离相同,速度又相同,固然一定会在相同的时间走到相同的结点上,即D点。

代码实现

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
 struct ListNode *p=headA;
   struct ListNode *q=headB;
   while(p!=q)
   {
       if(p==NULL)
            p=headB;
       else
            p=p->next;
       if(q==NULL)
            q=headA;
       else 
            q=q->next;
   }
   return q;
}

24.两两交换链表中的节点

题目描述

  给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.

解题思路

  本题需要两两交换链表中相邻的节点,这说明在在整个过程中都是功能相同的操作,这让我们就很自然的想到使用递归的方式去处理。递归的一个重要思想就是两部分:1.找到最简单的子问题求解,2.其他问题不考虑内在细节,只考虑整体逻辑,那我们现在来设计递归关系。

  首先看我们的函数swapPairs
  函数作用:两两交换链表中的节点
  输入:链表的头结点
  输出:操作后的链表的头结点
在这里插入图片描述
  1.对于本题,首先我们来看最简单的子问题,想要两两交换,最起码得有两个节点吧,所有没有节点或只有一个结点的时候我们就直接返回即可,也就是我们的终止条件。

  if((head==NULL) || (head->next==NULL))
    return head;

  2,实现两个节点的重新连接操作。首先我们要先连接第一个节点,前两个节点交换后,第一个节点要连接的是第二个节点的下一个,swapPairs(second->next)返回的是后面子链表交换后的第一个节点,所以直接取second->next进递归函数。最后second 连接 first就完成了。

// first 连接后面交换完成的子链表
first->next = swapPairs(second->next);
// second 连接 first
second->next = first;

  方法二:普通做法,每次一次连接两个节点,注意要先连接第一个节点。连接完成指向下一个位置即可。
在这里插入图片描述

代码实现

  递归版本

struct ListNode* swapPairs(struct ListNode* head){
    // 1. 终止条件:当前没有节点或者只有一个节点,肯定就不需要交换了
    if((head==NULL) || (head->next==NULL))
    return head;
    // 2. 调用单元
    // 需要交换的两个节点是 head 和 head.next
    struct ListNode* first = head;
    struct ListNode* second = head->next;
    // first 连接后面交换完成的子链表
    first->next = swapPairs(second->next);
    // second 连接 first
    second->next = first;
    // 3. 返回值:返回交换完成的子链表
    // seconde 变成了头结点
    return second;
    }

  非递归版本

struct ListNode* swapPairs(struct ListNode* head){
   //若节点小于2个,直接返回
    if(NULL == head || NULL == head->next)
        return head;

    struct ListNode* first = head;
    struct ListNode* second = head->next;
    //至少交换一次,返回新头节点指向原head的第二个节点
    struct ListNode* newHead = second;

    do{
        first->next=second->next;
        second->next=first;
        //移动first位置
        first=first->next;

        //根据下次是否有足够节点进行交换,决定是将已交换过的节点的next重行指向即将交换的节点,还是直接退出循环
        if(first&&first->next){
            second->next->next=first->next;
            second=first->next;
        }else{
            break;
        }
    } while(1);

    return newHead;
}

面试题 02.01. 移除重复节点

题目描述

  编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。

示例1: 输入:[1, 2, 3, 3, 2, 1]
输出:[1, 2, 3]
示例2: 输入:[1, 1, 1, 1, 2]
输出:[1, 2]
提示: 链表长度在[0, 20000]范围内。 链表元素在[0, 20000]范围内。
进阶:
如果不得使用临时缓冲区,该怎么解决?

解题思路

  思路一:定义两个指针current和p来逐个遍历链表,current元素依次和p比较,直到p为NULL,current向后移动一个。
  思路二:利用标记数组,标记当前值是否出现过

代码实现

  方法一:

struct ListNode* removeDuplicateNodes(struct ListNode* head){
  	//判断是否需要遍历
    if (head == NULL) return NULL;
    struct ListNode* current = head;
	//双指针逐个比较
    while (current) {
        struct ListNode* p = current;
        while(p->next) {
            if (p->next->val == current->val) {
                p->next = p->next->next;
            } else {
                p = p->next;
            }
        }
        current = current->next;
    }
    return head;
}

  方法二:

struct ListNode* removeDuplicateNodes(struct ListNode* head){
    //判断是否需要遍历
    if(head==NULL || head->next==NULL)  
        return head;
     //利用val的值在0~20000之间,标记是否出现过
    int index[20001] = {0}; 
    index[head->val] = 1;
    struct ListNode *pre = head, *q = head->next;
    while(q)
    {
     //当前val未出现过,保存当前val,修改pre和p指针
        if(index[q->val]==0)   
        {
            index[q->val] = 1;
            pre = q;
            q = q->next;
        }
        //当前值已经出现过了,删除当前节点
        else    
        {
            pre->next = q->next;
            q = pre->next;
        }
    }
    return head;
}

面试题52. 两个链表的第一个公共节点

题目描述

  输入两个链表,找出它们的第一个公共节点。
  如下面的两个链表:
在这里插入图片描述
  在节点 c1 开始相交。

示例 1:
在这里插入图片描述
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA
= 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:
在这里插入图片描述
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3,
skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2
(注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB= 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 解释:这两个链表不相交,因此返回 null。

注意:
如果两个链表没有交点,返回 null. 在返回结果后,两个链表仍须保持原有的结构。 可假定整个链表结构中没有循环。 程序尽量满足 O(n)
时间复杂度,且仅用 O(1) 内存。

解题思路

此题和上面面试题 02.07. 链表相交的解法相同,不再赘述。

21. 合并两个有序链表

题目描述

  将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

解题思路

  两个链表是排序过得,所以我们只需要同时遍历两个链表,比较链表元素的大小,将小的连接到新的链表中即可。最后,可能有一个链表会先遍历完,此时,我们直接将另一个链表连接到新链表的最后即可。

代码实现

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode head;
    struct ListNode *pre = &head;

    while (l1 && l2) {
        if (l1->val <= l2->val) {
            pre->next = l1;
            l1 = l1->next;
        } else {
            pre->next = l2;
            l2 = l2->next;
        }
        pre = pre->next;
    }
    //将剩下元素直接接在最后面
    pre->next = (NULL == l1 ? l2 : l1);
    return head.next;
}

面试题18. 删除链表的节点

题目描述

  给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
  返回删除后的链表的头节点。

示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

解题思路

  删除链表的节点,其实就是重新更改next指针的指向,让其跳过要删除的节点即可,因此,我们需要存储待删除节点的前一个元素。当删除的节点为头结点、中间节点、尾节点时需要分别讨论。
  1.删除的节点为头结点。定义一个空的节点,里面不存放元素,将next指针指向链表头。我们这个节点可以称作哨兵节点,只起指示作用,不存储元素。删除头结点的话,只需要将哨兵节点的next指针指向要删除节点的下一个即可。(其实也可以不定义哨兵节点,只不过删除起来比较麻烦,参考:史上最全单链表的增删改查反转等操作汇总以及5种排序算法(C语言)
  2.要删除的节点为中间节点时,我们遍历链表时要存储其前一个元素,让其指向删除节点的下一个即可。
  3.要删除的节点为尾节点,直接让pre节点指向NULL即可。

代码实现

struct ListNode* deleteNode(struct ListNode* head, int val){
    if(head==NULL)
    return NULL;
    struct ListNode* pre = head;
    struct ListNode* p = head;
    while(p)
    {  
        if(p->val == val)
        {
          //删除节点为尾节点
           if (p->next == NULL)
            {
                pre->next =NULL;
                return head;
            }
         //删除节点为头节点
            else if (p == head)
            {
                return head->next;
            }
         //删除节点为中间节点
            else  
            {
               pre->next = p->next;
              break;
            }

        }
    //记录下前一个节点
        pre = p;
        p = p->next;
    }
    
    return head;
}

445. 两数相加 II

题目描述

  给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
  你可以假设除了数字 0 之外,这两个数字都不会以零开头。
  进阶:
  如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例: 输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7

解题思路

  1 先将链表1和链表2中的数据存在stack中;
  2 从两个栈的栈顶开始取数据,相加,注意需要记录下是否有进位(存在carry变量中),每次都需要将进位一起加进去;
  3 当两个栈中有一个为空另一个不为空时,单独处理不为空的;
  4 如果都为空,表示数据全部加完;
  5 判断进位是否为0,如果为0表示没有进位,如果不为0表示有进位,将进位也需输出。

代码实现

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
   int stack1[100];
    int stack2[100];
    int top1 = -1;
    int top2 = -1;
    int carry = 0;
    int sum;
    struct ListNode *rlt = (struct ListNode*)malloc(sizeof(struct ListNode) * 1);

	//入栈
    while (l1 != NULL) {
        stack1[++top1] = l1->val;
        l1 = l1->next;
    }

    while (l2 != NULL) {
        stack2[++top2] = l2->val;
        l2 = l2->next;
    }

    rlt->next = NULL;
    while (top1 >= 0 && top2 >= 0) {
        int tmp = stack1[top1--] + stack2[top2--];
        sum = (tmp + carry) % 10;
        carry = (tmp + carry) / 10;
        rlt->val = sum;
        /*翻转链表链接  (正向连接为rlt->next = curr  rtl = curr)*/
        struct ListNode *curr = (struct ListNode*)malloc(sizeof(struct ListNode) * 1);
        curr->next = rlt;
        rlt = curr;
    }

    while (top1 >= 0) {
        int tmp = stack1[top1--];
        sum = (tmp + carry) % 10;
        carry = (tmp + carry) / 10;
        rlt->val = sum;
        struct ListNode *curr = (struct ListNode*)malloc(sizeof(struct ListNode) * 1);
        curr->next = rlt;
        rlt = curr;
    }

    while (top2 >= 0) {
        int tmp = stack2[top2--];
        sum = (tmp + carry) % 10;
        carry = (tmp + carry) / 10;
        rlt->val = sum;
        struct ListNode *curr = (struct ListNode*)malloc(sizeof(struct ListNode) * 1);
        curr->next = rlt;
        rlt = curr;
    }
	//进位值需要全部存储,如链表1[5],链表2[5],进位为1,我们需要全部存储10.
    if (carry != 0) {
        rlt->val = carry;
        return rlt;
    } else {
        return rlt->next;
    }
}

725. 分隔链表

题目描述

  给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。返回一个符合上述规则的链表的列表。
  举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

示例 1:
输入: root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:输入输出各部分都应该是链表,而不是数组。 例如, 输入的结点 root 的 val= 1, root.next.val = 2,
\root.next.next.val = 3, 且 root.next.next.next = null。 第一个输出 output[0]是 output[0].val = 1, output[0].next = null。 最后一个元素 output[4] 为 null,它代表了最后一个部分为空链表。
示例 2:
输入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。 提示: root 的长度范围: [0, 1000]. 输入的每个节点的大小范围:[0, 999]. k 的取值范围: [1, 50].

解题思路

  总体思路:类比给k个孩子分N个苹果,先平均分,再把多的从头往后分(实际操作时,是两个部分一起分的,因为要按照原来的顺序分)
  1.申请内存(注意是二维指针)[一段连续二维指针,便可写成数组的格式,用下标访问,output[i]对应的是第i个链表的表头]
  2.遍历链表得到length,算出mod-平均分完后多出来的结点数,size-每个部分本应有的结点数
  3.循环 分割链表(注意特殊情况:length<k,k以后的部分都应是NULL)
  3中的亮点:curSize的计算
  注意点:还有一个 *returnSize 参数需要赋值。 (因为传进来的是指针,直接赋值即可)

代码实现

struct ListNode** splitListToParts(struct ListNode* root, int k, int* returnSize){
    //注意申请内存的表达式
 struct ListNode** output=(struct ListNode**)malloc(k*sizeof(struct ListNode*)); 
    int length=0;
    struct ListNode* cur=root;
    while(cur!=NULL)
    {
        length++;
        cur=cur->next;
    }
     //遍历后不能忘了把指针重置到链表开头/
    cur=root;               
    int mod=length%k;
    int size=length/k;
    for(int i=0;i<k;i++)
    {
     //将每一个链表放进指针数组中
        output[i]=cur; 
       //如果i<k但是cur==NULL  那么证明是特殊情况(length<k) 只将cur(即NULL)赋给数组元素 后面的都不执行
        if(cur!=NULL)
        {       
        	//亮点 每一个子链表的实际大小=节点总数均分后的大小+余下的节点从前往后分
            int curSize=size+(mod-- >0? 1:0);
            //分割链表            
            for(int j=0; j<curSize-1; j++) cur=cur->next;   
            //存下链表节点的下一个位置,断开链表与之前链表的连接
            struct ListNode* next=cur->next;
            cur->next=NULL;
            cur=next;
        }
    }
    //题目中的一个参数(用来返回链表个数???)
    *returnSize=k;                
    return output;
}

面试题 02.08. 环路检测

题目描述

  给定一个有环链表,实现一个算法返回环路的开头节点。
  有环链表的定义:在链表中某个节点的next元素指向在它前面出现过的节点,则表明该链表存在环路。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1],pos = -1
输出:no cycle
解释:链表中没有环。
进阶: 你是否可以不用额外空间解决此题?

解题思路

  见下题

142. 环形链表 II

题目描述

  给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
  说明:不允许修改给定的链表。

示例 1: 在这里插入图片描述
输入:head = [3,2,0,-4], pos = 1 输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2: 在这里插入图片描述
输入:head = [1,2], pos = 0 输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3: 在这里插入图片描述
输入:head = [1], pos = -1 输出:no cycle 解释:链表中没有环。

进阶: 你是否可以不用额外空间解决此题?

解题思路

  利用两个指针,第一个指针移动一次到下一个节点,第二个指针移动一次到下下个节点。如果该链表是环形链表,那么当两个指针指向同一个节点时,该节点与目标节点的距离(正向)和head节点与目标节点的距离相等。
  假设快指针和慢指针相遇时慢指针走了k步,由于快指针比慢指针多走了一圈,所以环的长度为k。不难发现相遇节点与目标节点距离为k-(k-p)即为p,head与目标节点的距离也是p。

代码实现

struct ListNode *detectCycle(struct ListNode *head) {
        struct ListNode *slow = head;
        struct ListNode *fast = head;
        while (fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) {
                fast = head;
                while (slow != fast) {
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        } 
        return NULL;
}

83. 删除排序链表中的重复元素

题目描述

  给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3 输出: 1->2->3

解题思路

  由于是排序链表,我们只需要遍历链表依次两两判断是否重复就可以。

代码实现

struct ListNode* deleteDuplicates(struct ListNode* head){
    struct ListNode*    pCurNode    = head;
    struct ListNode*    pNextNode   = head;

    if(NULL == head) return head;

    pNextNode = pCurNode->next;
    while(pNextNode != NULL)
    {
        if(pCurNode->val == pNextNode->val)
        {
            struct ListNode* temp = pNextNode;
            pNextNode = pNextNode->next;
            pCurNode->next = pNextNode;
        }
        else
        {
            pCurNode = pNextNode;
            pNextNode = pNextNode->next;
        }
    }
    return head;
}

面试题 02.05. 链表求和22

题目描述

;给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。

示例:

输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295 输出:2 -> 1 -> 9,即912
进阶:假设这些数位是正向存放的,请再做一遍。

示例:

输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295 输出:9 -> 1 -> 2,即912

解题思路

1.由于需要原地修改,那么末尾结点前驱节点是必须拿到的(否则你先全部遍历完,然后再取)
2.进位处理,相加取余,最后还需要判断carry=1(这种只可能在链表终止,比如2->4->3,5->6->6)
最短遍历,取到尾结点前驱节点,同时carry进位存储
在上述遍历后,定有其中一个结束或者一起结束,都获得前结点,还要对此结点进行加减操作
判断上步是谁终止,将长的连l1上,然后继续运算。如果一起断,那么最后判断carry=1额外申请结点

代码实现

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
    struct ListNode *p, *q;
    struct ListNode *dummy = (struct ListNode *)malloc(sizeof(struct ListNode));
    q = dummy;
    int carry = 0;  // 进位
    while (l1 || l2 || carry) {  // 最长遍历与carry进位1处理
        p = (struct ListNode*)malloc(sizeof(struct ListNode));
        carry += (l1 ? l1->val : 0) + (l2 ? l2->val : 0);
        p->val = carry % 10;
        carry /= 10;
        l1 = l1 ? l1->next : 0;
        l2 = l2 ? l2->next : 0;  // 每次需要判断,因为l1->val取值问题
        p->next = NULL;
        q->next = p;  // 1.有dummy直接连接此轮p结点
        q = p;  // 2.q保存此轮p,到下轮就是p的上节点
    }
    return dummy->next;
}

203. 移除链表元素

题目描述

  删除链表中等于给定值 val 的所有节点。

示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

解题思路

  之前有一题是依次判断链表是否是头结点,中间节点,尾节点。分情况来讨论。我们可以定义一个哨兵节点,其实也可以不用分情况。

代码实现

struct ListNode* removeElements(struct ListNode* head, int val){
   //哨兵节点,置于链表头之前
struct ListNode *swap = malloc(sizeof(struct ListNode));
swap->next = head;

struct ListNode *curr = head, *prev = swap;
while (curr != NULL){ //当前值不为空

    if (curr->val == val){
        prev->next = curr->next; //删除当前节点
    } else{
        prev = curr;  //否则,向后继续判断,当前节点变成前一个节点
    }
    curr = curr->next; //下一个节点移到当前节点,继续判断
}

head = swap->next;
free(swap);  //判断完毕,释放哨兵节点

return head;
}

61. 旋转链表

题目描述

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释: 向右旋转 1 步: 5->1->2->3->4->NULL 向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释: 向右旋转 1 步: 2->0->1->NULL向右旋转 2 步: 1->2->0->NULL 向右旋转 3 步: 0->1->2->NULL 向右旋转 4 步: 2->0->1->NULL

(后续更新)

猜你喜欢

转载自blog.csdn.net/qq_16933601/article/details/106605715
今日推荐