链表习题精选(持续更新中)

第一题

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。

请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。

你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。

struct ListNode* oddEvenList(struct ListNode* head)
{
    if (head == NULL)
    {
        return head;
    }
    struct ListNode* odd = head;//直接把奇数的第一个作为奇数组头节点
    struct ListNode* even = head->next;//把偶数的第一个作为偶数组头节点
    struct ListNode* evenHead = even;
    while (even != NULL && even->next != NULL)
    {
        odd->next = even->next;
        odd = odd->next;
        even->next = odd->next;
        even = even->next;
    }
    odd->next = evenHead;把偶数组接在奇数组之后
    return head;

}

思考:此处的even != NULL && even->next != NULL能否调换?

答案是不可以,因为此处通过&&的短路现象避免一些访问空指针的错误

第二题

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

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


struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    if(list1==NULL)
    {
        return list2;
    }
    else if(list2==NULL)
    {
        return list1;
    }
    struct ListNode* newHead=list1;
    struct ListNode* cur2=list2;
    struct ListNode* cur1=list1;
    if(list1->val>list2->val)
    {
        newHead=list2;
        cur2=cur2->next;
    }
    else
    {
        cur1=cur1->next;
    }
    struct ListNode* cur=newHead;
    while(cur1!=NULL&&cur2!=NULL)
    {
        if(cur1->valval)
        {
            cur->next=cur1;
            cur=cur->next;
            cur1=cur1->next;
        }
        else
        {
            cur->next=cur2;
            cur=cur->next;
            cur2=cur2->next;
        }
    }
    if(cur1==NULL)
    {
        cur->next=cur2;
    }
    else
    {
        cur->next=cur1;
    }
    return newHead;
}

本题主要是需要考虑情况完备,以及新手在编程时一定不要吝啬于多创建一个常量进行记录,能够让思路清晰很多

当然本题也可以通过带哨兵位的链表,当然思路大差不差,时间复杂度也都是O(n),空间复杂度依然为O(1)。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{    
   if (list1 == NULL)
    {
       return list2;
    }
    else if (list2 == NULL)
    {
     return list1;
    }
    struct ListNode* dummyHead = malloc(sizeof(struct ListNode));
    dummyHead->val = 0;
    struct ListNode* cur = dummyHead, * cur1 = list1, * cur2 = list2;
    while (cur1 != NULL && cur2 != NULL)
    {
        if (cur1->val <= cur2->val)
        {
            cur->next = cur1;
            cur1 = cur1->next;
            cur = cur->next;
        }
        else
        {
            cur->next = cur2;
            cur2 = cur2->next;
            cur = cur->next;
        }
    }
    if (cur1 != NULL)
    {
        cur->next = cur1;
    }
    else if (cur2 != NULL)
    {
        cur->next = cur2;
    }
    struct ListNode* ret = dummyHead->next;
    free(dummyHead);
    return ret;
}

第三题,排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

方法一:自顶向下归并排序

对链表自顶向下归并排序的过程如下。

第1步.找到链表的中间,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针(快二慢一)当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。对两个子链表分别排序。

第2步.不断递归向下二分后,对最小的两个子链表分别排序合并

第3步.将最后两个排序后的子链表合并,得到完整的排序后的链表。

上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 1,即当链表为空或者链表只包含 1 个节点时,不需要对链表进行拆分和排序。

//合并函数,因为递归的地方已经解决了传入链表为空的问题,故可以不进行NULL的判断
struct ListNode* merge(struct ListNode* head1, struct ListNode* head2) {
    struct ListNode* dummyHead = malloc(sizeof(struct ListNode));
    dummyHead->val = 0;
    struct ListNode *temp = dummyHead, *temp1 = head1, *temp2 = head2;
    while (temp1 != NULL && temp2 != NULL) {
        if (temp1->val <= temp2->val) {
            temp->next = temp1;
            temp1 = temp1->next;
        } else {
            temp->next = temp2;
            temp2 = temp2->next;
        }
        temp = temp->next;
    }
    if (temp1 != NULL) {
        temp->next = temp1;
    } else if (temp2 != NULL) {
        temp->next = temp2;
    }
    return dummyHead->next;
}
//实现递归并终止的递归的函数
struct ListNode* toSortList(struct ListNode* head, struct ListNode* tail) {
    if (head == NULL) {
        return head;
    }
    if (head->next == tail) {
        head->next = NULL;
        return head;
    }
    struct ListNode *slow = head, *fast = head;
    while (fast != tail) {
        slow = slow->next;
        fast = fast->next;
        if (fast != tail) {
            fast = fast->next;
        }
    }
    struct ListNode* mid = slow;
    return merge(toSortList(head, mid), toSortList(mid, tail));
}

//调用的排序函数
struct ListNode* sortList(struct ListNode* head) {
    return toSortList(head, NULL);
}

时间复杂度:O(nlogn),其中 n 是链表的长度。

空间复杂度O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。

