@清晰掉 循环链表特质

我们学一个算法,一定是为了用吧,所谓“学以致用”吗?那么判断两个链表是否相交有什么用呢?这是因为一旦两个链表出现相交的情况,就可能发生这样的情况,程序释放了链表La的所有节点,这样就导致了另外一个与之有相交节点的链表Lb中的节点也释放了,而Lb的使用者,可能并不知道事实的真相,这会带来很大的麻烦。

1.问题分析

  看看两个链表相交到底是怎么回事吧,有这样的的几个事实:(假设链表中不存在环)

  (1)一旦两个链表相交,那么两个链表中的节点一定有相同地址。

  (2)一旦两个链表相交,那么两个链表从相交节点开始到尾节点一定都是相同的节点。

  分析出来了问题的本质,那么思路也就自然有了。

2.问题解法

2.1 哈希解法:

  既然连个链表一旦相交,相交节点一定有相同的内存地址,而不同的节点内存地址一定是不同的,那么不妨利用内存地址建立哈希表,如此通过判断两个链表中是否存在内存地址相同的节点判断两个链表是否相交。具体做法是:遍历第一个链表,并利用地址建立哈希表,遍历第二个链表,看看地址哈希值是否和第一个表中的节点地址值有相同即可判断两个链表是否相交。

  时间复杂度O(length1 + length2)

  空间复杂度O(length1)

  分析:时间复杂度是线性的,可以接受,并且可以顺便找到第一个相交节点,但是却增加了O(length1)的空间复杂度,这显然不能令人满意。

2.2 问题转化

  如果两个链表中存在相交节点,那么将第二个链表接到第一个链表的后面,然后从第二个链表的表头开始遍历,如果存在环,则遍历过程一定会回到链表二的表头节点。可是这种方法似乎并不能找到第一个相交节点。怎么办呢?怎样才能判断链表中是否存在环,并且找到环的开始节点呢?

  网上看到了这样的一个解法:设置两个指针fast和slow,初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表),这样就可以判断两个链表是否相交了,程序如下:

bool IsExitsLoop(slist *head)
{
  slist *slow = head, *fast = head;

  while ( fast && fast->next ) 
  {
    slow = slow->next;
    fast = fast->next->next;
    if ( slow == fast )

      break;
  }
  return !(fast == NULL || fast->next == NULL);
}

