注:使用到的java节点类定义如下:
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; }
}
题目链接:203. 移除链表元素 - 力扣(LeetCode)
需要注意:头结点如何移除。
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。
方法:设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。
class Solution203 {
public ListNode removeElements(ListNode head, int val) {
if (head == null) return head;
// 为避免删除头节点带来的额外操作,设置dummy节点来进行操作统一
ListNode dummy = new ListNode(-1, head);
ListNode pre = dummy;
ListNode cur = head;
while (cur != null){
if (cur.val == val){
pre.next = cur.next; // 当前位置的前一个节点指向当前位置的下一个节点
} else {
pre = cur;
}
cur = cur.next;
}
return dummy.next;
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(1)
题目链接:707. 设计链表 - 力扣(LeetCode)
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点
可以说这五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目!
class MyLinkedList {
int size; // 链表长度
ListNode head; // 虚拟头节点
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
// 因为有虚拟头节点,所以查找第index+1个节点
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
// 链表的开始插入一个节点,等价于在0个元素前添加
public void addAtHead(int val) {
addAtIndex(0,val);
}
// 链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if (index > size){
return;
}
if (index < 0){
index = 0;
}
size ++;
// 找到要插入节点的前驱
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
ListNode addval = new ListNode(val);
addval.next = pred.next;
pred.next = addval;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size){
return;
}
size -- ;
if (index == 0){
head = head.next;
return;
}
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
}
题目链接:206. 反转链表 - 力扣(LeetCode)
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
方法一:定义一个新的链表,实现链表元素的反转(会占用内存空间)
方法二:通过双指针反转原链表(只需要改变链表的next指针的指向)
方法三:递归法
这里给出两种递归法,第一种和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。第二种从后往前递归,自己感觉会更好理解一点。
class Solution206 {
// 通过定义一个新链表来存储反转后的链表
public ListNode reverseList(ListNode head) {
if (head == null){
return head;
}
ListNode newHead = null; // 新链表
ListNode cur = head;
while (cur != null) {
ListNode newNode = new ListNode(cur.val);
newNode.next = newHead; // 新节点指向新的链表的头结点
newHead = newNode; // 更新新链表的头节点
cur = cur.next;
}
return newHead;
}
// 通过双指针反转原链表(只需要改变链表的next指针的指向)
public ListNode reverseList2(ListNode head) {
if (head == null){
return head;
}
ListNode pred = null;
ListNode cur = head;
while(cur != null){
ListNode temp = cur.next; // 保存cur下一个节点
cur.next = pred;
pred = cur;
cur = temp;
}
return pred;
}
// 将双指针法改写成递归方法
public ListNode reverseList3(ListNode head) {
return reverse(null, head);
}
private ListNode reverse(ListNode prev, ListNode cur) {
if (cur == null) {
return prev;
}
ListNode temp = null;
temp = cur.next; // 先保存下一个节点
cur.next = prev; // 反转
return reverse(cur, temp);
}
// 从后往前递归 (更好理解)
public ListNode reverseList4(ListNode head) {
// 递归终止条件,当前节点为空或当前节点的下一个节点为空
if (head == null || head.next == null){
return head;
}
// 递归反转剩余链表
ListNode newHead = reverseList4(head.next);
// 反转当前节点与下一个节点的连接
head.next.next = head;
head.next = null;
return newHead;
}
}