1,链表
- 动态数组有一个明显的缺点,就是造成内存空间的浪费
- 那么要使用多少内存空间就开辟多少内存空间,我们就是可以使用 链表
- 链表 :是链式存储的一种线性表。所有元素的内存地址不一定是连续的
- 链表存储会创建一个一个node,其中node包含(元素element,指向下一个node的指针next)
- 链表有 头节点 和 尾节点 ,尾节点在最后指向null
2,链表的设计
-
创建LinkedList的类来管理链表数据,size用来管理链表的内存空间大小,first用来指向下一个节点
-
node节点包含:element元素,next指向下一个节点
public class LinkedList { private int size; private Node first; // 私有类, 链表中的节点 private class Node { E element; Node next; // 构造方法 public Node(E element, Node next) { this.element = element; this.next = next; } } }
3,接口的设计
3.1,接口设计列表
/**
* 清除所有元素
*/
void clear();
/**
* 元素的数量
*/
int size();
/**
* 是否包含某个元素
* @param element
* @return
*/
boolean contains(E element);
/**
* 获取index位置的元素
* @param index
* @return
*/
E get(int index);
/**
* 设置index位置的元素
* @param index
* @param element
* @return 原来的元素ֵ
*/
E set(int index, E element);
/**
* 在index位置插入一个元素
* @param index
* @param element
*/
void add(int index, E element);
/**
* 删除index位置的元素
* @param index
* @return
*/
E remove(int index);
/**
* 查看元素的索引
* @param element
* @return
*/
int indexOf(E element);
3.2,清除元素clear()
public void clear() {
size = 0;//内存空间清0
first = null;//地址指向null
}
- 清空size,first地址指向null
3.3,元素数量
public int size() {
return size;
}
- 返回size的数量
3.4,是否包含元素
//ELEMENT_ON_FOUND = -1
public boolean contains(E element) {
return indexOf(element) != ELEMENT_ON_FOUND;
}
- 直接调用查询函数,看是否存在
3.5,获取index位置的元素
public E get(int index) {
//直接在node中获取element
return node(index).element;
}
- 在node中根据下表取出对应元素
3.6,修改index位置的元素
public E set(int index, E element) {
// 找到对应节点, node方法中已经判断了索引是否越界
Node<E> node = node(index);
// 记录旧元素
E old = node.element;
// 覆盖元素
node.element = element;
// 返回旧元素
return old;
}
3.7,在对应index添加元素
public void add(int index, E element) {
// 检查索引是否越界
rangeCheckForSize(index);
// 当插入到0的位置时
if (index == 0) {
// 将first指向新节点, 新节点的next指向first之前指向的节点
first = new Node<E>(element, first.next);
}else {
// 找到指定位置前面的节点
Node<E> prev = node(index - 1);
// 将前面节点的next指向新节点, 新节点的next指向prev之前指向的节点
prev.next = new Node<>(element, prev.next);
}
size++;
}
- 在对应index添加元素,只需要创建新的node,插入到指定位置
- index==0在0位置插入元素,将first指向新节点,新节点的next指向first之前指向的节点
- index!=0,找到新节点位置之前的oldNode节点,oldNode的next指向新的节点,新节点的next指向oldNode之前指向的节点。
3.8,删除index位置元素
public E remove(int index) {
// 检查索引是否越界
rangeCheck(index);
// 记录需要删除的节点
Node<E> old = first;
// 当删除第0个元素时, 将first的next指向索引为`1`的节点即可
if (index == 0) {
first = first.next;
}else {
// 找到前一个元素
Node<E> prev = node(index - 1);
// 记录需要删除的节点
old = prev.next;
// 将prev的next指向需要删除节点的后一个节点
prev.next = old.next;
}
// size-1
size--;
// 返回删除的元素
return old.element;
}
- 当index为0时,只需要将first的next指向索引为“1”的节点
- 当index不为0时,需要只找到index索引的前一个节点,指向index索引的后一个节点
3.9,查看元素的索引
private static final int ELEMENT_ON_FOUND = -1;
public int indexOf(E element) {
// 取出头结点
Node<E> node = first;
// 当element为null时的处理
if (element == null) {
// 遍历节点, 找到存储为null的节点, 返回索引
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
}else {
for (int i = 0; i < size; i++) {
// 遍历节点, 找到存储的元素与指定元素相等的节点, 返回索引
if (element.equals(node.element)) return i;
node = node.next;
}
}
// 没有找到元素对应的节点, 返回ELEMENT_ON_FOUND
return ELEMENT_ON_FOUND;
}
- 遍历节点里所有的元素,找到对应的索引的位置,返回就好
4,算法面试题
4.1,LeetCode--237. 删除链表中的节点
面试题地址:leetcode-cn.com/problems/de…
分析:
- 首先分析代码,给我们的是当前要删除的node。我们之前删除节点是先找到要删除节点前面的节点(oldNode)。然后将oldNode的next指向现在节点的后一个节点。
- 现在我们只有当前的节点,所以我们需要换一个思路。
解答:
-
我们只需要把当前节点的后面一个节点(nextNode)的element,覆盖当前node的element。将当前node的next指向后一个节点的next即可。
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; } }public void deleteNode(ListNode* node) {
node.val = node.next.val; node.next = node.next.next; }
4.2,leetcode-剑指 Offer 24. 反转链表
面试题链接:leetcode-cn.com/problems/fa…
分析:
- 本题给我们一个头节点,经过反转以后,我们要返回新的头节点。
解答一:递归
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;//当head或head.next为null,直接返回head
ListNode newHead = reverseList(head.next);//调用reverseList,反转head之后的元素
head.next.next = head;//反转head
head.next = null;//head.next指向null。
return newHead;
}
- 调用reverseList反转函数进行反转,反转head之后的所有元素
- 反转head。head.next为head之后的节点(nextNode)。nextNode.next指向head。
- 把head的next指向null。
解答二:插头法
public ListNode reverseList(ListNode head) {
ListNode newHead = null;
while(head != null){
ListNode temp = head.next;
head.next = newHead;
newHead = head;
head = temp;
}
return newHead;
}
-
创建临时的node为newHead=null,为head的下一个节点
-
把head.next先指向newHead
-
newHead指向当前的head
-
当前的head指向temp所在的node。
4.3,leetcode-141.链表是否有环
题目链接:leetcode-cn.com/problems/li…
分析:
-
传入一个头节点,判断链表是否有环,返回bool值
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null ) return false;
//创建慢node
ListNode slow = head;
//创建快node
ListNode fast = head.next;
while (fast != null && fast.next != null) {
//如果相等有环
if(slow == fast) return true;
//慢node一次走一个
slow = slow.next;
//快node一次走两个
fast = fast.next.next;
}
return false;
}
解答:快慢指针法
- 创建慢node为slow,快node为fast
- 慢node的slow一次走一个next,快node的fast一次