算法训练-Week01

311 阅读6分钟

Week01 学习笔记

1 学习算法方法

1.1 课程学习流程

  • 课前预习,把ppt先过一遍
  • 倍速看视频,不懂的地方暂停反复看
  • 做课后练习题,刻意练习
  • 构建自己的知识图谱,逐渐完善

1.2 做题流程 五毒神掌

  • 5分钟看题,不要着急做,先理解题目
  • 10分钟内想不出来,看题解,理解别人的代码
  • 自己写一遍,尽量不再看题解
  • 自己AC之后,看国际站上的高分解题方法
  • 回来自己再做一遍
  • 一天后,一周后再来做一遍,忘记了就再重复一遍上面的流程,直到不看题解就能做出来
  • 面试前集中过一遍

1.3 数据结构与算法脑图

数据结构与算法脑图

2 编程习惯

  • 设置好最适合自己的编程环境
  • 摒弃之前的坏习惯
  • 自顶向下的编程方式,结构清晰

3 复杂度分析

3.1 时间复杂度

只看最高的复杂度

常见的时间复杂度

  • O(1) 常数级别,如散列表操作
  • O(log n) 对数级别,如二分查找
  • O(n) 线性级别,如计数排序
  • O(n^2) 平方级别,如冒泡排序
  • O(2^n) 指数级别,如斐波拉契递归
  • O(n!) 阶乘

3.2 空间复杂度

有没有额外申请空间,原地操作为O(1)

  • 数组的长度
  • 递归的深度

4 数组、链表、跳表

4.1 数组

  • 随机访问,O(1)时间复杂度
  • 插入删除,O(n)时间复杂度 (涉及到元素的搬移)

Java中的ArrayList就是对数组的封装,默认数组大小是10, 每次插入都会进行元素的copy, 如果大小不够,就会进行扩容,申请一个当前大小二倍的空间,再把当前元素copy过去。

4.2 链表

  • 查找,O(n)时间复杂度
  • 插入删除,O(1)时间复杂度

Java中的LinkList使用双向链表来实现

添加节点操作

  1. new.next = node.next
  2. node.next = new

删除节点操作

  1. node.pre.next = node.next
  2. node.next = null

4.3 跳表

  • 插入删除查找,O(log n)时间复杂度

只能用于有序列表

通过空间换时间,增加索引,优化链表的查找速度,空间复杂度O(n)

对标的是平衡树(AVL)和二分查找

实际应用有:Redis、LevelDB等

5 栈、队列、双端队列、优先队列

5.1 栈

  • 后进先出
  • 添加删除,O(1)时间复杂度

Java中Stack的API,其内部通过数组实现

  • peek() 查看栈顶元素
  • pop() 出栈
  • push(e) 入栈
  • search(e) 搜索栈内元素,返回该元素在栈内的深度,即下标+1

5.2 队列

  • 先进先出
  • 添加删除,O(1)时间复杂度

Java中的Queue是一个接口,他的实现有好多种,如LinkedListPriorityQueueLinkedBlockingQueue

抛出异常 返回值 说明
add(e) offer(e) 入队
remove() poll() 出队
element() peek() 查看队首元素

5.3 双端队列

  • 两端都可以进出的队列
  • 添加删除,O(1)时间复杂度

Java中的Deque也是一个接口,他继承了Queue

抛出异常 返回值 说明
addFirst(e) offerFirst(e) 在队头添加元素
addLast(e) offerLast(e) 在队尾添加元素
getFirst() peekFirst() 查看队首元素
getLast() peekLast() 查看队尾元素
removeFirst() pollFirst() 删除队首元素
removeLast() pollLast() 删除队尾元素

5.4 优先队列

  • 可以按优先级出队
  • 插入,O(1)时间复杂度
  • 取出,O(log n)时间复杂度
  • 底层实现有:堆(heap)、二叉树搜索树(bst)等

Java中的是PriorityQueue,继承AbstractQueue,最终实现了Queue

6 本周作业

6.1 用 add first 或 add last 这套新的 API 改写 Deque 的代码

Deque<String> deque = new LinkedList<>();

// 双端队列 实现 队列 先进先出
deque.addLast("a");
deque.addLast("b");
deque.addLast("c");
System.out.println(deque);