第四题

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val){
    if(head==NULL)
    {
        return NULL;
    }
    struct ListNode *dummyHead=(struct ListNode*)malloc(sizeof(struct ListNode));
    dummyHead->next=head;
    struct ListNode*cur=dummyHead;
    //利用哨兵位,避免传入空指针的情况,而对下一个数据进行检索的写法。
    while(cur->next!=NULL)//当遇到倒数第二个节点时 if else语句已经解决了尾数据是该删还是不该删
    {
        if(cur->next->val==val)
        {
            cur->next=cur->next->next;//跳过该节点指向下下个节点
        }
        else
        {
            cur=cur->next;//直接接入下一个数据,若接入的是尾数据则跳出循环
        }
    }
    return dummyHead->next;
}

第五题

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

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


struct ListNode* reverseList(struct ListNode* head){
    
    struct ListNode* dummyHead=malloc( sizeof(struct ListNode) );
    struct ListNode*cur1=head;
    struct ListNode*cur2=NULL;//尾的next置为空
    while(cur1)//依次取下链表的数据连接在NULL上面
    {
        struct ListNode *tmp=cur1->next;
        cur1->next=cur2;
        cur2=cur1;
        cur1=tmp;
    }
    return cur2;
}

第六题

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

本题是第三题自上向下排序链表时,利用快慢指针找到中间节点二分的题目


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

第七题

输入一个链表,输出该链表中倒数第k个结点。

初学者如果没有见过这类题目很可能会写出如下代码

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    if(pListHead==NULL||k<0)
    {
        return NULL;
    }
    struct ListNode *cur=pListHead;
    int lenth=0;
    while(cur)
    {
        cur=cur->next;
        lenth++;
    }
    if(lenthnext;
    }
    return cur;
}

本题如果采用双指针,先让快指针走k步,在使慢指针和快指针一起走k步,返回的慢指针即是倒数第k个节点

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
     struct ListNode* slow = pListHead;
        struct ListNode* fast = slow;
        while(k--)
        {
            if(fast)
                fast = fast->next;
            else
                return NULL;
        }
         
        while(fast)
        {
            slow = slow->next;
            fast = fast->next;
        }
         
        return slow;
    }

第八题,分割链表

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
  public:
    ListNode* partition(ListNode* pHead, int x) {
//不需要把x的节点进行插入
        if (pHead == NULL) {
            return NULL;
        }
        ListNode* lessList = (ListNode*)malloc(sizeof(ListNode));
        ListNode* greaterList = (ListNode*)malloc(sizeof(ListNode));
        ListNode* cur1 = lessList;
        ListNode* cur2 = greaterList;
        while (pHead) {
            if ((pHead->val) next = pHead;
                cur1 = cur1->next;
            } else {
                cur2->next = pHead;
                cur2 = cur2->next;
            }
            pHead = pHead->next;
        }
        cur2->next = NULL;//不要忘记把尾置成空!!!
        cur1->next = greaterList->next;
        ListNode* ret = lessList->next;
        free(lessList);
        free(greaterList);
        return ret;
    }
};

第九题,回文链表

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        int arr[900]={0};
        int i=0;
        int j=0;
        for(i=0;i<900&&(A!=NULL);i++)
        {
            arr[i]=A->val;
            A=A->next;
            j++;
        }
        int left=0,right=j-1;
        while(left

不过上面这种写法严格来说空间复杂度O(n),这样只是利用了题目的漏洞。

下面提供另一种解法,通过快慢指针找到链表的中点,然后将后半段链表逆置

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        if (A == NULL || A->next == NULL)
            return true;
        ListNode *slow=A,*fast=A;
        while(fast!=NULL&&fast->next!=NULL)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
        ListNode *cur=slow;
        ListNode *prev=NULL;//逆序一个链表的写法
        ListNode *next=slow;
        while(cur)
        {
            next=cur->next;
            cur->next=prev;
            prev=cur;//这两句不能调换,prev不仅保存了最后一个节点的位置,
            cur=next;//同时保存了每次循环的时候prev都是cur的前一个数据。
        }
        ListNode *cur1=A;
        ListNode *cur2=prev;
        while(cur1&&cur2)
        {
            if((cur1->val)!=(cur2->val))
            {
                return false;
            }
            cur1=cur1->next;
            cur2=cur2->next;
        }
        return true;
    }
};

前半段链表->

后半段链表->的结尾都是同一个节点。

第十题,相交链表

本题的思路很多,下面提供一些参考

1.比如用哈希表下其中一个链表的所有节点,然后遍历剩下的一个链表

struct HashTable {
    struct ListNode *key;
    UT_hash_handle hh;
};

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct HashTable *hashTable = NULL;
    struct ListNode *temp = headA;
    while (temp != NULL) {
        struct HashTable *tmp;
        HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
        if (tmp == NULL) {
            tmp = malloc(sizeof(struct HashTable));
            tmp->key = temp;
            HASH_ADD(hh, hashTable, key, sizeof(struct HashTable *), tmp);
        }
        temp = temp->next;
    }
    temp = headB;
    while (temp != NULL) {
        struct HashTable *tmp;
        HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
        if (tmp != NULL) {
            return temp;
        }
        temp = temp->next;
    }
    return NULL;
}

