撕烂数据爆锤算法:LeetCode刷题记录之链表

LeetCode刷题总结——链表

          • 前言
          • 正文
            • 一、链中是否有环
            • 二、只能出现一次
            • 三、两相邻交换
            • 四、k倍翻转
            • 五、不要重复的
            • 六、环的入口
            • 七、合并链表
            • 八、表中翻转
          • 尾言
            • 小小推荐

前言

  这里记录了有关链表知识的我不会解的和我认为值得收藏的编程题目及题解,主要是为了提高自己的编程能力。希望自己勤加练习,越做越熟练。嗯,都是用C/C++实现的。
撕烂数据爆锤算法:LeetCode刷题记录之链表_第1张图片


正文

一、链中是否有环

 题目描述:

  判断给定的链表中是否有环

 扩展:
  你能给出不利用额外空间的解法么?

 题目代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        
    }
};

 解题思路:

  用快慢指针解题:设置一个快指针和一个慢指针分别指向头结点,快慢指针通过循环以不同的速度走链表,快指针每次走两步,慢指针每次走一步,每走一个循环判断快指针是否与慢指针相等,若相等,则有环,否则则无环。因为,如果链表无环,慢指针永远不可能追上快指针,只有当有环时,快慢指针才能相遇。

 题解代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *first,*slow;
        first=head;
        slow=head;
        while(first && first->next)
        {
            first=first->next->next;
            slow=slow->next;
            if(first==slow)
                return true;
        }
        return false;
    }
};

二、只能出现一次

 题目描述:

  删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次

 例如:

  给出的链表为1->1->2,返回1->2.
  给出的链表为1->1->2->3->3,返回1->2->3.

 示例:

  输入
   {1,1,2}

  输出
   {1,2}

 题目代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) {
        // write code here
    }
};

 解题思路:

  设置一个指针变量,指向头结点。进入循环,若前一个元素与后一个元素相等,将前一个元素的下一个元素指向后一个元素的下一个元素,若不相等指针变量指向后一个元素。

 题解代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) {
        // write code here
        if(!head)
            return head;
        ListNode *h;
        h=head;
        while(h->next)
        {
            if(h->val==h->next->val)
                h->next=h->next->next;
            else
                h=h->next;
        }
        return head;
    }
};

三、两相邻交换

 题目描述:

  将给定的链表中每两个相邻的节点交换一次,返回链表的头指针

 例如:

  给出1->2->3->4,你应该返回链表2->1->4->3。
你给出的算法只能使用常量级的空间。你不能修改列表中的值,只能修改节点本身。

 示例:

  输入
   {1,2},2

  输出
   {2}

 题目代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* swapPairs(ListNode* head) {
        // write code here
    }
};

 解题思路:

  递归实现:因为是每两个相邻结点交换一次,所以把链表以两个节点为一组分组,然后设置一个指针变量指向每两个结点中的第二个节点。交换两节点位置,使前一节点指向下两个结点中的第二个一节点,而后一节点指向前一节点。从最后一组开始,一组一组实现交换,然后把交换好的结果返回给上一组,直到全部交换成功。

 题解代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* swapPairs(ListNode* head) {
        // write code here
        if(!head || !head->next)
            return head;
        ListNode *h;
        h=head->next;
        head->next=swapPairs(h->next);
        h->next=head;
        return h;
    }
};

四、k倍翻转

 题目描述:

   将给出的链表中的节点每k个一组翻转,返回翻转后的链表。如果链表中的节点数不是k的倍数,将最后剩下的节点保持原样。你不能更改节点中的值,只能更改节点本身。只允许使用常数级的空间。

 例如:

   给定的链表是1->2->3->4->5
   对于 k = 2, 你应该返回 2->1->4->3->5
   对于 k = 3, y你应该返回 3->2->1->4->5

 示例:

  输入
    {1,2,3,4,5},2

  输出
    {2,1,4,3,5}

 题目代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseKGroup(ListNode* head, int k) {
        // write code here
    }
};

 解题思路:

  首先通过循环计算出链表节点个数n,若n

 题解代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseKGroup(ListNode* head, int k) {
        // write code here
        int n,i,j;
        ListNode *h,*pre,*cur,*temp,*hpre;
        h=head;
        n=0;
        while(h)
        {
            n++;
            h=h->next;
        }
        if(n<k || k<2 || !head->next || !head)
            return head;
        hpre=new ListNode(3);
        hpre->next=head;
        pre=hpre;
        cur=head;
        for(i=0;i<n/k;i++)
        {
            for(j=1;j<k;j++)
            {
                temp=cur->next;
                cur->next=temp->next;
                temp->next=pre->next;
                pre->next=temp;
            }
            pre=cur;
            cur=cur->next;
        }
        return hpre->next;
    }
};

