面试小提-笔记筑:Algorithm Part

推荐阅读:

  1. 【阿里】算法工程师笔试题整理(13&14年)
    2.剑指offer题目及答案

————————————————————————————————

本笔记只是备忘录,并不是详细解决方案,请使用ctrl+F查询

Algorithm

1. 打印字符串全排列

递归后需要马上还原(因为所有的),因为递归是一颗深度优先的树(也就是栈),是先从叶子开始改变的

2. 二叉搜索树转为双向链

中序遍历二叉搜索树正好是递增的,只需要把结点左右指针连接中序中前后的结点。(二叉搜索树没有相同值的点)

3. 递归求排列

求排列时若用递归法,递归叶子回溯到父亲时需要还原改变(叶子最先改变,也就是说是从字符串数组最后两位开始的),不然就不是同一串字符串出发的(画出排列树推吧)

3. 复制复杂链表

每个结点复制一个接在该结点后面,a->a’->b->b’->c->c’,同时复制原结点所指向的任意结点,最后奇偶结点分离(跳着分离)

4. 最小的k个数

partition函数(快排)最后得到的index就是他在序列中的位置,左边的就是前index个数。(期间可以用二分法逐渐缩小区间,由于是用一个固定位置的数作pivot所以最后得到的index是针对整个数组而言的)

5. 集合和最大堆基于红黑树
6. 最大子数组和

如果累加和为负则重新从新元素开始

7. 从1到n数里包含1的个数

O(log10N) , 十位上,每轮出现10次是指[10,19]这10个数 解析

8. 逆序对

归并排序的同时进行计数

9. 数组排成最小的数

可以用sprintf将数字写入字符串

10. 二叉树最低公共祖先

(若是二叉搜索树)比一个结点大,比另一个结点小;(若有指向父节点的指针)寻找两个链表的公共结点;(什么都不是)用栈递归到叶子再逐个往上退栈。

11. 二叉树路径和等于某个数

传递vector的引用(用vector作栈方便顺序输出),递归遍历,进入结点时压入该结点,退出时弹出(恢复成父节点进入的情景)

12. 不能继承的类

虚拟继承virtual 父类,把父类构造函数设为私有,并且父类含有一个友元函数,友元函数的类型是由模板类给出的,所以虚拟继承的时候需要把子类的类型当成参数传入;(只能在堆上)私有构造与析构,用静态getInstance函数返回一个新的父类对象。

13. C++按类中声明的元素顺序初始化成员
14. 求方程正整数解个数

求方程正整数解个数

15. 多路平衡归并排序

外排序,m个归并项(子表个数)k路归并的归并趟数s=logk(m),败者树

16. n阶乘末尾0的个数

(n除以5,即求5的质因数个数)
存在0必然是52的结果,所以必然含有5!(5432*1),所以只要求多少个5的倍数即可

17. mongodb为什么没有自增id

因为并发情况下同时插入,分布式存储的情况下无法协调分配连续数字(协调需要消耗网络资源),并且内部也不是按id顺序存的,因为文档长度可变,不知道什么时候文档变长要放到后面去

18. 数n的约数个数

(约数:能把别人整除的叫约数)自然数的约数的个数是有限的,质数的约数是1和本身;合数一定有3个以上的约数。由于约数个数定理知道:约数的个数等于:所有质因数的指数加上1后的乘积;
若一个数分解质因数后为(am)*(bn),其中a,b均为质因数;m,n均为相应质因数的指数.
则约数个数为(m+1)(n+1).(因为a0,a1 … a^m,都是它的约数共m+1个)

例如:
(1)12=2^2 * 3,质因数有2和3,其指数分别为2和1,那么12的约数有(2+1) *(1+1)=6(个);
(2)60=22 * 3 * 5,质因数2, 3, 5的指数分别为2, 1, 1,那么60的约数有(2+1) * (1+1) * (1+1)=12(个)

19. 一个数所有约数之和