2.通过分别计算出两个链表的长度进行相减后得到

gap=abs(lenA-lenB)。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA==NULL||headB==NULL)
    {
        return NULL;
    }
    struct ListNode *cur1=headA,*cur2=headB;
    int lenA=0,lenB=0;
    while(cur1)
    {
        cur1=cur1->next;
        lenA++;
    }
    while(cur2)
    {
        cur2=cur2->next;
        lenB++;
    }
    struct ListNode *longList=headA, *shortList=headB;
    if(lenAnext;
    }
    while(longList!=shortList)
    {
        longList=longList->next;
        shortList=shortList->next;
    }
    return longList;
}

3.或者通过双指针

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA==NULL||headB==NULL)
    {
        return NULL;
    }
    struct ListNode *cur1=headA,*cur2=headB;
    while(cur1!=cur2)
    {
        cur1=cur1==NULL?headB:cur1->next;
        cur2=cur2==NULL?headA:cur2->next;
    }
    return cur1;
}

两个链表相交时会返回相交节点,不相交时会互相同时走到对方链表为空的时候,从而返回空指针

第十一题 判断循环链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
    if(head==NULL)
    {
        return false;
    }
    struct ListNode *slow=head,*fast=head->next;
    while(fast!=slow)
    {
        if(fast==NULL)
        {
            return false;
        }
        slow=slow->next;
        fast=fast->next;
        if(fast==NULL)
        {
            return false;
        }
        fast=fast->next;
    }
    return true;
}
typedef struct ListNode Node;
bool hasCycle(struct ListNode *head) {
   Node* slow = head;
   Node* fast = head;
 
  while(fast && fast->next)
  {
    slow = slow->next;
    fast = fast->next->next;
 
    if(slow == fast)
      return true;
  }
 
  return false;
}

第十二题,输出循环节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *slow=head,*fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            struct ListNode* cur1=slow,*cur2=head;
            while(cur1!=cur2)
            {
                cur1=cur1->next;
                cur2=cur2->next;
            }
            return cur1;
        }
    }
    return NULL;
}

具体证明:

假设不循环部分的长度为L,循环部分的长度为O,快慢指针在环内相遇的时候距离节点的距离为K

又快指针比慢指针多走1倍可得 L+N*O+K=2(L+K)

解得L=N*O-K。N为快指针在圈内转的次数。

第十三题,复制带随机指针的链表。

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */

struct Node* copyRandomList(struct Node* head) {
    if(head==NULL)
    {
        return NULL;
    }
    struct Node *cur=head;
    if(head->next=NULL)
    {
        struct Node *newNode=(struct Node*)malloc(sizeof(struct Node));
        newNode->val=cur->val;
        newNode->next=cur->next;
        newNode->random=cur->random;
        return newNode;
    }
    while(cur)
    {
        struct Node *newNode=(struct Node*)malloc(sizeof(struct Node));
        newNode->val=cur->val;
        newNode->next=cur->next;
        newNode->random=cur->random;
        cur->next=newNode;
        cur=newNode->next;
    }
    struct Node*newhead=head->next,*cur1=head,*cur2=head->next;
    while (cur2->next)
    {
    cur1->next = cur2->next;
    cur1 = cur1->next->next;
    struct Node* nextNext = cur2->next->next;
    cur2->next = nextNext;
    cur2 = nextNext;
   }
    return newhead;
}

笔者第一次做的时候忽略了复制结点与原链表是不相同的,从而导致出错。

以下是修改后的代码

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */

struct Node* copyRandomList(struct Node* head) {
            // 1.拷贝链表,并插入到原节点的后面
        struct Node *cur = head;
        while(cur)
        {
           struct Node* next = cur->next;
           struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
            copy->val = cur->val;
            // 插入
            cur->next = copy;
            copy->next = next;
            // 迭代往下走
            cur = next;
        }
 
        // 2.置拷贝节点的random
        cur = head;
        while(cur)
        {
           struct Node* copy = cur->next;
            if(cur->random != NULL)
                copy->random = cur->random->next;
            else
                copy->random = NULL;
 
            cur = copy->next;
        }
 
        // 3.解拷贝节点,链接拷贝节点
       struct Node* copyHead = NULL, *copyTail = NULL;
        cur = head;
        while(cur)
        {
           struct Node* copy = cur->next;
           struct Node* next = copy->next;
 
            // copy解下来尾插
            if(copyTail == NULL)
            {
                copyHead = copyTail = copy;
            }
            else
            {   
                copyTail->next = copy;
                copyTail = copy;
            }
 
            cur->next = next;
 
            cur = next;
        }
 
        return copyHead;
    
}

你可能感兴趣的:(链表,数据结构,c语言,算法,学习)