本篇主要为leetcode上链表部分中等及困难难度练习题,链表部分的习题画图十分重要,只要链表结构画出来,再写代码解决问题就容易很多。做完这些难度高的习题后发现,其实中等难度的习题只是将两个甚至三个以上的知识点融汇到一个题目中,当掌握链表的基本处理方法后,并用心去钻研,解决这些习题其实也不是想象中的那么难.
本篇中的例题代码均已更新在gitee代码仓库中,仓库链接:gitee代码仓库
希望我的解决思路可以给大家带来帮助,也希望大家可以指出我思路或者逻辑上的不足☺☺
目录
两数相加
原题链接:2.两数相加
题目描述:
给你两个非空链表,表示两个非负整数。它们的每位数字都是按照逆序的方式存储的,并且每个结点只能存储一位数字。请你将两数相加,并以相同形式返回一个表示和的链表.
例如:
输入:l1 = [2, 3, 4], l2 = [5, 6, 4]
输出:[7, 0, 8]
解释:342 + 465 = 807
以上述输入为例,可以构造出如下模型
解题思路:
根据题意,链表中存储的是数字逆序的,例如应该是456,则链表中存储的顺序是6->5->4,则链表的头结点刚好就是个位,从个位开始相加,一直加到最高位,在此过程中需要保存每次相加后是否出现进位,定义一个carry变量用来存储是否进位,如果进位了,将其赋值为1,反之赋值为0,之后依次遍历两个链表将其各个结点相加并存储在新的链表中即可.
代码如下:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode newList = new ListNode(0);
ListNode cur = newList;
int carry = 0;//表示是否进位,若进位则为1,反之则为0
while(l1 != null || l2 != null) {
int x = l1 != null ? l1.val : 0;//如果链表为空则为0,反之则为该结点的值
int y = l2 != null ? l2.val : 0;
int value = x + y + carry;
carry = value / 10;
value = value % 10;
cur.next = new ListNode(value);
cur = cur.next;
//判断是否已有链表为空,将不为空的直接链接在新链表后面即可
if(l1 != null) {
l1 = l1.next;
}
if(l2 != null) {
l2 = l2.next;
}
}
//所有循环结束后,判断是否有进位存在
if(carry == 1) {
cur.next = new ListNode(carry);
}
return newList.next;
}
删除链表倒数第N个结点
原题链接:19.删除链表倒数第N个结点
题目描述:
给你一个链表,删除链表的倒数第n个结点,并返回链表的头结点.
例如:
输入:head = [1,2,3,4,5],n = 2;
输出:[1,2,3,5]
解决思路 :
该题与寻找链表中倒数第k个结点题目类似,依旧是利用的快慢指针的思想,在解决问题时,需要建立一个虚拟结点newNode,这样将原本的找倒数第k个结点,转变成寻找该结点的前驱,找到前驱后就可以直接进行删除操作了
例如要寻找倒数第二个结点,则具体过程如下图所示:
则当fast== null || fast.next == null 时,slow刚好指向要删除结点的前驱位置,则直接进行删除操作即可.
具体代码如下:
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode newNode = new ListNode(0, head);//虚拟结点,指向head
ListNode fast = head;
ListNode slow = newNode;
while(n != 1) {
if(fast == null) {
return null;
}
fast = fast.next;
n--;
}
while(fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return newNode.next;
}
合并k个升序链表
原题链接:23.合并k个升序链表
题目描述:
给你一个链表数组,每个链表都已经按升序排序。请你将所有链表合并到一个升序链表中,返回合并后的链表.
例如:
输入:lists = [[1,4,5], [1,3,4], [2,6] ]
输出:[1,1,2,3,4,4,6]
解题思路 :
首先写一个合并两个升序链表的方法,以这个方法为基础,依次将所给的链表两两传入,将其合并为一个升序链表。假设原来有5个链表,分别标号为①、②、③、④、⑤,首先将①和②合并,③和④合并,由于此时只剩⑤一个链表,则先不管这个链表,就这样一直两两合并,直至所有的链表完成合并结束.
假设下图中每个结点表示一条链表,则上述描述过程如下图所示
具体代码实现:
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0) {
return null;
}
int k = lists.length;
while(k > 1) {
int index = 0;
for(int i = 0; i < k; i += 2) {
if(i == k-1) {
lists[index++] = lists[i];
} else {
lists[index++] = merge2Lists(lists[i], lists[i + 1]);
}
}
k = index;
}
return lists[0];
}
private ListNode merge2Lists(ListNode l1, ListNode l2) {
ListNode newList = new ListNode();
ListNode cur = newList;
while(l1 != null && l2 != null) {
if(l1.val > l2.val) {
cur.next = l2;
cur = cur.next;
l2 = l2.next;
} else {
cur.next = l1;
cur = cur.next;
l1 = l1.next;
}
}
cur.next = l1 == null ? l2 : l1;
return newList.next;
}
两两交换链表中的结点
原题链接:24.两两交换链表中的结点
题目描述:
给你一个链表,两两交换其中相邻的结点,并返回交换后的链表的头结点,你必须在不修改结点内部值的情况下完成本题(即,只能进行结点交换)
例如:
输入: head = [1,2,3,4]
输出: [2,1,4,3]
解题思路:
当链表结点个数为偶数时,两两交换,若结点个数为奇数,则最后一个结点不交换.
在进行结点交换前,建立一个虚拟结点,使其指向head,这样便于结点交换操作
根据上述的思路,若cur.next和cur.next.next不为空,则此时cur为待交换的这两个结点的前驱结点,利用该结点即可完成两个结点的交换,之后再使cur = cur.next,即代码中的cur = node1,即可完成代码的实现.
具体代码实现如下:
public ListNode swapPairs(ListNode head) {
ListNode newNode = new ListNode(0, head);
ListNode cur = newNode;
while(cur.next != null && cur.next.next != null) {
ListNode node1 = cur.next;
ListNode node2 = cur.next.next;
cur.next = node2;
node1.next = node2.next;
node2.next = node1;
cur = node1;
}
return newNode.next;
}