刷题的一天——链表结构

1、给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

示例:

输入:[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.

这个题可以按照链表的方式做,也可以结合双指针的方式进行解答。

(1)链表+数组

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
    vector<ListNode*>A = {head};                    //设定数组A,给数组A一个head
    while (A.back()->next !=NULL){                  //这里就要用到链表的指针,如果A指向的下一个为NULL则遍历完毕
        A.push_back(A.back()->next);
    }
    return A[A.size()/2];                             //最后在遍历完毕的A取后半部分得到结果
    }
};

主要的思路就是让数组A接受链表的开头,然后利用指针遍历,最后取A的后半部分。

时间复杂度为O(n),空间复杂度O(n)(额外的数组A)。

(2)指针+链表

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
    int i = 0;
    ListNode*ans = head;
    while(ans !=NULL){
        i++;
        ans = ans->next;                 //遍历链表获取总数i
    }
    int j = 0;
    ans = head;
    while (j < i/2){
        j++;
        ans = ans ->next;                 //让ans指针指到中间节点处,最后输出的就是ans以及之后的
    }
    return ans;
    }
};

利用ans的指针指到中间节点之后输出,就可以得到后半部分的数组。

时间复杂度O(n),空间复杂度O(1),相对于数组+链表法有着进一步的优化。

(3)快慢指针法(这个方法真的好聪明!学到了!)

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
    ListNode*fast = head;           //fast每次走两个元素
    ListNode*slow = head;           //slow每次走一个元素,当fast遍历完毕的时候,slow刚好到中间节点
    while ( fast != NULL & fast ->next !=NULL){    //条件要注意,不仅fast不能为NULL;fast ->next也不能为NULL
      fast = fast ->next ->next;
      slow = slow ->next;
    }
     return slow;                     //成功输出slow
    }
};

时间复杂度O(n),空间复杂度O(1)。

这个方法虽然和指针+链表的方法时间复杂度和空间复杂度差距不大,但是这个方法使得代码更加简洁,可以设置更少的参数。这种思想十分值得学习。

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

示例:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

这个第一实际时间想到的就是用链表,因为要跳过一个节点,所以要设置两个链表,也就是下面的方法(1)。

(1)计算链表总长之后再找到需要跳过的节点

代码:

* Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    int length(ListNode*head){                //设置这个是为了保证head不改变的情况下获得总长度length,函数内部的数值改变不输出
        int length = 0;
        while(head->next != NULL){
            length++;
            head = head ->next;
        }
        return length;
    }
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode*forward = new ListNode(0,head);
    ListNode*ans = forward;
    int i = length(head);
    for (int j =0;j != i-n+1;++j){
       ans = ans ->next;
    }
    ans ->next = ans->next->next;        //前面的遍历,到了需要取消的节点的时候就跳过指针
    ListNode*ans1 = forward -> next;     //这里要注意是foward指向next,也就相当于forward最后一个结点指向了ans的开头;类似head ->next的效果
    return ans1;
    }
};

时间复杂度O(n),也就是链表的长度;空间复杂度O(1)。

(2)栈

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode*dummy = new ListNode(0,head);
    ListNode*ans = dummy;
    stack<ListNode*>stk;
    while(ans){
     stk.push(ans);
     ans = ans ->next;
    }
    for (int i = 0;i<n;++i){
        stk.pop();                           //弹出删除结点以及之后的结点
    }
    ListNode*pre = stk.top();                //栈最顶上的就是删除结点的前一个
    pre->next = pre->next->next;
    ListNode*ans1 = dummy->next;
    return ans1;

    }
};

这里是利用栈push和pop的操作来找到需要删除的结点。简单易懂还方便。

空间复杂度O(n),时间复杂度O(n)。空间复杂度变大是因为用到了栈。

(3)双指针

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode*dummy = new ListNode(0,head);
    ListNode*first = head;
    ListNode*last = dummy;                         //这里为什么等于dummy没有太懂
    for(int i = 0; i<n;++i){
        first = first->next;                         //让first先走n步
    }
    while(first){
        first = first->next;
        last = last->next;
    }
    last ->next=last->next->next;                       //之后last就可以刚好到删除结点的前面
    ListNode*ans = dummy->next;
    return ans; 
}
};

这里主要的思想是让first先走n步,之后last再追,当first走到尾部的时候last就可以刚好到删除结点的前面,就可以直接操作跳过删除结点了。这一种方法时间复杂度O(n),空间复杂度O(1)。开销虽然和(1)差不多,但是这个方法相对更加巧妙。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值