链表问题总结

1.删除链表的倒数第n个节点


给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

在只循环一次的情况下,这题有两种解法

  1. 一种是用数组存储每次遍历的链表,这种比较简单但浪费一定空间。
  2. 第二种是快慢指针,快指针先移动n步,然后快慢指针一起移动直到快指针到表尾,此时慢指针的地址为所要删除节点的前一个节点。
    注意:需要考虑边界条件,此题的边界条是当n为链表的长度时,此时删除的节点为头节点,需要返回head->next。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(!head)return NULL;
        ListNode* r = head;
        while(n>0){
            r = r->next;
            n--;
        }
        if(!r)
            return head->next;
        ListNode* l = head;
        while(r->next){
            r = r->next;
            l = l->next;
        }
        l->next = l->next->next;
        return head;
    }
};

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

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

 

示例:

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

  1. 遍历链表,记住第一个交换的前一个节点,然后交换。这种方法比较简单,边界情况有两种,第一种是第一次交换时,前一个节点为空,解决方法是第一次交换特殊处理不放进循环。第二种情况是最后剩下的为单数,这个只需要判断第一个node->next是否为空就行。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head || !head->next)return head;
        ListNode* n1 = head;
        ListNode* n2 = head->next;
        ListNode* n3;
        n1->next = n2->next;
        n2->next = n1;
        head = n2;
        n3 = n1;
        n1 = n1->next;
        while(n1 && n1->next){
            n2 = n1->next;
            n1->next = n2->next;
            n2->next = n1;
            n3->next = n2;
            n3 = n1;
            n1 = n1->next;
        }
        return head;
    }
};
  1. 递归解决,终止条件为!head || !head->next,返回值为处理完后的子链表,传递给两个交换节点的前一个,后一个节点再指向前一个,再返回。递归比遍历方法简化在遍历是从后往前的,所以不需要记录前一个节点,因为他直接作为返回值传递给上一个函数,因此也不需要考虑头节点的情况。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head || !head->next)return head;
        ListNode* node = head->next;
        head->next = swapPairs(node->next);
        node->next = head;
        return node;
    }
};

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

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

示例 1:

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

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

解:

其实大部分链表问题都可以分为两种,遍历和递归,此题也不例外。

  1. 遍历,遍历也比较简单,因为没有重复的数字,所以需要一个prev节点,涉及至少三个节点,操作比较繁琐,但算法时间复杂度为O(n)。

  2. 递归,每次递归需要分两种情况,当存在重复元素时,函数返回值应赋值给最后一个元素的下一个元素,并返回下一个元素。当不存在重复元素时,函数返回值赋值给下一个元素,此时是返回当前节点。

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

4.分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

示例:

输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5

这道题用递归做很麻烦因为他是要对链表的两边进行操作,这时可以利用链表的插入特性进行一趟循环遍历将链表分隔。

思路:创建两个节点,这两个节点分别插入大于等于x和小于x的节点,最后将插入小于节点的尾节点的下一个指针指向头节点(这是一个虚拟节点,没有任何意义,只是为了方便插入)的下一个节点,便完成了节点的分隔,这利用了链表插入以及连接方便的特点,所以开销非常小。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* n1 = new ListNode(0);
        ListNode* n2 = new ListNode(0);
        ListNode* n3 = n1;
        ListNode* n4 = n2;
        while(head){
            if(head->val < x){
                n3->next = head;
                n3 = head;
            }else{
                n4->next = head;
                n4 = head;
            }
            head = head->next;
        }
        n3->next = n2->next;
        n4->next = NULL;
        return n1->next;
    }
};

5.翻转链表

这是一道很经典的题目,问题不难,包含两种解法,分别是迭代和递归

  1. 迭代法需要三个指针,当前指针,前一个指针,后一个指针,原因很简单,不解释了。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head || !head->next) return head;
        ListNode* prev = NULL;
        ListNode* node = head;
        ListNode* next = head->next;
        while(head){
            next = head->next;
            head->next = prev;
            prev = head;
            head = next;
        }
        return prev;
    }
};
  1. 递归
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head || !head->next)return head;
        ListNode* next = head->next;
        ListNode* ret = reverseList(head->next);
        next->next = head;
        head->next = NULL;
        return ret;
    }
};

这种递归每次都返回尾节点,因为递归的特性,所有不需要记录前一个结点,进行翻转后直接返回就行。

第二种递归(我觉得这种比较好理解)

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

这种递归每次返回下一个节点,只需要将返回的值的下一个节点指向当前节点,再返回就行。需要注意两点,第一个是返回的结果,因为题目要求返回头节点,所以我们需要在执行递归的时候记录尾结点,最后用来返回。第二个是递归完成后,因为是从head开始执行递归,递归完成后head的节点并没有翻转,所以最后需要将head节点执行空节点,否则就会形成一个环,执行会超出时间限制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值