一个数所有约数之和 等于先把每个质因数从0次幂一直加到其最高次幂,再把每个相应质因数幂的和相乘.
若一个数分解为(am)*(bn),则这个数所有约数的和为:
(a0+a1+a2+a3+…+am)(b0+b1+b2+b3+…+bn).

例如:
(1)12=223,则12所有约数的和为:(20+21+22)*(30+3^1)=74=28;
(2)60=2235=(20+21+22)*(30+31)*(50+5^1)=746=168.

20. Trie 树
21. 需要同时移动多少步A和B才同时指向一个节点

长度为100的循环链表,指针A和指针B都指向了链表中的同一个节点,A以步长为1向前移动,B以步长为3向前移动,一共需要同时移动多少步A和B才能再次指向同一个节点?

  • 假定经过n步A、B再次相遇。则A经过的结点为n,B经过的结点为3n;此刻B必然比A多经过了整数倍的链表长度(圈的长度),假定经过了i倍的链表长度,则有3n-n=100i,即2n=100i;满足该等式的最小整数位i=2,n=100。即A经过了100个结点,B经过了300个结点,二者再次相遇。
    • 也可以认为跑圈100m,A速度1m/s B速度3m/s,B比A多跑一圈耗时100/(3-1)=50s
22. 模拟加减乘

注意 ^按位异或& 按位与| 按位或

加法运算

将一个整数用二进制表示,其加法运算就是:相异(^)时,本位为1,进位为0;同为1时本位为0,进位为1;同为0时,本位进位均为0.

故不计进位的和为sum = a^b,进位就是arr = a&b,(与sum相加时先左移一位,因为这是进位)。完成加法直到进位为0.

int Add(int a, int b){
    return b ? Add(a^b, (a&b)<<1) : a;
}
求相反数
//求a的相反数:将各位取反加一
int negative(int a){     //get -a
    return Add(~a, 1);
}

减法运算

a-b = a+(-b)  
根据补码的特性,各位取反加1即可(注意得到的是相反数,不是该数的补码,因为符号位改变了)
(上面用二进制实现的加减法可以直接应用于负数)

int Minus(int a, int b)
{
    return Add(a, negative(b));
}
乘法运算

原理上还是通过加法计算。将b个a相加
参考二进制数转十进制数,每位有一个2次幂的权重,当该位为1时,加上 2^x 倍 a
所以权重 a 不断 a<<=1

//正数乘法
int Multi(int a, int b){
    int ans = 0;
    while(b){
        if(b&1)
            ans = Add(ans, a);
        a = a << 1;
        b = b >> 1;
    }
    return ans;
}
//正数除法
int Divide(int a, int b)
{
    int coun = 0;
    while(a >= b)
    {
        a = Minus(a, b);
        coun = Add(coun, 1);
    }
    return coun;
}
除法运算

除法运算是乘法的逆。看a最多能减去多少个b,

//判断是否是负数,0,正数
int isneg(int a)
{
    return a & 0x8000;
}
int iszero(int a)
{
    return !(a & 0xFFFF);
}
int ispos(int a)
{
    return (a&0xFFFF) && !(a&0x8000);
}

//处理负数的乘法和除法
int My_Multi(int a, int b)
{
    if(iszero(a) || iszero(b))
        return 0;
    if(isneg(a))
    {
        if(isneg(b))
            return Multi(negative(a), negative(b));
        else
            return negative(Multi(negative(a), b));
    }else if(isneg(b))
        return negative(Multi(a, negative(b)));
    else
        return Multi(a, b);
}

int My_Divide(int a, int b)
{
    if(iszero(b))
    {
        cout << "Error!" << endl;
        exit(1);
    }
    if(iszero(a))
        return 0;
    if(isneg(a))
    {
        if(isneg(b))
            return Divide(negative(a), negative(b));
        else
            return negative(Divide(negative(a), b));
    }else if(isneg(b))
        return negative(Divide(a, negative(b)));
    else
        return Divide(a, b);
}

