
算法
五道口纳什
wx公众号/B站:五道口纳什
展开
-
斐波那契 —— 矩阵形式推导
https://blog.youkuaiyun.com/lanchunhui/article/details/505693111. 矩阵形式的通项(Fn+2Fn+1)=(1,1,10)⋅(Fn+1Fn)(Fn+2Fn+1)=(1,11,0)⋅(Fn+1Fn)\begin{pmatrix}F_{n+2}\\F_{n+1}\end{pmatrix}=\begin{pmatrix}1,&1\\1...原创 2018-07-07 17:16:40 · 9826 阅读 · 0 评论 -
数学归纳法证明时间复杂度
1. T(n)=1+∑j=0n−1T(j)T(n)=1+\sum\limits_{j=0}^{n-1}T(j)欲证明 T(n)=2nT(n)=2^n(为了简化问题的方便,这里忽略了问题的背景信息,边界条件:T(0)=1T(0)=1)。已知边界条件:T(0)=20=1T(0)=2^0=1,由数学归纳法,化结论为条件,则有:T(n)====1+∑j=0n−1T(j)1+∑j=0n−12j1+20+21+原创 2017-07-26 14:33:10 · 2453 阅读 · 0 评论 -
中位数与顺序统计量
1. 基本定义在一个由 nn 个元素组成的集合中,第 ii 个顺序统计量(order statistic)是该集合中第 ii 小的元素。如,在一个元素集合中,最小值是第一个顺序统计量(i=1i=1),最大值是第 nn 个顺序统计量(i=ni=n)。用非形式化的描述来说,一个中位数(median)是它所属集合的中间元素,当 nn 为奇数时,中位数唯一,位于 i=n+12i=\frac{n+1}2 处原创 2017-07-20 09:12:09 · 1086 阅读 · 0 评论 -
渐进记号的相关证明(使用极限的方式)
极限的方式对渐进记号的相关证明方式更为直观简洁。原创 2017-02-28 22:14:42 · 3501 阅读 · 0 评论 -
算法 Tricks(六)—— 判断一个数是否为完全平方数
int(sqrt(n)) * int(sqrt(n)) == n ? 1:0;原创 2016-11-17 17:29:32 · 4970 阅读 · 0 评论 -
算法 Tricks(五)—— 二进制逻辑运算
判断某数的二进制形式的某位(第 k 位)是否为 1,将其与 2k2^k 相与;将某数的二进制形式的某位(第 k 位)置 1,将其与 2k2^k 相或;原创 2016-09-22 08:29:12 · 1411 阅读 · 0 评论 -
算法 Tricks(四)—— 获取一个数二进制形式第一个不为 0 的位置
int n = ...;int flag = 1;while ((flag & n) == 0) flag <<= 1; // & 运算时,其实判断的是二者的二进制形式;原创 2016-09-21 23:33:50 · 1452 阅读 · 0 评论 -
算法 Tricks(三)—— 判断序列是否为等差数列
判断一个数列不是等差数列,要比判断一个数列是等差数列比较容易。bool progressive = true;for (int i = 0; i < A.size() - 1; ++i){ if (A[i+1] - A[i] != A[1] - A[0]) progressive = false;}原创 2016-09-09 10:52:50 · 5765 阅读 · 0 评论 -
数的分解、分解质因数
def primes(n): x, l = 2, [] while True: if x > n: break if n % x == 0: n //= x l.append(x) x -= 1 x += 1 return l>> p原创 2016-06-29 22:17:30 · 1818 阅读 · 0 评论 -
二分法的常见应用
1. 计算 ana^n (数的幂)2. 计算 AnA^n (矩阵的幂)由于矩阵乘法具有结合律,因此 A4=A∗A∗A∗A=(A∗A)∗(A∗A)=A2∗A2A^4 = A * A * A * A = (A*A) * (A*A) = A^2 * A^2 我们可以得到这样的结论:当n为偶数时,An=An/2∗An/2A^n = A^{n/2} * A^{n/2}当n为奇数时,An=An/2∗An原创 2016-06-23 12:49:07 · 2800 阅读 · 0 评论 -
辗转相除将分数化为连分数形式
# 这里只考虑分子小于分母的情形,# 分子大于分母,也极为简单def frac2continued(num, den): l = [] den, num = num, den while True: l.append(num // den) if num % den == 0: break num,原创 2016-06-20 11:53:14 · 3078 阅读 · 0 评论 -
排列(permutation)的末尾 0 的个数
AnmA_m^n套路还是一样,计算参与连乘的几个数(Anm=m⋅(m−1)⋯(m−n+1)A_m^n=m\cdot (m-1)\cdots (m-n+1))关于 5 这个因子的个数。那么又该如何计算呢?这里提供一个思路:就是把 m!m! 关于5 这个因子的个数减去 (m−n)!(m-n)! 关于 5 这个因子的个数。def numOfTailZeroFac(n): cnt = 0原创 2016-06-19 22:14:08 · 1028 阅读 · 0 评论 -
阶乘末尾 0 的个数
如求 100 的阶乘末尾 0 的个数;思路:一个数 n 的阶乘末尾有多少个 0 取决于从 1 到 n 的各个数的因子中 2 和 5 的个数, 而 2 的个数是远远多余 5 的个数的(每 5 个连续的数,如 3,4,5,6,7,当然会有 1 个是 5 的倍数,会有两个是 2 的倍数,), 因此求出 5 的个数即可.原创 2016-06-19 11:21:43 · 1582 阅读 · 0 评论 -
二分查找 —— 有序数组不小于(不大于)某数的第一个(最后一个)元素
def bisearch(l, e, lo, hi): while lo < hi: mi = (lo + hi)//2 if e > l[mi]: lo = mi + 1 else: hi = mi return hi注:(1)不同于寻找等于某值的情况(且找到即可,不要求第一个),体现在代码中,就是判断逻辑;原创 2016-06-08 12:43:00 · 7168 阅读 · 2 评论 -
算法常用结论
1. 子序列(Subsequence)问题首先需要明确的是:问题求解的对象是否是连续子序列;序列,当然在具体编程实现时,就是数组,或者更高级一点的容器。已知,长度为 NN 的序列:1.1 要求连续长度为1 + 长度为 2 + 长度为 3 + … + 长度为 N1+2+3+⋯+N=N(1+N)21+2+3+\cdots+N=\frac{N\left(1+N\right)}21.2 不要求连续那就称原创 2016-06-08 09:37:32 · 1064 阅读 · 0 评论 -
二分查找 —— 从三分支到二分支
二分查找,三分支向二分支的转变,降低的是时间复杂度的常系数。原创 2016-06-06 22:46:02 · 1229 阅读 · 0 评论 -
算法 Tricks(二) —— 大数的处理
大数显然不能直接使用 int、long 等存储,会溢出。一种典型的大数的处理手段是使用数组模拟。int num[5] = {1, 2, 3, 4, 5};int carrier = 0;int n = 6; // 12345 * 6for (int i = 4; i >= 0; --i){ A[i] = A[i] * n + carrier; carrier = A[原创 2016-06-01 10:38:10 · 912 阅读 · 0 评论 -
算法 Tricks(一)—— 字符串和数组的翻转
所谓字符串和数组的翻转,其实是对应位的交换,考察的是下标之间的关系,要交换的下标的关系为: swap(s[i],s[n−1−i])swap(s[i], s[n-1-i]) 也即下标之和为数组的长度减1,而与数组的长度为奇数还是偶数无关。void ReverseArray(int *A, int n){ for (int i = 0; i < n/2; ++i){ sw原创 2016-06-01 10:11:21 · 1051 阅读 · 0 评论 -
二分查找的变体 —— Fibnaccian Search
二分查找首先来看二分查找的基础版本:template <typename T>static int fibSearch(T *A, int lo, int hi, T const& e){}原创 2016-05-28 18:10:17 · 1560 阅读 · 0 评论 -
斐波那契数列(面向对象版)
class Fib{private: int f, g;public: Fib(int n){ f = 1; g = 0; while (g < n) next(); } int get() { return g;} int next(){ g += f; f = g -原创 2016-05-27 20:13:01 · 1640 阅读 · 1 评论 -
随机化算法 —— 数组置乱器的实现
void permute(int a[], int n){ for (int i = n; i > 0; --i){ swap(a[i-1], a[rand()%i]); } // a[i-1] 与 a[0, i)(包括 a[i-1]) 中某一随机元素交换}原创 2016-05-26 17:32:17 · 1754 阅读 · 0 评论 -
数据结构与算法 —— 向量的扩容策略与分摊时间复杂度
以可扩充向量(动态数组)为例,可以考查对该结构的连续 nn 次(查询、插入或删除等)操作,将所有操作中用于内部数组扩容的时间累计起来,然后除以 nn。只要 nn 足够大,这一平均时间就是用于扩容处理的分摊时间成本。动态数组,无论是 C++ STL 中的 vector 还是 Java 中的 ArrayList 都无一例外进行数组的扩充时,都是将容量以 2 为比例按指数速度增长。以下我们讲看到此种扩充机原创 2016-05-26 16:17:14 · 1913 阅读 · 0 评论 -
数据结构与算法的分析 —— 渐进复杂度(三个记号)
对于某些问题,一些算法更适合于用小规模的输入,而另一些则相反。幸运的是,在评价算法运行效率时,我们往往可以忽略掉其处理小规模问题时的能力差异,转而关注其在处理大规模数据时的表现。道理是显见的,处理大规模的问题时,效率的些许差异都将对实际执行效率产生巨大的影响。这种着眼长远,更为关注时间复杂度的总体变化趋势和增长速度的策略和方法,即所谓的渐进分析(asymptomatic analysis)。大 OO原创 2016-05-26 10:31:12 · 10625 阅读 · 0 评论 -
数据结构与算法的分析 —— 平均时间复杂度 vs 分摊时间复杂度
平均时间复杂度:假定各种输入实例的出现符合某种概率分布(如均匀独立随机分布)之后,进而估计出的加权时间复杂度均值。分摊时间复杂度,纵观连续的足够多次操作,并将其间总体所需的运行时间分摊至各次操作。与平均时间复杂度的本质不同在于,这里强调,操作序列必须是的确能够真实发生的,其中各次操作之间存在前后连贯的时序关系。原创 2016-05-26 09:43:18 · 5369 阅读 · 0 评论 -
数据结构(C++)—— 向量(Vector)
1. 动态内存管理1.1 扩容template <typename T> void Vector<T>::expand(){ if (_size < _capacity) return; if (_capacity < DEFAULT_CAPACITY) _capacity = DEFAULT_CAPACITY; T *oldElem = _elem; _elem原创 2016-05-25 23:47:40 · 1601 阅读 · 0 评论 -
数据结构与算法的分析
算法的分析包括两部分内容: 1. 时间复杂度; 2. 空间复杂度。1. 空间复杂度分析1.1 装填因子(load factor)我们以向量(Vector)的实现为例。向量实际规模与其内部数组容量的比值,即 _size/_capacity,亦称作装填因子,它是衡量空间利用率的重要指标。所以动态内存管理的一大目标即是:如何才能保证向量的装填因子既不至于超过1,也不至于太接近于0?原创 2016-05-25 23:38:02 · 1006 阅读 · 0 评论 -
【算法】—— 最大子序列和问题
暴力(O(n2)O(n^2))int MaxSubseqSum(int A[], int N){ int ThisSum, MaxSum, i, j; MaxSum = 0; for (i = 0; i < N; ++i){ ThisSum = 0; // 表示遍历,i表示起始节点,每一次都是新的开始 for (j原创 2016-05-19 22:02:24 · 1271 阅读 · 0 评论 -
Python 数据结构与算法 —— 从分治的角度看快速排序、归并排序
这里给出分治语义的一种通用性实现:def divide_and_conquer(S, divide, combine): if len(S) <= 1: return S L, R = divide(S) A = divide_and_conquer(L, divide, combine) B = divide_and_conquer(R, divide, combi原创 2016-04-18 22:43:35 · 1003 阅读 · 0 评论 -
【笔试/面试】—— 数组中第二大的数
设置两个变量维护最大值和次大值;int find_sec_max(int *seq, int n){ int max = seq[0]; int sec_max = INT_MIN; for (int i = 1; i < n; ++i) { if (seq[i] > max) { sec_max = max原创 2016-04-18 10:51:28 · 1062 阅读 · 0 评论 -
神奇的位运算
~:各位取反运算首先需要明确的一点:计算机存储的是二进制,是补码。~(取反运算)针对的便是补码,而且是对全部的二进制位进行取反,不论是否为符号位。5 的补码:整数的补码,仍然为其自身,0000 0101,~5 (1)各位取反,1111 1111 1111 1010 (2)发现是负数,计算机对其取反(符号位除外)再加1,1000 0000 0000 0110 ⇒ -6-5 的补码, 1000原创 2016-04-16 09:15:51 · 1030 阅读 · 0 评论 -
【剑指 offer】(二十四)—— 二叉搜索树的后序遍历序列
还在于边界值的判断。边界值判断的思路:有没有进入 if 判断(也即 if 中的处理逻辑有没有被执行)关于 for 和 while 循环, 没有进入走到最后中间 break题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。{5, 7, 6, 9, 11, 10, 8} ⇒ true{7, 4, 6, 5} ⇒ falsebool VerifySeqOfBST(i原创 2016-04-15 17:18:13 · 992 阅读 · 0 评论 -
【笔试/面试】—— 从大数相减到大数求除(大数求余)
进行大数相除的一个简单思路是:从被除数中减去除数,每减去一次,就将结果加1,直到被除数小于除数为止,此时的被除数即为大数求余的余数。从被除数中减去除数使用的是大数减法,结果+1使用的是大数加法。原创 2016-04-14 20:49:47 · 1865 阅读 · 0 评论 -
【笔试/面试】—— 从同余定理到大数求余
所谓大数,即为无法使用诸如 int/long等基本数据类型保存的数据,比如1234656789101112,显然在 Python 世界不存在任何关于整型精度的问题。既然无法使用基本数据类型保存大数,我们就使用 string 的相关操作来模拟整数的基本运算。同余定理求余算子本质上实现了一种映射,如任意一个整数对 5 求余,得到的余数只在 0, 1, 2, 3, 4 之中。(a×b)%c=(a%c×b原创 2016-04-14 20:21:04 · 2837 阅读 · 0 评论 -
【笔试/面试】—— 二叉树的最远距离
问题定义如果我们把二叉树看成一个图,父子节点之间的连线看成是双向的,我们姑且定义“距离”为两节点之间边的个数,写一个程序求一棵二叉树中相距最远的两个节点之间的距离。 分析计算一个二叉树的最大距离有两个情况:情况 A:路径经过左子树的最深节点,通过根节点,再到右子树的最深节点;情况 B:路径不穿过根节点,而是左子树(右子树为空)或右子树(左子树为空)的最大距离路径,取其大者;只需计算这两个原创 2016-04-13 17:28:52 · 1321 阅读 · 0 评论 -
【笔试/面试】—— 二叉树的深度和宽度
二叉树这一数据结构,为算法设计带来, logN\log N 的时间复杂度因子; 递归的程序结构。 二叉树的深度:从根节点(root node)到叶子节点(leaf node)依次经过的节点(含内部节点(internal node),也含根节点和叶节点)形成树的一条路径,最长路径的长度为树的深度。二叉树的宽度:二叉树的每一层都有一定数量的节点,节点数最多的那一层的节点数叫做二原创 2016-04-13 16:44:30 · 1610 阅读 · 0 评论 -
解决算法问题的思路 —— 从问题描述到数学表达
处理一些现实问题时,有必要明确潜在的问题(及其解)将如何表达(表达为数学、付诸于程序),这对于优化函数的设计是非常通用的。选择一个题解的简单表示方法,有一种非常通用的表达方式,就是数字序列。原创 2016-04-10 09:30:41 · 1232 阅读 · 0 评论 -
【剑指 offer】(二十九)—— 数组中出现次数超过一半的数字(及该数字出现的次数)
题目:数组中有一个数字出现的次数超过数组长度的一半,请找出该数字,如输入一个长度为 9 的数组 {1, 2, 3, 2, 2, 2, 5, 4, 2}。由于数字 2 在数组中出现了 5 次(5>9/2),超出数组长度的一半,因此输出为2.思路 1:统计计数,统计每一个元素出现的次数,时间复杂度为 O(n)O(n)。如果某元素出现的次数超过 n/2,直接输出,不可能有第二个元素出现的次数还超过 n/原创 2016-04-09 21:57:01 · 813 阅读 · 0 评论 -
【剑指 offer】(二十二)—— 栈的压入、弹出序列
题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可以作为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如 序列1、2、3、4、5 是某栈的压栈序列,序列 4、5、3、2、1 是该压栈序列对应的一个弹出序列,但 4、3、5、1、2 就不可能是该压栈序列的弹出序列。解决这一问题的关键在于,将我们的思维过程转化为具体可行的算法流程,最后付诸于程序实现。总结入栈和出栈的过程,我们可原创 2016-04-09 21:18:26 · 805 阅读 · 0 评论 -
【剑指 offer】(二十三)—— 从上往下打印二叉树(或曰层次遍历、广度优先遍历)
从广度优先到深度优先,只差一个数据结构。从上往下打印二叉树,实质是在考察树的遍历问题,显然不同于更为经典的先序、中序和后序遍历,而属于一种层次遍历,或曰广度优先遍历。二叉树节点的定义如下:struct BinaryTreeNode{ int val; BinaryTreeNode* lft; BinaryTreeNode* rgt;};使用队列(FIFO)实现广度优先#原创 2016-04-09 19:06:21 · 930 阅读 · 0 评论 -
【剑指 offer】(二十一)—— 包含 min 函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数,在该栈中,调用 min、pop、push 的时间复杂度都是 O(1)O(1)。思路:定义一个辅助栈,用以保存当前栈的最小值,需保持辅助栈和当前栈的高度一致,以实现二者push、pop的同步;(1)栈的数据结构定义template<typename T>class StackWithMin{public: vo原创 2016-04-09 15:31:06 · 847 阅读 · 0 评论