链表理论基础
- 链表在内存中可以散乱分布,数组一定是连续分布
- 链表的增删改查只需要修改指针,数组的增删需要覆盖操作
- 性能比较
插入/删除 | 查询 | 适用场景 | |
---|---|---|---|
数组 | O(n) | O(1) | 数据量固定,频繁查询,少增删 |
链表 | O(1) | O(n) | 数据量不固定,频繁增删,少查询 |
面试时,有时会笼统地将数组的查询时间复杂度记为O(1),其实是不准确的。有关数组操作的时间复杂度
203.移除链表元素
- 原链表
注意:
- 头结点为空指针的情况,务必考虑周全
/**
* 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 head;
}
ListNode cur = head;
while (head != null && head.val == val) {// 对头结点的单独处理,注意若队头连续几个结点的值都等于val,所以用while
head = head.next;
}
while (cur != null && cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
}
- 虚拟头结点
注意: - C++如果移除一个节点之后,需要手动在内存中删除这个节点;java 、python 这些语言不需要
- 删除元素必须要有前驱,因此current指针应该初始位置指向dummy
- 最后return的应该是dummy.next而不是head,因为head可能会被删除
- 注意这种写法 ListNode dummy = new ListNode(-1, head);
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
// 使用虚拟头结点的情况
ListNode dummy = new ListNode(-1, head);
ListNode cur = dummy;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummy.next;
}
}
707.设计链表
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点
思路1:单链表
class MyLinkedList {
int size;// size存储链表元素的个数
ListNode head;// 虚拟头结点
// 初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
// 获取第index个节点数值
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;
}
// 在链表最前面插入一个节点
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
if (index < 0) {
index = 0;
}
size++;
// 找到要插入结点的前驱
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
ListNode insert = new ListNode(val);
insert.next = pre.next;
pre.next = insert;
}
// 删除第index个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
思路2:双链表
class MyLinkedList {
class ListNode{
int val;
ListNode next,prev;
ListNode(int x){val=x;}
}
int size;// size存储链表元素的个数
ListNode head, tail;
// 初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
tail = new ListNode(0);
head.next = tail;
tail.prev = head; // pre或prev有什么区别吗
}
// 获取第index个节点数值
public int get(int index) {
if (index < 0 || index >= size) {
return -1;// 合法性
}
ListNode cur = head;
// 通过判断 index < (size - 1) / 2 来决定是从头结点还是尾节点遍历,提高效率
if (index < (size - 1) / 2) {
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
} else {
cur = tail;
for (int i = 0; i <= size - index - 1; i++) {
cur = cur.prev;
}
}
return cur.val;
}
// 在链表最前面插入一个节点
public void addAtHead(int val) {
ListNode cur = head;
ListNode newNode = new ListNode(val);
newNode.next = cur.next;
cur.next.prev = newNode;
cur.next = newNode;
newNode.prev = cur;
size++;
}
/** Append a node of value val to the last element of the linked list. */
public void addAtTail(int val) {
ListNode cur = tail;
ListNode newNode = new ListNode(val);
newNode.next = tail;
newNode.prev = cur.prev;
cur.prev.next = newNode;
cur.prev = newNode;
size++;
}
/**
* Add a node of value val before the index-th node in the linked list. If index
* equals to the length of linked list, the node will be appended to the end of
* linked list. If index is greater than the length, the node will not be
* inserted.
*/
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
if (index < 0) {
index = 0;
}
ListNode cur = head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
ListNode newNode = new ListNode(val);
newNode.next = cur.next;
cur.next.prev = newNode;
newNode.prev = cur;
cur.next = newNode;
size++;
}
/** Delete the index-th node in the linked list, if the index is valid. */
public void deleteAtIndex(int index) {
if (index >= size || index < 0) {
return;
}
ListNode cur = head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.next.next.prev = cur;
cur.next = cur.next.next;
size--;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
注意:
- 操作的第n个结点应该为cur.next
- new是关键字,不可以做结点名字
- 插入结点时按照上图的顺序,否则将丢失后面的结点
这道题快整了一个下午了!链表杀我!!!可恶(〃>皿<)还是不很清楚!尤其是双链表,但实在是学不下去了
206.反转链表
- 思路1:双指针
双指针遍历,一个temp用来记录
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
ListNode temp;
while (cur != null) {
temp = cur.next;// 记录下一个指针
cur.next = pre;// 翻转
// 更新,向前推进
pre = cur;
cur = temp;
}
return pre;
}
}
- 思路2:递归方法
还是有点糊涂
- 想法与双指针一样,按照双指针的思路捋递归的方法
- 注意return
class Solution {
public ListNode reverse(ListNode pre, ListNode cur) {//翻转函数
if (cur == null) {
return pre;
}//判断结束
ListNode temp = cur.next;
cur.next = pre;
return reverse(cur, temp);
}
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
}