这篇文章是leetcode
刷题系列的番外篇——刷题小知识点。主要是编程题中的一些冷知识、小技巧。
leetcode
刷题系列其它文章组织如下:
1
. 数组
2
. 链表
3
. 字符串
4
. 二叉树
5
. 队列和栈
6
. 动态规划
7
. 数据结构设计
8
. 刷题小知识点
数字和字符串互转
数字转字符串使用std::to_string
函数。比如可以用来快速得到数字的位数。字符串转数字有std::stoi
,std::stol
等。
对于字符串变量使用流stringstream
对于需要将字符串中的子串按照一定的分隔符依次读取,可以这么做
1 | stringstream ss(str); |
数字0-9
和单个字符互转
1 | // '0' 的 ASCII 码值为 48 (十进制) |
单个字符转为字符串
string(1, c)
string().append(1, c)
数字转为字符串
std::to_string()
将迭代器前进和后退n
步
1 | // 它们都返回前进或后退之后的迭代器 |
对于vector
的push_back
不适用的情况
若已知结果vector
的大小,可提前创建一个该大小的vector
,然后直接使用索引向vector
中从后向前赋值。比如,从大到小地往vector
中添加值,但又要求最终vector
是递增的情况。
对于在for
循环里使用size()
函数可能会出现的意外情况
拿vector
来说,其size()
函数接口返回的是向量的元素个数,它是size_t
类型,即无符号整型unsigned int
或unsigned long
类型,如果拿它与另一个整数相减得到了一个负数,那么这个负数将会被隐式的转型为一个超级大的数(因为它的类型是无符号的嘛),这时候在for
循环里的判条件将会产生意想不到的结果。
1 | vector<int> nums{1, 2, 3}; // nums.size() == 3 为 unsigned 类型 |
卡特兰数
问题:一个合法的表达式由()
包围,()
可以嵌套和连接,如(())()
也是合法括号表达式。现在有n
对()
,它们可以组成的合法括号表达式的个数为多少?
用栈检验括号序列是否有序。设置一个栈,每读入一个括号,若是左括号,则压栈;若是右括号,并且与当前栈顶的左括号相匹配,则出栈,继续读下一个括号,如果读入的右括号与当前栈顶的左括号不匹配,则属于不合法的情况。在初始和结束时,栈应该是空的。n
对括号出栈顺序的总数就是本题中所有合法括号表达式的数目。
假设序列为[0, 1, 2, ..., n-1]
。对于序列入栈出栈,长度为0
的序列的出栈序列总数记为C(0)
,长度为1
的序列的出栈序列总数记为C(1)
,长度为n
的序列的出栈序列总数记为C(n)
,易知C(0) = 0
,C(1) = 1
。
假设序列为[0, 1, 2, ..., n-1]
,其中有k
。对于序列入栈出栈,若k
最后出栈,则出栈次序为,先是比k
小的[0, 1, ... , k-1]
,情况有C(k)
种,后是比k
大的[k+1, ..., n-1]
,情况有C(n-k-1)
,最后是k
。[0, 1, ... , k-1]
和[k+1, ..., n-1]
都是合法出栈次序。
例如对于序列[0, 1, 2, 3, 4, 5, 6, 7]
,k=5
,一种出栈次序为[0, 2, 1, 4, 3, 7, 6, 5]
。
由于k
为不同值的情况相互独立,而且比k
大的[k+1, ..., n-1]
和比k
小的[0, 1, ... , k-1]
也相互独立,可以推出:
1 | int catalan(int n) { |
相似问题:
圆周上有标号为
1, 2, 3, 4, ..., 10
的共计10
个点,这10
个点配对可连成5
条弦,且这些弦两两不相交的方式数目?(C(5)
)游乐园门票
5
元一张,每人限购一张。现在有10
个小朋友排队购票,其中5
个小朋友每人只有5
元的钞票一张,另5
个小朋友每人只有10
元的钞票一张,售票员没有准备零钱。问:有多少种排队方法,使售票员总能找的开零钱?(将持5
元者到达视作将5
元入栈,持10
元者到达视作使栈中某5
元出栈,C(5)
)饭后,姐姐洗碗,妹妹把姐姐洗过的碗一个一个放进碗橱摞成一摞。一共有
n
个不同的碗,洗前也是摞成一摞的,也许因为小妹贪玩而使碗拿进碗橱不及时,姐姐则把洗过的碗摞在旁边,问:小妹摞起的碗有多少种可能的方式?(C(n)
)n + 2
条边的多边形,能被分割成三角形的方案数有(C(n)
)。例如6
边型的分割方案有C(4) = 14
。拥有
n + 1
个叶子节点的二叉树的数量为多少?(C(n)
)。例如4
个叶子节点的所有二叉树形态有C(3) = 5
。n*n
的方格地图中,从一个角到另外一个角,不跨越对角线的路径数,例如,4×4方格地图中的路径有:再来一道阿里巴巴的笔试题目:说16个人按顺序去买烧饼,其中8个人每人身上只有一张5块钱,另外8个人每人身上只有一张10块钱。烧饼5块一个,开始时烧饼店老板身上没有钱。16个顾客互相不通气,每人只买一个。问这16个人共有多少种排列方法能避免找不开钱的情况出现。
C8 = 1430
,所以总数为1430 * 8! * 8!
。这里乘以两个8!
是因为每个人都不一样,持5
块和10
块分别的有8!
中排列方式。它不像括号生成,左括号和左括号,或者右括号和右括号没区别。腾讯实习招聘笔试题:在图书馆一共
6
个人在排队,3
个还《面试宝典》一书,3
个在借《面试宝典》一书,图书馆此时没有了面试宝典了,求他们排队的总数?C3 = 5
,同题7
理,总数为5 * 3!* 3!= 180
。
向上取整
如x/y
向上取整,可以用(x+y-1)/y
。更方便的是调用标准库函数std::ceil(double(x)/y)
。
将一个整数变成无限循环小数
例如,123
变成0.123123123...
。可将123
除以999
。一般的,对于整数a
,变成无限循环小数的方式是a / (a的位数 - 1)
。
证明:

另外,求取一个整数a
的位数可以用to_string(a).size()
或1 + (int)log10(a)
。
整数拼接比大小
要求两个整数x
,y
如何拼接得到结果更大或更小时,就要想到先转成字符串,然后比较string(x) + string(y)
和string(y) + string(x)
。
位运算总结
计算二进制数中
1
的个数- 巧用
n & (n - 1)
:n - 1
导致二进制数字n
最右边的1
变成0
,此1
右边的0
变成1
;n & (n - 1)
导致二进制数字n
最右边的1
变成0
,其余位不变。因此可以循环执行此过程并计数,直到n
变为0
。 n & -n
可以获取n
的二进制位表示中最低位的1
。因为,负数表示正数取反加一,再相与后会导致除了最低位的1
保持不变,其它位全变为0
。
- 巧用
异或运算
异或运算简单理解就是不进位的加法运算。
1 + 1 = 0
,1 + 0 = 0 + 1 = 1
,0 + 0 = 0
。所以异或运算满足加法的一些运算规律:交换律
x ^ y = y ^ x
。结合律
(x ^ y) ^ z = x ^ (y ^ z)
。分配律
(x1 & y1) ^ (x1 & y2) ^ (x2 & y1) ^ (x2 & y2) = (x1 ^ x2) & (y1 ^ y2)
。对任何数
x
有x ^ 0 = x
,x ^ x = 0
,x ^ FFFFFFFF = !x
。
判断奇偶性
数字的奇偶性取决于其最低位为
1
还是0
。1
2
3
4
5
6if(a & 1) {
// 奇数
}
else {
// 偶数
}
哈希表的键设定问题
最常用的做为键的就是整数和字符串了,大多数题目都是这样。但有时候会遇到一种情况,就是说我们希望用两个以上的变量值的组合来确定对应的值,这怎么处理呢?
小技巧是,将这些变量统一转为字符串,再拼接在一起做为键。如:
1 | unordered_map<string, int> mapping; |
模幂运算的技巧
先说常用的求模运算技巧,对乘积的结果求模,等价于先对每个因子都求模,再对因子求模的结果的乘积再求模。
证明过程如下:
假设A, B, C, D
为任意常数,那么,
又因为,
所以,
求幂运算的话,就是要知道可根据幂指数的奇偶性优化求解效率:

同余定理
最小公倍数和最大公约数
1 | // 辗转相除法求最大公约数 |
1 | // 最小公倍数 |