Leetcode 算法之链表 —— Java 题解

216 阅读17分钟

链表是一种线性数据结构,其中每一个节点元素都是一个单独的对象,链表中的结点通过每个元素的引用字段连接起来。

在求解链表类型题目时,我们可以使用 dummy 虚拟头结点连接到链表头部,更方便操作。

在操作链表时,也可以尝试使用前驱结点、后继结点来进行链表的连接、断开、插入等操作。

通过以下 16 道算法题,理解数据结构链表吧。

206. 反转链表 - 简单

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例:

rev1ex1.jpg

输入:head = [1,2,3,4,5]

输出:[5,4,3,2,1]

题解:

需要注意链表指针的操作,可以使用迭代法或者递归法求解。

递归法:

  1. 递归反转传入的链表头结点 head 以及前驱结点 prev ,初始时,prev 为空

  2. 如果 head 为空,那么直接返回前驱结点

  3. 保存头结点的下一个结点 next 的指针

  4. head 连接到且前驱结点 prev 上,实现反转

  5. 递归,此时链表头结点为 next,前驱结点为 head

迭代法:

  1. 使用一个虚拟头结点 dummy 作为反转后的链表的头部
  2. 遍历链表
  3. 声明一个临时结点 s 指向当前遍历的结点 p
  4. 当前遍历结点 p 移动到下一个结点
  5. s 称为 p 结点的前驱结点,令 s 的下一个结点指向虚拟头结点的下一个结点
  6. 令虚拟头结点的下一个结点指向 s,实现反转

代码:

// 递归法
public ListNode reverseList(ListNode head){
    return reverseList(head, null);
}

private ListNode reverseList(ListNode head, ListNode prev) {
    if (head == null) {
        return prev;
    }
    ListNode next = head.next;
    head.next = prev;
    return reverseList(next, head);
}

// 迭代法
public ListNode reverseList(ListNode head){
    ListNode dummy = new ListNode(), p = head;

    while (p != null) {
        ListNode s = p;
        p = p.next;
        s.next = dummy.next;
        dummy.next = s;
    }
    return dummy.next;
}

92. 反转链表 II - 中等

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

示例:

rev2ex2-16633407592404.jpg

输入:head = [1,2,3,4,5], left = 2, right = 4

输出:[1,4,3,2,5]

题解:

拿到链表之后,我们先遍历链表,直到遍历到待翻转部分链表的前一个结点,即前驱结点 pre

随后使用一个指针 start 指向待翻转部分链表的头部

此后继续遍历链表,直到遍历到待翻转部分链表的最后一个结点,即 end

再使用一个指针指向待翻转部分链表的后一个结点,即后继结点succ

随后翻转链表,原先的头结点 start 变成尾结点,尾结点 end 变成头结点。拼接链表,让前驱结点拼接到 endstart 拼接上 succ

代码:

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(0, head);
        ListNode pre = dummy;
        // 让 pre 指向待翻转部分链表的前驱结点
        for (int i = 1; i < left; i++) {
            pre = pre.next;
        }
        // start 指向待翻转部分链表的头部,end 指向待翻转部分链表的尾部
        ListNode start = pre.next;
        ListNode end = start;
        for (int i = 1; i < right-left+1; i++) {
            end = end.next;
        }
        // 让 succ 执行待翻转部分链表的后继结点
        ListNode succ = end.next;
        // 切断链表
        end.next = null;
        // 翻转链表,随后拼接链表
        pre.next = reverse(start);
        start.next = succ;
        return dummy.next;
    }

    private ListNode reverse(ListNode head) {
        ListNode dummy = new ListNode();
        while (head != null) {
            ListNode next = head;
            head = head.next;
            next.next = dummy.next;
            dummy.next = next;
        }
        return dummy.next;
    }
}

21. 合并两个有序链表 - 简单

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

merge_ex1.jpg

输入:l1 = [1,2,4], l2 = [1,3,4]

输出:[1,1,2,3,4,4]

题解:

声明一个虚拟头结点 dummy 指向合并后的指针的头部,并维护一个指针 p 总是指向合并链表的尾部。

比较两个链表的当前所指向结点的值,令指针 p 指向值较小的结点,并且值较小的节点的指针移动到下一个结点。

代码:

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    ListNode dummy = new ListNode(), p = dummy;
    while (list1 != null && list2 != null) {
        if (list1.val <= list2.val) {
            p.next = list1;
            list1 = list1.next;
        }else {
            p.next = list2;
            list2 = list2.next;
        }
        p = p.next;
    }
    p.next = list1 == null ? list2 : list1;
    return dummy.next;
}

