对链表进行插入排序
- 思路 : 插入排序的基本思想 两个指针 l r [0,l] 维护有序区间 [r,n] 维护无序区间 r == l + 1(1) 初始 l == 0 有序序列只有一个元素 无序序列指的是 :除了第0 个元素 全是无序序列 一个元素默认为有序。
- 插入排序 : 打了直接往后扩 小了 寻找合适的位置。
- 这里要搞清楚 数组和链表的区别 : 数组 头插 插入元素 有序序列元素要往后移动、 而链表 直接更新 指针即可 对链表而言 增删快 查找慢 对 数组而言 查找快 增删慢 。
- 涉及到头插 : 为了使节点的插入操作具有一致性 引入 虚拟头节点 dump
- 类似 数组操作 维护两个指针 l r 维护 两个区间 链表区间的特殊性 称 l 及 遍历过的 成为有序区间
- 比他大 扩 比他小 找合适插入位置
图示
code
class Solution {
public ListNode insertionSortList(ListNode head) {
if (head == null || head.next == null) return head;
// 维护 已经有序的区间
ListNode l = head;
// 维护 无序的区间
ListNode r = l.next;
ListNode dump = new ListNode();
dump.next = head;
// 最后一个元素不为空的条件
while ( l.next != null) {
r = l.next;
if (r.val >= l.val) {
l = r;
r = r.next;
continue;
}
// 寻找将要插入的节点位置
ListNode curInserted = dump;
while (curInserted.next.val < r.val) curInserted = curInserted.next;
//与数组不同的是 链表 调整指针这块
// 首先 更新区间
l.next = r.next;
// 调整位置 插入节点对应位置的指向下一位绝对是相同的
r.next = curInserted.next;
// 成功插入到当前位置。
curInserted.next = r;
// l 永远是 有序右边界
r = l.next;
}
return dump.next;
}
}
复制带随机指针的链表
- 思路 :
- 解法一 哈希表法 存储键值对 直接复制 双 O(N)
- 解法二 省去哈希表空间 达到 空间复杂度 O(1) 时间复杂度 O(n)
- 具体实现 : 1、复制每个节点的值 并 连接到 该节点之后 例子 :1 -> 2 -> 3 复制为
- 1 - > 1' -> 2 -> 2' -> 3 -> 3'
- 2、 复制他的next 指针 和 他的random 指针
- 3、 split 拆分 两个 链表
- 4、 返回复制的链表
code
解法一
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return head;
Node p = head;
Map<Node,Node> map = new HashMap<>();
Node cloneNode = null;
// 存储哈希映射
while (p != null) {
cloneNode = new Node(p.val);
map.put(p,cloneNode);
p = p.next;
}
p = head;
// 开始复制 通过 键 的 next 和 random 指针 来进行 链表的复制
while (p != null) {
map.get(p).next = map.get(p.next);
map.get(p).random = map.get(p.random);
p = p.next;
}
return map.get(head);
}
}
解法二
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return head;
Node p = head;
Node cloneNode = null;
Node next = null;
while (p != null) {
next = p.next;
cloneNode = new Node(p.val);
cloneNode.next = next;
p.next = cloneNode;
p = next;
}
// 检验是否复制成功
// for (p = head;p != null; p = p.next) System.out.print(p.val + " ");
// 复制 随机 指针
for (p = head; p != null ; p = p.next.next) {
cloneNode = p.next;
cloneNode.random = p.random == null ? null : p.random.next;
}
// 分割 链表
Node newHead = head.next;
for (p = head ; p != null ; ) {
next = p.next.next;
cloneNode = p.next;
p.next = next;
cloneNode.next = next == null ? null : next.next;
p = next;
}
return newHead;
}
}
环形链表 II
- 思路 :
- 解法一 放哈希表 匹配一遍找记录 找到了 就是入环点
- 解法二 与 [环形链表] 思路相同 在 day02 有介绍过 这里有个 细节 是 起点必须是相同的 如果不同的话 虽然可以走到一起 证明有环 但是 在后面的 while(s != f) 找寻环的起点的时候 一步一步走就会出错 造成死循环 / 或者是 答案错误。 你可以尝试 在结束时 s / f 走掉少掉或者多掉的那一步,这样就不会出错了。 还有一个时 如果 环的起点在 第二个 、初始值设置为 head.next 和 head.next 是行不通的 也会造成死循环
code
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode s = head;
ListNode f = head;
while ( f != null && f.next != null) {
s = s.next;
f = f.next.next;
if (s == f) break;
}
if (f == null || f.next == null) return null;
s = head;
while (s != f) {
s = s.next;
f = f.next;
}
return s;
}
}
排序链表
- 归并排序 :思想 : 分:划分成很多个小的问题,然后递归处理,治:将分阶段得到的答案整合起来
- 将 每层的 n 个元素 都划分为 2 / n 的 元素 , 知道 元素个数为 1 时 即为有序 ,然后合并.
- 具体代码 如下
图解
code
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null ) return head;
ListNode s = head;
ListNode f = head.next;
// f != null 偶数的情况下 它可以跑到 null
while (f != null && f.next != null) {
s = s.next;
f = f.next.next;
}
f = s.next;
s.next = null;
ListNode left = sortList(head);
ListNode right = sortList(f);
return merge(left,right);
}
private ListNode merge(ListNode left , ListNode right) {
ListNode dump = new ListNode();
dump.next = left;
ListNode p = dump;
while (left != null && right != null) {
if (left.val <= right.val) {
p.next = left;
left = left.next;
}else {
p.next = right;
right = right.next;
}
p = p.next;
}
if (left != null) p.next = left;
if (right != null) p.next = right;
return dump.next;
}
}
LRU 缓存
-
思路 : put (int key , int value) 键值对形式 自然要用到 哈希表
-
最近最少使用原则 :最少使用的元素 应该 很快找到并删除 这有个 时间的问题 和 寻找的问题。从头插一直插到尾部、最久远的应该距离链表头部更近。 这时候想快速找到并删除它,应该从 链表头部开始找、 找到并删除他 、 尾部插入最新的数据 (这里的最新数据包括 put 过 和 get 过的两种类型的数据 都应该放在尾部)。 可以知道 需要大量的删除操作 使用双向链表更为合理 单链表 每次删除 需要进行遍历 显得很笨重
code (这个 code 只是击败了 双 5% 有待提高 日后再看)
class LRUCache {
public Map<Integer,Node> map ;
public LinkedList<Node> list ;
int n ;
public LRUCache(int capacity) {
n = capacity;
list = new LinkedList<>();
map = new HashMap<>();
}
// 提到过 get 后的数据属于新数据 应当查到 tail 前
public int get(int key) {
System.out.println("get : " + key);
if (!map.containsKey(key)) return -1;
Node cur = map.get(key);
remove(cur);
insert(cur);
return cur.val;
}
public void insert(Node node ) {
list.addLast(node);
}
public void remove(Node node) {
list.remove(node);
}
public void put(int key, int value) {
if (map.containsKey(key)) {
System.out.println("update");
Node cur = map.get(key);
remove(cur);
cur.val = value;
insert(cur);
System.out.println(list.size());
}else {
if (list.size() >= n) {
System.out.println("空间超出 溢出缓存");
int k = list.removeFirst().key;
map.remove(k);
}
System.out.println("put");
map.put(key,new Node(key,value));
insert(new Node(key,value));
}
}
static class Node {
int val;
int key;
public Node() {}
public Node(int key, int val) {
this.key = key;
this.val = val;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node node = (Node) o;
return val == node.val && key == node.key;
}
@Override
public int hashCode() {
return Objects.hash(val, key);
}
}
}
链表篇 完结