热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

LeetCode伴侣:从零开始LeetCode内置数据结构库的开发

一、引言刷过LeetCode的同学应该都知道:LeetCode是一个在线OJ系统,经常会有些涉及到链表、二叉树、数组的题目,在线做题如果出

一、引言

刷过 LeetCode 的同学应该都知道:

LeetCode 是一个在线 OJ 系统,经常会有些涉及到链表、二叉树、数组的题目,在线做题如果出现了奇怪的问题,也不能自行打断点查看哪里出了问题;想要自己本地打开 IDE 进行编译调试吧,又发现没有对应的链表、二叉树、数组这种拿来即可使用的数据结构。

因此,我开始萌生了自己写一个 LeetCode 内置数据结构库的想法。中间因为种种原因,或因为能力不够,或因为知识欠缺,或因为琐事太多,一直一直也未能完成这个心愿。

这两天刚好学习了动态链接库的编写,正好应景就初步完成了一个简略的 LeetCode 内置数据库的开发 :)

为了方便 LeetCode 在线刷题可以拿到本地调试运行,也为了提升自己的技术水平,我对自己这个 LeetCode 内置数据结构库提出了以下需求:

  1. 动态链接库:只需要拿到 .Lib 文件、.dll 文件、.h 文件,即可拿到我的 LeetCode 解决方案中调用调试运行

  2. 初步决定实现的数据结构:数组(LCArray)、二叉树(LCBinaryTree)、单链表(LCSingleLinkedList),暂时决定先实现这三个,后期可以视情况进行添加

  3. 实现功能:

    1. 接受指定格式输入初始化:例如二叉树的输入 [1, 2, 3, null, 4, 5],再比如数组的输入 [1, 2, 3, 4, 5]
    2. 能够获取到数据结构:例如二叉树就可以返回一个根节点(有些程序就可以直接拿着这个根节点进行算法演算),例如单链表返回根节点
    3. 能够打印数据结构:打印出当前的数据结构的信息

需求还算比较简单的,可能有些同学不了解为什么要做这么一个数据结构库,这里我以二叉树为例子进行讲解:

比如我们在 LeetCode 上的一道跟二叉树有关的题目的 Custom TestCase 中输入如下字符串:

[1, 2, 3, null, 4, null, 5]

我们就可以看到 LeetCode 为我们显示的二叉树结构图:

LeetCode VisualLizer

其中,输入的每个值都是一个节点,只是输入为 null 的节点默认为空节点。

因此,为了实现这样一个内置的数据结构机制,我萌生并实现了这么一个 LeetCode 内置数据结构库。

二、LeetCode::LCArray:内置数组

那么现在开始,整个项目命名为 LeetCodeDataStructure,定义一个名字控件 namespace,并且定义了三种内置数据结构:数组、二叉树、单链表,另外定义了单链表节点和二叉树的叶子节点。

整个项目是动态链接库项目,这是为了方便这个库链接到其他项目中使用,这也是我设计的初衷。关于如果编写、调用动态链接库,可以查看我写的以下两篇博客:

简单 Demo:C++编写、调用动态链接库
简单Demo:动态调用自己编写的动态链接库

想要实现一个能够接受输入为:

[1, 2, 3, 4, 5]

的数组结构,并且能够将其转换为 std::vector 结构,其实还是比较简单的。

  1. 初始化:接受一个 std::string 类型的输入 ” [1, 2, 3, 4, 5]”,通过正则表达式判断输入是否合理,匹配字符串中的数字并将其压入记录即可

  2. 输出:直接返回一个 std::vector 即可

  3. 打印:遍历打印即可

LeetCode::LCArray 的实现还算比较简单的,主要就是正则表达式的运用:

