Skip to content

算法练习笔记 I

以此笔记本记录算法题目的解题思路与题型分类。

本笔记本以实战编程为主,理论与思想主要写在《算法导论》阅读笔记中。

[toc]

ACM模式

一般情况下,会给多组数据!

一组数据的格式会给定。

import sys
for line in sys.stdin:
    a = line.split()
    print(a)

strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。

单行输入

in_list = list(map(lambda x:int(x),input().strip().split()))
print(in_list)

多行输入

while True:
        try:
            line = input().strip()
            print(line)
            # 指定情况终止
            if line == '0 0':
                break
        except:
            break

数据结构

链表-两数之和

https://leetcode-cn.com/problems/add-two-numbers/

前缀树Trie

Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较。

class TrieNode:
    """
    前缀树的链式存储
    """
    def __init__(self):
        self.val = 0
        self.next = [None for _ in range(26)]

找出自除数

https://leetcode-cn.com/problems/self-dividing-numbers/

这道题需要解决的问题是如何找出n的每一位数?

  1. str()
  2. n,d = divmod(n,10)

线段树

线段树是一个二叉树。

节点保存 [start,end]的最小值,最大值,和总和等信息

给定数组:[1,5,3,7,3,2,5,7]

segment tree interval representation

线段树的构建是自顶向上的!!

**差分数组

https://leetcode-cn.com/problems/minimum-moves-to-make-array-complementary/

我们要维护一个数组;

对数组的操作是 nums[a:b]这个区间内所有的元素+1;

为了避免 for n in nums[a:b],可以使用差分数组,diff[a]+1,diff[b]-1来记录

nums[i] = sum(diff[:i])

双指针,滑动窗口

排序+双指针

https://leetcode-cn.com/problems/3sum/

本来是N\^3算法,通过双指针降低为N\^2。

如果相邻两个颜色均相同则删除

https://leetcode-cn.com/problems/remove-colored-pieces-if-both-neighbors-are-the-same-color/

使用一个滑动窗口来寻找连续的相同颜色,并记录。

有效的山脉数组

https://leetcode-cn.com/problems/valid-mountain-array/

双指针夹心

和大于等于target的最短子数组

滑动窗口模板

初始化左边界 left = 0
初始化返回值 ret = 最小值 or 最大值
for 右边界 in 可迭代对象:
    更新窗口内部信息
    while 根据题意进行调整:
        比较并更新ret(收缩场景时)
        扩张或收缩窗口大小
    比较并更新ret(扩张场景时)
返回 ret

仅仅翻转字母

https://leetcode-cn.com/problems/reverse-only-letters/

使用双指针或者栈

栈是一个高效的翻转工具。

乘积小于k的子数组

https://leetcode-cn.com/problems/subarray-product-less-than-k/

滑动窗口,主要问题是,窗口扩张和未扩展之前的子数组增加数量的计算。

搜索旋转排序数组

当数组是局部有序的,还是可以使用二分查找!

盛最多水的容器

双指针,对于盛水问题,双指针收缩时有一个原则:

放弃当前的短板,往内侧收缩才有可能使得盛更多的水!

二分查找

def binary_search(a,target):
    l = 0
    r = len(a)# 二分查找并不包括右边界
    while l<r-1:
        M = (L + R) // 2
        #逻辑操作
        #对比a[M]与target的大小
        #更新L或者R
        #更新要注意等号条件,等号应该给到左边界
    return L

每个小孩最多能分到多少糖果

https://leetcode-cn.com/problems/maximum-candies-allocated-to-k-children/

绝对插值和(非侵入式二分)

最原始的遍历方法是 10^5 *10^5 , O(n^2) 的方法,通过引入二分查找来做成O(nlgn),一般情况下,n=10^5

二分查找时候要注意侵入与非侵入:

当我们的范围定义为[l,r) 的时候,l = mid 保留了这个mid值,但是r=mid就会删除mid值。为了保留mid,要做r=mid+1

