算法-链表
一、概述
线性表就是数据排成一条线一样的结构. 对不对, 你第一个想到的是什么结构.
- 数组
- 链表
- 栈
- 队列
非线性表:
- 二叉树
- 图
- 堆
二、数组
数组是一组连续内存空间存储的具有相同类型的数据,整个排列像一条线一样,是一种线性表数据结构。
数组的下标为什么要从 0 开始?
数组之所以广泛使用,是因为它支持随机访问。
- 顺序访问:链表在内存中不是按顺序存放的,而是通过指针连在一起,访问某一元素,必须从链头开始顺着指针才能找到某一个元素。
- 随机访问:数据在内存中都是按顺序存放的,通过下标直接触达到某一个元素存放的位置,随机访问,一定要满足两个条件:
- 连续的内存空间
- 相同类型的数据
公式:
int[n] = base_address + n * data_size
- base_address,表示数组的首地址
- n,表示偏移量
- data_size,表示数组类型的字节数
假如将数组的首个下标从 1 开始 ,读取 下标为n 的数据公式:
int[n] = base_address + (n-1) * data_size
与上面的公式的区别,多了一次 n-1 操作
虽然也能读取数组中的值,但是多了一次减法的指令运算。数组是一个最基础、最简单的数据结构。要知道上层API内部很多都会依赖于数组,而互联网应用又讲究一个高并发,如果千万级QPS,如此高频的访问量,这个冗余的减运算 就会放大无数倍,产生巨大的性能损耗。所以要从 0 开始。
三、链表
链表:是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
双向链表(double linked list):双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针。
四、翻转链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
// 1 -> 2 -> 3 -> 4 -> 5
//prev = 1
// current = 2;
ListNode prev = head;
ListNode current = head.next;
// 1 2 -> 3 -> 4 -> 5
// prev = 1
// current = 2;
prev.next = null;
while (current != null) {
// First time
/*
next = 3
1 <- 2 3 -> 4 -> 5
prev = 2
current = 3
*/
// Second time
/*
next = 4
1 <- 2 <- 3 4 -> 5
prev = 3
current = 4
*/
// third time
/*
next = 5
1 <- 2 <- 3 <- 4 5
prev = 4
current = 5
*/
// fourth time
/*
next = null
1 <- 2 <- 3 <- 4 <-5
prev = 5
current = null
*/
ListNode next = current.next;
current.next = prev;
prev = current;
current = next;
}
//exit
//return prev = 5
return prev;
}
}
五、翻转链表2(指定区域内链表翻转)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if (head == null || m >= n) {
return head;
}
ListNode dummy = new ListNode(-1);
//增加一个前置节点
dummy.next = head;
head = dummy;
//m 位置设置为head节点
for (int i = 1; i < m; i++) {
head = head.next;
}
ListNode prevM = head;
ListNode mNode = head.next;
ListNode nNode = mNode;
ListNode postN = nNode.next;
//区域内翻转
// nNode => prev;
//postN => current
for(int i = m; i < n; i++) {
/**
ListNode next = current.next;
current.next = prev;
prev = current;
current = next;
**/
ListNode next = postN.next;
postN.next = nNode;
nNode = postN;
postN = next;
}
//调整整个链表
mNode.next = postN;
prevM.next = nNode;
return dummy.next;
}
}
六、深度拷贝带随机指针的链表
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
public Node copyRandomList(Node head) {
if(head == null) {
return null;
}
Map<Node, Node> map = new HashMap<Node, Node>();
Node newHead = head;
//先深度拷贝node 节点,使用HashMap
while (newHead != null) {
if (!map.containsKey(newHead)) {
Node node = new Node(newHead.val);
map.put(newHead, node);
}
if (newHead.random != null) {
Node random = newHead.random;
if (!map.containsKey(random)) {
Node copyRandom = new Node(random.val);
map.put(random, copyRandom);
}
map.get(newHead).random = map.get(random);
}
newHead = newHead.next;
}
//从头开始,从HashMap拿出弄得,组装新链表
newHead = head;
while (newHead != null) {
Node next = newHead.next;
map.get(newHead).next = map.get(next);
newHead = newHead.next;
}
return map.get(head);
}
解法2:
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution2 {
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
copy(head);
copyRandom(head);
return split(head);
}
public void copy(Node head) {
Node node = head;
while(node != null) {
Node copy = new Node(node.val);
copy.next = node.next;
node.next = copy;
node = copy.next;
}
}
public void copyRandom(Node head) {
Node node = head;
while(node != null && node.next != null) {
if (node.random != null) {
node.next.random = node.random.next;
}
node = node.next.next;
}
}
public Node split(Node head) {
Node result = head.next;
Node move = head.next;
while(head != null && head.next != null) {
head.next = head.next.next;
head = head.next;
if (move != null && move.next != null) {
move.next = move.next.next;
move = move.next;
}
}
return result;
}
}
7、链表相加(值相加)
/**
* 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 addTwoNumbers(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
//进位值
int carry = 0;
//新链表
ListNode head = new ListNode(-1);
ListNode pre = head;
while (l1 != null && l2 != null) {
int number = l1.val + l2.val + carry;
carry = number / 10;
ListNode node = new ListNode(number % 10);
//组装新链表
pre.next = node;
pre = pre.next;
l1 = l1.next;
l2 = l2.next;
}
//如果l1 还有值
while (l1 != null) {
int number = l1.val + carry;
carry = number / 10;
ListNode node = new ListNode(number % 10);
pre.next = node;
pre = pre.next;
l1 = l1.next;
}
//如果l2 还有值
while (l2 != null) {
int number = l2.val + carry;
carry = number / 10;
ListNode node = new ListNode(number % 10);
pre.next = node;
pre = pre.next;
l2 = l2.next;
}
//链表相加结束,最后的值相加有进位
if (carry != 0) {
ListNode node = new ListNode(carry);
pre.next = node;
}
return head.next;
}
}
八、手写LRU缓存
class LRUCache {
private class CacheNode {
CacheNode prev;
CacheNode next;
int key;
int value;
public CacheNode(int key, int value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
private int capacity;
private Map<Integer, CacheNode> valNodeMap = new HashMap();
private CacheNode head = new CacheNode(-1, -1);
private CacheNode tail = new CacheNode(-1, -1);
public LRUCache(int capacity) {
this.capacity = capacity;
tail.prev = head;
head.next = tail;
}
public int get(int key) {
if (!valNodeMap.containsKey(key)) {
return -1;
}
CacheNode current = valNodeMap.get(key);
current.prev.next = current.next;
current.next.prev = current.prev;
moveToTail(current);
return valNodeMap.get(key).value;
}
public void put(int key, int value) {
if (get(key) != -1) {
valNodeMap.get(key).value = value;
return;
}
if (valNodeMap.size() == capacity) {
valNodeMap.remove(head.next.key);
head.next = head.next.next;
head.next.prev = head;
}
CacheNode insert = new CacheNode(key, value);
valNodeMap.put(key, insert);
moveToTail(insert);
}
private void moveToTail(CacheNode current) {
current.prev = tail.prev;
tail.prev = current;
current.prev.next = current;
current.next = tail;
}
}
解法2:LinkdHashMap实现LRU算法
//存放入的key和value
LinkedHashMap<Integer, Integer> cache;
int cap;
public LRUCache(int capacity) {
cache = new LinkedHashMap<>();
cap = capacity;
}
public int get(int key) {
if(!cache.containsKey(key)) {
return -1;
}
//访问后,要把这个key-value Entry变为最近访问的,即放到链表的最后面
int value = cache.get(key);
cache.remove(key);
cache.put(key, value);
return value;
}
public void put(int key, int value) {
//如果之前存在相同的key,那么应该更新value,且变为最近访问的
if(cache.containsKey(key)) {
cache.remove(key);
cache.put(key, value);
} else {
//如果之前不存在相同的key,那么应该放到最后面,
//但是放之前需要先看看容量是否够,如果已满,需要淘汰最近最久未访问的(链表头部)
if(cache.size() == cap) {
int firstKey = cache.keySet().iterator().next();
cache.remove(firstKey);
}
cache.put(key, value);
}
}