文章目录
题目一:移除链表元素
链接:移除链表元素
删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
题目解析
1.删除节点是中间节点,找到删除节点的前一个结点。
2.删除节点是头节点,直接将 head 后移(最后处理这种情况)。
程序测试
public class LinkedListTest {
// 给内部类加 static 效果和其他的 static 类似. 让这个类和 LinkedListTest 类相关, 而和对象不相关.
// 后续如果要创建 ListNode 的实例的话, 就不需要依赖 LinkedListTest 的实例
public static class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
//链表为空 直接返回
return null;
}
ListNode prev = head; // prev 始终指向 cur 的前一个位置
ListNode cur = head.next;
while (cur != null) {
if (cur.val == val) {
//1.要删除的元素是中间节点
prev.next = cur.next; // cur 对应的节点就被删掉了
cur = cur.next; // cur 指向下一个元素, 方便进行下次循环
} else {
// 不是要删除的节点
prev = cur;
cur = cur.next;
}
}
if (head.val == val) {
//2.要删除的节点是头节点间
head = head.next;
}
return head;
}
}
题目二:反转链表
链接:反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
题目解析
1.在原链表上直接逆置,创建 3 个引用,分别是 prev
,cur
,next
。
2.利用三个引用逆置链表。
3.当 next=null
时,cur就是原链表的最后一个结点,也是新链表的 header 。
程序测试
public ListNode reverseList(ListNode head) {
if (head == null) {
//空链表不需要逆置
return head;
}
if (head.next == null) {
//链表只有一个结点不需要逆置
return head;
}
ListNode newHeader = null;
ListNode cur = head;
ListNode prev = null;
while (cur != null) {
ListNode next = cur.next;
if (next == null) {
newHeader = cur; // cur 就是原链表的尾结点 新链表的 head
}
cur.next = prev; //逆置结点
prev = cur; //后移
cur = next; //后移
}
return newHeader;
}
题目三:链表的中间节点
链接:链表的中间节点
给定一个带有头结点 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,我们返回第二个结点。
提示:
给定链表的结点数介于 1 和 100 之间。
题目解析
先求链表长度,长度/2,走 长度/2 步。
程序测试
public ListNode middleNode(ListNode head) {
int midLength = size(head) / 2;
ListNode cur = head;
for(int i = 0; i < midLength; i++){
cur = cur.next;
}
return cur;
}
public int size(ListNode head) {
int size = 0;
ListNode cur = head;
while (cur != null) {
size++;
cur = cur.next;
}
return size;
}
题目四:链表中倒数第k个节点
链接:链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个结点。
题目解析
先求链表长度,走 length-k 步。
注意:对 k 值进行合法性判断。
程序测试
public ListNode FindKthToTail(ListNode head, int k) {
if (head == null) {
return null;
}
int size = size(head);
if (k <= 0 || k > size) {
// k 是非法值
return null;
}
int steps = size - k;
ListNode cur = head;
for (int i = 0; i < steps; i++) {
cur = cur.next;
}
return cur;
}
public int size(ListNode head) {
int size = 0;
ListNode cur = head;
while (cur != null) {
size++;
cur = cur.next;
}
return size;
}
题目五:合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
题目解析
1.拿两个引用分别指向两个链表的 head ,比较其 val ,将 val 小的尾插到新链表上,并移动引用。
2.如果新链表为空 newHeader = newNode; newTail = newNode
;如果新链表不为空 newHeader.next = newNode; newTail = newNode
。
3.如果其中一个链表为空,直接将剩余部分移动到新链表上。
注意:步骤2可以优化。
程序测试
版本一
注意:newTail.next = new ListNode(cur1.val)
,使用创建新结点尾插是防止原链表的cur.next
依然指向原链表的下一个结点,并未断开造成的死循环问题。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode newHeader = null; //结果链
ListNode newTail = newHeader; //结果链的最后一个结点
ListNode cur1 = l1;
ListNode cur2 = l2;
while (cur1 != null && cur2 != null) {
if (cur1.val < cur2.val) { // 把 cur1 对应的节点插入到新链表的末尾
if (newHeader == null) {
newHeader = cur1;
newTail = cur1;
}else{
newTail.next = new ListNode(cur1.val);
newTail = newTail.next;
}
cur1 = cur1.next;
} else { // 把 cur2 对应的节点插入到新链表的末尾
if (newHeader == null) {
newHeader = cur2;
newTail = cur2;
}else{
newTail.next = new ListNode(cur2.val);
newTail = newTail.next;
}
cur2 = cur2.next;
}
}
// 当循环结束时, 意味着当前 cur1 和 cur2 一定有一个到达了链表末尾.
// 把另外一个没到末尾的剩下的元素都连接在最终链表的尾部
if(cur1 != null){
newTail.next = cur2;
}
if(cur2 != null){
newTail.next = cur2;
}
return newHeader;
}
版本二
优化了版本一中对结果链的尾插时两种情况的判断。注意此时返回的是 newHead.next
。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
// l1 为空, 最终结果就是 l2
return l2;
}
if (l2 == null) {
return l1;
}
ListNode newHead = new ListNode(-1);
ListNode newTail = newHead;
ListNode cur1 = l1;
ListNode cur2 = l2;
while (cur1 != null && cur2 != null) {
if (cur1.val < cur2.val) {
// 把 cur1 对应的节点插入到新链表的末尾
newTail.next = cur1;
newTail = newTail.next;
cur1 = cur1.next;
} else {
newTail.next = cur2;
newTail = newTail.next;
cur2 = cur2.next;
}
}
// 当循环结束时, 意味着当前 cur1 和 cur2 一定有一个到达了链表末尾.
// 把另外一个没到末尾的剩下的元素都连接在最终链表的尾部
if (cur1 == null) {
newTail.next = cur2;
} else {
newTail.next = cur1;
}
return newHead.next;
}
题目六:链表分割
链接:链表分割
编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。
给定一个链表的头指针 ListNode* pHead,请返回重新排列后的链表的头指针。
注意:分割以后保持原来的数据顺序不变。
题目解析
1.定义两个新链表 bigHead
和 smalHead
(有头结点)。
2.便利原链表,与 k 作比较,比 k 小的尾插到smalHead
,比 k 大的尾插到 bigHead
。
3.把两个链表合并成一个链表 smallTail.next = bigHead.next
。
程序测试
注意:尾插时一定要创建新的结点。
public ListNode partition(ListNode pHead, int x) {
if(pHead == null){
return null;
}
if(pHead.next == null){
//只有一个结点
return pHead;
}
ListNode bigHead = new ListNode(-1);
ListNode bigTail = bigHead;
ListNode smallHead = new ListNode(-1);
ListNode smallTail = smallHead;
for(ListNode cur = pHead; cur != null; cur = cur.next){
if(cur.val<x){
// 插入到 smallTail 后面, 创建崭新的结点
// (新结点的 next 一定是 null)
smallTail.next = new ListNode(cur.val);
smallTail = smallTail.next;
}else{
bigTail.next = new ListNode(cur.val);
bigTail = bigTail.next;
}
}
smallTail.next = bigHead.next;
return smallHead.next;
}