五、不要重复的

 题目描述:

  给出一个排好序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。

 例如:

  给出的链表为1->2->3->3->4->4->5, 返回1->2->5.
  给出的链表为1->1->1->2->3, 返回2->3.

 示例:
  输入
   {1,2,2}

  输出
   {1}

 题目代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) {
        // write code here
    }
};

 解题思路:

  递归实现。相当于把链表分为了两组,一组为不重复节点,一组为重复节点,遇到不重复节点继续向后走(即继续递归),遇得到重复节点,要找到与该节点不相等的节点,然后才能继续向后走,最后通过递归依次返回后续连接好的不重复节点。具体操作:先判断head节点和head下一节点是否为空,为空直接返回head。不为空,判断head节点的值是否和head下一节点值想等,若不相等head的下一个节点指向下一个不重复的的节点,继续向后走,若相等,用temp记录head节点的值,通过循环,找到与temp不相等的节点,使head指向该节点,若此时head为空,返回NULL,若不为空,则继续向后走。并将后续连接好的不重复节点依次返回。

 题解代码:

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */
 
class Solution {
public:
    /**
     *
     * @param head ListNode类
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) {
        // write code here
        if(!head || !head->next)
            return head;
        if(head->val!=head->next->val)
        {
            head->next=deleteDuplicates(head->next);
            return head;
        }
        else
        {
            int temp=head->val;
            head=head->next;
            while(head->val==temp)
            {
                head=head->next;
                if(!head)
                    return NULL;
            }
            return deleteDuplicates(head);
        }
    }
};

六、环的入口

 题目描述:

  对于一个给定的链表,返回环的入口节点,如果没有环,返回null

 拓展:

  你能给出不利用额外空间的解法么?

 题目代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        
    }
};

 解题思路:

  快慢指针方法:将两指针分别放在链表头(X)和相遇位置(Z),并改为相同速度推进,则两指针在环开始位置相遇(Y),如图所示。撕烂数据爆锤算法:LeetCode刷题记录之链表_第2张图片
证明过程:X,Y,Z分别为链表起始位置,环开始位置和两指针相遇位置,由快指针速度的慢指针速度的2倍。快指针与慢指针均从X出发,在Z相遇。此时,慢指针行使距离为a+b,快指针为a+b+n(b+c)。所以2*(a+b)=a+b+n*(b+c),推出
a=(n-1)b+nc=(n-1)(b+c)+c;得到,将此时两指针分别放在起始位置和相遇位置,并以相同速度前进,当一个指针走完距离a时,另一个指针恰好走出 绕环n-1圈加上c的距离。
  (转载于牛客网LPL006大佬的题解)

 题解代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *first,*slow;
        first=head;
        slow=head;
        while(first && first->next)
        {
            first=first->next->next;
            slow=slow->next;
            if(first==slow)
            {
                first=head;
                while(first!=slow)
                {
                    slow=slow->next;
                    first=first->next;
                }
                return first;
            }
        }
        return NULL;
    }
};

七、合并链表

 题目描述:

  合并k个已排序的链表并将其作为一个已排序的链表返回。分析并描述其复杂度。

 题目代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

/**
 * 
 * @param lists ListNode类一维数组 
 * @param listsLen int lists数组长度
 * @return ListNode类
 */
struct ListNode* mergeKLists(struct ListNode** lists, int listsLen ) {
    // write code here
}

 解题思路:

