代码随想录算法训练营day3 | 题目203、题目 707、题目 206
题目一描述
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
- 列表中的节点数目在范围
[0, 10<sup>4</sup>]内 1 <= Node.val <= 500 <= val <= 50
解题思路
比较初级的链表,注意while循环的条件
代码实现
方法一:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null){
return null;
}
ListNode dummyHead = new ListNode(-1, head);
ListNode Pnode = new ListNode();
Pnode.next = dummyHead;
while(Pnode.next!=null){
if(Pnode.next.val == val){
Pnode.next = Pnode.next.next;
}else{
Pnode = Pnode.next;
}
}
return dummyHead.next;
}
}
技巧总结
学会虚拟头节点的初始化
题目二描述
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList 类:
-
MyLinkedList()初始化MyLinkedList对象。 -
int get(int index)获取链表中下标为index的节点的值。如果下标无效,则返回-1。 -
void addAtHead(int val)将一个值为val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。 -
void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素。 -
void addAtIndex(int index, int val)将一个值为val的节点插入到链表中下标为index的节点之前。如果index等于链表的长度,那么该节点会被追加到链表的末尾。如果index比长度更大,该节点将 不会插入 到链表中。 -
void deleteAtIndex(int index)如果下标有效,则删除链表中下标为index的节点。
示例:
输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3
提示:
0 <= index, val <= 1000- 请不要使用内置的 LinkedList 库。
- 调用
get、addAtHead、addAtTail、addAtIndex和deleteAtIndex的次数不超过2000。
解题思路
第一次遇到这样的题,复习了类的定义和初始化了
代码实现
方法一:
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
head = new ListNode(0); //手动加了一个头结点,这个指针始终指向第一个结点,并且不会移动,也不计入size
}
public int get(int index) {
if (size <= index)
return -1;
ListNode res = head;
for (int i = 0; i <= index; i++) {
res = res.next;
}
return res.val;
}
public void addAtHead(int val) {
ListNode res = new ListNode(val, head.next);
head.next = res;
size++;
}
public void addAtTail(int val) {
ListNode tail = new ListNode(val);
ListNode temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = tail;
size++;
}
public void addAtIndex(int index, int val) {
if(index > size) return;
else if (index <= size) {
ListNode dummy = head;
ListNode res = new ListNode(val);
for (int i = 0; i < index; i++) {
dummy = dummy.next;
}
res.next = dummy.next;
dummy.next = res;
size++;
}
}
public void deleteAtIndex(int index) {
if(index >= size) return;
else if (index < size) {
ListNode dummy = head;
for (int i = 0; i < index; i++) { // 注意这里的循环次数,最好画图示意一下
dummy = dummy.next;
}
dummy.next = dummy.next.next;
size--;
}
}
}
技巧总结
注意for (int i = 0; i < index; i++) {}会执行index次
题目三描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000] -5000 <= Node.val <= 5000
进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
解题思路
反转链表,迭代有两种解法,递归一种解法
- 12345,1指向3,2指向1,1指向4,3指向2,需要储存每次的头节点
- 12345,1指向null,2指向1,3指向2,需要储存每次的移动到下次的节点(不容易出错)
- 递归,递归的条件有三
总问题可以分解成两个子问题
子问题的解法和总问题一样
存在最小子问题
先递后归,先入栈,再单元操作
代码实现
方法一(自己写的):
// 顺着思路容易想到这种,但是还是记住第二种吧。
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) return null;
ListNode fast = new ListNode(0);
ListNode slow = new ListNode(0);
fast = head;
slow = head; // 初始化
while(slow.next != null){
fast = slow.next;
slow.next = fast.next;
fast.next = head;
head = fast;
}
return head;
}
}
方法二(记住这种)
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) return null;
ListNode fast = head;
ListNode slow = null; // 头结点反转后要指向null,注意初始化
while(fast != null){ // 模拟一下最后结束的情况,设置while条件,根据先移动的指针来设计
ListNode temp = fast.next;
fast.next = slow;
slow = fast;
fast = temp;
}
return slow;
}
}
方法三(递归)
class Solution {
public ListNode reverseList(ListNode head) {
// 找到最后一个结点,然后跳出递归
if(head == null || head.next == null)
return head;
// 先递,先压栈,并把结果一层层接收上来
// 如果传的是head,那就死循环了,所以传的是下一个结点为开头的子串,最后找到最后一个结点
// 这个的返回值是反转之后的头结点,也就是找到的最后一个结点
ListNode cur = reverseList(head.next);
// 后归,后续再操作
// 这里是考虑只有两个结点的时候(最小的子问题),即跳出并返回最后那次递归之后,进行的操作
// 这里想的是最后两个结点 1->2->3->4->5 中的 4->5
// 操作后变成 1->2->3->4<-5
// |
// null
// 那么对于3->(4<-5)的子串,也可以有同样的,如下的操作,使得4指向3,3指向ull
// 由于一层层压栈的时候head不一样,最后执行的时候这里的head也不一样,会按照4,3,2,1的顺序
head.next.next = head;
head.next = null;
return cur;
}
}
技巧总结
一般反转数组不需要虚拟头结点
初始化很重要
学会使用临时结点