递归算法

二叉树

二叉树有两种存储方式,一种是用指针存储,一种是使用顺序存储。

顺序存储:如果一个结点在数组的下标为 node,那么它的左子结点下标为 \textit{node} \times 2 + 1,右子结点下标为 node×2+2

img

Left = index * 2 + 1

Right = index * 2 + 2

Parent(i) = floor((i-1)/2)

二叉函数展开为链表

后序遍历

https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/

二叉搜索树的序列化与反序列化

二叉搜索树的特点:左小右大

https://leetcode.cn/problems/serialize-and-deserialize-bst/

二叉搜索树第k小的元素

https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/

二叉搜索树的中序遍历,得到升序数组。

通过中序遍历,后序遍历的数组构建二叉树

https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/

通过后续遍历找到当前root,再通过root在中序遍历中找左右子树。递归构建

回溯算法

分割回文串

https://leetcode-cn.com/problems/palindrome-partitioning/

模拟一刀一刀地切过程,保证每一次切时,前面的字符是回文串。

迷路的机器人

https://leetcode-cn.com/problems/robot-in-a-grid-lcci/

记录下已经回溯过的点,避免重复探索。

零钱兑换

每次选择一种硬币,path为选择的硬币。

https://leetcode-cn.com/problems/coin-change/

深度优先搜索

迷你语法分析器

https://leetcode-cn.com/problems/mini-parser/solution/mi-ni-yu-fa-fen-xi-qi-by-leetcode-soluti-l2ma/

深度优先搜索或者栈。

找逆序对,归并排序

https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/

左指针每次右移,都计算其“贡献度”。

如果右边全部大于左边,左指针贡献度一直是0

如果有右边的先走了,他会比后来入队的左指针小,每次它都贡献1。

广度优先搜索

最大人工岛问题

https://leetcode-cn.com/problems/making-a-large-island/

对于每个算出面积的岛屿可以为它打上独一无二的标签,以方便计算后面的面积扩张与避免重复。

动态规划

最大公共子数组问题

贪心算法

排布二进制网格的最少交换次数

https://leetcode-cn.com/problems/minimum-swaps-to-arrange-a-binary-grid/

翻卡片游戏

https://leetcode-cn.com/problems/card-flipping-game/

任何正反面不重复的卡都可能是答案!

美化数组中的最小删除数字

https://leetcode-cn.com/problems/minimum-deletions-to-make-array-beautiful/

贪心的思想

小trick

找到指定长度的回文数

https://leetcode-cn.com/problems/find-palindrome-with-fixed-length/

回文数组可以使用字符串进行构建。

长度为5的回文数中,第n大的为:100+n-1

这是解题的关键

分类讨论算法:强密码检验器

https://leetcode-cn.com/problems/strong-password-checker/solution/qiang-mi-ma-jian-yan-qi-by-leetcode-solu-4fqx/

困难题

情况1,长度小于6

情况2,长度大于等于6,小于等于20

情况3,大于20

分类讨论算法:HTML实体解析器

&XX; 匹配这样的字符串,不能从前向后匹配,因为&后面可能没有;也可能有&。但是; 前面匹配唯一一个&。

密码验证合格程序:

https://www.nowcoder.com/practice/184edec193864f0985ad2684fbc86841?tpId=37&tqId=21243&rp=1&ru=/exam/oj/ta&qru=/exam/oj/ta&sourceUrl=%2Fexam%2Foj%2Fta%3FtpId%3D37&difficulty=undefined&judgeStatus=undefined&tags=&title=

模拟:删除第p个元素

如何判断是否为质数

一个大于1的自然数,除了1和它本身外,不能被其他自然数(质数)整除(2, 3, 5, 7等),换句话说就是该数除了1和它本身以外不再有其他的因数。

把字母进行二进制编码进行哈希

如何把一个字母编码到一个二进制中?

使用位运算:

bin(1 << (ord('b') - ord('a')))

使用数字计算:

