用《剑指offer》带你走进数据结构与算法(链表简单篇)

链表的概念:

链表是以节点为单位,每个元素都是一个独立对象,在内存的储存空间是非连续的。链表的节点对象具有两个成员变量:【值 val】【后继节点引用 next】

struct ListNode{
    int val;//节点值
    ListNode *next;//后继节点引用
    ListNode(int x) : val(x), next(NULL) {}
}

在了解链表的概念以后,我们用几个简单的题目来熟悉一下链表的操作。

注:本题来自《剑指offer》T6

1.输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例:

输出:head=[1,3,2]

输出:[2,3,1]

思路:看到这个题目的直觉是把链表中数据放到数组中,再将数据倒序输出,但是这样会多定义一个数组储存数据增加空间复杂度,此时想到栈具有先进后出的性质,我们可以运用栈灵活解题。

实现:

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> result;
        stack<int> st;//创建一个栈,用于储存链表的节点数据
        ListNode* curr=head;//创建一个指针,初始时指向链表的头节点
        while(curr!=NULL)//指针值向的元素非空时
        {
            st.push(curr->val);//将指针指向的节点数据压入栈内
            curr=curr->next;//将指针移到后驱节点
        }
        while(!st.empty()){//栈非空时
            result.push_back(st.top());//将栈顶的元素压入vector内
            st.pop();//弹出栈顶元素
        }
        return result;
    }
};

复杂度分析:

时间复杂度O(n):入栈和入栈共用O(n)时间。

空间复杂度O(n):辅助栈stack和数组result共用O(n)的额外空间。

注:本题来自《剑指offer》T18

2.给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点

返回删除后链表的头节点

示例:

输入:head=[4,5,1,9],val=5

输出:[4,1,9]

 分析:看到此题自然想到双指针

1.设置两个指针指向头节点prev(带删节点的前驱节点)和curr(当前节点)

2.遍历链表查找节点值为val的节点,找到则删除,否则继续查找

实现:

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head->val==val){//头节点为待删除的节点
            return head->next;
        }
        ListNode* prev=head;//当前节点
        ListNode* curr=head->next;//保存待删节点的下一节点
        while(curr!=nullptr&&curr->val!=val)
        {
            prev=curr;
            curr=curr->next;
        }
        if(curr!=nullptr)prev->next=curr->next;
        return head;
    }
};

此题也可以采用单指针,递归,栈等多种方式完成,这里不再过多讲解,有兴趣的可以自己尝试 

复杂度分析:

时间复杂度O(n):只遍历了链表一次

空间复杂度O(1)

注:本题来自《剑指offer》T24

3.定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点

示例:

输入:1->2->3->4->5->NULL

输出:5->4->3->2->1->NULL

此题有两种解题思路

1.迭代法

分析:首先我们创建curr储存后一个节点,然后创建prev储存前一个节点

每一次操作我们只需要将当前节点的next改为指向前一个节点即可,此时存在一个问题:我们将下一个节点指向next后,curr->next->next我们就无法访问到,因此我们还需要一个变量temp来记录curr->next

具体实现:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* curr=head;
        ListNode* prve=nullptr;
        while(curr)
        {
            ListNode* temp=curr->next;
            curr->next=prve;
            prve=curr;
            curr=temp;
        }
        return prve;
    }
};

复杂度分析:

时间复杂度:O(n),其中n是链表的长度。需要遍历链表一次

空间复杂度:O(1)。

2.递归法

分析:使用递归法遍历链表,越过尾节点后终止递归,回溯时修改各节点的next引用指向。

具体实现:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
     if(head==nullptr||head->next==nullptr) return head;//终止条件
     ListNode *p=reverseList(head->next);//递归后继节点
     head->next->next=head;//修改节点引用指向
     head->next=nullptr;
     return p;//返回反转链表的头节点
    }
};

复杂度分析:

时间复杂度:O(n):需要遍历链表一次

空间复杂度:O(n):遍历链表的递归深度达到n,系统需要在栈区使用O(n)大小额外空间。

有了前面几个题目的铺垫,我们已经初步了解快慢指针,链表反转的知识,接下来我们通过下面这个题目综合运用

注:此题来自《剑指offer》T27

4.给定一个链表的头节点head,请判断是否为回文链表

如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的

示例:

输入:head=[1,2,3,3,2,1]

输出:true

思路:

1.找到前半部分链表的尾节点(采用快慢指针,慢指针走一步,快指针走两步)

2.反转后半部分链表

3.判断是否是回文

4.恢复链表

5.返回结果

实现:

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head==nullptr){
            return true;
        }
        ListNode* midList=findMid(head);//寻找前半部分链表的尾节点
        ListNode* reveresHalfEnd=reverseList(midList->next);//反转后半部分链表

        ListNode* p1=head;
        ListNode* p2=reveresHalfEnd;
        bool result=true;
        while(p2!=nullptr&&result)//判断是否是回文
        {
            if(p1->val!=p2->val){
                result=false;
            }
            p1=p1->next;
            p2=p2->next;
        }
        midList->next=reverseList(reveresHalfEnd);//恢复链表
        return result;//返回结果
    }
    ListNode* findMid(ListNode* head)
    {
        ListNode* fastptr=head;//快指针
        ListNode* slowptr=head;//慢指针
        while(fastptr->next!=nullptr&&fastptr->next->next!=nullptr)
        {
            fastptr=fastptr->next->next;//快指针走两步
            slowptr=slowptr->next;//慢指针走一步
        }
        return slowptr;//返回前半部分链表的尾节点
    }
    ListNode* reverseList(ListNode* head)//反转链表
    {
        ListNode* curr=head;
        ListNode* prev=nullptr;
        while(curr!=nullptr)
        {
            ListNode* temp=curr->next;
            curr->next=prev;
            prev=curr;
            curr=temp;
        }
        return prev;
    }
};

此题通用可以用递归或栈的方法解决,不再讨论

复杂度分析:

时间复杂度:O(n)

空间复杂度:O(1)

———————————————————————————————————————————

对以上题目或链表有问题可私信作者微信询问

 

如果你觉得这篇文章不错,留下你宝贵的点赞关注收藏。

你的支持是我更新的巨大动力!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_johan

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

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

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

打赏作者

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

抵扣说明:

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

余额充值