Luna Tech

Tutorials For Dummies.

学习算法和刷题的框架思维

2021-07-20


0. 参考文章

学习算法和刷题的框架思维


1. 只有两种数据结构的存储方式

  1. 数组(顺序存储)
  2. 链表(链式存储)

而数据结构则可以用不同的存储方式去实现。

比如「图」的两种表示方法,邻接表就是链表,邻接矩阵就是二维数组。

数组可以节省空间,但是操作往往比较复杂(因为需要缩容扩容),而链表操作简单,但所需空间更多(要存储指针)。

Redis 提供列表、字符串、集合等等几种常用数据结构,但是对于每种数据结构,底层的存储方式都至少有两种,以便于根据存储数据的实际情况使用合适的存储方式。

我们常常会因为看到各种数据结构的名词而觉得头晕,其实换个角度思考,不管什么数据结构,都要用这两种存储方式来实现,这样一来是不是就豁然开朗了呢~

数组和链表的优缺点

数组(连续存储)

链表(存储空间不连续)


2. 四种数据结构的操作方式

之前也做过类似的总结,无外乎遍历+访问,也就是增删查改这四个基本操作。

数据结构存在的意义,就是在不同的场景下满足高效的增删查改需求。

遍历 + 访问的两种形式

  1. 线性(迭代 for/while)
  2. 非线性(递归 recursive)

几种遍历框架

数组(线性迭代)


void traverse(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        // 迭代访问 arr[i]
    }
}

链表(线性迭代)

/* 基本的单链表节点 */
class ListNode {
    int val;
    ListNode next;
}

void traverse(ListNode head) {
    for (ListNode p = head; p != null; p = p.next) {
        // 迭代访问 p.val
    }
}

链表(非线性递归)


/* 基本的单链表节点 */
class ListNode {
    int val;
    ListNode next;
}

void traverse(ListNode head) {
    // 递归访问 head.val
    traverse(head.next);
}

二叉树(非线性递归)

/* 基本的二叉树节点 */
class TreeNode {
    int val;
    TreeNode left, right;
}

void traverse(TreeNode root) {
    // 前序遍历代码位置
    traverse(root.left)
    // 中序遍历代码位置
    traverse(root.right)
    // 后序遍历代码位置
}

N 叉树(非线性递归)

/* 基本的 N 叉树节点 */
class TreeNode {
    int val;
    TreeNode[] children;
}

void traverse(TreeNode root) {
    for (TreeNode child : root.children)
        traverse(child);
}

图(非线性递归)

Todo: 补充图的遍历框架

N 叉树的遍历又可以扩展为图的遍历,因为图就是好几 N 叉棵树的结合体。你说图是可能出现环的?这个很好办,用个布尔数组 visited 做标记就行了.


3. 怎么刷 leetcode?

数据结构是工具,算法是通过合适的工具解决特定问题的方法。

学习算法之前,最起码得了解那些常用的数据结构,了解它们的特性和缺陷。

作者建议先刷二叉树,因为二叉树是最容易培养框架思维的,而且大部分算法技巧,本质上都是树的遍历问题。

刷完整个「二叉树专题」再去做「回溯、动规、分治专题」,因为只要涉及递归的问题,都是树的问题