String peek = deque.peekFirst();
System.out.println(peek);
System.out.println(deque);

while (deque.size() > 0) {
    System.out.println(deque.pollFirst());
}

System.out.println(deque);

// 双端队列 实现 栈 后进先出
deque.addFirst("a");
deque.addFirst("b");
deque.addFirst("c");
System.out.println(deque);

String peek2 = deque.peekFirst();
System.out.println(peek2);
System.out.println(deque);

while (deque.size() > 0) {
    System.out.println(deque.pollFirst());
}

System.out.println(deque);

6.2 分析 Queue 和 Priority Queue 的源码

6.2 .1 Queue

Java中的Queue是一个接口,继承了Collection接口

主要方法有:

// 添加一个元素,添加失败会抛出异常 IllegalStateException
boolean add(E e);

// 添加一个元素,添加失败返回false
boolean offer(E e);

// 删除队首元素,删除失败会抛出异常 NoSuchElementException
E remove();

// 删除队首元素,删除失败返回null
E poll();

// 查看队首元素,不存在则抛出异常 NoSuchElementException
E element();

// 查看队首元素,不存在返回null
E peek();

6.2.2 Priority Queue

Java中的PriorityQueue继承了AbstractQueue,类关系图如下:

PriorityQueue继承关系

主要看一下他的入队和出对方法

  • 入队offer(e)
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    // 修改次数加一,无论添加还是删除,这个值都会加一
    modCount++;
    int i = size;
    // 判断是否需要扩容,默认数组大小是11
    // 扩容会比较是否原始大小是否小于64,如果小于扩容一倍大小,否则扩容50%
    if (i >= queue.length)
        grow(i + 1);
    siftUp(i, e);
    // 大小加一
    size = i + 1;
    return true;
}

// 扩容
private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1));
    // overflow-conscious code
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
}

// 将元素x插入到最小二叉堆的k位置
private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x, queue, comparator);
    else
        siftUpComparable(k, x, queue);
}

private static <T> void siftUpComparable(int k, T x, Object[] es) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    while (k > 0) {
        // 找到插入位置的父结点
        int parent = (k - 1) >>> 1;
        Object e = es[parent];
        // 如果插入结点比父结点大,则不用调整位置
        if (key.compareTo((T) e) >= 0)
            break;
        // 如果插入数据比父结点大,将插入结点跟父结点数据互换
        es[k] = e;
        k = parent;
    }
    // 最后找到了调整后的位置
    es[k] = key;
}

// 和siftUpComparable相同,只不过使用传入的比较器
private static <T> void siftUpUsingComparator(int k, T x, Object[] es, Comparator<? super T> cmp) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = es[parent];
        if (cmp.compare(x, (T) e) >= 0)
            break;
        es[k] = e;
        k = parent;
    }
    es[k] = x;
}
  • 出队poll()
public E poll() {
    final Object[] es;
    final E result;

    if ((result = (E) ((es = queue)[0])) != null) {
        modCount++;
        final int n;
        // 先减少堆大小,把最后位的数据缓存在x里
        final E x = (E) es[(n = --size)];
        es[n] = null;
        if (n > 0) {
            // 把x跟0位置及其子结点循环比较,直到x插入到了合适位置
            final Comparator<? super E> cmp;
            if ((cmp = comparator) == null)
                siftDownComparable(0, x, es, n);
            else
                siftDownUsingComparator(0, x, es, n, cmp);
        }
    }
    return result;
}

private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
    // assert n > 0;
    Comparable<? super T> key = (Comparable<? super T>)x;
    int half = n >>> 1;           // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = es[child];
        int right = child + 1;
        if (right < n && ((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
            c = es[child = right];
        if (key.compareTo((T) c) <= 0)
            break;
        es[k] = c;
        k = child;
    }
    es[k] = key;
}

private static <T> void siftDownUsingComparator(
    int k, T x, Object[] es, int n, Comparator<? super T> cmp) {
    // assert n > 0;
    int half = n >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = es[child];
        int right = child + 1;
        if (right < n && cmp.compare((T) c, (T) es[right]) > 0)
            c = es[child = right];
        if (cmp.compare(x, (T) c) <= 0)
            break;
        es[k] = c;
        k = child;
    }
    es[k] = x;
}