计算统中数值一律用补码来表示,因为补码可以使符号位和数值位统一处理,同时可以使减法按照加法来处理。
原码 -> 补码: 数值位取反加1 数值位不包括符号位
补码 -> 原码: 对该补码的数值位继续 取反加1
补码的绝对值:(称为真值)正数的真值就是本身,负数的真值是各位(包括符号位)取反加1(即变成原码并把符号位取反)
b -> -b : 各位(包括符号位)取反加1

旋转字符:字符串前面的部分排在后面

“abcd"的旋转串是"abcd”,“bcda” … “dabc”
首先目标串与原串长度要相等

  • 可以设字符s+s,只要是s+s的子串都是旋转字符
    比如:"abcd"+"abcd",即"abcdabcd"中寻找子串,时间复杂度是根据查找子串的时间来定的,所以如果是采用KMP则是O(N)
字符串提取出现的正负整数总和

注意可以负负得正
设置符号位positive,cur当前字符,num每一位不断往高位挪的积累数(字符串转化为数字),res数字总和

  • 当cur为数字时,num = num * 10 + positive? cur:-cur;
  • 当cur不为数字的时候,说明此时刚刚结束数字的转化,或者是开始部分没有数字,无论哪种都应该加上刚刚转化的数字num,并把num置零重新开始计数
    此时需要注意当cur为负号时,前一位为负则positive=true,否则为负;如果不是负号,则重置positive为正
字符串转换成int范围内数字

字符串可以是只出现一个0,即 0
字符串不能出现 --0-02A12
如果没有出现违法,只要检查是否每个字符都是数字就可以,

  • 接下来进入判断,由于int范围是-21474836482147483647
    先把所有的数字都转为负数(转为负数可以不用再写一遍正数的,最后如果是整数再负负得正回去)
  • 判断是否溢出,因为-214748364 * 10 + (-8) 刚好是最大整数
    先判断是否 ( res < = -214748364 )|| (res == -214748364 && cur <= -8 )
    同时也要注意如果是正整数,则转换后不能大于 2147483647
字符串replace a with b
for(for...){
	if(a[i++]==b[j]){ 
		j++ ;
		if(j==源串长){ // 不用源串长-1,前面++过了
			标记a串 [i-源串长,i] 为0或者某个特定符表示需要删掉
			//(为了节省时间我们可以不删掉,只是不打印就可以)
		}
	}
	else j=0;
}

当a[i]=0而a[i-1]!=0时表示可以开始将前面积累的字符串打印了
res = res + cur + to
注意最后是如果末尾全是0,没有到不是0的过度,我们需要再手动把这部分转换加上 res += cur

计算连续出现字符的个数

aabbbcc -> a_2_b_3_c2
这种连续计数,在字符变化后结束上一轮计数的都是一种做法,注意末尾部分没有转换需要手动加上

  • 本题从第二个字符开始判断每个字符和前一个字符如果不一样,进入转换,记录数字个数,重置计数个数为1(因为当转换出现,和前个不一样,前个计数为1,例如ab,从b开始,a计数为1)

如果是 a_2_b_3_c2 -> aabbbcc,求第5个字符是什么,此处是b
由于有"_",可以因此分别数字与字符,保存当前字符cur,记录数字,当数字大于需要求的位置则返回

判断是否每个字符只出现一次

可以用Map
但是如果只花费O(1)空间,只能使用堆排序,将数组排序对比前后是否相同

浮点数高精度幂
替换空为%20

先扫描一遍记录总长度为len+2*blank,再从后往前复制

一般挪动数组元素的,都需要从右往左复制

reverse字符串
reverse整个串

while(start<end){
swap(c[start],c[end]);
start++;
end–;
}

reverse前后部分顺序

abcde->deabc
先翻转abc,再翻转de,再翻转整个串

变成回文串需要的最少字符

str长为N, N ∗ N N*N NN 的动态规划表 dp[i][j]表示str[i…j]这段字符最少需要多个字符使str[i…j]变成回文串