// 构造:将字符串输入转化为 std::vector 内置类型
// 正则:\[.*\[|\].*\] [[ 和 ]]
// 正则:\[(\s*\d\s*[,]\s*)*\s*\d\s*\] 匹配 [1, 2, 3, 4, 5, 6]
// 正则:\d 匹配 1
bool LeetCode::LCArray::_ConstructArray(std::string strInput)
{
std::string strTemp(strInput);
std::smatch match;
std::regex regexBracket("\\[.*\\[|\\].*\\]");
std::regex regexString("\\[(\\s*\\d\\s*[,]\\s*)*\\s*\\d\\s*\\]");
std::regex regexInteger("\\d");
// 检测:如果匹配到了 [[ 或者 ]],证明字符串不合理
if (std::regex_search(strTemp, match, regexBracket)) {
return false;
}
// 检测:如果匹配正确,则表示字符串合理
strTemp = strInput;
if (std::regex_search(strTemp, match, regexString)) {
std::string strInteger = match.str();
std::smatch matchInteger;
while (std::regex_search(strInteger, matchInteger, regexInteger)) {
std::string integerTemp = matchInteger.str();
m_vecArray.push_back(std::atoi(integerTemp.c_str()));
strInteger = matchInteger.suffix().str();
}
return true;
} else {
return false;
}
}

这个函数是 LeetCode::LCArray 类的核心实现,用于匹配读取其中的数据,并将其转换为 std::vector类型。

三、LeetCode::LCBinaryTree:内置二叉树

内置二叉树的实现是一个比较难的问题,主要难点在于:

  1. 输入:二叉树的构建在引言里已经展示过了,输入是类似 [1, 2, 3, null, 4, 5] 的字符串数据,要编写正确的正则表达式能够读取出来数据

  2. 二叉树的建立:要建立一个二叉树结构,可以使用 std::vector 来记录所有节点的信息(包括空节点),然后再在其中遍历设置每个节点的指向即可

  3. 二叉树的输出:这里我模仿 Linux 的树结构显示法显示了二叉树的结构,在子节点上以 L 和 R 来区分是左子节点还是右子节点

分析了以上三个难点,接下来我们来一一突破。

首先,要书写一个能够识别 [1, 2, 3, 4, null, 5] 的正则表达式:

\[(\s*\d\s*,|\s*null\s*,)*(\s*\d\s*|\s*null\s*)\]

这里我通过多次尝试,写出了这么一个正则表达式,验证也是正确的(有关正则表达式的内容可以点击这里进行学习 正则表达式30分钟入门教程)。

然后,二叉树的建立其实还算简单。主要是如何设置二叉树的节点的指向。我们可以把所有的输入都与一个二叉树节点一一对应起来(即使为 null 也认为是一个叶子节点,不过值为 null 而已,我们可以以 -1 来替代之)。

cout tree node

如图,我们的标记是从 0 开始的,因此数组中记录的节点信息就是如图显示的顺序,其中 3 和 5 都是空节点。根据查看我们能够总结出一个规律,那就是:

第 i 个节点的左子节点必然是第 i * 2 + 1 个节点
第 i 个节点的右子节点必然是第 i * 2 + 2 个节点

根据这个规律,我们就可以设置出每个节点的指向了,然后拿到第一个节点(也就是根节点),就相当于拿到了一个二叉树结构了。

接下来,最后一个难点:如果输出二叉树结构。

仔细思考下,其实也不难,主要是控制好遍历的层次,每一层子节点的递进,控制好缩进一层即可,主要实现是一个递归函数:

// 打印:打印一个节点
void LeetCode::LCBinaryTree::_PrintTreeNode(const TreeNode node, int depth, enum EPrintDirection eDirection) const
{
int myDepth = depth;
std::cout <<"|";
while (myDepth--) std::cout <<" ";
switch (eDirection)
{
case EPrintDirection_Root: std::cout <<"---";
break;
case EPrintDirection_Left: std::cout <<"L--";
break;
case EPrintDirection_Right:std::cout <<"R--";
break;
}
std::cout <std::endl;

if (node.left != nullptr)
_PrintTreeNode(*node.left, depth + 1, EPrintDirection_Left);
if (node.right != nullptr)
_PrintTreeNode(*node.right, depth + 1, EPrintDirection_Right);
}

其中定义了一个联合对象来记录当前节点的属性:是左子节点还是右子结点,或者它是根节点。

