算法-链表之简单算法题(续)

本来以为一篇就能写完的,后来又感觉一篇多了一些,所以关于链表的简单算法题有加了个续篇,和上一篇一样,难度不会太大。

  • 链表中倒数第k个结点
  • 合并两个排序的链表
  • 找到两个链表中的第一个公共结点

1.链表中的倒数第k个结点

问题描述:输入一个链表,计算链表中的倒数第k个结点(从1开始计数)。

算法思想

我们可以分析链表结点个数n和倒数第k个结点之间的关系,如果链表有n个结点,那么它的倒数第k个结点也就是链表顺着数的第n-k+1个结点。
思路一:先遍历链表,得到结点个数n,再遍历链表,找到第n-k+1个结点。这一种思路需要两次遍历链表,时间复杂度为O(n)。
思路二:能不能一次遍历就找到结点呢?这就是接下来要说的了。我们可以使用两个指针,第一个指针从头结点开始,向前走k-1步,然后第二个指针指向首结点,接下来两个指针顺序后移,当第一个指针指向最后一个结点的时候,第二个指针刚好指向倒数第k个几点。两个指针之间间隔为k-1。

代码:

算法思想清楚了之后,代码就简单多了。写出代码之后,我们需要关注其鲁棒性,针对特殊的输入,代码能否完成任务。这些都是代码中需要考虑到的。
特殊输入:
1.如果pHead为NULL,那么不存在倒数第K个结点,应该返回NULL。
2.k接收的无符号数,如果输入k=0,因为k是无符号数,for循环k-1次的时候,得到的将不会是-1,而是一个非常大的数字,for循环的次数会超过我们的预期。
3.如果输入的k大于链表结点总数,那么在for循环遍历链表的时候,会出现指向NULL的指针。

链表的数据结构定义:

typedef struct ListNode *list_pointer;
typedef struct ListNode
{
    int value;
    list_pointer link;
};
list_pointer pHead;

查找链表中的倒数第k个结点(从1开始计数)。

list_pointer FindKthNodeFromEnd(list_pointer pHead, unsigned int k)
{
    //链表中的结点计数从1开始,所以不存在倒数第0个结点
    if (pHead == NULL || k == 0) {
        return NULL;
    }
    list_pointer pAhead = pHead;
    list_pointer pBehind;

    for (int i = 0; i < k - 1; i++)
    {
        if (pAhead->link)//防止k大于链表结点个数的情况发生
        {
            pAhead = pAhead->link;
        }
        else
        {
            fprintf(stderr, "K is bigger than length of linklist\n");
            exit(1);
        }
    }

    pBehind = pHead;
    while (pAhead->link)//判断pAhead是不是指向最后一个结点
    {
        pAhead = pAhead->link;
        pBehind = pBehind->link;
    }
    return pBehind;
}

相关题目

类似的题目还有很多,我们可以借助两个指针,很快的得到结果。
题目一:求链表的中间结点,如果链表结点数为奇数,则输出中间结点,如果链表结点数为偶数, 则输出中间两个中的一个。
算法思想:定义两个指针,一个指针一次走一步,另一个指针一次走两步,当走得快的指针到达链表尾是,走得慢的刚好指向链表中间结点。
题目二:判断一个单向链表是否是环形链表。
算法思想:定义两个指针,从首结点开始,一个指针一次走一步,另一个指针一次走两步,如果走的快的指针追到了走得慢的指针,那么这个链表就是环形的,如果走的快的指针走到了链表的末尾都没有追到慢的指针,那么就不是环形链表。

代码

这里写了题目一的代码,可以参考一下。需要注意的代码中的while的循环条件,判断了pAhead和pAhead->link,这里是判断是否指向了尾节点,如果链表结点数为偶数,是用pAhead==NULL来判断是否到尾节点的;如果链表有奇数个结点,就是用pAhead->link==NULL来判断的。

//找中间结点
list_pointer getTheMiddleNode(list_pointer pHead)
{
    if (pHead == NULL){
        fprintf(stderr, "the linklist is empty.\n" );
        exit(1);
    }
    if (pHead->link == NULL)//如果链表中只有一个结点,那么中间结点就是该结点
        return pHead;
    list_pointer pAhead = pHead, pBehind = pHead;
    //如果走的快的指针没有指向尾结点
    while (pAhead && pAhead->link)
    {
        pAhead = pAhead->link->link;
        pBehind = pBehind->link;
    }
    return pBehind;
}

2.合并两个排序的链表

问题描述:输入两个排好序的链表,合并两个链表,并返回合并后的链表的头结点。

算法思想

用两个指针,一个指向a链表的结点,一个指向b链表的结点。都从链表头开始,依次比较。针对两个链表中的第一个结点,比较大小,小的结点加到合并的之后的链表中,然后加入合并链表的那个链表指针后移,然后计算省下结点的的头结点,接下来将计算出来的结点接到合并链表的链表尾,剩余的结点也是这样处理。这样分析下来,就是典型的递归了。使用递归,我们可以很快的解决这道题。

代码