  递归实现,两两顺序链表按顺序合并。每次选出两个待合并链表的头节点,对两个链表中的所以节点按大小进行选择,按顺序合成一个新的链表,并返回。具体操作:设置一个链表指针h用来记录两个合成链表中的一个的头节点,另一个待合成链表头节点从链表数组中依次选取,与链表指针进入合并。当合并完成返回新合成的链表,并使链表指针h指向该新合成链表头节点,然后h继续与下一个待合成链表进行合并。合并过程:每次递归,先依次判断给定节点是否为空,若其中一节点为空,返回另一节点。若都不为空,在第一次递归中,判断节点值大小,若link1节点的值

 题解代码:

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */
 
/**
 *
 * @param lists ListNode类一维数组
 * @param listsLen int lists数组长度
 * @return ListNode类
 */
struct ListNode* merge(struct ListNode* link1,struct ListNode* link2){
    if(!link1)
        return link2;
    if(!link2)
        return link1;
    if(link1->val<=link2->val)
    {
        link1->next=merge(link1->next,link2);
        return link1;
    }
    else
    {
        link2->next=merge(link1,link2->next);
        return link2;
    }
}
 
struct ListNode* mergeKLists(struct ListNode** lists, int listsLen ) {
    // write code here
    int i,j,flag;
    flag=0;
    struct ListNode* h;
    if(listsLen==0)
        return NULL;
    for(i=0;i<listsLen;i++)
        if(lists[i])
        {
            flag=1;
            h=lists[i];
            break;
        }
    if(flag==0)
        return NULL;
    for(j=i+1;j<listsLen;j++)
    {
        if(lists[j])
            h=merge(h,lists[j]);
    }
    return h;
}

八、表中翻转

 题目描述
  将一个链表 m位置到 n 位置之间的区间反转,要求时间复杂度O(1) ,空间复杂度 O(n)。

 例如:

  给出的链表为 1→2→3→4→5→NULL,m=2,n=4,
  返回 1→4→3→2→5→NULL

 注意:

  给出的满足以下条件:1≤m≤n≤链表长度1 \leq m \leq n \leq 链表长度1≤m≤n≤链表长度

 示例1
  输入
   {1,2,3,4,5},2,4

  输出
   {1,4,3,2,5}

 题目代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // write code here
    }
};

 解题思路:

  先判断n,m,head值是否正确,得到需要翻转的区域的所有节点,对其进行翻转。具体操作:创建一个节点hpre(分配内存)和设置三个指针变量pre、h、temp。其中hpre节点的下一个节点指向头节点head,使pre指向hpre,h指向头节点head。对翻转区域的节点进行移位,只需移动k-1次,因为最前面的节点不需要再移动到最前面。将temp指向h的下一个节点,h的下一个节点指向temp的下一个节点,temp的下一个节点指向pre的下一个节点,pre的下一个节点指向temp,这样temp指向的节点就在该组节点的最前面。k-1次之后,该区域节点翻转完成。

 题解代码:

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */
 
class Solution {
public:
    /**
     *
     * @param head ListNode类
     * @param m int整型
     * @param n int整型
     * @return ListNode类
     */
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // write code here
        int i;
        ListNode *h,*hpre,*pre,*temp;
        if(m==n || !head || !head->next)
            return head;
        hpre=new ListNode(0);
        hpre->next=head;
        pre=hpre;
        h=head;
        i=0;
        while(h)
        {
            i++;
            if(i==m)
            {
                for(i=0;i<n-m;i++)
                {
                    temp=h->next;
                    h->next=temp->next;
                    temp->next=pre->next;
                    pre->next=temp;
                }
                return hpre->next;
            }
            pre=h;
            h=h->next;
        }
    }
};

尾言

  暂时记录这么些题,以后遇到相同知识点的题会继续增加进来。

小小推荐

  撕烂数据爆锤算法系列:

   单链表
   循环链表
   内排序之插入算法
   内排序之交换算法
   广度、深度优先搜索例题之炸弹人

感谢阅览,希望大家多多
点赞、收藏、转发
一波三连
拜~~~

你可能感兴趣的:(撕烂数据爆锤算法,链表,算法,c++)