23. 合并K个升序链表 - 困难

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例:

输入:lists = [[1,4,5],[1,3,4],[2,6]]

输出:[1,1,2,3,4,4,5,6]

解释:链表数组如下: [1->4->5,1->3->4,2->6] 将它们合并到一个有序链表中得到。

1->1->2->3->4->4->5->6

题解:

使用分治法合并链表数组中的链表。

将数组分割成两个子集,然后各自合并子集中的链表,最后只会得到两个链表,合并这两个链表。

  1. 对半分割数组,得到两个新的数组,继续分割这两个数组
  2. 分割到最后,子数组只会有一个元素,即只有单个链表,合并相邻的两个子数组,即合并两个链表,得到一个合并后的链表
  3. 其余各个子数组也合并完成,两两合并这些子链表,最终得到一个升序的链表

代码:

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return merge(lists, 0, lists.length - 1);
    }
	
    private ListNode merge(ListNode[] lists, int l, int r) {
        // 只有单个元素,返回这个链表
        if (l == r) {
            return lists[l];
        }
        if (l > r) {
            return null;
        }
        // 分割子集
        int mid = (r - l) / 2 + l;
        // 合并两个链表,递归分割子集,最终分割到只有一个元素,再结束递归,逐渐合并成单条链表
        return mergeTowList(merge(lists, l, mid), merge(lists, mid + 1, r));
    }

    // 合并两个升序链表
    private ListNode mergeTowList(ListNode a, ListNode b) {
        ListNode dummy = new ListNode();
        ListNode p = dummy;

        while (a != null && b != null) {
            if (a.val <= b.val) {
                p.next = a;
                a = a.next;
            } else {
                p.next = b;
                b = b.next;
            }
            p = p.next;
        }

        p.next = a == null ? b : a;
        return dummy.next;
    }
}

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例:

swap_ex1.jpg

输入:head = [1,2,3,4]

输出:[2,1,4,3]

题解:

创建一个虚拟头结点dummy,连接到链表头部。

再创建一个临时结点 temp,初始指向 dummy

我们交换链表中的相邻两个结点的条件是,temp 当前指向的结点后面要有两个结点,即 temp.next != null && temp.next.next != null

如果后面有两个结点,那么就交换接下来的两个结点 n1n2

temp.next = n2
n1.next = n2.next;
n2.next = n1;

此后令 temp 指向 n1,继续执行以上步骤

代码:

public ListNode swapPairs(ListNode head) {
    ListNode dummy = new ListNode(-1, head);
    ListNode temp = dummy;
    while (temp.next != null && temp.next.next != null) {
        ListNode node1 = temp.next;
        ListNode node2 = temp.next.next;
        temp.next = node2;
        node1.next = node2.next;
        node2.next = node1;
        temp = node1;
    }
    return dummy.next;
}

25. K 个一组翻转链表 - 困难

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例:

reverse_ex1.jpg

输入:head = [1,2,3,4,5], k = 2

输出:[2,1,4,3,5]

题解:

把链表分为三部分:

已翻转 -> 待翻转 -> 未翻转
  1. 声明一个虚拟头结点 dummy,连接链表 head
  2. 声明两个辅助指针,待翻转部分的前驱结点pre,待翻转部分的末尾 end,初始时preend 都指向 dummy
  3. 循环 k 次,确定待翻转部分的尾部节点,即 end
  4. 如果 end 指向空,那么即待翻转部分不够 k 个,直接让已翻转部分拼接待翻转部分即可
  5. 否则,声明一个辅助结点 next,指向未翻转部分的头部
  6. 声明一个辅助结点 start,指向待翻转部分的头部
  7. 翻转待翻转部分链表,让前驱结点的 next 指向翻转后的链表的头部;此前未翻转时的头部结点已经成为尾部,可以作为未翻转部分的前驱结点

代码:

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(-1, head);
        ListNode pre = dummy, end = dummy;

        while (end.next != null) {
            for (int i = 0; i < k && end != null; i++) {
                end = end.next;
            }
            if (end == null) {
                break;
            }
            ListNode start = pre.next;
            ListNode next = end.next;
            end.next = null;
            pre.next = reverse(start);
            start.next = next;
            pre = start;
            end = start;
        }

        return dummy.next;
    }
    
    private ListNode reverse(ListNode head) {
        ListNode dummy = new ListNode(-1, head);
        while (head != null) {
            ListNode s = head;
            head = head.next;
            s.next = dummy.next;
            dummy.next = s;
        }
        return dummy.next;
    }

}

