链表的经典算法题详细解析

目录

第一题 奇偶链表

第二题 合并升序链表

第三题 排序链表

第四题 删除链表节点

第五题 反转链表

第六题 链表的中间节点

第七题 链表的倒数第k个节点

第八题 分割链表

第九题 回文链表

第十题 相交链表

第十一题 判断循环链表

第十二题 输出循环节点

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


第一题 奇偶链表

给定单链表的头节点 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->val<cur2->val)
        {
            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个节点

输入一个链表,输出该链表中倒数第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(lenth<k)
    {
        return NULL;
    }
    int i=lenth-k;
    cur=pListHead;
    while(i--)
    {
        cur=cur->next;
    }
    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) <x) {
                cur1->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<right)
        {
            if(arr[left]!=arr[right])
            {
                return false;
            }
            left++;
            right--;
        }
        return true;
        // write code here
    }
};

不过上面这种写法严格来说空间复杂度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(lenA<lenB)
    {
        longList=headB;
        shortList=headA;
    }
    int gap=abs(lenA-lenB);
    while(gap--)
    {
        longList=longList->next;
    }
    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;
    
}

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
### 回答1: 《Java算法经典五十答案解析》是一本详细解答了50道经典的Java算法的书籍。该书以简洁清晰的语言,提供了对每一个问详细分析和解答。 首先,该书为读者提供了每一道问目描述,确保读者可以清楚地了解问的要求和限制条件。接着,对于每个问,书中给出了多种解法,包括基础算法、优化算法和高级数据结构等。对于每种解法,书中都提供了简洁明了的代码示例,帮助读者理解和实现算法。 在解答过程中,书中还给出了详细的分析和解释,说明每个解法的原理和关键步骤。这样读者不仅能够理解解法的思路,还可以深入了解算法的运行机制和效率。同时,书中还包括了每个解法的时间复杂度和空间复杂度分析,帮助读者评估算法的效率和可行性。 此外,该书还涵盖了一些常见的算法技巧和应用,例如动态规划、贪心算法和分治算法等。通过对这些经典算法的解答,读者可以掌握并熟悉这些常见的算法思想和技巧,从而能够更自信和高效地解决实际问。 总之,《Java算法经典五十答案解析》是一本集理论和实践于一体的算法书籍。它不仅提供清晰明了的问描述和解答示例,还通过详细的分析和解释,帮助读者理解和掌握算法的原理和应用。无论是新手还是有经验的Java程序员,都可以从中学习到有关算法数据结构的知识,并且在实际工作中应用这些知识解决问。 ### 回答2: Java算法经典五十是一套经典目集合,涵盖了Java算法的各个方面。下面是对其中一些目的解析。 1. 二分查找: 二分查找是一种高效的查找算法,通过不断缩小查找范围,直到找到目标元素或确定目标元素不存在。使用二分查找算法需要对数组进行排序,然后再进行查找。 2. 冒泡排序: 冒泡排序是一种简单但效率较低的排序算法,通过不断比较相邻元素的大小,将较大的元素交换到右侧,较小元素交换到左侧。重复这个过程,直到整个数组有序。 3. 快速排序: 快速排序是一种高效的排序算法,通过选择一个基准元素,将数组分为比基准元素小和比基准元素大的两部分,然后递归地对这两部分进行排序。 4. 查找字符串中出现次数最多的字符: 通过遍历字符串中的每个字符,使用一个HashMap来记录每个字符出现的次数,最后找到出现次数最多的字符。 5. 判断两个字符串是否是旋转字符串: 将原字符串重复拼接,然后判断目标字符串是否是拼接后的字符串的子字符串。 以上只是对其中几道目的解析,Java算法经典五十涵盖了更多类型的算法,包括字符串处理、数组操作、链表操作等等。通过解析和理解这些目,可以更好地掌握和应用Java算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

葬送的代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值