最后的显示效果还是非常棒的:
Binry Tree

四、LeetCode::LCSingleLinkedList:内置单链表

内置单链表的实现其实比较简单,识别输入:

[1, 2, 3, 4, 5]

与前面的 LeetCode::LCArray 是一模一样的,因此正则初始化模块是可以代码复用的。

那么就剩下单链表数据结构的构建了,其实也比较简单:

// 构造:将 std::vector 转化为二叉树
bool LeetCode::LCSingleLinkedList::_MakeSingleLinkedList(std::vector<int> vecSingleLinkedList)
{
// 第一步构造:赋值
for (auto val : vecSingleLinkedList) {
ListNode newListNode(val);
m_vecSingleLinkedList.push_back(newListNode);
}
// 第二步构造:指向
for (int i = 0; i if (i != m_vecSingleLinkedList.size() - 1)
m_vecSingleLinkedList[i].next = &m_vecSingleLinkedList[i + 1];
else
m_vecSingleLinkedList[i].next = nullptr;
}
return true;
}

也就是两个循环,第一次全部复制构建所有的链表节点,第二次设置指向,而链表没那么多弯弯绕绕的东西,直接依次指向下一个即可,最后一个节点指向为空即可。

五、欣赏:LeetCode内置数据结构库展示

这里新建另外一个测试项目,静态调用动态链接库 LeetCodeDataStructure,用来测试我们的 LeetCode 内置数据结构库的功能是否正常:

内置数组:LeetCode::LCArray
LCArray

内置二叉树:LeetCode::LCBinaryTree
LCBinaryTree

内置单链表:LeetCode::LCSingleLinkedList
LCSingleLinkedList

怎么样,尽管很简略,还是能看够用的吧 ^_^

六、总结

能够开发出来这个一个 LeetCode 内置数据结构库,我很开心。

怎么说呢,第一次为自己的实际需求开发了一个很小型的库,真的成就感爆棚。在开发过程中遇到了不少的麻烦,其中有一个 DLL 编程中调用 std::vector 模板库出现警告的问题,也是琢磨了很久很久,最后选择忽略警告,这个问题同样遇到的同学可以点击这篇博客:
C4251告警“需要有 dll 接口由 class“xxx”的客户端使用”的解释及解决方法

尽管遇到了这么多问题,最后还是一一解决。

创造的快感,无与伦比:)

编程的本质是创造
这是一件那么伟大的事情
以至于我每天都在为此感到自豪

永远相信,更美好的事情即将发生 ^_^

ps: 附上本博客的实验代码的 GitHub 托管地址,感兴趣的同学可以自行查看:

wangying2016/LeetCodeDataStructure


推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了Oracle存储过程的基本语法和写法示例,同时还介绍了已命名的系统异常的产生原因。 ... [详细]
  • 从Oracle安全移植到国产达梦数据库的DBA实践与攻略
    随着我国对信息安全和自主可控技术的重视,国产数据库在党政机关、军队和大型央企等行业中得到了快速应用。本文介绍了如何降低从Oracle到国产达梦数据库的技术门槛,保障用户现有业务系统投资。具体包括分析待移植系统、确定移植对象、数据迁移、PL/SQL移植、校验移植结果以及应用系统的测试和优化等步骤。同时提供了移植攻略,包括待移植系统分析和准备移植环境的方法。通过本文的实践与攻略,DBA可以更好地完成Oracle安全移植到国产达梦数据库的工作。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 本文介绍了PHP常量的定义和使用方法,包括常量的命名规则、大小写敏感性、全局范围和标量数据的限制。同时还提到了应尽量避免定义resource常量,并给出了使用define()函数定义常量的示例。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • 本文介绍了关系型数据库和NoSQL数据库的概念和特点,列举了主流的关系型数据库和NoSQL数据库,同时描述了它们在新闻、电商抢购信息和微博热点信息等场景中的应用。此外,还提供了MySQL配置文件的相关内容。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
author-avatar
aGreadyCat__895
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有