重温:数据结构与算法 - 05链表(二)

xwzz.jpg

重温:数据结构与算法 - 开篇
重温:数据结构与算法 - 复杂度分析(一)
重温:数据结构与算法 - 复杂度分析(二)
重温:数据结构与算法 - 数组
重温:数据结构与算法 - 链表(一)

前章,介绍了常见的链表数据结构有:

  • 单向链表
  • 循环链表
  • 双向链表
  • 双向循环链表

本章主要做几道练习题来加深对链表数据结构印象,每题都需考虑以下边界问题:

  • 如果链表为空时,代码是否能正常工作?
  • 如果链表只包含一个结点时,代码是否能正常工作?
  • 如果链表只包含两个结点时,代码是否能正常工作?
  • 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?

练习1:单向链表反转

反转前.png

如何将已知的单向链表反转?

反转后.png

思路:遍历所有结点,将当前结点的next指针指向前结点,操作完成后原尾结点作为首结点即可。

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
 public static Node reverseNode(Node first) {
    if (first == null) return null;
    if (first.next == null) return first;

    // 从首结点开始,首结点的上结点为空结点
    Node curNode = first;
    Node preNode = null;

    while (curNode != null) {
        Node tempNext = curNode.next;   // 暂存当前结点的下一结点

        curNode.next = preNode;     // 让当前结点指向上一结点

        // 切换到下一结点
        preNode = curNode;
        curNode = tempNext;
    }

    return preNode; //循环结束,上一结点就是首结点
}

测试:

// 测试数据
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
Node node5 = new Node(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = null;
// 测试代码
printAll(node1);
Node result = reverseNode(node1);
printAll(result);

输出:

1 2 3 4 5  // 反转前
5 4 3 2 1  // 反转后

练习2:循环链表检测

给你一个任意链表结点,如何校验其是否是循环链表?

思路: 通过快、慢指针同时遍历链表,快指针步长为2,慢指针步长为1,当快指针追上慢指针就是循环链表,否则快指针遍历到尾结点(next->null)就非循环链表。
时间复杂度:O(n)
空间复杂度:O(1)

/**
 * 检测链表是否是循环链表
 * */
public static boolean checkCircleNode(Node first) {
    if (first == null) return false;
    Node fast = first.next;
    Node slow = first;
    while (fast != null && fast.next != null) {
        if (fast == slow) return true;
        fast = fast.next.next;  // 快指针走两步
        slow = slow.next;       // 慢指针走一步
    }
    return false;
}

测试:

    Node node1 = new Node(1);
    Node node2 = new Node(2);
    Node node3 = new Node(3);
    node1.next = node2;
    node2.next = node3;
    node3.next = node1;
    System.out.println(checkCircleNode(node1));

    Node node4 = new Node(4);
    Node node5 = new Node(5);
    node4.next = node5;
    node5.next = null;
    System.out.println(checkCircleNode(node4));

    Node node6 = new Node(6);
    System.out.println(checkCircleNode(node6));

    Node node7 = new Node(7);
    node7.next = node7;
    System.out.println(checkCircle(node7));

    System.out.println(checkCircleNode(null));

输出:

true
false
false
true
false

练习3:合并有序链表

如何将两个从小到大的有序链表合并为一个有序链表?例如:

  • 链表1 : [ 1 , 3 , 5 , 7 , 9 ]
  • 链表2 : [ 2 , 4 , 6 , 8 , 10]
  • 合并后链表:[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10]

思路:
1、比较两者首结点、以最小结点作为合并链表的首结点
2、定义两个指针p,q,分别指向这两链表首结点,定义一个临时指针temp指向合并链表的首结点
3、同时开始遍历两个链表,如果有一个链表先到达尾部,则停止遍历
4、遍历中,取p、q指针数据的较小者链接到合并链表的尾部
5、遍历结束,temp拼接p链表或者q链表剩余结点(两链表长度可能不等)
6、返回合并链表首结点
时间复杂度:O(n)
空间复杂度:O(1)

public static Node mergeSortedNode(Node list1, Node list2) {
    if (list1 == null) return list2;
    if (list2 == null) return list1;

    // p,q 指针分别指向 链表1、链表2
    Node p = list1;
    Node q = list2;

    // 确认合并链表的首结点
    Node head;
    if (p.data <= q.data) {
        head = p;
        p = p.next;
    } else {
        head = q;
        q = q.next;
    }

    //开始遍历排序
    Node tempNode = head;
    while (p != null && q != null) {
        // 取小值链接到合并链表尾部
        if (p.data <= q.data) {
            tempNode.next = p;
            p = p.next;
        } else {
            tempNode.next = q;
            q = q.next;
        }
        // 开启下一轮
        tempNode = tempNode.next;
    }

    // 拼接剩余尾部
    if (p == null) {
        tempNode.next = q;
    } else {
        tempNode.next = p;
    }
    return head;
}

测试:

// 测试数据1
public static Node getNodeList1() {
    Node node1 = new Node(1);
    Node node2 = new Node(3);
    Node node3 = new Node(5);
    Node node4 = new Node(7);
    Node node5 = new Node(9);
    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;
    node5.next = null;
    return node1;
}
// 测试数据2
public static Node getNodeList2() {
    Node node1 = new Node(2);
    Node node2 = new Node(4);
    Node node3 = new Node(6);
    Node node4 = new Node(8);
    Node node5 = new Node(10);
    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;
    node5.next = null;
    return node1;
}
// 测试代码
private static void test() {
    Node list1 = getNodeList1();
    list1.print();

    Node list2 = getNodeList2();
    list2.print();

    Node mergeNode = mergeSortedNode(list1, list2);
    mergeNode.print();
}

输出:

1   3   5   7   9
2   4   6   8   10
1   2   3   4   5   6   7   8   9   10

练习4:删除链表的倒数第 n 个结点

思路:如何定位到倒数第n结点?

1、定义快指针,指向正数第n结点;
2、再定义慢指针指向首结点;
3、快慢指针同时以步长1开始遍历链表,当快指针到达尾结点时,慢指针正好停留在倒数第n结点
4、考虑到单链表删除,需要知道前结点,所以在第3步遍历前定义pre指针指向慢指针的前结点;
5、当遍历完成可能出现两种情况:

  • 情况1:pre指向结点不为null,删除慢指针结点即可;
  • 情况2:pre指向null,此时要删除的正好是首结点。
    时间复杂度:O(n)
    空间复杂度:O(1)
 public static Node deleteLastTh(Node list, int n) {
    if (list == null || n < 1) return list;

    Node fast = list;
    int i = 1;
    while (fast != null && i < n) {
        fast = fast.next;
        i++;
    }
    //不存在倒数第n个结点
    if (fast == null) return list;

    //开始运算,定位倒数n结点
    Node slow = list;
    Node pre = null;
    while (fast.next != null) {
        fast = fast.next;
        pre = slow;
        slow = slow.next;
    }

    if (pre == null) {
        //倒数n结点正好是首结点
        list = list.next;
    } else {
        //循环结束,slow就为倒数第n个结点,操作pre结点删除slow结点
        pre.next = pre.next.next;
    }
    return list;
}

测试:

    //测试数据
    Node node1 = new Node(1);
    Node node2 = new Node(2);
    Node node3 = new Node(3);
    Node node4 = new Node(4);
    Node node5 = new Node(5);
    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;
    node5.next = null;

    //测试代码
    node1.print();
    Node node = deleteLastTh(node1, 3);
    node.print();

输出:

1   2   3   4   5
1   2   4   5

练习5:求链表的中间结点

思路:
1、定义快慢指针同时遍历链表,快指针步长为2,慢指针步长为1
2、当快指针到达尾结点,慢指针正好在中心结点
时间复杂度:O(n)
空间复杂度:O(1)

public static Node findMiddleNode(Node list) {
    if (list == null) return null;
    Node slow = list;
    Node fast = list;
    while (fast.next != null && fast.next.next != null) {
        slow = slow.next;       // 步长1:慢指针
        fast = fast.next.next;  // 步长2:快指针
    }
    return slow;
}

测试:

    Node node1 = new Node(1);
    Node node2 = new Node(2);
    Node node3 = new Node(3);
    Node node4 = new Node(4);
    Node node5 = new Node(5);
    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;
    node5.next = null;

    node1.print();
    Node middleNode = findMiddleNode(node1);
    System.out.println(middleNode.data);

输出:

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

推荐阅读更多精彩内容