动态数组有一个明显的缺点:
可能造成内存空间的大量浪费
。能否用到多少就申请多少内存?
链表就可以,链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的。
1. 链表的设计
- 链表大部分接口和动态数组是一样的,对其进行抽取。将公共的方法抽取到抽象类中实现抽象类在实现公共的接口。
1.1 接口设计
起名为
ListForObject
是因为其适应所有数据类型的数据。ListForObject
接口定义了所有需要实现的方法。和静态常量ELEMENT_NOT_FOUND
。
public interface ListForObject {
static final int ELEMENT_NOT_FOUND = -1;
/**
* 数组长度
*
* @return
*/
int size();
/**
* 数组是否为空
*
* @return
*/
boolean isEmpty();
/**
* 是否包含某个元素
*
* @param element
* @return
*/
boolean contains(E element);
/**
* 添加元素到后面
*
* @param element
*/
void add(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);
/**
* 清除所有元素
*/
void clear();
}
- 定义抽象方法实现
ListForObject
接口 , 实现公共方法,由于是抽象方法,不需要实现接口的全部方法,将需要实现的方法交给子类实现。
public abstract class AbstractList implements ListForObject {
protected int size;
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
@Override
public void add(E element) {
add(size, element);
}
/**
* 检查索引范围
*
* @param index
*/
protected void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
/**
* 数组下标越接将抛出异常
*
* @param index
*/
private void outOfBounds(int index) {
throw new IndexOutOfBoundsException("index = " + index + " size = " + size);
}
/**
* 添加元素的时候 index 索引检查
*
* @param index
*/
protected void rangeCheckForAdd(int index) {
if (index < 0 || index > size) {
outOfBounds(index);
}
}
}
- 定义实现类
LinkedList
继承抽象类AbstractList
,实现未实现的方法:
public class LinkedList extends AbstractList {
private Node firstNode;
/**
* 静态内部类
*
* @param
*/
private static class Node {
E element;
Node next;
private Node(E element, Node next) {
this.element = element;
this.next = next;
}
}
/**
* 获取 index 位置节点的 element
* 由于获取节点中进行了 index 范围判断所以不需要在判断
*
* @param index
* @return
*/
@Override
public E get(int index) {
return node(index).element;
}
/**
* 设置 index 位置节点的元素信息为 element
*
* @param index
* @param element
* @return
*/
@Override
public E set(int index, E element) {
rangeCheck(index);
Node node = node(index);
E oldElement = node.element;
node.element = element;
return oldElement;
}
/**
* 向链表中指定位置添加一个元素
*
* @param index
* @param element
*/
@Override
public void add(int index, E element) {
// 考虑当 index 等于 0 的情况
if (index == 0) {
// firstNode 位置存的是第一个节点的地址
firstNode = new Node<>(element, firstNode);
} else {
// 在 index 位置插入数据需要先找到 index - 1 位置
Node prev = node(index - 1);
// 注意前后 prev.next 是不一样的
prev.next = new Node(element, prev.next);
}
size++;
}
/**
* 从链表中移除一个 节点
*
* @param index
* @return
*/
@Override
public E remove(int index) {
Node node = firstNode;
if (index == 0) {
firstNode = firstNode.next;
} else {
// 需要移除节点的上一个节点
Node prev = node(index - 1);
node = prev.next;
prev.next = node.next;
}
size--;
return node.element;
}
/**
* 查询 某个元素在链表中的位置
*
* @param element
* @return
*/
@Override
public int indexOf(E element) {
Node node = firstNode;
if (element == 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 (node.element.equals(element)) {
return i;
}
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
@Override
public void clear() {
size = 0;
firstNode = null;
}
/**
* 返回第 index 个节点
*
* @param index
* @return
*/
private Node node(int index) {
rangeCheck(index);
Node node = firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
/**
* 重写 toString
*
* @return
*/
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size = ").append(size).append(" ; ").append(" [ ");
// firstNode 指向第一个节点
Node node = firstNode;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(",");
}
string.append(node.element);
node = node.next;
}
string.append(" ] ");
return string.toString();
}
}
1.2 实现细节
- 获取指定
index
位置的节点 :Node
node(int index)
/**
* 返回第index 个节点
*
* @param index
* @return
*/
private Node node(int index) {
rangeCheck(index);
Node node = firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
- 实现链表的添加操作:
void add(int index, E element)
首先,需要获取到需要添加节点位置的前一个节点 (node(index-1)
),newNode.next = prev.next
;prev.next = newNode
/**
* 向链表中指定位置添加一个元素
*
* @param index
* @param element
*/
@Override
public void add(int index, E element) {
// 考虑当 index 等于 0 的情况
if (index == 0) {
// 在 index 位置插入数据需要先找到 index - 1 位置
Node prev = node(index - 1);
// firstNode 位置存的是第一个节点的地址
firstNode = new Node<>(element, firstNode);
} else {
// 在 index 位置插入数据需要先找到 index - 1 位置
Node prev = node(index - 1);
// 注意前后 prev.next 是不一样的
prev.next = new Node(element, prev.next);
}
size++;
}
2. 【LeetCode】删除链表中的节点
- 题目 : 题目链接
- 材料 :
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
}
}
- 分析 :删除传递过来的节点
- 使用该节点的下一个节点的值覆盖该节点的值。
- 将该节点的 next 指向下一个节点的next;
- 代码实现:
public class ListNode {
int val;
ListNode next;
public ListNode(int x) {
val = x;
}
}
public void deleteNode(ListNode node) {
// 使用下一个节点的值将自己当前的值覆盖
node.val = node.next.val;
// 将下一个节点的 next 赋值给自己
node.next = node.next.next;
}
3. 【LeetCode】反转链表(非递归)
- 题目: 题目链接
- 材料:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
}
}
- 解题思路分析(迭代方式) :
- 需要定义一个新的头节点
ListNode newNode = null
。 - 需要定义一个临时变量
ListNode temp = header.next
用于存放变换之后的头节点。 - 将旧链表的头节点插入到新链表的头部,
head.next = newNode;
将新链表的头指针指向链表的头部。 - 旧链表的头指针重新指向新的头节点。
head = temp;
- 代码实现 :
public class ListNode {
int val;
ListNode next;
public ListNode(int x) {
val = x;
}
}
/**
* 使用迭代的方式实现 链表反转
*
* @param head
* @return
*/
private static ListNode iterationList(ListNode head) {
if (head == null || head.next == null) {
return null;
}
ListNode newHead = null;
while (head != null) {
ListNode temp = head.next;
head.next = newHead;
newHead.next = head;
head = temp;
}
return newHead;
}
4. 【LeetCode】判断一个链表是否环形链表
- 题目:环形链表
- 解题分析 :
- 使用快慢指针进行求解 , slow 作为慢指针每次步进 1。 slow=slow.next 。fast作为快指针每次步进 2 。 fast = fast.next.next; 由于一快一慢,如果链表有环则快慢指针就会相遇。 fast = slow。
- 由于快指针步进比较大,如果链表没环则优先到达链表末端。 fast == null 或者 fast.next == null;
- 代码实现 :
public boolean hasCycle(ListNode head) {
// 1. 如果头结点为空或者只有一个节点表示没有环
if (head == null || head.next == null) {
return false;
}
ListNode slowNode = head;
ListNode fastNode = head.next;
// 由于 fast 走的快, 如果 fast 遇到节点为 null 表示不是环形链表
while (fastNode != null && fastNode.next != null) {
if (slowNode == fastNode) {
return true;
}
// 走一步
slowNode = slowNode.next;
// 走两步
fastNode = fastNode.next.next;
}
// 如果开始条件都不满足则返回false
return false;
}