文章目录
1. 动态数组的缺点
- 每次申请指定大小的空间,造成内存空间的大量浪费
- 能否用到多少就申请使用多少呢?链表
2. 链表
2.1 什么是链表
- 链表是一种链式存储结构的线性表,所有元素的内存地址不一定连续
2.2 单链表的结构
2.3 单链表和单链表接口的设计
-
链表的设计
-
链表接口的设计
- 链表和前文动态数组(DynamicArray)有相同的功能 但有不同的实现 可以把相同的方法抽取为接口 相同方法的相同实现 抽取为抽象类
- 链表和前文动态数组(DynamicArray)有相同的功能 但有不同的实现 可以把相同的方法抽取为接口 相同方法的相同实现 抽取为抽象类
package com.java.liangfw.linkedList2;
/**
* @version 1.0
* @Author: liangfangwei
* @Date: 2021/4/19 16:22
*/
public interface List<T> {
static final int ELEMENT_NOT_FOUND = -1;
void add(int index, T ele);
T get(int index);
T set(int index, T ele);
void remove(int index);
int indexOf(T ele);
void clear();
}
package com.java.liangfw.linkedList2;
/**
* @version 1.0
* @Author: liangfangwei
* @Date: 2021/4/19 16:25
*/
public abstract class AbstractList<T> implements List<T> {
/**
* protected 只能子类使用
*/
protected int size;
protected int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean contains(T ele) {
return indexOf(ele) != ELEMENT_NOT_FOUND;
}
/**
* 添加元素到末尾
*
* @param ele
*/
public void add(T ele) {
add(size, ele);
}
/**
* protected 子类或者同包 使用的校验方法
* @param index
*/
protected void outOfBounds(int index) {
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
protected void rangeCheck(int index) {
if (index < 0 || index > size) {
outOfBounds(index);
}
}
}
package com.java.liangfw.linkedList2;
import com.sun.deploy.util.StringUtils;
/**
* @version 1.0
* @Author: liangfangwei
* @Date: 2021/4/19 16:38
*/
public class LinkedList<T> extends AbstractList<T> {
private Node first;
static class Node<T> {
T element;
Node next;
public Node(T element, Node next) {
this.element = element;
this.next = next;
}
}
/**
* 给某个索引位置添加元素 索引从0开始
* 1.判断索引是否合格
* 2.添加
* 2.1 给第一个位置添加 first =newNode
* 2.2 非首尾添加 找到index-1的node pre.next=newNode(ele,index位置的node)
* 2.3 尾添加 最找到倒数第二个元素 pre.next=newNode
*
* @param index
* @param ele
*/
@Override
public void add(int index, T ele) {
// 头
if (index == 0) {
first = new Node(ele, first);
// 头尾之前+尾
} else {
// 获取index前的元素
Node<T> preNode = getIndeNode(index - 1);
preNode.next = new Node(ele, getIndeNode(index));
}
size++;
}
@Override
public T get(int index) {
return getIndeNode(index).element;
}
/**
* 1.找到index位置的元素 indexNode.element=ele
*
* @param index
* @param ele
* @return
*/
@Override
public T set(int index, T ele) {
return getIndeNode(index).element = ele;
}
/**
* 删除某位置的元素
* 找到index-1 位置的preNode.next=pre.next.next;
*
* @param index
*/
@Override
public void remove(int index) {
if (index == 0) first = first.next;
else {
Node<T> preNode = getIndeNode(index - 1);
preNode.next = preNode.next.next;
}
size--;
}
@Override
public int indexOf(T ele) {
Node<T> tempNode = first;
for (int i = 0; i < size; i++) {
if (ele.equals(tempNode.element)) return i;
tempNode = tempNode.next;
}
return ELEMENT_NOT_FOUND;
}
@Override
public void clear() {
size = 0;
first = null;
}
/**
* 获取某个索引位置的元素
*
* @param index
*/
private Node<T> getIndeNode(int index) {
rangeCheck(index);
Node temp = first;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
LinkedList.Node<T> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(node.element);
node = node.next;
}
string.append("]");
return string.toString();
}
}
2.4 链表和数组复杂度比较
3. 单链表练习
3.1 删除链表元素
- 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
现有一个链表 – head = [4,5,1,9],它可以表示为:
示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该 链表应变为 4 -> 1 -> 9.
示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
链表至少包含两个节点。
链表中所有节点的值都是唯一的。
给定的节点为非末尾节点并且一定是链表中的一个有效节点。
不要从你的函数中返回任何结果。
- 思路分析:
- 方法一:找到待删除元素之前的元素preNode proNode的下一个节点指向下下个节点即可 preNode.next=preNode.next.next;
- 方法二: 将待删除元素的值替换为后面元素的值 相当删除了该元素 。找到待删除的元素,node.val=node.next.val node.next=node.next.next;
- 方法一:
public E remove(int index) {
Node<E> node = first;
if (index == 0) {
first = first.nextNode;
} else {
Node<E> preNode = getIndexNode(index - 1);
node = preNode.nextNode;
preNode.nextNode = node.nextNode;
}
size--;
return node.element;
}
public class _237_删除链表中的节点 {
static class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
/**
* 删除链表的的节点
思路一: 如果传入index 找到index-1 preNode的值
preNode.next=preNode.next.next;
思路二:如果传入node ,待删除节点的值替换为next节点的值 待删除节点的next 替换为 nextNode
node.val=node.next.val;
node.next=node.next.next;
*/
public void deleteNode(ListNode node) {
node.val=node.next.val;
node.next=node.next.next;
}
}
3.2 反转链表
- 反转一个单链表。分别使用迭代和递归实现
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
- 思路分析:
- 迭代方法:初始化一前一后两个指针 pre=null,cur=head;改变指针指向,实现局部反转,两指针同时向前移动一位
- 迭代方法:初始化一前一后两个指针 pre=null,cur=head;改变指针指向,实现局部反转,两指针同时向前移动一位
public ListNode reverseList2(ListNode head) {
ListNode pre=null;
ListNode cur=head;
ListNode curNextpNode;
while (cur!=null){
curNextpNode=cur.next;
//1. 实现局部反转
cur.next=pre;
// 2. 指针同时向前移动
pre=cur;
cur=curNextpNode;
}
return pre;
}
- 递归
- 递归的使用条件:
- (1)大问题可以拆成两个子问题
- (2)子问题的求解方式和大问题一样
- (3)存在最小子问题
- 递归求解的过程?
- 求最小子问题的解得出较大问题的解
- 由规模问题的解不断得出更大问题的解
- 最后得出原来问题的解
- 例子:递归求解的过程?1+2+3…+n
- 递归的使用条件:
int sum(int n){
if(n<=1) return n;
return n+sum(n-1);
}
- 递归求解的思路
- 反转单链表 可以拆解为 头+反转剩余部分的两个子问题
- 剩余部分可以拆解为头+反转剩余部分的子问题 反复如此
- 最终只需要反转最后的一个节点
public ListNode reverseList(ListNode head) {
// 最小子问题的解
if(head==null||head.next==null) return head;
// 分解问题的过程
ListNode p = reverseList(head.next);
// 解合并的过程
head.next.next=head;
head.next=null;
return p;
}
3.3 判断是否有环
- 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
- 思路分析: 快慢指针 两指针相遇有环 快指针=null 则无环
- 快指针每次走两步 faster=faster.next.next;
- 慢指针每次走一步 slow=slow.next;
- 如果if(faster==slow)有环
- 循环条件为( faster!=null && faster.next!=null )
public boolean hasCycle(ListNode head) {
ListNode fasterPoint = head;
ListNode slowPoint = head;
while (fasterPoint != null && fasterPoint.next != null) {
fasterPoint = fasterPoint.next.next;
slowPoint = slowPoint.next;
if (slowPoint == fasterPoint) return true;
}
return false;
}