[图解算法]--链表--(持续更新)

258 阅读4分钟

我比较笨,记录一下心得,免得到时候忘记

反转链表 - LC206

反转一个单链表
示例
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

先上代码

private static ListNode reverseList_206(ListNode head) {
    ListNode pre = null;
    ListNode current = head;
    while (current != null) {
        //暂存
        ListNode temp = current.next;
        //改变指向
        current.next = pre;

        //重新赋值(移动指针位置)
        pre = current;
        current = temp;
    }

    return pre;
}

时间复杂度:O(n)
空间复杂度:O(1)

一开始我很纠结这两步,感觉这合并一下不就是current.next = current么,怎么看怎么别扭,后面我才恍然大悟,原来这两代码,执行的是两个不一样的逻辑。。。

current.next = pre;
pre = current;

分步来分析一下逻辑吧,免得下次再看的时候又看不明白了

首先我们初始化是如下状态 当我们执行current.next = pre c的next指向了pre,但是pre此时还是null
结合实际例子,那么就是 此时,1已经指向null了,就是当前节点来说已经完成了局部反转,即current指向了pre,下一步就是指针的移动了,pre往前移动一位(占据现在的current位置),而current也要往前移动一位

//重新赋值(移动指针位置)
pre = current;
current = temp;

之后再进行重复的局部反转操作 然后再是移位,直到最后c移动到了null的位置 这时候current != null就是false了,这时候我们返回pre,那么pre代表的链表就是反转之后的链表了。

链表中对环的检测 - LC141

快慢指针

用两个指针去遍历链表,一个slow指针一次走一步,一个fast指针一次走两步,如果两个指针相遇了,那么说明有环,如果fast指针遍历完了,那么说明没有环。

private static boolean hasCycleSlowAndFast_141(ListNode head) {
    if (head == null) {
        return false;
    }

    ListNode slow = head;
    ListNode fast = head.next;

    if (fast == null) {
        return false;
    }

	//不相等时进去判断
    while (slow != fast) {
    	//如果fast指针走完了,那么说明没有环
        if (fast == null || fast.next == null) {
            return false;
        }
        //走一步
        slow = slow.next;
        //走两步
        fast = fast.next.next;
    }
    //快慢指针相等了,说明有环
    return true;
}

时间复杂度:O(n)
空间复杂度:O(1)

哈希法

将每一个数据存入Hash表中,存入前判断是否已经存在

private static boolean hasCycleHashSet_141(ListNode head) {
    if (head == null) {
        return false;
    }
    Set<ListNode> set = new HashSet<>();
    while (head != null) {
        if (set.contains(head)) {
            return true;
        } else {
            set.add(head);
        }
        head = head.next;
    }
    return false;
}

时间复杂度:O(n)
空间复杂度:O(n) :取决于添加到哈希表中的元素数目,最多可以添加 n 个元素。

合并两个有序链表 - LC21

遍历

首先两个链表分别有各自的头结点h1,h2,首先对h1和h2进行大小比较,如果h1较小,那么h1作为新的头结点并且h1指针往后移动一位,之后再用后一位的节点与h2进行大小比较,小的放在新链表中,以此类推

private static ListNode mergeTwoLists_21(ListNode l1, ListNode l2) {
        //哨兵节点,方便链表的输出
        ListNode preHead = new ListNode(-1, null);

        ListNode pre = preHead;
        while (l1 != null && l2 != null) {
            if (l1.val > l2.val) {
                pre.next = l2;
                l2 = l2.next;
            } else {
                pre.next = l1;
                l1 = l1.next;
            }
            pre = pre.next;
        }

        //l1或者l2肯定有一个是null,那么另外那个就直接拼接到末尾即可
        pre.next = l1 == null ? l2 : l1;

		//返回哨兵节点的后一位节点就可以了
        return preHead.next;
    }

时间复杂度:O(n + m)
空间复杂度:O(1)

递归

递归这东西,用文字很难描述,先上代码,等会图解

private static ListNode mergeTwoLists2_21(ListNode l1, ListNode l2) {
    if (l1 == null) {
        return l2;
    } else if (l2 == null) {
        return l1;
    } else if (l1.val < l2.val) {
        l1.next = mergeTwoLists2_21(l1.next, l2);
        return l1;
    } else {
        l2.next = mergeTwoLists2_21(l1, l2.next);
        return l2;
    }
}

时间复杂度:O(n + m)
空间复杂度:O(n + m):其中 n 和 m 分别为两个链表的长度。递归调用 mergeTwoLists 函数时需要消耗栈空间,栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n+m 次,因此空间复杂度为 O(n+m)

我们假设有两个链表1->2->5->nulll3->4->6->null,让我们一步一步来看递归的时候发生了什么

删除链表的倒数第N个节点 - LC19

还是先上代码,然后上图解

private static ListNode removeNNode_19(ListNode head, int n) {
    //这里fast还是从head开始,并没有牵扯到哨兵节点
    ListNode fast = head;
    //fast节点先走N步
    for (int i = 0; i < n; i++) {
        fast = fast.next;
    }

	//创建一个哨兵节点,指向头节点
    ListNode newNode = new ListNode(-1, head);
	//slow节点等fast走完了再开始走
    ListNode slow = newNode;
    //fast如果还没走到链表末尾,那么就和slow一起走
    while (fast != null) {
        fast = fast.next;
        slow = slow.next;
    }

	//fast走到结尾了,那么slow就把原来的next指针指向下下个
    slow.next = slow.next.next;

	//通过哨兵节点返回真正的头结点
    return newNode.next;
}

时间复杂度:O(N)
空间复杂度:O(1)

看一下图解

链表的中间节点 -- LC876

返回链表的中间节点,这里我就只写快慢指针的方法了,比较简单

private static ListNode middleNode_876(ListNode head) {
    ListNode slow = head;
    ListNode fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }

    return slow;
}

时间复杂度:O(N)
空间复杂度:O(1)