i = 0
for char in set(word):
    n = ord(char) - ord("a")
    i += 2 ** n
print(bin(i))

使用any和all

all([1,1,1]) = True
any([1,0,0]) = True

如何按列解包:

[col for col in zip(*board)]

排列组合的计算:数字中各个digit都不同的个数

n=0,1个

n=1, 10个

n=2,91个

n>=2时,假设第一位不用0:

第n位为 $$ 9*A_{9}^{n-1} $$ 所以递推公式为: $$ res =9\times A_{9}^{n}+...+9\times A_{9}^{2}+9\times A_{9}^{1}+10 $$

曼哈顿距离:最短路径

https://leetcode-cn.com/problems/escape-the-ghosts/

需要计算多个点到一个target的最短路径,而且这个地图是无限的,没有障碍的。

不要被广度优先算法禁锢住思想,使用曼哈顿距离直接计算出最短路径!

O(1) 时间插入、删除和获取随机元素

为了确保严格 O(1),我们不能使用拒绝采样和在数组非结尾位置添加/删除元素

维护一个哈希表;

一个数组,为了方便地修改,我们要方便地找到数的index

hash[num] = index 用这样的哈希表来维护数组。

random.choice()

前缀树:键值映射

https://leetcode-cn.com/problems/map-sum-pairs/

class TrieNode:
    def __init__(self):
        self.val = 0
        self.next = [None for _ in range(26)]

暴力遍历中的缩小搜索空间:最大回文数乘积

https://leetcode-cn.com/problems/largest-palindrome-product/

n位的回文数字,这个范围实际上并不算大,可以暴力遍历。

找公约数的过程要注意减小搜索空间。

双向扫描:字符的最短距离

https://leetcode-cn.com/problems/shortest-distance-to-a-character/

问题转化

ans = min(和左边c的距离,和右边c的距离)

假设两个哨兵c

文件的最长绝对路径

https://leetcode-cn.com/problems/longest-absolute-file-path/

处理文件字符串的时候,会遇到 \n\t等字符,注意转义。

深度优先搜索可以使用栈。

贪心:和为 K 的最少斐波那契数字数目

https://leetcode-cn.com/problems/find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k/

贪心算法,斐波那契数增长的很快,不必担心数据大小问题。

分治:漂亮数组

https://leetcode-cn.com/problems/beautiful-array/

难在将问题分割为子问题。

寻找并简化递归关系:旋转函数

https://leetcode-cn.com/problems/rotate-function/

注意要将递归关系简化成方便计算的形式。

(未完成)求凸包:安装栅栏

https://leetcode-cn.com/problems/erect-the-fence/solution/an-zhuang-zha-lan-by-leetcode-solution-75s3/

为了便于理解,本次使用 O(n^2)的算法。

首先找到最左边的一个点。

我们要找到“最外侧的点”:

​ 比如:最左侧点为A,怎么找到B点是最外侧呢?

​ 假定B‘ 点,然后计算其他点R:是否存在R,让B'R 与 AB’ 向量的夹角大于180度?

​ 如果存在就说明这个B‘点不合格,R点在更外侧。

如何判断两个向量的夹角大小呢?使用向量叉积。

自定义排序,自定义比较规则:重新排列日志文件

https://leetcode-cn.com/problems/reorder-data-in-log-files/

对于需要排序的问题,有些时候有比较优先级,可以自己定义一个比较函数,用于比较。

def func():

    return (priority1,priority2,..,)

sort(key=func)

队列模拟,约瑟夫环:丢手绢

https://leetcode-cn.com/problems/find-the-winner-of-the-circular-game/

递归,约瑟夫环:

第一次排除第k个人,第二次还是第k个,人的总数从n变为n-1

贪心:增减字符串匹配

https://leetcode.cn/problems/di-string-match/

贪心

二分查找:搜索二维矩阵 II

定位有序数组,在改数组进行二分。

https://leetcode.cn/problems/search-a-2d-matrix-ii/