下面看看怎么找环的入口,当fast与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:
2s = s + nr
s= nr
设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)
(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点(从相遇点向后遍历循环回到入口点的距离),于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇点为环入口点,也即为两个链表的第一个相同节点。程序描述如下:

slist* FindLoopPort(slist *head)
{
    slist *slow = head, *fast = head;
    while ( fast && fast->next )
    {
         slow = slow->next;
         fast = fast->next->next;
         if ( slow == fast )  break;
    }
    if (fast == NULL || fast->next == NULL)
        return NULL;
    slow = head;
    while (slow != fast)
    {
         slow = slow->next;
         fast = fast->next;
    }
    return slow;
}

这种解法似乎是非常犀利,逻辑推理很棒,但是这种解法的时间复杂度是怎么样的呢??slow每次前进一步,fast每次前进两步,这样的话遍历多少步会结束呢???(求人解释)

2.3 抓住要点

  不妨遍历每个链表保存最后一个节点,看看最后一个节点是否是同一个节点,这种情况时间复杂度是O(length1 + length2)。基本也不需要什么空间,似乎是一个不错的想法哦,那么怎么找到第一个相交节点呢?可以遍历的过程中记录链表的长度L1和L2(假设L1>L2)这是遍历找到第一个链表中的第L1 - L2节点,然后链表一从第L1-L2个节点开始遍历,链表二从第一个节点遍历,每次前进一步,直到找到第一个相同的节点,则可以认为两个链表存在相交节点,并且该点即为第一个相交节点(原来这里写错了,感谢Ider指出这个错误)。这种解法的时间复杂度也是线性的,但是如果两个链表长度相差不多时,时间复杂度还是不错的。

  到这里,我知道的几种典型的解法就说完了。欢迎大神们提供新的思路!!

3.问题扩展:(思考)

  baidu曾经出过这样的一个笔试题目,归根到底也是找到两个链表是否存在相同的节点,但是数据量很大,即链表长度是上亿的。想想那么应该怎么处理呢?

时间: 2024-10-13 07:04:06

@清晰掉 循环链表特质的相关文章

@清晰掉 malloc是如何分配内存的?

任何一个用过或学过C的人对malloc都不会陌生.大家都知道malloc可以分配一段连续的内存空间,并且在不再使用时可以通过free释放掉.但是,许多程序员对malloc背后的事情并不熟悉,许多人甚至把malloc当做操作系统所提供的系统调用或C的关键字.实际上,malloc只是C的标准库中提供的一个普通函数,而且实现malloc的基本思想并不复杂,任何一个对C和操作系统有些许了解的程序员都可以很容易理解. 这篇文章通过实现一个简单的malloc来描述malloc背后的机制.当然与现有C的标准库

@清晰掉 各种类型32位与64位下各类型长度对比

64 位的优点:64 位的应用程序可以直接访问 4EB 的内存和文件大小最大达到4 EB(2 的 63 次幂):可以访问大型数据库.本文介绍的是64位下C语言开发程序注意事项. 1. 32 位和 64 位C数据类型 32和64位C语言内置数据类型,如下表所示: 上表中第一行的大写字母和数字含义如下所示:I表示:int类型L表示:long类型P表示:pointer指针类型32表示:32位系统64表示64位系统如:LP64表示,在64位系统下的long类型和pointer类型长度为64位.64位Li

@清晰掉 GDB调试器中的战斗机

GDB 的命令很多,本文不会全部介绍,仅会介绍一些最常用的.在介绍之前,先介绍GDB中的一个非常有用的功能:补齐功能.它就如同Linux下SHELL中的命令补齐一样.当你输入一个命令的前几个字符,然后输入TAB键,如果没有其它命令的前几个字符与此相同,SHELL将补齐此命令.如果有其它命令的前几个字符与此相同,你会听到一声警告声,再输入TAB键,SHELL将所有前几个字符与此相同的命令全部列出.而GDB中的补齐功能不仅能补齐GDB命令,而且能补齐参数. 本文将先介绍常用的命令,然后结合一个具体的

@清晰掉 qsort()

qsort函数描述: http://www.cnblogs.com/sooner/archive/2012/04/18/2455011.html qsort()函数实现: /*** *qsort.c - quicksort algorithm; qsort() library function for sorting arrays * Copyright (c) Microsoft Corporation. All rights reserved. * *Purpose: * To implem

@清晰掉 swap函数

swap函数估计是一个各种各样程序都会频繁用到的子程序,可是你知道它究竟有多少种不同的写法吗?下面我就列举我知道的几种swap函数来跟大家分享一下. (1)经典型---嫁衣法 无论是写程序还是干其他事情,一旦涉及到交换,就总是会遇到第三方.这个第三方可能是公正的监督者,也可能是一个徒为他人做嫁衣的可怜虫.在经典法的交换程序中,我们就需要有一个可怜虫来为我们提供暂时的服务.程序如下: void swap(int *a,int *b){int temp; temp=*a; *a=*b; *b=tem

@清晰掉 传递数组给函数

一维数组 传递数组给函数的3个原则 1.函数调用时只需传递数组名. 2.在函数定义中,形参的类型必须与数组的相同,数组的大小不必指定. 3.函数原型必须定义为参数是一个数组. 1 #include <stdio.h> 2 #include <stdlib.h> 3 void sort(int x[], int m); 4 int main() 5 { 6     int i; 7     int marks[5] = {40, 90, 73, 81, 35}; 8       9

@清晰掉 GNU C __attribute__

__attribute__((packed))详解 1. __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法.这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的,我在windows下,用vc的编译器也不是紧凑的,用tc的编译器就是紧凑的.例如: 在TC下:struct my{ char ch; int a;} sizeof(int)=2;sizeof(my)=3;(紧凑模式) 在G

成功产品人必备的20项特质

怎样成为一个好的产品经理?本文通过观察数百位产品经理,总结了产品人必备的20项特质,一起开看看吧,希望对大家有所帮助. 01.从问"为什么"开始 你必须能清晰的回答为什么会有人用你的产品,你的产品能为他们解决生活上的什么难题等这样的问题.你要关注用户写的评论.一旦确定了这个产品的定位和愿景,就一定要围绕这个定位和愿景开发产品.正如亚马逊CEO杰夫.贝佐斯所说:"Be stubborn on the vision, flexible on thedetails."(坚

带项目的一些体会以及合格的 Leader 应该具备什么特质?(转)

许多项目有这样几种 Leader: 1. 泛泛而谈型 很多时候 Leader 仅仅给出一个大方向,提一些高屋建瓴的理论方向,事情还是交由普通开发人员去做.完了可能又会回头埋怨开发人员的水平不行,没有达到他的预期.最终软件产品磕磕畔畔发布,达不到预期,责任推到开发人员头上,Leader 又高屋建瓴祭出华丽的重构方案或二期方案. 2. 纯技术型 这样的 Leader 也有很多,可能来自于团队内部的大牛升职,也可能来自外部,他们有个共同的特点,爱“玩”技术.这样的 Leader 也许是具备了太多“工程