目录
一 虚拟头结点
对于链表题目,我们需要区分head节点是第一个有效节点还是一个虚拟头节点(即不存放任何值,仅仅用来指向第一个有效节点),对于leetcode中关于链表的题目,head头结点指的就是第一个有效节点,并不是虚拟头节点;通常在解题中,为了方便链表的一些操作,我们会给链表设置一个虚拟头结点,但是最终返回链表的时候记得返回虚拟头结点的下一个节点
1. leetcode 203.移除链表元素
leetcode 203.移除链表元素
题目要求:
删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
public class 移除链表元素 {
/**
* 方法一:直接在原来的链表进行删除
* 由于单链表的局限性,删除一个节点需要找到它的前一个节点
* 那么如果待删除节点是第一个节点head,没有前一个节点,这种情况就得单独处理
* @param head
* @param val
* @return
*/
public ListNode removeElements1(ListNode head, int val) {
if (head == null) {
return null;
}
// 特殊情况:链表的头结点值等于val,那么直接将head指针往后移
while (head != null && head.val == val) {
head = head.next;
}
ListNode cur = head;
while (cur != null && cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
/**
* 方法二:使用虚拟头结点
* @param head
* @param val
* @return
*/
public ListNode removeElements2(ListNode head, int val) {
if (head == null) {
return null;
}
// 创建一个节点,用来充当虚拟头结点
ListNode node = new ListNode(0);
node.next = head;
ListNode cur = node;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
// 注意这里需要返回虚拟头结点的下一个节点
return node.next;
}
}
二 快慢指针
1. leetcode 876.链表的中间节点
leetcode 876.链表的中间节点
题目描述:
给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
示例1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL
示例2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
public class 链表的中间节点 {
/**
* 使用快慢指针,注意循环结束的条件
* 注意:如果题目是要求当链表的节点数为偶数,返回第一个中间节点,那么循环结束的条件应该改成快指针的下一节点和下下节点都不为空
* @param head
* @return
*/
public ListNode middleNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
2. leetcode 141.环形链表
leetcode 141.环形链表
题目描述:
给定一个链表,判断链表中是否有环。
/**
* 判断链表是否有环,可以使用快慢指针法,快指针一次走两步,慢指针一次走一步,如果最终快慢指针相遇,说明有环,如果快指针为null,说明无环
* 只要存在环,那么快慢指针一定会相遇,因为相对于快指针来说,慢指针是每次一步一步的去靠近快指针,那么迟早会相遇
* 而如果快指针每次不是走两步,而是三步或者四步,那么快慢指针就有可能错过,不会相遇
*/
public class 环形链表 {
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
// 定义快慢指针
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
}
补充:如果题目要求是判断链表是否有环,如果有环,返回环的入口,那么解题思路应该为:先用快慢指针判断链表是否有环,如果有,则让慢指针回到第一个节点(有效节点),快指针保持在相遇位置,然后快慢指针各走一步,当再次相遇的时候,说明到了环的入口
三 反转链表
1. 剑指 Offer 24.反转链表
剑指 Offer 24. 反转链表
题目描述:
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
public class 反转链表 {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = null; // 指向当前节点的前一个节点
ListNode cur = head; // 指向当前节点
ListNode next = null; // 指向当前节点的后一个节点
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
2. leetcode 92.反转链表II
leetcode 92.反转链表II
题目描述:
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
public class 反转链表II {
/**
* 思路:把链表分成三条(前部分链表,反转链表和后部分链表)
* 找到前部分链表的尾节点(第m-1个节点)就可以开始反转,最后把三个链表拼接
* 前部分链表(1~m-1)
* 反转链表(m~n)
* 后部分链表(n~最后)
*
* 由于1<=m<=n<=链表长度
* 所以应该注意m=1和n=链表长度这两种特殊情况
*
* @param head
* @param m
* @param n
* @return
*/
public ListNode reverseBetween1(ListNode head, int m, int n) {
if (head == null || head.next == null) {
return head;
}
// 注意:m=1和m!=1要区分处理
if (m == 1) {
// 定位到第n个节点
ListNode node = head;
for (int i = m; i < n; i++) {
node = node.next;
}
// 保存第n+1个节点
ListNode tail = node.next;
node.next = null;
// 对m-n部分节点进行反转
ListNode reverse = reverse(head);
// 遍历到反转后的链表的最后一个节点(后面需要用到)
ListNode end = reverse;
while (end.next != null) {
end = end.next;
}
// 让反转后的链表的最后一个节点指向第n+1个节点
end.next = tail;
return head;
}
// 首先定位到第m-1个节点
ListNode cur = head;
for (int i = 1; i < m - 1; i++) {
cur = cur.next;
}
// 保存第m个节点
ListNode temp = cur.next;
cur.next = null;
// 定位到第n个节点
ListNode node = temp;
for (int i = m; i < n; i++) {
node = node.next;
}
// 保存第n+1个节点
ListNode tail = node.next;
node.next = null;
// 对m-n部分节点进行反转
ListNode reverse = reverse(temp);
// 遍历到反转后的链表的最后一个节点(后面需要用到)
ListNode end = reverse;
while (end.next != null) {
end = end.next;
}
// 让第m-1个节点指向反转后的链表
cur.next = reverse;
// 让反转后的链表的最后一个节点指向第n+1个节点
end.next = tail;
return head;
}
/**
* 使用虚拟头结点:好处是不需要对m=1和m!=1区分讨论
* @param head
* @param m
* @param n
* @return
*/
public ListNode reverseBetween2(ListNode head, int m, int n) {
if (head == null || head.next == null) {
return head;
}
// 定义一个虚拟头结点
ListNode newNode = new ListNode(0);
newNode.next = head;
// 首先定位到第m-1个节点
ListNode cur = newNode;
for (int i = 1; i < m - 1; i++) {
cur = cur.next;
}
// 保存第m个节点
ListNode temp = cur.next;
cur.next = null;
// 定位到第n个节点
ListNode node = temp;
for (int i = m; i < n; i++) {
node = node.next;
}
// 保存第n+1个节点
ListNode tail = node.next;
node.next = null;
// 对m-n部分节点进行反转
ListNode reverse = reverse(temp);
// 遍历到反转后的链表的最后一个节点(后面需要用到)
ListNode end = reverse;
while (end.next != null) {
end = end.next;
}
// 让第m-1个节点指向反转后的链表
cur.next = reverse;
// 让反转后的链表的最后一个节点指向第n+1个节点
end.next = tail;
return newNode.next;
}
/**
* 反转链表
* @param head
* @return
*/
public ListNode reverse(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = null;
ListNode cur = head;
ListNode next = null;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
3. leetcode 25.K 个一组翻转链表
leetcode 25.K 个一组翻转链表
题目描述:
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
public class k个一组翻转链表 {
/**
* 使用递归解法
* 思路:先对链表的前k个节点进行反转,然后将反转后链表的最后一个节点指向k个节点之后的剩余节点(这部分节点直接使用递归反转)
* @param head
* @param k
* @return
*/
public ListNode reverseKGroup(ListNode head, int k) {
// 递归的出口,如果链表为空或者只有一个节点,无需反转
if (head == null || head.next == null) {
return head;
}
// 先对链表的前k个节点进行反转(由于是递归处理,因此这里需要考虑链表的节点数小于k的情况)
// 1.定位到链表的第k个节点
ListNode start = head;
ListNode end = head;
for (int i = 0; i < k - 1 && end != null; i++) {
end = end.next;
}
// 退出循环,如果end为null,说明链表的节点数小于k,无需反转
if (end == null) {
return head;
}
// 2.保存第k个节点的后一个节点,防止链表断联
ListNode temp = end.next;
end.next = null;
// 3.对k个节点进行反转操作
ListNode reverse = reverse(start);
// 4.对于已经反转好的k个节点,遍历到最后一个节点
ListNode node = reverse;
while (node.next != null) {
node = node.next;
}
// 将反转后链表的最后一个节点指向k个节点之后的剩余节点(这部分节点直接使用递归反转)
node.next = reverseKGroup(temp, k);
return reverse;
}
/**
* 反转链表
* @param head
* @return
*/
public ListNode reverse(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = null;
ListNode cur = head;
ListNode next = null;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
四 合并链表
1. leetcode 21.合并两个有序链表
leetcode 21.合并两个有序链表
题目描述:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例2:
输入:l1 = [], l2 = []
输出:[]
public class 合并两个有序链表 {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode head = new ListNode(0);
ListNode cur = head;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
cur = cur.next;
} else {
cur.next = l2;
l2 = l2.next;
cur = cur.next;
}
}
if (l1 != null) {
cur.next = l1;
}
if (l2 != null) {
cur.next = l2;
}
return head.next;
}
/**
* 递归法
* @param l1
* @param l2
* @return
*/
public ListNode mergeTwoLists2(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
if (l1.val < l2.val) {
l1.next = mergeTwoLists2(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists2(l1, l2.next);
return l2;
}
}
}
2. leetcode 23.合并K个升序链表
leetcode 23.合并K个升序链表
题目描述:
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例1:
输入: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
示例2:
输入:lists = []
输出:[]
示例3:
输入:lists = [[]]
输出:[]
public class 合并K个升序链表 {
/**
* 方法一:借助优先队列实现堆排序
* 思路:维护一个根据链表的第一个节点的值进行排序的小根堆,首先将所有链表入堆,然后依次弹出堆中的堆顶元素
* 由于是小根堆,所以每次出堆的都是所有链表中第一个节点值最小的那个链表
* 将出堆的这个链表的第一个节点添加到结果链表中,并将该链表以第二个节点作为头结点的形式入堆,继续维护一个小根堆
* @param lists
* @return
*/
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) {
return null;
}
// 创建一个大小为k的小根堆
PriorityQueue<ListNode> queue = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
// 所有链表入堆
for (ListNode listNode : lists) {
if (listNode == null) {
continue;
}
queue.offer(listNode);
}
// 创建一个带头结点的结果链表
ListNode head = new ListNode(0);
ListNode cur = head;
while (!queue.isEmpty()) {
// 堆顶元素弹出
ListNode node = queue.poll();
// 如果堆顶元素指针域不为空,那么后半部分链表继续入堆
ListNode next = node.next;
node.next = null;
if (next != null) {
queue.offer(next);
}
cur.next = node;
cur = cur.next;
}
return head.next;
}
}
五 链表求和
1. leetcode 2.两数相加
leetcode 2.两数相加
题目描述:
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807
示例2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
public class 两数相加 {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 创建新链表的虚拟头结点
ListNode head = new ListNode(0);
// 辅助节点来定位新链表
ListNode cur = head;
// 辅助变量代表是否向上一位进1
int count = 0;
// 循环判断的条件不要忘了加上 count!=0 这个条件
while (l1 != null || l2 != null || count != 0) {
int val1 = l1 != null ? l1.val : 0;
int val2 = l2 != null ? l2.val : 0;
int sum = val1 + val2 + count;
count = sum / 10;
ListNode node = new ListNode(sum % 10);
cur.next = node;
cur = cur.next;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
// 注意返回的是第一个节点,而不是虚拟头结点
return head.next;
}
}
2. leetcode 445.两数相加 II
leetcode 445.两数相加 II
题目描述:
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7
public class 两数相加II {
/**
* 方法一:先对链表进行翻转,最终将结果链表再翻转一次
* @param l1
* @param l2
* @return
*/
public ListNode addTwoNumbers1(ListNode l1, ListNode l2) {
// 先对链表l1和l2进行反转
ListNode r1 = reverse(l1);
ListNode r2 = reverse(l2);
// 对反转后的链表进行计算
// 1.创建新链表的虚拟头结点
ListNode head = new ListNode(0);
// 2.辅助节点来定位新链表
ListNode cur = head;
// 3.辅助变量代表是否向上一位进1
int count = 0;
// 4.循环判断的条件不要忘了加上 count!=0 这个条件
while (r1 != null || r2 != null || count != 0) {
int val1 = r1 != null ? r1.val : 0;
int val2 = r2 != null ? r2.val : 0;
int sum = val1 + val2 + count;
count = sum / 10;
ListNode node = new ListNode(sum % 10);
cur.next = node;
cur = cur.next;
if (r1 != null) {
r1 = r1.next;
}
if (r2 != null) {
r2 = r2.next;
}
}
// 对结果链表进行反转一次得到最终结果
return reverse(head.next);
}
/**
* 反转链表
* @param head
* @return
*/
public ListNode reverse(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = null;
ListNode cur = head;
ListNode next = null;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
/**
* 方法二:使用栈
* 思路:由于我们希望对链表的节点进行逆序操作,这时候可以想到栈
* 将两个链表的所有节点先依次放到两个栈中,最后再分别弹出进行计算
* 存放结果的链表再使用头插法插入节点
* @param l1
* @param l2
* @return
*/
public ListNode addTwoNumbers2(ListNode l1, ListNode l2) {
// 创建两个栈
LinkedList<Integer> stack1 = new LinkedList();
LinkedList<Integer> stack2 = new LinkedList();
// 依次将两个链表的节点值push到栈中
while (l1 != null) {
stack1.push(l1.val);
l1 = l1.next;
}
while (l2 != null) {
stack2.push(l2.val);
l2 = l2.next;
}
// 创建一个带有头结点的链表,用来存放结果值
ListNode head = new ListNode(0);
// 辅助变量代表是否向上一位进1
int count = 0;
// 依次弹出两个栈中的元素进行计算
while (!stack1.isEmpty() || !stack2.isEmpty() || count != 0) {
int val1 = stack1.size() > 0 ? stack1.pop() : 0;
int val2 = stack2.size() > 0 ? stack2.pop() : 0;
int sum = val1 + val2 + count;
count = sum / 10;
ListNode node = new ListNode(sum % 10);
// 使用头插法插入链表
node.next = head.next;
head.next = node;
}
return head.next;
}
}
六 排序链表
1. leetcode 148.排序链表
leetcode 148.排序链表
题目描述:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例3:
输入:head = []
输出:[]
public class 排序链表 {
/**
* 方法一:堆排序
* 思路:维护一个根据链表的节点值进行排序的小根堆,遍历链表中的每个节点,让每个节点一次入堆
* (注意:入堆的节点应该将其指针域设置为空,让该节点成为一个独立的节点方可入堆,否则会导致整条链表入堆,产生死循环)
* 待所有节点入堆完毕之后,再依次弹出堆中的每个节点插入到新链表的尾部即可,由于是小根堆,因此会根据节点值从小到大出堆
* @param head
* @return
*/
public ListNode sortList1(ListNode head) {
if (head == null) {
return null;
}
// 创建小根堆
PriorityQueue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode l1, ListNode l2) {
return l1.val - l2.val;
}
});
// cur节点用来遍历链表
ListNode cur = head;
// temp节点用来保存当前节点的下一节点,防止链表断联,因为要将当前节点的指针域置空
ListNode temp = null;
// 链表中所有节点依次入堆
while (cur != null) {
temp = cur.next;
cur.next = null;
queue.offer(cur);
cur = temp;
}
// 创建一个带有虚拟头结点的链表
ListNode newNode = new ListNode(0);
cur = newNode;
// 依次出堆,使用尾插法插入链表的尾部
while (!queue.isEmpty()) {
cur.next = queue.poll();
cur = cur.next;
}
return newNode.next;
}
/**
* 方法二:归并排序/自顶向下的归并排序(使用递归实现,时间复杂度是O(nlogn),空间复杂度不为O(1))
* 思路:归并排序使用分治思想,先将链表分成两半,分别进行排序,再将排好序的两个有序链表进行合并
* @param head
* @return
*/
public ListNode sortList2(ListNode head) {
// 递归终止的条件
if (head == null || head.next == null) {
return head;
}
// 使用快慢指针法定位到链表的中间节点(如果链表节点数是偶数,返回第一个中间节点)
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
// 退出循环时,slow节点为中间节点
// 用一个辅助节点来保存中间节点的下一个节点
ListNode temp = slow.next;
// 将链表从中间节点断开成两部分
slow.next = null;
// 递归对中间节点左右两部分链表继续进行拆分
ListNode l1 = sortList2(head);
ListNode l2 = sortList2(temp);
// 合并有序链表
ListNode res = mergeTwoLists(l1, l2);
return res;
}
/**
* 方法三:归并排序/自底向上的归并排序(使用循环实现,时间复杂度是O(nlogn),空间复杂度为O(1))
* 思路:
* @param head
* @return
*/
public ListNode sortList3(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 计算链表的长度
ListNode node = head;
int len = 0;
while (node != null) {
len++;
node = node.next;
}
// 创建一个虚拟头结点方便操作
ListNode newNode = new ListNode(0);
newNode.next = head;
// 第一次循环步长为1、第二次为2、第三次为4……直至步长大于等于链表长度退出循环
for (int step = 1; step < len; step *= 2) {
// 辅助节点cur用来遍历链表,辅助节点pre用来指向每次排好序的子链表
// 每次循环开始,cur和pre都得回到链表头
// 注意:这里cur必须是指向虚拟头结点的下一个节点,不能指向head,因为head指向的是初始链表的第一个节点
// 排序之后链表的第一个节点可能发生改变了,也就是head不一定是再指向链表第一个节点了
ListNode cur = newNode.next;
ListNode pre = newNode;
while (cur != null) {
ListNode node1 = cur; // 第一部分头结点
ListNode node2 = split(node1, step); // 第二部分头结点
cur = split(node2, step); // 更新cur
ListNode listNode = mergeTwoLists(node1, node2); // 对两部分有序链表进行合并,并返回头结点
pre.next = listNode; // pre的指针域指向排好序的子链表
// pre节点移动到排好序的子链表最后一个节点
while (pre.next != null) {
pre = pre.next;
}
}
}
return newNode.next;
}
/**
* 链表断链操作:并返回断链后第二部分链表头
* @param head
* @param step:步长(可以理解成断开的第一部分链表节点数)
* @return
*/
public ListNode split(ListNode head,int step){
if (head == null) {
return null;
}
ListNode cur = head;
// 不要忘记步长大于链表节点数的情况
for (int i = 1; i < step && cur.next != null; i++) {
cur = cur.next;
}
ListNode temp = cur.next;
cur.next = null; // 切断连接
return temp;
}
/**
* 合并两个有序链表
* @param l1
* @param l2
* @return
*/
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode head = new ListNode(0);
ListNode cur = head;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
cur = cur.next;
} else {
cur.next = l2;
l2 = l2.next;
cur = cur.next;
}
}
if (l1 != null) {
cur.next = l1;
}
if (l2 != null) {
cur.next = l2;
}
return head.next;
}
}