160. 相交链表 - 简单

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

160_statement.png

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3

输出:Intersected at '8'

解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。

从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。

在 A 中,相交节点前有 2 个节点;

在 B 中,相交节点前有 3 个节点。

— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。

换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。

题解:

对于如下链表

image-20220627145116391.png

绿色部分长度为 a,红色部分长度为 b,蓝色相交部分长度为 c

维护两个指针 n1, n2 分别指向链表 A 和 B,遍历链表,当其中一个指针为 null 时,将其指向另外一条链表,继续遍历,如果指针遍历到的结点相同,即为交点。若同时指向 null,说明没有交点。

证明,n1 第一遍历到 null 时,走过的路程为 a+c; 同理,n2 走过的路程为 b+c

重新指向另外一条链表后,有交点的情况下,直到两个结点指向相同时,n1 走过的路程为 b; n2 走过的路程为 a。总共走过的路程为 a+c+b = b+c+a,因此指向的点可以证明为交点。

在没有交点的情况下,两者走过的总路程为 a+b = b+a,最后都同时指向了 null

代码:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode n1 = headA, n2 = headB;
            while (n1 != n2) {
                n1 = n1 == null ? headB : n1.next;
                n2 = n2 == null ? headA : n2.next;
            }
            return n1;
    }
}

234. 回文链表 - 简单

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

示例:

pal1linked-list.jpg

输入:head = [1,2,2,1]

输出:true

题解:

利用快慢指针确定链表的中点,然后从中点处反转链表,得到链表 reverse

随后逐个比较 head 链表和 reverse 链表的值,如果发现不相等,那么不是回文链表,返回 false;如果能够遍历完,那么是回文链表,返回 true

代码:

class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode fast = head, slow = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        fast = reverse(slow);
        slow = head;
        while (fast != null) {
            if (slow.val != fast.val) {
                return false;
            }
            slow = slow.next;
            fast = fast.next;
        }
        return true;
    }

    private ListNode reverse(ListNode head) {
        ListNode dummy = new ListNode(-1);
        while (head != null) {
            ListNode s = head;
            head = head.next;
            s.next = dummy.next;
            dummy.next = s;
        }
        return dummy.next;
    }
}

83. 删除排序链表中的重复元素 - 简单

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

示例:

list1.jpg

输入:head = [1,1,2]

输出:[1,2]

题解:

  1. 使用一个指针 cur 维护当前遍历的链表结点,初始指向 head
  2. 循环遍历链表,遍历条件为 cur != null && cur.next != null
  3. 如果当前结点 cur 与其下一个结点的值相等,那么令 cur 指向其下一个的下一个结点
  4. 如果值不相等,那么令 cur 指向下一个结点

代码:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode cur = head;
        while (cur != null && cur.next != null) {
            if (cur.val == cur.next.val) {
                cur.next = cur.next.next;
            } else {
                cur = cur.next;
            }
        }
        return head;
    }
}

82. 删除排序链表中的重复元素 II - 中等

给定一个已排序的链表的头 head删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表

示例:

linkedlist1.jpg

输入:head = [1,2,3,3,4,4,5]

输出:[1,2,5]

题解:

  1. 有可能头结点就是重复的元素,为了方便删除节点,声明一个虚拟头结点 dummy 连接头结点
  2. 声明一个指针 cur 表示当前遍历结点
  3. 如果当前遍历结点的下两个元素相等,即 cur.next.val == cur.next.next.val,那么不断的删除这些重复的结点:
    1. 使用一个变量 x 保存重复结点的值,即 x = cur.next.val
    2. 如果当前遍历结点 cur 的下一个结点的值等于 x,即 cur.next.val == x,那么删除掉这个节点,即令 cur.next = cur.next.next
    3. 不断重复过程 3.2,需要注意可能 cur.next 可能为空
  4. 如果当前遍历结点的下两个元素不相等,那么让 cur 指向下一个结点

代码:

public ListNode deleteDuplicates(ListNode head) {
    // 虚节点连接 head
    ListNode dummy = new ListNode(-1, head);
    ListNode cur = dummy;
    while (cur.next != null && cur.next.next != null) {
        if (cur.next.val == cur.next.next.val) {
            int x = cur.next.val;
            while (cur.next != null && cur.next.val == x) {
                cur.next = cur.next.next;
            }
        } else {
            cur = cur.next;
        }
    }
    return dummy.next;
}

328. 奇偶链表 - 中等

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。

请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。

你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。

示例:

oddeven-linked-list.jpg