dp[i][j]=
{ 0 , i = j 0 , s t r [ i ] = s t r [ j ] a n d ∣ i − j ∣ = 2 1 , s t r [ i ] ! = s t r [ j ] a n d ∣ i − j ∣ ≥ 2 d p [ i + 1 ] d p [ j − 1 ] , s t r [ i ] = s t r [ j ] a n d ∣ i − j ∣ ≥ 2 m a x d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] + 1 , s t r [ i ] = = s t r [ j ] a n d ∣ i − j ∣ ≥ 2 \begin{cases} 0, &amp; i=j \\ 0 , &amp; str[i]=str[j] \quad and \quad |i-j|=2 \\ 1 , &amp; str[i]!=str[j] \quad and \quad|i-j|\geq2 \\ dp[i+1]dp[j-1] , &amp; str[i]=str[j] \quad and \quad |i-j|\geq2 \\ max{dp[i+1][j],dp[i][j-1]}+1 , &amp; str[i]==str[j] \quad and \quad |i-j|\geq2&amp;\end{cases} 0,0,1,dp[i+1]dp[j1],maxdp[i+1][j],dp[i][j1]+1,i=jstr[i]=str[j]andij=2str[i]!=str[j]andij2str[i]=str[j]andij2str[i]==str[j]andij2

未完待续

判断最长左右括号

()()(())合法,返回最长的合法出现的括号位置

  • 先检查()的数量是否相等
  • 设dp[i]表示str[0…i]位置上,以str[i]结尾的最长有效括号长度 . . . . ( ( ) ( ) ) 为 6 ....(()())为6 ....(()())6
    1. str[i]==(,有效括号长度子串以)结尾,故dp[i]=0;
    2. str[i]==),dp[i-1]代表str[i-1]结尾的最长有效括号长度,上一处没有被匹配过的位置i-1-dp[i-1]上如果是(,可以和str[i]凑成一对,此时dp[i]=dp[i-1]+2,同时dp[i-1]前面即str[i-2]之前的dp[i-2]也应该算上
  • 最后求解 Max(dp[0..N-1]) 就是最终结果
计算字符串 4*((3*(-4)±4*3))的结果

每次碰到(就进入递归进入下一层,)就结束递归,并传递参数 (char [], 下一个位置位置 )数组返回(计算的结果, 结束的位置)

Recur(char [] , int i){
	while(i < length && ch[i] !=')'){
		if( ch[i] 是数字 ){
			pre = pre * 10 + ch[i] - '0';
			i++;
		}else if (ch[i] 是操作符 ){
			队列a入队pre
			队列a入队ch[i]
			pre = 0;
			i++;
		}else(ch[i] == '('){
			array = 递归本函数(ch,i+1)
			pre = array[0];
			i = array[1]+1;
		}
	}
}
整数N的二进制全排中0左边必有1的个数
按顺序拼接字符串使总结果字典序最小
  • 先按照拼接的方式将数组排列,即 return (a+b)>(b+a)
  • 将排序后数组从头到尾组合起来

贪心算法,有详细需证明它的贪心策略是正确

新类型字符a、Aa、AA 中第k个字符是哪种类型的

从k-1个字符开始往左对大写字母计数,碰到小写字母停止

  • 根据奇偶判断kk+1是AA,还是Aa,还是k+1是单独的a
回文段最少切割数

dp[i]是子串str[i…len-1]需要切几次才能使str[i…len-1]全切成回文串,dp[0]是最后结果

从右往左,i初始为len-1

  • 如果 str[i…j]是回文串,dp[i]=dp[j+1]+1,只需从str[j+1…len-1]寻找最经济的切割法
  • j 从 [i…len-1] 遍历,dp[i]=Min{dp[j+1]+1},i<=j<len且str[i..j]为回文串
  • 快速判断是否是回文串的方法是回文子串长度的相同方法,设p[i][j]为i…j间是否是回文串的判定
    以下情况p[i][j]是回文串:
    • str[i…j]长度为1
    • str[i…j]长度为2,且2个字符相等
    • 子串str[i+1…j-1]是回文串(即p[i][j]=true),且str[i]==str[j],最外面的两个字符串相等。
    • i 是从右往左,j是从i往右,所以i..j间长度是展开的便于计算p[i][j]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值