当前位置: 首页 > news >正文

陕西建设机械官方网站培训课程有哪些

陕西建设机械官方网站,培训课程有哪些,动态网页怎么写,大牌网页设计目录 一.leetcode剑指 Offer II 027. 回文链表 1.问题描述 2.问题分析与求解 (1) 快慢指针法定位链表的中间节点 (2) 将链表后半部分进行反转 附:递归法反转链表 (3) 双指针法判断链表是否回文 二.带头双向循环链表的实现 1.头文件 2.节点内存申请接口和链表初始化接口…

目录

一.leetcode剑指 Offer II 027. 回文链表

1.问题描述

2.问题分析与求解

(1) 快慢指针法定位链表的中间节点

(2) 将链表后半部分进行反转

附:递归法反转链表

(3) 双指针法判断链表是否回文

二.带头双向循环链表的实现

1.头文件

2.节点内存申请接口和链表初始化接口

3.链表的打印和查找接口

4.链表的增删接口

5.链表销毁接口


一.leetcode剑指 Offer II 027. 回文链表

剑指 Offer II 027. 回文链表 - 力扣(Leetcode)

1.问题描述

给定一个链表的头节点head,请判断其是否为回文链表。(是回文链表则程序返回true,不是回文链表则程序返回false)

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

题解接口:

/*** 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:bool isPalindrome(ListNode* head) {}
};

2.问题分析与求解

如果要求题解的时间复杂度为O(N),空间复杂度为O(1),那么本题的求解要分为三个部分:

  1. 用快慢指针法找到链表中间位置节点
  2. 将链表后半部分进行反转
  3. 用双指针法将链表前半部分和后半部分进行比对来判断链表是否回文

(1) 快慢指针法定位链表的中间节点

  • 思路是两个指针同时遍历链表,快指针一次走两步(fast=fast->next->next),慢指针一次走一步(slow = slow->next)。

  • 当快指针结束遍历时,慢指针恰好会指向中间位置的节点(对于奇数个节点的链表而言,慢指针最后会指向中间节点,对于偶数个节点的链表而言,慢指针最后会指向中间两个节点的第二个节点)

寻找中间位置节点的接口:

    ListNode * FindMid(ListNode * head){ListNode* fast = head;ListNode* slow = head;while(fast && fast->next)   //注意循环的限制条件{fast = fast->next->next;slow = slow->next;}return slow;}
  • 我们以偶数个节点链表的情况为例,简单地证明一下慢指针最后会指向中间两个节点的第二个节点:

对于奇数个节点链表的情况也可以作相似的证明。 

(2) 将链表后半部分进行反转

  • 定位链表中间位置节点后,我们便可以将链表的后半部分进行反转.

  • 完成链表反转的最优方法是三指针反转法(动画):

三指针反转链表

的接口:

    ListNode * reverse (ListNode * head){ListNode * cur = (nullptr == head)? nullptr : head->next;ListNode * pre = head;while(cur){ListNode* Next = cur->next;cur->next = pre;pre = cur;cur = Next;}if(head)head->next = nullptr;   //记得将被反转部分链表的尾结点的指针域置空return pre;             //pre最终指向反转链表的表头}

附:递归法反转链表

递归算法经常出现在单链表问题的题解中,其原因在于:递归算法可以利用多个函数栈帧来存储每一个链表节点的地址(而单链表的缺陷正是在于寻址困难),所以递归算法经常作为单链表问题的可行解之一.(但是递归算法由于压栈开销较大,往往并不是最优解,比如递归法反转链表在时间和空间上的开销都要比三指针反转法更大)

然而以思维训练和加深对递归的理解为目的,这里尝试着解构一下递归反转单链表的算法。

反转单链表递归函数的建立:

  • 递归反向遍历链表节点的框架:
        ListNode* reverseList (ListNode * head){if(head->next == nullptr)//递归的结束条件{return head;}reverseList(head->next);return head;   }

    递归框架可以实现反向遍历单链表(图解)

  • 在递归函数反向遍历链表节点的过程中我们可以加入修改节点指针域的操作
    ListNode* reverseList (ListNode * head){if(head->next == nullptr)//递归的结束条件{return head;}reverseList(head->next);head->next->next = head; head->next = nullptr;return head;}

递归函数修改节点指针域过程动画解析:

  • 我们希望函数能够将反转后链表新头节点的地址作为最终返回值带回:
        ListNode* reverseList (ListNode * head){if(head->next == nullptr)//递归的结束条件{return head;}ListNode* newhead = reverseList(head->next); //利用newhead将新的头节点地址逐层带回head->next->next = head; head->next = nullptr;return newhead;}

    递归函数将新的头节点地址逐层带回的过程图解:

     

  • 递归反转单链表的接口:

        ListNode* reverseList(ListNode* head) {if(nullptr == head || nullptr == head->next)//设置递归的限制条件,构建递归框架{return head;}ListNode * newhead = reverseList(head->next);//newhead是为了将新的头节点地址逐层带回到最外层递归函数作为返回值head->next->next = head;//从原尾结点开始实现反向链接head->next = nullptr;//这里逐层置空是为了最后将新的尾结点指针域置空return newhead;           }

由于递归算法开销比较大,所以题解接口中我们采用三指针反转法来完成链表的反转

(3) 双指针法判断链表是否回文

经过前两个步骤后,链表后半部分完成了反转:

最后在题解接口中用双指针判断链表是否回文即可

题解代码: 

class Solution 
{
public:ListNode * FindMid(ListNode * head)   //快慢指针法寻找链表中间位置节点的接口{ListNode* fast = head;ListNode* slow = head;while(fast && fast->next){fast = fast->next->next;slow = slow->next;}return slow;                       //返回链表中间位置节点}ListNode * reverse (ListNode * head)   //反转链表的接口(三指针翻转法){ListNode * cur = (nullptr == head)? nullptr : head->next;ListNode * pre = head;while(cur){ListNode* Next = cur->next;cur->next = pre;pre = cur;cur = Next;}if(head)       head->next = nullptr;   //记得将被反转部分链表的尾结点的指针域置空return pre;             //pre最终指向反转链表的表头}bool isPalindrome(ListNode* head) {ListNode* mid = FindMid(head);ListNode * reversehead = reverse(mid);ListNode * tem = reversehead;while(reversehead){if(reversehead->val != head->val){return false;}reversehead = reversehead->next;head = head->next;}reverse(tem);             //恢复原链表return true;}
};

 

二.带头双向循环链表的实现

链表共有8个种类,然而在现实中大多情形下能派上用场的链表只有两种:

  1. 无头单向非循环链表:实际中无头单向非循环链表常作为其他数据结构的子结
    ,如哈希桶、图的邻接表等等

  2. 带头双向循环链表:该种链表结构是由设计C++STL的大神设计出来的,结构优良,使用和实现起来都比较方便(每个节点都有两个指针域,比较耗空间)

每个带头双向循环链表都有一个哨兵头节点,该节点不存储有效数据.

带头双向循环链表的环状示意图:

 

1.头文件

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int LTDataType;typedef struct LTNode
{LTDataType val;struct LTNode* pre;           //指向前一个节点的指针struct LTNode* next;          //指向下一个节点的指针
}LTNode;//各个链表操作接口的声明
LTNode* BuyLTNode(LTDataType x);
void ListPrint(LTNode* phead);
LTNode* ListInit();
LTNode* ListFind(LTNode* phead, LTDataType x);void ListInsert(LTNode* pos, LTDataType x);
void ListErase(LTNode* pos, LTNode* phead);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopFront(LTNode* phead);
void ListPopBack(LTNode* phead);
void ListPushBack(LTNode* phead, LTDataType x);
void ListDestory(LTNode* phead);

2.节点内存申请接口和链表初始化接口

节点内存申请接口:

LTNode* BuyLTNode(LTDataType x)  //向系统申请链表节点空间的接口
{LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));if (NULL == NewNode){perror("malloc failed:");exit(-1);}NewNode->next = NULL;NewNode->pre = NULL;NewNode->val = x;return NewNode;
}

链表初始化接口:

LTNode* ListInit()    //链表初始化接口(链表初始化时创建哨兵节点,接口返回哨兵节点的地址)
{LTNode* phead = BuyLTNode(-1);phead->next = phead;phead->pre = phead;return phead;
}

无头双向循环链表的初始化就是申请一个哨兵节点:

使用时在主函数中用一个LTNode类型的指针接收该哨兵节点的地址.比如:

int main ()
{phead = ListInit();// 其他链表操作return 0;
}

3.链表的打印和查找接口

无头双向循环链表的遍历过程是从哨兵节点的下一个结点开始哨兵节点前一个节点结束。

void ListPrint(LTNode* phead)     //打印链表接口(注意不要打印哨兵节点中的无效数据)
{assert(phead);LTNode* tem = phead->next;while (tem != phead){printf("%d ", tem->val);tem = tem->next;}printf("\n");
}
LTNode* ListFind(LTNode* phead, LTDataType x)  //根据节点中存储的数据查找某个链表节点
{assert(phead);LTNode* tem = phead->next;while (tem != phead){if (x == tem->val){return tem;}tem = tem->next;}return NULL;
}

4.链表的增删接口

  • 删除pos地址处节点的接口 
void ListErase(LTNode* pos, LTNode* phead)    //删除pos位置的节点
{assert(pos && pos != phead);LTNode* Pre = pos->pre;LTNode* Next = pos->next;Pre->next = Next;Next->pre = Pre;free(pos);pos = NULL;
}
  • 在pos地址节点的后一个位置插入一个节点的接口:

 

void ListInsert(LTNode* pos, LTDataType x)    //在pos位置后插入一个链表节点的接口
{assert(pos);LTNode* newnode = BuyLTNode(x);LTNode* Next = pos->next;pos->next = newnode;newnode->pre = pos;newnode->next = Next;Next->pre = newnode;
}

头插,头删以及尾插尾删接口通过复用上面的两个接口即可实现

void ListPushFront(LTNode* phead, LTDataType x) //头插一个节点
{assert(phead);ListInsert(phead, x);
}
void ListPopFront(LTNode* phead)                //头删一个节点
{assert(phead && phead->next != phead);ListErase(phead->next, phead);
}
void ListPopBack(LTNode* phead)                 //尾删一个节点
{assert(phead && phead->pre != phead);ListErase(phead->pre, phead);
}
void ListPushBack(LTNode* phead, LTDataType x)  //尾插一个节点
{assert(phead);ListInsert(phead->pre, x);
}
  • 注意哨兵节点不允许在删除数据操作中被删除

5.链表销毁接口

void ListDestory(LTNode* phead)                 //销毁链表的接口
{assert(phead);LTNode* tem = phead->next;while (tem != phead){LTNode* Next = tem->next;free(tem);tem = Next;}free(phead);phead = NULL;
}
  •  注意哨兵节点最后才销毁

可以看见,带头双向循环链表各个操作接口的时间复杂度都是O(1),这点充分体现了其数据结构的优良性。 

 

 

 

 

 

http://www.dt0577.cn/news/38236.html

相关文章:

  • 苏州高端网站建设机构优化大师怎么卸载
  • 网站搜索功能怎样做关键词的作用
  • 会计可以做网站么百度文库官网登录入口
  • 渭南是哪个省嘉兴关键词优化报价
  • 做网站环境配置遇到的问题新闻 近期大事件
  • 大连网站制作信ls15227阿里指数查询入口
  • 网络免费推广网站网站编辑怎么做
  • 长沙企业建站销售电话百度帐号申请注册
  • 网站开发java架构云建站模板
  • dw做框架网站搜索引擎优化方法有哪些
  • 扶贫网站建设优势地推拉新app推广平台
  • 好康的网站代码百度竞价推广联系方式
  • soho怎么做网站外包公司是正规公司吗
  • 电商的网站怎么做的济南seo优化外包服务公司
  • 织梦网站首页是哪个文件网络推广企业
  • 西安品牌网站建设seo咨询师
  • 乐从做网站软件培训机构排名
  • 中山哪里可以做网站seo的基本内容
  • 上海市建设工程定额官方网站自己怎么做一个网页
  • 米拓建站免费模板磁力帝
  • 网站数据库 权限设计搜索引擎培训班
  • 海外网站搭建2023年中国进入一级战备状态了吗
  • 网站用的是建站公司的系统邯郸百度推广公司
  • 制作微信网页页面优化算法
  • 保定网站开发公司网站加速器
  • 如何做简单网站微信营销软件排行榜
  • 做外贸网站用什么软件百度百家
  • 做邮轮上哪个网站订票好网络营销论文5000字
  • h5网站设计中国行业数据分析网
  • 产业互联网公司排名宜昌网站seo收费