//合并两个排好序的链表,用递归,每次都比较链表中剩余结点的头结点
list_pointer Merge(list_pointer pHead1, list_pointer pHead2)
{
    if (pHead1 == NULL)
        return pHead2;
    if (pHead2 == NULL)
        return pHead1;

    list_pointer pMerge = NULL;//合并之后的链表的头结点
    //每次都比较链表中剩余结点的头指针
    if (pHead1->value < pHead2->value)
    {
        pMerge = pHead1;
        pMerge->link = Merge(pHead1->link, pHead2);
    }
    else
    {
        pMerge = pHead2;
        pMerge->link = Merge(pHead1, pHead2->link);//接到上一个结点后面
    }
    return pMerge;
}

3.找到两个链表中的第一个公共结点

问题描述:输入两个链表,计算两个链表的公共结点。

算法思想

思路一:蛮力法,遍历第一个链表,每遍历一个结点时,在第二个链表上顺序遍历所有结点,如果第二个链表上有结点和第一个链表的结点相等,那么就找到了公共结点。这种思路的时间复杂度是O(n^2),效率很低。
思路二:首先分析有公共结点的两个链表的特点,如果两个链表存在公共结点,那么两个链表都一定存在一个结点,它的link指向相同。单向链表的每个结点只能有一个指向,所以从第一个公共结点开始,之后它们的所有结点都是重合的,不可能再分叉。所以两个有公共结点而部分重合的链表,看起来像Y

首先,可以确定,如果两个链表有公共结点,那么公共结点一定是在链表尾部。如果从两个链表的最后一个结点开始比较,遇到最后一个相等的结点就是它们的公共结点了。但是链表要定位到最后一个结点就需要从头遍历,最先遍历到的结点最后比较,最后遍历到的结点(尾结点)第一个比较,所以我们可以使用栈。先遍历两个链表,用两个栈来存储每个结点。每次比较栈顶结点,直到比较到最后一个相等的结点。时间复杂度O(m+m),m,n分别是两个链表的长度,空间复杂度也是O(m+n)。

思路三:用栈的目的就是定位到最后一个元素,方便从后往前遍历。这样就解决了两个链表的结点数不一致时,到达尾结点的时间不同。换一种思路。如果提前知道链表长度,我们就可以知道长的链表比短的链表多几个元素,那么长的链表先走几步,然后两个链表开始同时向后遍历,遇到的第一个相同的结点就是其公共结点了。这种思想的时间复杂度是O(m+n),但是我们不再需要额外的空间辅助了。

代码

思路三代码。

//计算链表长度
unsigned int getLength(list_pointer p)
{
    list_pointer pNode = p;
    int length = 0;
    while (pNode)
    {
        length++;
        pNode = pNode->link;
    }
    return length;
}

//计算两个链表的第一个公共结点
ListNode* FindFirstCommonNode(list_pointer pHead1, list_pointer pHead2)
{
    if (!pHead1 || !pHead2)
        return NULL;
    //得到两个链表的长度
    unsigned int nLength1 = getLength(pHead1);
    unsigned int nLength2 = getLength(pHead2);

    int nLengthDif = nLength1 - nLength2;
    list_pointer pListHeadLong = pHead1;
    list_pointer pListHeadShort = pHead2;

    if (nLength1 < nLength2)
    {
        nLengthDif = nLength2 - nLength1;
        pListHeadLong = pHead2;
        pListHeadShort = pHead1;
    }

    //长的链表先走
    for (int i = 0; i < nLengthDif; i++)
    {
        pListHeadLong = pListHeadLong->link;
    }

    while (pListHeadLong && pListHeadShort
        && pListHeadLong != pListHeadShort)
    {
        pListHeadLong = pListHeadLong->link;
        pListHeadShort = pListHeadShort->link;
    }
    //得到第一个公共结点
    ListNode *theCommonNode = pListHeadLong;
    return theCommonNode;
}

总结

遇到这些问题的时候,常规思路的解决方法我们的通常都能想到,但是时空效率很低,看到这些高效的解决方法的时候,我觉得真是很奇妙,快捷方便的解决了问题。我们可以从这些方法之中学习一种思路,比如说一个指针解决不了的,试试两个指针,总结要解决的问题和链表特有的存储结构的关系等。这次就到这里了,不足之处,请多指教~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 转载请注明出处:http://www.jianshu.com/p/c65d9d753c31 在上一篇博客《数据结构...
    Alent阅读 3,490评论 4 74
  • 大学的时候不好好学习,老师在讲台上讲课,自己在以为老师看不到的座位看小说,现在用到了老师讲的知识,只能自己看书查资...
    和珏猫阅读 1,434评论 1 3
  • 好多天没有更新文章,今天更新的是链表的复杂算法题,这一篇完了之后,链表相关的也结束了,会进入下一个环节。 圆圈中最...
    zero_sr阅读 782评论 0 3
  • //leetcode中还有花样链表题,这里几个例子,冰山一角 求单链表中结点的个数----时间复杂度O(n)这是最...
    暗黑破坏球嘿哈阅读 1,509评论 0 6
  • 1.把二元查找树转变成排序的双向链表 题目: 输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。 要求不...
    曲终人散Li阅读 3,287评论 0 19