输入: head = [1,2,3,4,5]

输出: [1,3,5,2,4]

题解:

声明三个指针:

  1. odd 负责遍历链表中的奇数结点
  2. even 负责遍历链表中的偶数结点
  3. evenHead 指向第一个偶数结点,方便后续“奇数链表”连接“偶数链表”

开始连接“奇数链表”和“偶数链表”

  1. 循环遍历链表,遍历条件是,偶数结点不为空并且其下一个结点也非空,这样才有必要让奇数结点连接下一个奇数结点。
  2. 将奇数结点连接下一个奇数结点,即 odd.next = even.next
  3. 令奇数结点指向下一个结点,即 odd = odd.next
  4. 令偶数结点连接下一个偶数结点,即 even.next = odd.next
  5. 令偶数结点指向下一个结点,即 even = even.next
  6. 循环结束后,即“奇数链表”和“偶数链表”生成完毕,连接这两个链表
  7. 返回链表

代码:

public ListNode oddEvenList(ListNode head) {
    if (head == null) {
        return null;
    }
    ListNode odd = head, evenHead = head.next, even = evenHead;
    while (even != null && even.next != null) {
        odd.next = even.next;
        odd = odd.next;
        even.next = odd.next;
        even = even.next;
    }
    odd.next = evenHead;
    return head;
}

725. 分隔链表 - 中等

给你一个头结点为 head 的单链表和一个整数 k ,请你设计一个算法将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。

这 k 个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。

返回一个由上述 k 部分组成的数组。

示例:

split1-lc.jpg

输入:head = [1,2,3], k = 5

输出:[[1],[2],[3],[],[]]

解释: 第一个元素 output[0] 为 output[0].val = 1 ,output[0].next = null 。 最后一个元素 output[4] 为 null ,但它作为 ListNode 的字符串表示是 [] 。

题解:

  1. 首先确定链表的长度 n
  2. 使用一个 ListNode[] res 数组存放结果
  3. 随后确定分割后的每一部分的长度,每一部分的长度至少为 n/k,前面的 n%k 部分长度加一,即 n/k+1
  4. 开始遍历链表,分割链表,分割 k 次,使用指针 cur 遍历链表
  5. 当前分割部分的链表长度 partSizen/k,如果是前 n%k 部分,那么其长度加一
  6. res 对应位置存放当前分割部分链表的头部
  7. 遍历链表到 partSize 处,断开与后面结点的连接,同时令 cur 指向下一个结点,继续分割

代码:

public ListNode[] splitListToParts(ListNode head, int k) {
    // 结果集
    ListNode[] res = new ListNode[k];
	// 确定链表长度
    int n = 0;
    ListNode cur = head;
    while (cur != null) {
        n++;
        cur = cur.next;
    }
	
    // 确定每个分割链表的长度
    int quotient = n / k, remainder = n % k;

    int idx = 0;
    cur = head;
    for (int i = 0; i < k && cur != null; i++) {
        
        int partSize = quotient + (idx < remainder ? 1 : 0);
        res[idx++] = cur;
        for (int j = 1; j < partSize; j++) {
            cur = cur.next;
        }
        // 切断链表
        ListNode next = cur.next;
        cur.next = null;
        cur = next;

    }

    return res;
}

61. 旋转链表 - 中等

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例:

rotate1.jpg

输入:head = [1,2,3,4,5], k = 2

输出:[4,5,1,2,3]

题解:

  1. 将链表转换为循环链表,同时确定原链表的长度 len
  2. 旋转链表,每个结点向右移动 k 个位置,因此旋转后,新的头结点 head 为原链表的倒数第 len-(k%len) 个结点
  3. 确定了新的头结点后,断开循环链表,已知链表的长度,从头结点移动 len-1 次,到达链表尾部,断开循环链表

代码:

public ListNode rotateRight(ListNode head, int k) {
    if (head == null || head.next == null || k == 0) {
        return head;
    }
    int len = 0;
    ListNode cur = head;
    // 变成循环链表
    while (cur.next != null) {
        len++;
        cur = cur.next;
    }
    len++;
    cur.next = head;
    cur = head;
    // 头结点变成倒数第 K 个
    for (int i = 0; i < len - (k % len); i++) {
        cur = cur.next;
    }
    head = cur;
    // 移动长度 len,断开链表
    for (int i = 1; i < len; i++) {
        cur = cur.next;
    }
    cur.next = null;
    return head;
}

19. 删除链表的倒数第 N 个结点 - 中等

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例:

remove_ex1.jpg

输入:head = [1,2,3,4,5], n = 2

