电子商务网站 技术方案,梅林固件 搭建wordpress,食品类网站模板,企业网站设计与推广1.6 如何检测一个较大的单链表是否有环【出自 ALBB 笔试题】难度系数#xff1a;★★★★☆ 题目描述#xff1a;被考察系数#xff1a;★★★★★单链表有环指的是单链表中某个结点的 next 域指向的是链表中在它之前的某一个结点#xff0c;这样在链表的尾部形成一个环形结…1.6 如何检测一个较大的单链表是否有环【出自 ALBB 笔试题】难度系数★★★★☆ 题目描述被考察系数★★★★★单链表有环指的是单链表中某个结点的 next 域指向的是链表中在它之前的某一个结点这样在链表的尾部形成一个环形结构。如何判断单链表是否有环存在分析与解答方法一蛮力法定义一个 HashSet 用来存放结点的引用并将其初始化为空从链表的头结点开始向后遍历每遍历到一个结点就判断 HashSe 中是否有这个结点的引用如果没有说明这个结点是第一次访问还没有形成环那么将这个结点的引用添加到指针 HashSet 中去。如果在HashSet 中找到了同样的结点那么说明这个结点已经被访问过了于是就形成了环。这种方法的时间复杂度为 ON空间复杂度也为 ON。方法二快慢指针遍历法定义两个指针 fast快与 slow慢二者的初始值都指向链表头指针 slow 每次前进一步指针 fast 每次前进两步两个指针同时向前移动快指针每移动一次都要跟慢指针比较如果快指针等于慢指针就证明这个链表是带环的单向链表否则证明这个链表是不带环的循环链表。实现代码见后面引申部分。引申如果链表存在环那么如何找出环的入口点分析与解答当链表有环的时候如果知道环的入口点那么在需要遍历链表或释放链表所占的空间的时候方法将会非常简单下面主要介绍查找链表环入口点的思路。如果单链表有环 那么按照上述方法二的思路 当走得快的指针 fast 与走得慢的指针 slow相遇时 slow 指针肯定没有遍历完链表而 fast 指针已经在环内循环了 n 圈 1n。如果slow 指针走了 s 步则 fast 指针走了 2s 步 fast 步数还等于 s 加上在环上多转的 n 圈假设环长为 r则满足如下关系表达式2s s nr由此可以得到 s nr设整个链表长为 L入口环与相遇点距离为 x起点到环入口点的距离为 a。则满足如下关系表达式a x nra x n - 1r r n-1r L - a a n-1r L - a - xL - a - x为相遇点到环入口点的距离从链表头到环入口点的距离n-1*环长相遇点到环入口点的长度于是从链表头与相遇点分别设一个指针每次各走一步两个指针必定相遇且相遇第一点为环入口点。实现代码如下/** 构造链表 */ fun constructList LNode { val head LNode head.next null var tmp LNode var cur head //构造第一个链表 for i in 1..7 { tmp LNode tmp.data i tmp.next null cur.next tmp cur tmp } cur.next head.next.next.next return head } /** * 判断单链表是否有环 * param head 链表头结点 * return null 无环否则返回 slow 与 fast 相遇点的结点 */ fun isLoophead LNode LNode { if head.next null return null //初始 slow 与 fast 都指向链表第一个结点 var slow head.next var fast head.next while fast null fast.next null { slow slow.next fast fast.next.next if slow fast return slow } return null } /** * 找出环的入口点 * param head fast 与 slow 相遇点 * return null 无环否则返回 slow 与 fast 指针相遇点的结点 */ fun findLoopNodehead LNode meetNode LNode LNode { var first head.next var second LNode meetNode while first second { first first.next second second.next } return first } fun mainargs ArrayString { val head constructList//头结点 val meetNode isLoophead val loopNode LNode if meetNode null { println有环 loopNode findLoopNodehead meetNode println环的入口点为 ${loopNode.data} } else { println无环 } }程序的运行结果如下 有环 环的入点为 3运行结果分析示例代码中给出的链表为 1-2-3-4-5-6-7-3 3 实际代表链表第三个结点。因此 IsLoop 函数返回的结果为两个指针相遇的结点 所以 链表有环 通过函数 FindLoopNode可以获取到环的入口点为 3。算法性能分析这种方法只需要对链表进行一次遍历因此时间复杂度为 On。另外由于只需要几个指针变量来保存结点的地址信息因此空间复杂度为 O1。1.7 如何把链表相邻元素翻转【出自 TX 笔试题】难度系数★★★☆☆ 题目描述被考察系数★★★★☆把链表相邻元素翻转例如给定链表为 1-2-3-4-5-6-7则翻转后的链表变为2-1-4-3-6-5-7。分析与解答方法一交换值法最容易想到的方法就是交换相邻两个结点的数据域这种方法由于不需要重新调整链表的结构因此比较容易实现但是这种方法并不是考官所期望的解法。方法二就地逆序主要思路通过调整结点指针域的指向来直接调换相邻的两个结点。如果单链表恰好有偶数个结点那么只需要将奇偶结点对调即可如果链表有奇数个结点那么只需要将除最后一个结点外的其他结点进行奇偶对调即可。为了便于理解下图给出了其中第一对结点对调的方法。在上图中当前遍历到结点 cur通过 1 6 6 个步骤用虚线的指针来代替实线的指针实现相邻结点的逆序。其中 1 4实现了前两个结点的逆序操作 5和 6两个步骤向后移动指针接着可以采用同样的方式实现后面两个相邻结点的逆序操作。实现代码如下/* 把链表相邻元素翻转 */ fun reverse2head LNode { // 判断链表是否为空 if head.next null return var cur head.next // 当前遍历结点 var pre LNode head // 当前结点的前驱结点 var next LNode // 当前结点后继结点的后继结点 while cur null cur.next null { next cur.next.next // 见图 1 pre.next cur.next // 见图 2 cur.next.next cur // 见图 3 cur.next next // 见图 4 pre cur // 见图 5 cur next // 见图 6 } } fun mainargs ArrayString { val head LNode head.next null var tmp LNode var cur LNode head for i in 1..7 { tmp LNode tmp.data i tmp.next null cur.next tmp cur tmp } print顺序输出 cur head.next while cur null { print${cur.data} cur cur.next } reverse2head print\n 逆序输出 cur head.next while cur null { print${cur.data} cur cur.next } } 程序的运行结果如下 顺序输出 1 2 3 4 5 6 7 逆序输出 2 1 4 3 6 5 7上例中由于链表有奇数个结点因此链表前三对结点相互交换而最后一个结点保持在原来的位置。算法性能分析这种方法只需要对链表进行一次遍历因此时间复杂度为 On。另外由于只需要几个指针变量来保存结点的地址信息因此空间复杂度为 O1。1.8 如何把链表以 K 个结点为一组进行翻转【出自 MT 笔试题】难度系数★★★☆☆ 题目描述被考察系数★★★★☆K 链表翻转是指把每 K 个相邻的结点看成一组进行翻转 如果剩余结点不足 K 个 则保持不变。假设给定链表 1-2-3-4-5-6-7 和一个数 K如果 K 的值为 2那么翻转后的链表为 2-1-4-3-6-5-7。如果 K 的值为 3那么翻转后的链表为 3-2-1-6-5-4-7。分析与解答主要思路 首先把前 K 个结点看成一个子链表采用前面介绍的方法进行翻转把翻转后的子链表链接到头结点后面 然后把接下来的 K 个结点看成另外一个单独的链表进行翻转把翻转后的子链表链接到上一个已经完成翻转子链表的后面。具体实现方法如下图所示。下面 K3 为例介绍具体实现的方法 1首先设置 pre 指向头结点然后让 begin 指向链表第一个结点找到从 begin 开始第K3 个结点 end。 2为了采用本章第一节中链表翻转的算法需要使 end.nextnull在此之前需要记录下 end 指向的结点用 pNext 来记录。 3使 end.nextnull从而使得从 begin 到 end 为一个单独的子链表从而可以对这个子链表采用 1.1 节介绍的方法进行翻转。 4对以 begin 为第一个结点 end 为尾结点所对应的 K3 个结点进行翻转。 5由于翻转后子链表的第一个结点从 begin 变为 end因此执行 pre.nextend把翻转后的子链表链接起来。 6把链表中剩余的还未完成翻转的子链表链接到已完成翻转的子链表后面主要是针对剩余的结点的个数小于 K 的情况。 7让 pre 指针指向已完成翻转的链表的最后一个结点。 8让 begin 指针指向下一个需要被翻转的子链表的第一个结点通过 beginpNext 来实现。接下来可以反复使用步骤 1 8对链表进行翻转。实现代码如下/** * 对不带头结点的单链表翻转 */ private fun reversehead LNode LNode { if head.next null return head var pre LNode head //前驱结点 var cur head.next //当前结点 var next LNode //后继结点 pre.next null //使当前遍历到的结点 cur 指向其前驱结点 while cur null { next cur.next cur.next pre pre cur cur next } return pre } /** * 对链表 K 翻转 */ fun reverseKhead LNode k Int { if head.next null || k 2 return var i 1 var pre LNode head var begin head.next var end LNode var pNext LNode while begin null { end begin //对应图中第1步找到从 begin 开始第 K 个结点 while i K { if end.next null end end.next else //剩余结点的个数小于 K return i } pNext end.next //2 end.next null //3 pre.next reversebegin //4 5 begin.next pNext //6 pre begin //7 begin pNext //8 i 1 } } fun mainargs ArrayString { val head LNode head.next null var tmp LNode var cur LNode head for i in 1..7 { tmp LNode tmp.data i tmp.next null cur.next tmp cur tmp } print顺序输出 cur head.next while cur null { print${cur.data} cur cur.next } reverseKhead 3 print\n 逆序输出 cur head.next while cur null { print${cur.data} cur cur.next } }程序的运行结果如下 顺序输出 1 2 3 4 5 6 7 逆序输出 3 2 1 6 5 4 7运行结果分析由于 K3因此链表可以分成三组 1 2 3、 4 5 6、 7。对 1 2 3翻转后变为 3 2 1对 4 5 6翻转后变为 6 5 4由于 7这个子链表只有 1 个结点小于 3 个因此不进行翻转所以翻转后的链表就变为 3-2-1-6-5-4-7。算法性能分析这种方法只需要对链表进行一次遍历因此时间复杂度为 On。另外由于只需要几个指针变量来保存结点的地址信息因此空间复杂度为 O1。1.9 如何合并两个有序链表【出自 ALBB 笔试题】难度系数★★★☆☆ 题目描述被考察系数★★★★☆已知两个链表 head1 和 head2 各自有序例如升序排列请把它们合并成一个链表要求合并后的链表依然有序。分析与解答分别用指针 head1、 head2 来遍历两个链表如果当前 head1 指向的数据小于 head2 指向的数据则将 head1 指向的结点归入合并后的链表中否则将 head2 指向的结点归入合并后的链表中。如果有一个链表遍历结束则把未结束的链表连接到合并后的链表尾部。下图以一个简单的示例为例介绍合并的具体方法由于链表按升序排列首先通过比较链表第一个结点中元素的大小来确定最终合并后链表的头结点接下来每次都找两个链表中剩余结点的最小值链接到被合并的链表后面如上图中的虚线所示。在实现的时候需要注意要释放 head2 链表的头结点具体实现代码如下/** * 方法功能构造链表 */ fun constructListstart Int LNode { val head LNode head.next null var tmp LNode var cur head for i in start until 7 step 2 { tmp LNode tmp.data i tmp.next null cur.next tmp cur tmp } return head } fun printListhead LNode { var cur head.next while cur null { print${cur.data} cur cur.next } } /** * 合并两个升序排列的单链表 * param head1 单链表 * param head2 单链表 * return 合并后链表的头结点 */ fun mergehead1 LNode head2 LNode LNode { if head1.next null return head2 if head2.next null return head1 var cur1 head1.next //用来遍历 head1 var cur2 head2.next //用来遍历 head2 val head LNode //合并后链表的头结点 var cur LNode //合并后的链表在尾结点 //合并后链表的头结点为第一个结点元素最小的那个链表的头结点 if head1.data head2.data { head head2 cur cur2 cur2 cur2.next } else { head head1 cur cur1 cur1 cur1.next } //每次找链表剩余结点的最小值对应的结点连接到合并后链表的尾部 while cur1 null cur2 null { if cur1.data cur2.data { cur.next cur1 cur cur1 cur1 cur1.next } else { cur.next cur2 cur cur2 cur2 cur2.next } } //当遍历完一个链表后把另外一个链表剩余的结点链接到合并后的链表后面 if cur1 null { cur.next cur1 } if cur2 null { cur.next cur2 } return head } fun mainargs ArrayString { val head1 constructList1 val head2 constructList2 printhead1 printListhead1 print\nhead2 printListhead2 print\n 合并后的链表 mergehead1 head2.apply { printListthis } }程序的运行结果如下 head1 1 3 5 head2 2 4 6 合并后的链表 1 2 3 4 5 6算法性能分析以上这种方法只需要对链表进行一次遍历因此时间复杂度为 On。另外由于只需要几个指针变量来保存结点的地址信息因此空间复杂度为 O1。