Typescript-Algorithms
    Preparing search index...

    Module Top-100-Liked

    双指针

    container_with_most_water

    11.盛最多水的容器

    find_all_anagrams_in_a_string
    longest_substring_without_repeating_characters
    move_zeroes
    three_sum

    二分查找

    find_first_and_last_position_of_element_in_sorted_array
    find_minimum_in_rotated_sorted_array
    find_the_duplicate_number
    search_a_2_d_matrix
    search_in_rotated_sorted_array
    search_insert_position

    回溯算法

    combination_sum

    39.组合总和

    generate_parentheses
    letter_combinations_of_a_phone_number
    n_queens
    palindrome_partitioning
    permutations
    sub_sets

    动态规划

    climbing_stairs
    coin_change
    house_robber
    longest_increasing_subsequence
    longest_palindromic_substring
    maximum_product_subarray
    maximum_subarray
    minimum_path_sum
    partition_equal_subset_sum
    perfect_squares
    unique_paths
    word_break

    贪心算法

    jump_game_ii
    partition_labels

    哈希表

    group_anagrams
    longest_consecutive_sequence
    two_sum

    数组

    first_missing_positive
    merge_intervals
    product_of_array_except_self

    前缀积 + 后缀积

    rotate_array
    subarray_sum_equals_k

    栈和队列

    daily_temperatures

    本质上是在一个数组当中不断寻找右边第一个比当前数大的。通过维持一个从栈底到栈顶单调递减的栈,栈里放着所有没找到右边第一个比自己大的元素的元素。不断查看当前数和单调栈的栈顶元素,如果当前数大于栈顶元素,那么栈顶元素就是找到了右边第一个比自己大的元素,于是结算。

    decode_string

    表达式有层次关系的字符串要想办法用“栈”。这题涉及数字和字母,所以用双栈,同时看到「[」保存状态、重置状态,看到「]」恢复状态、处理。(本质:状态机思维,碰到一类字符先累加状态。)

    find_median_from_data_stream
    kth_largest_element_in_an_array
    largest_rectangle_in_histogram

    假设当前枚举到的柱子高度是构成矩形的高度。我们需要尽可能大的扩展宽度。因此我们不断往左和右扩展矩形,碰到大于等于当前枚举到的柱子高度,我们继续扩展,否则停止扩展。

    但是我们发现扩展的过程其实就是在左右两侧寻找比第一根当前柱子矮的柱子。因此我们想到使用单调栈来处理这个问题。

    longest_valid_parentheses
    min_stack

    用两个栈,一个栈用来放数字,一个单调栈用来放当前的最小值。

    sliding_window_maximum
    top_k_frequent_elements
    trapping_rain_water

    单调栈的做法可以想象成在填水泥。维护一个从栈底到栈顶递减的单调栈。当发现当前入栈柱子高度比栈顶柱子高度大时,说明找到了右边界。于是我们尝试找左边第一个比栈顶柱子高度大的柱子,这个柱子就是左边界。怎么找呢?其实只需要看栈是否出栈后还有元素没,有的话栈顶就是左边界。这样一来只需要比较左右边界的最小值,然后减去当前柱子高度,要填的水泥高度。

    valid_parentheses

    利用栈的先进后出特性,维护一个栈,遇到左括号就入栈,遇到右括号就出栈,然后判断出栈的左括号是否和右括号匹配。为了做到匹配这件事,我们还需要一个map记录右括号到左括号的映射关系。

    链表

    add_two_numbers

    按照加法运算的规则,从低位开始相加,如果相加结果大于10,则进位。

    copy_list_with_random_pointer

    深拷贝随机链表,使用双Map + 两次链表遍历。一个oldToNew记录旧节点与新节点的映射,一个newToOld记录新节点与旧节点的映射。其中:

    • newToOld用来从新节点找到旧节点。
    • oldToNew用来从旧random节点找到新random节点。
    intersection_of_two_linked_lists

    双指针解法,本质是构造两条等长链表。假设a长度为lenAb长度为lenB,共同长度为lenC,补齐各自缺少对方的节点,构造出两条同样长的新链表。 则lenA + lenC + lenB = lenB + lenC + lenA。 由于ab两个走的路程是一样长的,把相交节点叫NodeC,则ab两者走到的终点就是NodeC。其中NodeC可以是一个节点或者null空节点。

    // A: 1 -> 2 -> 3 
    // -> C -> 4 -> 5 -> null
    // B: 6 -> 7
    // A + B: 1 -> 2 -> 3 -> C(相交点) -> 4 -> 5 -> null -> 6 -> 7 -> C(相交点) -> 4 -> 5 -> null
    // B + A: 6 -> 7 -> C(相交点) -> 4 -> 5 -> null -> 1 -> 2 -> 3 -> C(相交点) -> 4 -> 5 -> null
    linked_list_cycle

    龟兔赛跑,乌龟一次走1步,兔子一次走2步。如果链表有环,那么兔子一定会追上乌龟。因为乌龟相对于兔子来说是静止的,兔子对乌龟来说每次都在前进一步。有环的话,兔子会比乌龟先进环,等到乌龟进环的时候,假如两者相差距离为n,那么兔子在环里走n步就能追上乌龟。因为我们说了,兔子相对于乌龟来说每次都在前进一步,所以n步就能追上乌龟。

    linked_list_cycle_ii
    lru_cache

    哈希表+循环双链表

    LRU缓存

    • 空链表时:dummy ⇄ dummy

    • 插入节点A后:dummy ⇄ A ⇄ dummy

    • 插入节点B后:dummy ⇄ B ⇄ A ⇄ dummy

    • 删除节点A后:dummy ⇄ B ⇄ dummy

    • 删除节点B后:dummy ⇄ dummy

    merge_k_sorted_lists
    merge_two_sorted_lists

    尾插法,两个链表谁小谁就插到新链表的后面,直到两个链表合并完成。

    palindrome_linked_list

    寻找链表的中间节点【快慢指针】 + 反转链表【头插法】 + 对比反转部分和原链表剩余部分。

    remove_nth_node_from_end_of_list
    • 假设一把尺子,l代表尺子的左边,r代表尺子的右边。那么假如从l到r有m个节点,我们把这把尺子的右边对齐整条链表的右边。那么从左往右数是1到m个节点,则从右往左数肯定也是1到m个节点。因此,如果你要删除倒数第m个节点的话,那么l就是倒数第m个节点。

    • 为了删除掉节点B,我们需要找到B之前的节点A,通过A.next = A.next.next来删除B节点。

    • 为了解决链表长度为1的情况,我们还需要使用一个虚拟头节点来解决这个问题。

    reverse_linked_list

    设计三个指针pre、cur、nxt。其中pre指向当前节点的前一个节点,cur指向当前节点,nxt指向当前节点的下一个节点。利用头插法完成反转。这样一想,我们就能够知道pre、cur、nxt的另一层意思:

    • pre代表新链表的头节点
    • cur代表准备插入到新链表的节点
    • nxt代表下一个准备插入到新链表的节点。
    reverse_nodes_in_k_group

    “反转链表的一部分”跟“反转整条链表”不少操作是类似的。反转同样需要运用到三个指针pre、cur、nxt。其中pre指向当前节点的前一个节点,cur指向当前节点,nxt指向当前节点的下一个节点。利用头插法完成反转。

    相对于“反转整条链表”,“反转链表的一部分”需要应用到一些性质:

    • 反转链表部分结束之后,pre就是反转部分链表的头部。而cur在pre后边,它代表着反转部分的右边头部。
    • 我们把反转部分链表的尾部的前一个节点叫做p0,p0的next指向反转好的链表的尾部。 因此,反转完链表的部分后,我们还需要把p0的next节点的next指向cur,然后让p0的next指向pre,这样子才算完成了链表部分反转。

    特殊情况,当left为1的时候,反转部分的前一个节点是无法找到p0的,为了统一逻辑,我们引入一个虚拟头节点dummy,让dummy的next指向head。这样子我们就可以认为当left为1的时候,p0就是dummy。

    sort_list

    寻找链表的中间节点【快慢指针】的同时将链表分成两部分 + 归并排序【合并两个有序链表】。

    swap_nodes_in_pairs

    K个一组反转链表k=2的特殊情况,。

    二叉树

    binary_tree_inorder_traversal

    二叉树的中序遍历顺序是:左根右

    binary_tree_level_order_traversal

    层序遍历就是按照层级从上到下,从左到右遍历二叉树。它还有个名字叫广度优先搜索(BFS)。

    binary_tree_maximum_path_sum

    对于当前节点node,该节点的处的max值可能: 1、当前节点和两侧节点都要 2、当前节点和左右当中一侧的节点 3. 如果两侧贡献值都是负数,越加越小,那么直接不要两侧,也就是取0

    binary_tree_right_side_view

    BFS 层序遍历,每一层收集最右边的节点。

    construct_binary_tree_from_preorder_and_inorder_traversal
    • 利用前序遍历确定根节点,利用中序遍历确定左右子树的范围,通过递归构建二叉树。
    • 为了方便查找中序遍历的根节点,使用哈希表存储中序遍历的值和索引,建立值到索引的映射。
    convert_sorted_array_to_binary_search_tree

    二叉搜索树的中序遍历的结果就是一个升序的数组。通过二分查找,每次都找到数组的中间节点作为根节点,然后递归处理左右子树。

    diameter_of_binary_tree

    从子树中的叶子节点到当前节点的路径叫做“链”。遍历每一个节点,然后在每一层计算当前子树的最大链长,然后加上1,形成当前子树的左侧链长和右侧链长,然后更新最大值。

                     1
    dfs(l)+1 / \ dfs(r)+1
    2 3
    / \ \
    4 5 6
    flatten_binary_tree_to_linked_list

    核心思路:利用前序遍历将二叉树展开为链表,使用全局指针 cur 追踪链表末尾,按前序遍历顺序连接所有节点。

    关键点

    • 使用虚拟节点作为链表起始点
    • 前序遍历确保节点按正确顺序连接
    • 将每个节点的 left 设为 nullright 连接下一个节点
    invert_binary_tree

    翻转二叉树,就是将二叉树的左右子树交换。

    kth_smallest_element_in_a_bst

    利用二叉搜索树的中序遍历结果是升序的特性,则第K小的元素就是中序遍历的第K个元素。

    lowest_common_ancestor_of_a_binary_tree

    利用递归遍历二叉树,如果当前节点是p或q之一,或者p、q分别在左右子树中,则当前节点就是最近公共祖先。否则继续在包含p、q的子树中递归查找。

    maximum_depth_of_binary_tree

    二叉树的深度就是所有的根节点到叶子节点的节点数的最大值。

    path_sum_iii
    • 与“和为K的连续子数组”解法一致。将每个遍历到的node作为终点,统计有多少个起点到终点node的路径总和恰好等于targetSum。再转化成前缀和问题。
    • 给了“路径”限制:从上到下,也就是父到子的方向。
    • DFS 遍历这棵树,遍历到节点 node 时,假设 node 是路径的终点,那么有多少个起点,满足起点到终点node的路径总和恰好等于targetSum
    symmetric_tree

    如果一颗二叉树的所有子树的左右子树都轴对称,那么这棵二叉树就轴对称。

    validate_binary_search_tree

    利用二叉搜索树的中序遍历结果是升序的特性,来判断是否是二叉搜索树。

    网格图

    course_schedule

    三色标记法 + dfs

    implement_trie_prefix_tree

    Trie树

    number_of_islands

    四个方向dfs插旗

    rotate_image

    观察交换规律,看成上下左右,每个方向控制几个数。比如3*3的矩阵,上下左右分别控制2个数。t, b, l, r 指针围成一个圈,t控制上,b控制下,l控制左,r控制右。比如:

    3*3矩阵

    可以看成是t控制了第一排前两个数(1, 2),r控制了最后一列的前两个数(3, 6)。b控制了最后一排从右数的两个数(9, 8),l控制第一列的从下数的前两个数(7, 4)。错开来看,规律就出来了:

    • 第1轮-第1次

      • 坐标在 (b + 0, l) 的元素来到坐标在 (t, l + 0) 的元素位置,
      • 坐标在 (t, l) 的元素来到坐标在 (t, r) 的元素位置,
      • 坐标在 (t + 0, r) 的元素来到坐标在 (b, r) 的元素位置。
    • 第1轮-第2次

      • 坐标在 (b + 1, l) 的元素来到坐标在 (t, l + 1) 的元素位置
      • 坐标在 (t, l + 1) 的元素来到坐标在 (t + 1, r) 的位置
      • 坐标在 (t + 1, r) 的元素来到坐标在 (b, r - 1) 的位置

    次数的确定由 r - l 决定,或者 b - t 决定。而具体进行多少轮?只要 b > t && l < r 就继续,否则停止。

    rotting_oranges
    search_a_2_d_matrix_ii

    从右上角开始找,这样子判断一个数要么比target大,要么比target小,要么就是target。

    • 比target大,说明当前数所在列一定不可能了,因为这列最小的数都比target要大了,把这列消除掉
    • 比target小,说明当前数所在行一定不可能了,因为这行最大的数都比target要小了,把这行消除掉

    重复上述过程,消除行、消除列。直到找到为止或者越界为止。

    set_matrix_zeroes
    • 原地解法:利用矩阵本身作为辅助空间,将需要置零的行和列标记为 Infinity,最后再遍历矩阵将 Infinity 置零。
    • 辅助空间解法:利用辅助空间记录需要置零的行和列,最后再遍历矩阵将需要置零的行和列置零。
    spiral_matrix
    • 设定t,b,l,r四个边界分别代表上、下、左、右,同时也代表一个圈,每次将整个圈遍历完,然后更新边界,缩小这个圈。
    • 接下来根据不同解法,有不同的实现方式:
      • 标记解法:将已经访问过的元素标记为 Infinity,不需要判断越界的情况。
      • 不标记解法:在遍历的过程当中还要判断越界的情况,比如只有一行,或者一列,t更新后,t>b,或者l更新后,l>r,那么就说明没有下一圈了,直接退出。不然会导致重复遍历。

    其他

    next_permutation
    single_number