输出:[1,2,3,5]

题解:

使用快慢指针,我们定义两个指针 firstsecondfirst 总是领先 second n 个结点,因此当 first 遍历到链表末尾 null 时,second 刚好遍历到链表倒数第 n 个结点。

我们使用一个虚拟头结点 dummy 连接到链表上,让 seconddummy 处开始遍历,那么当 first 遍历到末尾时,second 刚好遍历到倒数第 n-1 个结点,更方便修改链表。

代码:

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(-1, head);
    ListNode first = head, second = dummy;
    for (int i = 0; i < n; i++) {
        first = first.next;
    }
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    second.next = second.next.next;
    return dummy.next;
}

148. 排序链表 - 中等

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例:

sort_list_1.jpg

输入:head = [4,2,1,3]

输出:[1,2,3,4]

题解:

归并排序。

使用快慢指针确定链表的中点,随后以中点为界,继续分割前半部分和后半部分,如此往复,直到分割到只剩下一个结点,将相邻两个结点合并,层层往上,最后合并为一个有序链表。

具体思路为:

  1. 我们拿到一个链表,确定它的头部 head 和尾部 tail,初始时 tail 为 null,是一个完整的链表,此后分割的过程中,tail 变成链表中的某个结点
  2. 如果 head 是一个空节点,直接返回,不用排序
  3. 如果 head 的下一个结点是 tail,那么断开他们的连接,并返回 head。此时链表已经分割到只剩下单个节点,可以开始合并
  4. 不是上面 2, 3 的情况,那么使用快慢指针确定链表的中点,以中点为界,继续分割视频
  5. fast 总是移动两次, slow 总是移动一次,直到 fast 指向 tail,此时 slow 就是待分割链表的中点,继续分割链表
  6. 分割链表的前半部分 l1 和分割链表的后半部分 l2 升序排序,第一次合并时,两个链表都是单个节点,必定升序,此后每次合并,都是两个升序链表,可以很方便的合并
  7. 将合并后的链表返回给上一层,继续合并

代码:

class Solution {
    public ListNode sortList(ListNode head) {
        return sortList(head, null);
    }

    private ListNode sortList(ListNode head, ListNode tail) {
        if (head == null) {
            return head;
        }
        if (head.next == tail) {
            head.next = null;
            return head;
        }
		
        // 确定中点
        ListNode fast = head, slow = head;
        while (fast != tail && fast.next != tail) {
            slow = slow.next;
            fast = fast.next.next;
        }
		// 继续分割列表
        ListNode l1 = sortList(head, slow);
        ListNode l2 = sortList(slow, tail);
        // 合并链表,层层往上
        return merge(l1, l2);
    }

    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode();
        ListNode cur = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = l1 == null ? l2 : l1;
        return dummy.next;
    }

}

147. 对链表进行插入排序 - 中等

给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。

插入排序 算法的步骤:

插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。 重复直到所有输入数据插入完为止。 下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。

对链表进行插入排序。

Insertion-sort-example-300px.gif

示例:

sort1linked-list.jpg

输入: head = [4,2,1,3]

输出: [1,2,3,4]

题解:

插入排序的基本思路为,将输入数据中的第一个元素划分到有序列表中并删除,此后不断从输入数据中取值并插入到有序列表中的合适位置。

使用一个虚拟头结点 dummy 连接 head。

使用一个 last 指针,指向有序部分中的最后一个元素,使用 cur 指针指向无序列表中的第一个元素

  1. 如果 last 的值小于 cur 的值,那么直接令 last 连接的 cur
  2. 否则,寻找合适的位置插入 cur 由于是单向链表,我们不能像数组一样从有序列表的最后一个元素往回查找,因此声明一个前驱结点 prev 从头开始遍历,直到 prev 的下一个结点的值比 cur 的大,插入 curprev 之后。同时,有序列表的最后一个元素连接到无序列表的第一个元素。
last.next = cur.next;
cur.next = prev.next;
prev.next = cur;
  1. 插入成功后,cur 指向下一个待插入元素,即 cur = last.next

代码:

public ListNode insertionSortList(ListNode head) {
    ListNode dummy = new ListNode(-1, head);
    ListNode last = head, cur = head.next;
    while (cur != null) {
        if (last.val <= cur.val) {
            last = last.next;
        } else {
            ListNode prev = dummy;
            while (prev.next.val <= cur.val) {
                prev = prev.next;
            }
            last.next = cur.next;
            cur.next = prev.next;
            prev.next = cur;
        }
        cur = last.next;
    }

    return dummy.next;
}