算法刷题 链表专题_day03

111 阅读2分钟

对链表进行插入排序

  • 思路 : 插入排序的基本思想 两个指针 l r [0,l] 维护有序区间 [r,n] 维护无序区间 r == l + 1(1) 初始 l == 0 有序序列只有一个元素 无序序列指的是 :除了第0 个元素 全是无序序列 一个元素默认为有序。
  • 插入排序 : 打了直接往后扩 小了 寻找合适的位置。
  • 这里要搞清楚 数组和链表的区别 : 数组 头插 插入元素 有序序列元素要往后移动、 而链表 直接更新 指针即可 对链表而言 增删快 查找慢 对 数组而言 查找快 增删慢 。
  • 涉及到头插 : 为了使节点的插入操作具有一致性 引入 虚拟头节点 dump
  • 类似 数组操作 维护两个指针 l r 维护 两个区间 链表区间的特殊性 称 l 及 遍历过的 成为有序区间
  • 比他大 扩 比他小 找合适插入位置

图示

1d373975ba7e2d128832178d2d02c84.jpg

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;
    }
}

排序链表

image.png

  • 归并排序 :思想 : 分:划分成很多个小的问题,然后递归处理,治:将分阶段得到的答案整合起来
  • 将 每层的 n 个元素 都划分为 2 / n 的 元素 , 知道 元素个数为 1 时 即为有序 ,然后合并.
  • 具体代码 如下

图解 image.png

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);
       }
   }
}

链表篇 完结