面试督促自己学习进步

        以下是我在找c++ 开发工程师和算法工程师时,面试官所问的问题,答案我会慢慢补充,记下来主要是为了督促我不断的学习进步,哈哈

算法相关:

1、如何用牛顿法和梯度下降法求平方根,有什么区别?

a、牛顿法的公式:

Theta :=\Theta -\frac{f(\Theta )}{f'(\Theta )}

它的本质是求f(x)=0 的解,而我们是要求x ** 2 - a = 0,也即f(x)= x ** 2 - a
牛顿法里,是做了个小小的变换:
x = x - (x ** 2 - a)/2 * x, 把后面展开化简一下,就是 x = (x + a/x)/2

b、梯度下降法的公式:

\Theta _j=\Theta _j-\alpha \frac{\partial{J(\Theta )}}{\partial{\Theta _j}}

它的本质是求f'(x)=0的解,而我们是要求x ** 2 - a = 0,也即f'(x)= x ** 2 - a
所以直接代入公式,不断的迭代就可以求出来

我用的同样的收敛条件,用梯度下降法,1的平方根是0.999951565806, 9的平方根是3.00001521392, 而用牛顿法,结果分别是1.0 和3.0,感觉牛顿法收敛的更快啊,不知道我的理解对不对啊

我对牛顿法和梯度下降法求导或者求极值的一点简单的理解:
我们知道牛顿法是求解f(x)=0 的解,如果f(x)刚好是某一个函数的导数,那么牛顿法相当于求极值就用的是二阶导了,而梯度下降法仅仅用的是一阶导,从这个角度看,这或许是牛顿法比梯度下降法收敛快的一个原因啊,但我想问一句,为什么二阶到就会比一阶导快呢?

牛顿法和梯度下降法求平方根的代码具体如下:  

# /usr/bin/python
# -*- encoding:utf-8 -*-

#Calculate the square root by gradient descent method
def getSquareRootByGD(a_radicand):
	#The denominator is 2.0 to make the result decimal
	middleResult = a_radicand / 2.0
	#For test
	# print 'middleResult', middleResult
	lastValue = 0
	while abs(lastValue - middleResult) > 1e-6:
		lastValue = middleResult
		middleResult = middleResult - 0.01 * (middleResult ** 2 - a_radicand)
	return middleResult
	
def getSquareRootByNewton(a_radicand):
	#The denominator is 2.0 to make the result decimal
	middleResult = a_radicand / 2.0
	lastValue = 0
	while abs(lastValue - middleResult) > 1e-6:
		lastValue = middleResult
		middleResult = (middleResult + a_radicand / middleResult)/2
	return middleResult

if __name__=='__main__':
	# #For test
	print 'Using gradient descent method to solve square root:'
	for radicand in range(50):
		print 'The square root of ', radicand, 'is ', getSquareRootByGD(radicand)
	
	print 'Using Newton method to solve square root:'
	for radicand in range(50):
		print 'The square root of ', radicand, 'is ', getSquareRootByNewton(radicand)

牛顿法的劣势:

上面也提到,当用牛顿法求极值时,要用到二阶导数,当函数为多元函数时,二阶导数的计算是很麻烦的,是个hessian矩阵(黑塞矩阵),因为是分母还要求它的逆,简单来说,假如函数f(x,y,z),有三个自变量,如果求二阶导数的话,就要

\left\{ \begin{matrix} \frac{\partial^{2}L(\theta)}{\partial x^{2}} & \frac{\partial^{2}L(\theta)}{\partial x\partial y} & \frac{\partial^{2}L(\theta)}{\partial x\partial z}\\ \frac{\partial^{2}L(\theta)}{\partial y\partial x} & \frac{\partial^{2}L(\theta)}{\partial y^{2}} & \frac{\partial^{2}L(\theta)}{\partial y\partial z}\\ \frac{\partial^{2}L(\theta)}{\partial z\partial x} & \frac{\partial^{2}L(\theta)}{\partial z\partial y} & \frac{\partial^{2}L(\theta)}{\partial z^{2}} \end{matrix} \right\}

而且还要求这个矩阵的逆矩阵,这里我还需要找一个具体的牛顿法公式的题来做一做

而梯度下降只需要一阶导数,是不存在这个问题的啊

2、常用的损失函数都有什么啊?

a、交叉熵损失

交叉熵是信息论中的一个概念,是用来衡量2个分布之间的距离,公式是:

H(p, q) = -\sum_xp(x)log(q(x))

那么放到机器学习中的交叉熵损失是:

-p(x)^{(i)}logq(x)^{(i)}

进一步放到逻辑回归中,交叉熵损失是:

-(y^{(i)}log\hat{y}^{(i)}+(1-y^{(i)})log(1-\hat{y}^{(i)}))

\hat{y}=\frac{1}{1+e^{-z}}\\

b、hinge损失

在svm中如下:

max(1-d^{(i)},0)

d^{(i)}=y^{(i)}(\omega^T\varphi(x^{i})+b)

c、平方差损失

\frac{1}{2}(y^{(i)}-\hat{y}^{(i)})^2

参考文章:

经典损失函数:交叉熵(附tensorflow)_Study memo-CSDN博客_交叉熵损失函数
3、gdbt 和 xgboost的区别?

a、相同点:
1)、首先 gdbt 和 xgboost 都是boosting系列的算法
2)、它们都是通过拟合残差的方式,即不断的迭代拟合yi-yhat,从而让预测值来不断的接近真实值

b、不同点:
拟合残差的方法不一样,下面具体说下gdbt和xgboost 是如何拟合残差的啊

1)gdbt

假如gdbt用来做回归,损失函数是平方差损失,基函数是cart决策树 
理论解释:

gdbt的损失函数如下:

L(\hat{y})=\frac{1}{2}\sum_{i=1}^{m}(y^{(i)}-\hat{y}^{(i)})^2
显然y^{(i)}是第i号样本的标记值,它是已知的,\hat{y}^{(i)}是第i个样本的预测值,它是未知,我们想利用梯度下降,初始化时给\hat{y}^{(i)}一组值,然后不断地梯度下降,最终得到一组\hat{y}^{(i)},使得损失函数的值最小,梯度下降公式如下:

\hat{y}^{(i)}:=\hat{y}^{(i)}-\alpha(\hat{y}^{(i)}-y^{(i)})=\hat{y}^{(i)}+\alpha(y^{(i)}-\hat{y}^{(i)})

可以看到,对损失函数关于\hat{y}^{(i)}求偏导,刚好是\hat{y}^{(i)}-y^{(i)},这里给我们一个启示,即便偏导不是{y}^{(i)}\hat{y}^{(i)}的差值,是其它的形式,那么我们对其它形式的值,进行拟合就行了啊

那我们具体如何实践呢?

假如有一堆样本,输出值y表征年龄,样本特征有购物金额、上网时长、经常到百度知道等特征,如下A、B、C、D四个用户的年龄分别为14、16、24、26,用gdbt进行梯度提升如下:


        首先,第一棵用cart决策树做回归,针对每一个{y}^{(i)},会得到一个\hat{y}^{(i)},其次第二棵树,是直接将y^{(i)}-\hat{y}^{(i)}作为输入,再利用cart决策树做回归,仔细想这个过程,第一棵树得到的\hat{y}^{(i)}其实相当于梯度下降的初始值,然后用\hat{y}^{(i)}加上第二棵树对y^{(i)}-\hat{y}^{(i)}的预测结果,就是新的\hat{y}^{(i)},这相当于一次梯度下降,因为,y^{(i)}-\hat{y}^{(i)}的预测结果,在某种程度上是不是可以相当于\alpha(y^{(i)}-\hat{y}^{(i)}),那为什么不直接加上y^{(i)}-\hat{y}^{(i)},要是这样的话,你还怎么迭代生成下一棵树呢?这个例子是比较巧合的,因为第二棵树生成以后,y^{(i)}-\hat{y}^{(i)}已经为0了,否则还可以对y^{(i)}-\hat{y}^{(i)}接着拟合,继续进行梯度下降,最终使得\hat{y}^{(i)}会无限的接近y^{(i)},甚至相等。  

       我对" y^{(i)}-\hat{y}^{(i)}已经为0了,否则还可以对y^{(i)}-\hat{y}^{(i)}接着拟合"持怀疑态度,其实是可以接着往下拟合的,经过第二颗树之后,14 被预测成了 15-1=14,残差是0,16被预测成了15-1=14,残差是2,24被预测成25+1=26,残差是-2,26被预测成25+1=26,残差是0,所以接下来需要对(0, 2, -2, 0)进行预测,上面说的0只是针对第二颗决策树,无法再往下分了而已,我还可以有第三棵决策树

    

2) xgoost

xgboost的损失函数:

J(f_t)=\sum_{i=1}^nL(y^{(i)},\hat{y}^{(i)}_{t-1}+f_t(x^{(i)}))+o(f_t)+C

其中f_t代表第t棵树对应的函数,L是损失函数,\hat{y}^{(i)}_{t-1}是直到第t-1颗树,每个样本的预测值,o(f_t)是正则项,C是常量,后面再说,正则项可以取

o(f_t)=\gamma T+\frac{1}{2}\lambda\sum_{j=1}^{T}\omega_j^2

那么我们现在的目的就是,第t棵树如何划分,也就是f_t(x^{(i)})应该如何取才能让当前的损失L最小,很容易理解f_t(x^{(i)})就是残差,因为如果,f_t(x^{(i)})=y^{(i)}-\hat{y}^{(i)}_{t-1} ,L就已经为0了,现在就来看一看,xgboost是如何得到最优的f_t(x^{(i)})的?

这里我们要用到二阶的泰勒展示,把J(f_t)展开,二阶泰勒展示如下:

f(x+\Delta{x})\approx f(x)+f^{'}(x)\Delta{x}+\frac{1}{2}f^{''}(x)\Delta{x}^2

令 \Delta{x}=f_t(x^{(i)})

x=\hat{y}^{(i)}_{t-1}

g^{(i)}=\frac{\partial L}{\partial{\hat{y}^{(i)}_{t-1}}}

h^{(i)}=\frac{\partial^2{L}}{\partial^2{\hat{y}^{(i)}_{t-1}}}

很容易得到:

L(y^{(i)},\hat{y}^{(i)}_{t-1}+f_t(x^{(i)}))\approx L(y^{(i)},\hat{y}^{(i)}_{t-1})+g^{(i)}f_t(x^{(i)})+\frac{1}{2}h^{(i)}f_t^2(x^{(i)})

这里要注意,y^{(i)} 是针对每一个样本的标记值,是已知的,接着

J(f_t)\approx \sum_{i=1}^nL(y^{(i)},\hat{y}^{(i)}_{t-1})+g^{(i)}f_t(x^{(i)})+\frac{1}{2}h^{(i)}f_t^2(x^{(i)})\quad + \quad o(f_t)+C

=\sum_{i=1}^ng^{(i)}f_t(x^{(i)})+\frac{1}{2}h^{(i)}f_t^2(x^{(i)})\quad + \quad o(f_t)+C\quad(because \quad L(y^{(i)},\hat{y}^{(i)}_{t-1})\quad is\quad a \quad constant)

接下来要做一个简单的变换,我们知道g^{(i)}h^{(i)}是直到t-1棵树,损失函数L对预测值\hat{y}^{(i)}_{t-1}的一阶导数和二阶导数,假设当前第t棵树,将所有样本分到了T个叶子结点中,每个叶子结点的值是\omega_j,而f_t(x^{(i)})肯定对应某一个\omega_j(我们暂不去管,具体这棵树是如何分的),那么公式就可以进行叶子结点求和的变换

=\sum_{j=1}^{T}\omega_j\sum_{i\in{I_j}}g^{(i)}+\omega_j^2\sum_{i\in{I_j}}\frac{1}{2}h^{(i)}\quad + \gamma{T}+\frac{1}{2}\lambda \sum_{j=1}^{T}\omega_j^2+C

=\sum_{j=1}^{T}\omega_j\sum_{i\in{I_j}}g^{(i)}+\frac{1}{2}\omega_j^2(\sum_{i\in{I_j}}h^{(i)}+\lambda)\quad +\gamma{T}+C

其中取正则项

o(f_t)=\gamma T+\frac{1}{2}\lambda\sum_{j=1}^{T}\omega_j^2

I_j是属于j号叶子结点的样本的集合,并且令

G_j=\sum_{i\in{I_j}}g^{(i)}

H_j=\sum_{i\in{I_j}}h^{(i)}

得到

J(f_t)=\sum_{j=1}^{T}\omega_jG_j+\frac{1}{2}\omega_j^2(H_j+\lambda)\quad +\gamma{T}+C

那么,我们想知道\omega_j如何取(即第t棵树如何划分),才能使J(f_t)最小,只需要J(f_t)\omega_j求偏导,并令导数等于0,如下

\frac{\partial{J(f_t)}}{\partial{\omega_j}}=G_j+(H_j+\lambda)\omega_j=0

\omega_j=-\frac{G_j}{H_j+\lambda}

然后将\omega_j带入J(f_t),得到如下
J(f_t)=-\frac{1}{2}\sum_{j=1}^{T}\frac{G_j^2}{H_j+\lambda}\quad + \gamma{T}+C

最后就可以用此公式,通过贪心法来划分第t棵树,选择使损失降低的最快的方向作为划分。

以上就是xgboost最核心的推导了,和gdbt相比,虽然都是拟合残差,但xgboost是利用自己推倒出的公式,进行树的划分,而gdbt的基分类器实际是cart决策树

参考文章:GBDT:梯度提升决策树 - 简书

参考视频:小象学院邹老师讲解xgboost的视频

4、正则化有没有听过?归一化和正则化的区别?汽车数量的问题,为什么要做归一化啊?

        正则化是针对模型的,它是为了防止过拟合,而归一化是针对数据的,它是为了让你所处理的数据,维持在一个量级上,所以一组数据,如果有汽车数量的特征,它的量级就会很大,或许是千级别的,也可能是万级别的,那么就可以对汽车数量的特征归一化,比如,让其维持在0到1的范围内,那么为什么要归一化呢?

        拿神经网络举例,可以加快神经网络的训练速度,因为我们可以在梯度下降时,选择大一点的学习率,更详细一点的解释如下:

        假设样本有2个特征,x_1的范围是1到1000,x_2的范围是0到1,那么 \omega_1\omega_2的范围将会非常不同,如果放在一个三维的图像上,代价函数,就有点像狭长的碗状,如果画出该函数的部分轮廓,它也将会一个狭长的函数,但是如果归一化的话,代价函数就会更加对称,类似圆形,如果你在不对称的图形上用梯度下降,就必须使用非常小的学习率,也就是说会训练很慢,如果在更对称的函数上,无论从哪个位置开始,都能够最直接的找到最小值,这样就可以在梯度下降中使用更大的步长,总的来说,代价函数圆一点,更容易优化,前提就是特征必须在相似的范围内,显然归一化可以做到这一点
5、我们知道 logistic 是做二分类的,那么它是怎么做多分类的啊?

         我们知道逻辑回归是把\theta^TX映射到sigmoid函数,而做多分类时,实际是softmax回归,是把\theta^TX映射到softmax函数,softmax函数如下:

p(c=k|x;\theta)=\frac{exp(\theta^T_kX)}{\sum_{l=1}^{K}exp(\theta^T_lX)}

可以看出,针对每一个类别,有一组参数(不像逻辑回归,只有一组参数),所以\theta 实际是一个k行n列的矩阵,n是由样本的特征数决定的,最后属于哪个类别的概率高,就属于哪一类呗


6、随机森林的使用场景?

a、决策树的弱点

决策树对训练数据有很好的分类能力,但对测试数据未必有很好的分类能力,也即容易产生过拟合

b、使用场景

1)决策树/随机森林,代码清晰、逻辑简单,在胜任分类问题的同时,可以作为数据分布的首个算法尝试

2)可以用来衡量特征重要性,还可以用来做异常值检测


7、用python 写kmeans算法

kmeans 的终止条件是迭代10次,代码如下:

# /usr/bin/python
# -*- encoding:utf-8 -*-

import numpy as np
import random as rd

def myOwnKmeans(a_listData, a_iCategory):
	print 'Points needing clustering\n', a_listData
	npListData=np.array(a_listData)
	listCenter = []
    
	# initialize the cluster centers
	for element in rd.sample(np.arange(npListData.shape[0]), a_iCategory):
		listCenter.append(npListData[element])
	# print listCenter
	nplistCenter = np.array(listCenter)
	print 'First Cluster Center\n', nplistCenter
    
	iterTimes = 0
	while iterTimes < 10:
		listResult = [list() for i in range(a_iCategory)]
		for data in npListData:
			minIndex = pointDis(data, nplistCenter)
			listResult[minIndex].append(data)
		npResult = np.array(listResult)
		print 'After clustering\n', npResult
		
		listCenter = []
		for cluster in npResult:
			listCenter.append(np.mean(cluster, axis=0))
		
		nplistCenter = np.array(listCenter)
		print 'new nplistCenter\n', nplistCenter
		
		iterTimes = iterTimes + 1

# Calculate the distance between two points
def pointDis(a_Point, a_OtherPoint):
	return np.argmin(np.sum((a_Point - a_OtherPoint) ** 2, axis=1))

if __name__ == '__main__':
	# test kmeans
	iCategory = 2
	test = [[1, 2, 3], [3, 4, 5], [7, 8, 9], [10, 11, 12]]
	myOwnKmeans(test, iCategory)
    
	# # test Initialization of deep and shallow copies of two-dimensional arrays
	# hhh = [list() for i in range(5)]
	# for element in test:
	# 	hhh[0].append(element)
	# print hhh
    
	# # test numpy interface  numpy.mean(a, axis, dtype, out,keepdims )
	# print 'mean value\n', np.mean(test, axis=0)
    
	# # Test Cluster Center Change Rate
	# test1 = [[0, 1, 3], [3, 4, 5]]
	# nptest1 = np.array(test1)
	# test2 = [[1, 2, 3], [7, 4, 5]]
	# nptest2 = np.array(test2)
	# print 'pointDis(', test1, test2, ')\n', pointDis(nptest1, nptest2)

8、xgboost 中用的是回归树还是分类树?

回归树啊

9、知道偏差和方差吗?

a、公式解释

对于样本x,y为x的真实标记,f(x;D)为训练集D上学得模型f在x上的预测输出,以回归任务为例,学习算法的期望预测为:

\overset{-}{f}(x)=E_D[f(x;D)]

则方差为:

var(x)=E_D[(f(x;D)-\overset{-}{f}(x))^2]

而期望输出与真实标记的差别成为偏差(bias),即

bias^2=(\overset{-}{f}(x)-y)^2

b、理论解释

偏差(Bias)描述的是预测值(估计值)的期望与真实值之间的差距。偏差越大,越偏离真实数据。

方差(Variance)描述的是预测值的变化范围,离散程度,也就是离其期望值的距离。方差越大,数据的分布越分散。

参考文章:

https://blog.csdn.net/u014229594/article/details/80587277
https://blog.csdn.net/jiayk2016/article/details/81227082

c、通过训练集和验证集的误差判断偏差、方差情况

偏差方差判断
Train set error1%15%15%0.5%
Dev set error11%16%30%1%
偏差方差判断high variancehigh biashigh variance & high baislow bais & low variance

总结起来如下:

如果训练集上误差都比较大,那就是欠拟合,可以认为这种算法偏差比较高
如果训练集误差小,验证集误差大,那么,那么可以认为此算法,高方差

是否高偏差主要看训练集的误差,是否高方差主要看误差从训练集到验证集的变化(这我总结的,不一定对)

参考:

吴恩达老师,深度学习课程

吴恩达机器学习:方差与偏差_机器学习笔记-CSDN博客_机器学习偏差和方差

文章 

GBDT:梯度提升决策树 - 简书

感觉,对方差和偏差的理解很是深刻,有时间,可以具体再记录一下啊

10、为什么说xgboost可以处理缺失值?

陈天奇论文中关于处理缺失值部分的伪代码:

a、关键符号解释:
        我们划分树的时候,肯定是以当前结点为基准进行划分,那么伪代码中的集合I,就是属于当前结点的样本的集合(注意,并不是指训练集的所有样本的集合),

        而I_k集合需要满足第i个样本的第k个特征不是缺失值,换句话说,只有第k个特征有值的样本才会出现在I_k集合中,举例说就是,I_1中所有的样本必须满足第1个特征有值,I_2同理,所以说它表示的是k个集合,从I_1\quad I_2 \quad I_3,一直到I_k

b、具体做法细节解释:
        因为划分树时用的是贪心法,所以对每个特征都要划分试一试(伪代码中的 for k=1 to m),而且对每一种特征的各种划分也都要试一试(for j in sorted(I_k,ascent order by x_{jk})
具体做法:
        以第一个小的for循环(缺失的样本放在了右结点)为例,我们知道对于每个样本的g^{(i)}h^{(i)}都是可以计算出来的,是已知的,那么当前结点的损失就计算出来了,并且I_k中的样本是根据x_{jk}排过序的,那么我们把第k个特征,值最小的样本放在左结点,此时左结点的一阶导、二阶导的总和以及损失肯定是可以计算出来的,然后你用总的一阶导数总和和二阶导数总和分别减去左结点的一阶导数总和和二阶导数总和,剩余的肯定就是右结点的一阶导数总和和二阶导数总和(里面肯定包括第k个特征有缺失值的样本的一阶导和二阶导),那么右结点的损失也就可以计算出来的,从而得到当前的损失下降程度,紧接着,我们将第k个特征值次小的样本再往左结点划分,再计算此时的损失下降程度,以此类推,同时还要计算缺失样本放在左结点的情况(第二个小的for循环),最终找到损失下降最多的划分作为当前的划分。

参考文章:

Xgboost如何处理缺失值_qiuchaoxi的博客-CSDN博客_xgboost如何处理缺失数据

11、说一说你对数据离散化的理解?

a、模型的需要,比如朴素贝叶斯
b、更接近于知识层面的表达,比如2000到6000,只需要打一个低薪的标记就可以了
c、可去除一些数据的干扰,比如年龄,突然来了个异常值300岁
d、容易计算,相当于数据少了
e、模型会更稳定啊,比如年龄长了一岁,不至于让结果变化太大
f、其实相当于简化了数据的复杂程度,拿逻辑回归模型来说,某种程度,可以降低过拟合

12、先空住

13、说说隐马尔可夫模型和CRF区别

自然语言处理这里确实不是很熟,先空住吧

14、gdbt在什么情况下,不如logistics算法

gbdt融合LR的核心思想(我的理解,不一定对啊):
gdbt得到的每棵树的每个叶子结点,相当于一个融合的特征,因为一个叶子结点肯定对应一组特征啊,特征值就是那个叶子结点的值
具体做法:
先把这些样本用gdbt训练一次,相当与每个样本,会映射到一个新的样本,然后我们将新得到的样本喂给LR,再做一次训练。

有空看看下面这篇文章,估计会更清楚啊

GBDT+LR算法解析及Python实现 - Bo_hemian - 博客园

15、kmeans算法的k值时如何选定的?

1、根据经验选
2、记得谱聚类可以得出最佳的聚类个数,所以我们可以先用谱聚类得到最佳的聚类个数,然后将其喂给kmeans 

这个都是不太确定的,到时把谱聚类看看,把这里写清楚了啊

16、激活函数有了解吗?

a、tanh 激活函数有类似数据中心化的效果,所以几乎在任何场合,tanh激活函数都比sigmoid效果好,但有个例外,在输出层,因为概率要在0到1,需要用sigmoid激活函数,而 tanh 函数的范围是-1 到1 啊  
b、tanh 和 sigmoid 激活函数 都有个共同的缺点:在自变量非常大或者非常小时,函数的斜率接近与0 (这个由函数图像可以看的出来),会导致梯度下降非常缓慢  
c、选激活函数的经验法则:
        如果你的输出结果是0到1,比如你做二分类,那么输出层可以用sigmoid函数,其它层都用Relu 激活函数,因为它已经是激活函数的默认选择了  
d、在实践中使用 Relu激活函数,会使你的神经网络学习速度快很多,相比于sigmoid和tanh激活函数,是因为Relu没有函数斜率接近与0,导致学习速度慢很多的效应  
e、神经网络有很多参数选择,我们真的不确定,只能去试一试,比如隐藏单元的个数,比如初始化权重,比如激活函数的选择,你需要在你的应用中去尝试   

参考文献:

吴恩达老师,深度学习视频

17、kmeans算法的缺点时什么,kmeams++算法的缺点是什么?

a、kmeans算法对初始聚类中心的选择特别敏感,一旦初始聚类中心选择的不好,效果就会特别差,所以衍生出了kmeans++算法,它对初始聚类中心的选择做了一些限制
b、kmeans可以把某个样本聚类某一类中,但它无法给出,这个样本属于这个类别的后验概率,所以衍生出了混合高斯分布
c、对噪声和孤立点数据敏感

18、boosting算法有了解吗?

       首先这里要先说几个基本的概念,集成学习、boosting、bagging、Bootstrap,我尽量都用自己的理解来描述。
       以分类任务为例,集成学习就是通过组合分类器来进行学习的一种思路,目前是有2个分支,一个分支是boosting,另外一个分支是bagging和随机森林(查阅了一些博客和文章,会把随机森林作为bagging 的一种代表性算法,其实也没错,毕竟随机森林是在bagging 的基础上改进过来的),前者的分类器之间不是相互独立的,就是下一个分类器的生成是在上一个分类器的基础上得到的,是一个串行的关系,而后者的分类器之间是相互的独立的,是一个并行的关系。

       上面的概念大致了解了,就很容易知道adboost、gdbt、xgboost 都是boosting 系列的算法,只不过它们之间生成下一个分类器所用的方法不一样而已,从名字就可以看出来,adboost的全称是adaptive boosting,通过自适应的调节样本的权重来得到下一个分类器,gdbt 的全称是Gradient Boosting Decison Tree,就是利用梯度下降生成下一棵树,xgboost 的全称是eXtreme Gradient Boosting,它是GDBT上的一种改进。
       最后简单说下 bagging和随机森林的区别,bagging 的全称是bootstrap aggregating,这里提到了bootstrap,bootstrap其实就是一种有放回的抽样,意思就是bagging就是一个有放回抽样的聚合,bagging的基分类器,可以是svm、决策树、logistic等,可以是各种分类器,仅仅是对样本用 bootstrap方法进行抽样,并不对属性进行抽样,随机森林做了一个怎样的改进呢?首先它的基分类器只能是决策树,而且针对每一个分类器,它不仅会对样本用 bootstrap方法抽样,还会对属性进行随机选择。

19、常用的推荐算法有哪些?

logistic 回归、FM算法、FFM算法、deepFM算法、协同过滤算法

20、常用的归一化方法有哪些?

a、线性归一化—Min-Max归一化

这种方法是对原始数据的线性变换,将数据归一到[0,1]中间,公式如下:

X_{norm}=\frac{X-X_{min}}{X_{max}-X_{min}}

这种方法对于outlier非常敏感,因为outlier影响了max或min值,所以这种方法只适用于数据在一个范围内分布的情况。

b、0均值归一化:StandardScaler

公式:

z=\frac{x-\mu }{\sigma }

其中\mu是均值,\sigma是方差

均值和方差的计算方法如下:

第一步,计算数据组的均值和离差。均值=  所有数值的总和 / 所有数值的个数总和。通过对每个数值减去均值得到离差。离差=数值-均值
第二步,求出所有离差的平方值。
第三步,将所有离差的平方值相加。
第四步,用离差的平方和除以数值总数减1
第五步,标准差是上述商的开方。综上所述,标准差的计算方法是,离差平方和/(数值总数-1),然后再开根号。

适用于,如果数据的分布本身就服从正态分布,就可以用这个方法,这种方法基本可用于有outlier的情况,但是,在计算方差和均值的时候outliers仍然会影响计算

参考文章:

https://blog.csdn.net/chenvast/article/details/83270827
https://www.cnblogs.com/bjwu/p/8977141.html

21、写python小程序,常用的代码?

A、初始化相关

1)、

code:

listData = [-1 for i in range(8)]

result:

[-1, -1, -1, -1, -1, -1, -1, -1]

2)、

code:

listData = [[] for i in range(8)]

result:

[[], [], [], [], [], [], [], []]

3)、

code: 

output_list = [x ** 2 if x % 2 == 0 else x ** 3 for x in range(10)]

result:

[0, 1, 4, 27, 16, 125, 36, 343, 64, 729]

4)、

code:

listData = [i ** 2 for i in range(8) if i%2==0]

result:

[0, 4, 16, 36]

B、循环相关

code:

for tmpIndex, charElement in enumerate(listData):

    print tmpIndex, charElement

result:

0 []
1 []
2 []
3 []
4 []
5 []
6 []
7 []


 

C、赋值

1)、字符当索引

code:

listHashTable=[0 for index in range(256)]

listHashTable[ord('a')]=100

print listHashTable[97]

result:

100

D、字典相关

1)、

code:

dictData={'songbw':0, 'xiaosh':1, 'majf':2}

if 'liuzhy' not in dictData:

    print 'sucess'

result:

sucess

只是觉得用这种方法判断一个key是否在字典里,很高端、很专业

2)、

code:

del dictData['songbw']

print dictData

result:

{'majf': 2, 'xiaosh': 1}

只是觉得,这样删除dictionary中的key,很cool

E、队列简单使用

# -*- coding:utf-8 -*-
# /usr/bin/python

from collections import deque
queue = deque([])

# add data
queue.append('songbw')
queue.append('liuzy')
print queue

# Fetch data
name = queue.popleft()
print 'name is ', name
print queue

# Judge whether the queue is empty
if queue:
    name = queue.popleft()
    print 'name is ', name
    print queue

if not queue:
    print 'queue is empty'

result:

deque(['songbw', 'liuzy'])
name is  songbw
deque(['liuzy'])
name is  liuzy
deque([])
queue is empty

F、栈的简单使用

stack = []
stack.append(5)
stack.append(6)
stack.append(7)
print stack

testValue = stack.pop()
print testValue, '  ', stack

while stack:
    testValue = stack.pop()
    print testValue, '  ', stack

result:

[5, 6, 7]
7    [5, 6]
6    [5]
5    []

这就是用list实现栈,当python中要用栈这种数据结构,这么用就可以了

G、链表、树、类的简单使用

class listNode:
    def __init__(self, a_value):
        self.m_value = a_value
        self.m_next = None

class binaryTreeNode:
    def __init__(self, a_value):
        self.m_value = a_value
        self.m_left = None
        self.m_right = None
        
class acessList:
    def getRootValue(self, a_listRoot):
        print a_listRoot.m_value
class acessTree:
    def getRootValue(self, a_treeRoot):
        print a_treeRoot.m_value
      
if __name__== '__main__':
#     build list
    listHead = listNode(5)
#     build tree
    treeHead = binaryTreeNode('A')
#     acess list
    testList = acessList()
    testList.getRootValue(listHead)
#     acess Tree
    testTree = acessTree()
    testTree.getRootValue(treeHead)

result:

5
A

知道这些,写个python小程序松松的啊

22、生成模型和判别模型的区别是什么?

先给出答案:
svm、逻辑回归 这些都是判别模型
朴素贝叶斯、lda、hmm 是生成模型啊


感性上:
1、判别模型有明显的边界,而生成模型没有


理性上呢
判别模型直接对 p(y/x)进行建模,推断从x到y的一种复杂的关系
而生成模型是对p(x,y)进行建模,它根据训练集能学习到这个分布,那么我们就可以计算p(y/x)的概率了,哪个y的概率大,就标记成哪个y,就拿朴素贝叶斯来说,通过 p(x,y)=p(y)p(x/y),对p(x,y)进行建模,通过 p(y/x)=p(x,y)/p(x),来对y进行标记


优缺点:
生成模型对数据量要求高,而判别模型不需要啊

23、顾客流失项目中问到的东西?

项目中用到的评价指标:召回率和precision、accuracy

accuray 是所有样本中,计算正确的          

recall 和 precision 都是以正样本为基础,只不过     recall 其实就是召回率,是以真实的正样本为分母     presion 是准确率,是以预测出来的正样本为分母

数据1000条,36个特征,正样本368,副样本 632条,正负样本比,大致是3:5

结果如下:

随机森林的效果:
训练集准确率: 0.976
训练集查准率: 0.9269406392694064
训练集查全率: 0.9902439024390244
训练集f1 Score: 0.9575471698113208

测试集准确率: 0.796
测试集查准率: 0.6551724137931034
测试集查全率: 0.5507246376811594
测试集f1 Score: 0.5984251968503937


先去掉 one-hot 编码试一试啊
把 address 的 one-hot 编码去掉

OOB Score:  0.7413333333333333
训练集准确率: 0.9866666666666667
训练集查准率: 0.9577464788732394
训练集查全率: 0.9951219512195122
训练集f1 Score: 0.9760765550239233
测试集准确率: 0.78
测试集查准率: 0.64
测试集查全率: 0.463768115942029
测试集f1 Score: 0.5378151260504201

去掉 所有的 one-hot 编码
OOB Score:  0.732
训练集准确率: 0.992
训练集查准率: 0.9715639810426541
训练集查全率: 1.0
训练集f1 Score: 0.9855769230769231
测试集准确率: 0.788
测试集查准率: 0.6904761904761905
测试集查全率: 0.42028985507246375
测试集f1 Score: 0.5225225225225225

归一化也去掉

OOB Score:  0.74
训练集准确率: 0.9893333333333333
训练集查准率: 0.966824644549763
训练集查全率: 0.9951219512195122
训练集f1 Score: 0.9807692307692307
测试集准确率: 0.764
测试集查准率: 0.6041666666666666
测试集查全率: 0.42028985507246375
测试集f1 Score: 0.4957264957264957

把年龄分组也去掉了
OOB Score:  0.7413333333333333
训练集准确率: 0.9906666666666667
训练集查准率: 0.9714285714285714
训练集查全率: 0.9951219512195122
训练集f1 Score: 0.983132530120482
测试集准确率: 0.776
测试集查准率: 0.6444444444444445
测试集查全率: 0.42028985507246375
测试集f1 Score: 0.5087719298245614


逻辑回归:
训练集准确率: 0.804
训练集查准率: 0.6933333333333334
训练集查全率: 0.5073170731707317
训练集f1 Score: 0.5859154929577465
测试集准确率: 0.752
测试集查准率: 0.5853658536585366
测试集查全率: 0.34782608695652173
测试集f1 Score: 0.43636363636363634

24、逻辑回归为什么称之为回归?

我自己的简单理解(不一定对啊):

我们知道

\hat{y}=\frac{1}{1+e^{-\theta^TX }}

所以,很容易得到

\theta^TX=\frac{log\hat y}{log(1-\hat y)}

可以看出,我们预测的值,做一个log变换之后是线性,显然它是一个回归值,其实逻辑回归是一个广义的线性模型。

25、判断2个字符串是否可以相互转换?

示例:
a  c  a
m  d  x
不可以
解释:第一列表明a和m可以相互转换,那么第三列,a就只能转换成m,而不能转换成x

a  c  t
m  d  a
不可以
解释:第一列表明a和m可以相互转换,那么第三列,t就不能转换成a了,因为a只能转换成m

a  c  m
m  d  a
可以
解释:a和m可以互转,c和d可以互转

a  c  t
m  d  x
可以
解释:a和m可以互转,c和d可以互转,t和x也可以互转

python代码如下:

# -*- coding:utf-8 -*-
# /usr/bin/python

# The logic is simple:
# If two characters do not exist, add them. Otherwise, at least one character exists and neet to satify its corresponding characters are equal

def isStringConvert(a_oneStr, a_anoAnoStr):
    iOneLen = len(a_oneStr)
    iAnoLen = len(a_anoAnoStr)
    if iOneLen != iAnoLen:
        return False
    dicData = {}
    for i in range(iOneLen):
        print a_oneStr[i], a_anoAnoStr[i]
        bOneExist = dicData.has_key(a_oneStr[i])
        bAnoExist = dicData.has_key(a_anoAnoStr[i])
        # Two letters can not be found, need to be added to dictionary
        #example
        #a
        #m
        if (not bOneExist) and (not bAnoExist):
            print 'add'
            dicData[a_oneStr[i]] = a_anoAnoStr[i]
            dicData[a_anoAnoStr[i]] = a_oneStr[i]
            continue
        #The letter that you can find need to satify the value is consistet
        #example
        #a  c  a
        #m d  x
        if bOneExist and a_anoAnoStr[i] != dicData[a_oneStr[i]]:
            print 'bOneExist',a_oneStr[i], a_anoAnoStr[i]
            return False
        # The letter that you can find need to satify the value is consistet
        #example
        #a  c  t
        #m d  a
        if bAnoExist and a_oneStr[i] != dicData[a_anoAnoStr[i]]:
            print 'bAnoExist', a_oneStr[i], a_anoAnoStr[i]
            return False
    return True

if __name__ == '__main__':
    strOne = 'songbw'
    strAno = 'xiaosh'
    if isStringConvert(strOne, strAno):
        print strOne, 'and', strAno, 'can convert'
    print '\n'

    strOne = 'sono'
    strAno = 'xiam'
    if isStringConvert(strOne, strAno):
        print strOne, 'and', strAno, 'can convert'

    print '\n'

    strOne = 'songia'
    strAno = 'xiamon'
    if isStringConvert(strOne, strAno):
        print strOne, 'and', strAno, 'can convert'

运行结果:

s x
add
o i
add
n a
add
g o
bAnoExist g o


s x
add
o i
add
n a
add
o m
bOneExist o m


s x
add
o i
add
n a
add
g m
add
i o
a n
songia and xiamon can convert

改进python代码(主要是逻辑好懂)如下:

# -*- coding:utf-8 -*-
# /usr/bin/python

# The logic is simple:
# If two characters do not exist, add them. Otherwise, at least one character exists and neet to satify its corresponding characters are equal

def isStringConvert(a_oneStr, a_anoAnoStr):
    iOneLen = len(a_oneStr)
    iAnoLen = len(a_anoAnoStr)
    if iOneLen != iAnoLen:
        return False
    dicData = {}
    for i in range(iOneLen):
        print a_oneStr[i], a_anoAnoStr[i]

        # The letter that you can find need to satify the value is consistet
        # example
        # a  c  a
        # m d  x
        if a_oneStr[i]  in dicData:
            if dicData[a_oneStr[i]] == a_anoAnoStr[i]:
                continue
            else:
                return False

        # The letter that you can find need to satify the value is consistet
        # example
        # a  c  t
        # m d  a
        if a_anoAnoStr[i] in dicData:
            if dicData[a_anoAnoStr[i]] == a_oneStr[i]:
                continue
            else:
                return False

        print 'add'
        # Two letters can not be found, need to be added to dictionary
        dicData[a_oneStr[i]] = a_anoAnoStr[i]
        dicData[a_anoAnoStr[i]] = a_oneStr[i]
    return True


if __name__ == '__main__':
    strOne = 'songbw'
    strAno = 'xiaosh'
    if isStringConvert(strOne, strAno):
        print strOne, 'and', strAno, 'can convert'
    else:
        print strOne, 'and', strAno, 'cannot convert'
    print '\n'

    strOne = 'sono'
    strAno = 'xiam'
    if isStringConvert(strOne, strAno):
        print strOne, 'and', strAno, 'can convert'
    else:
        print strOne, 'and', strAno, 'cannot convert'


    print '\n'

    strOne = 'songia'
    strAno = 'xiamon'
    if isStringConvert(strOne, strAno):
        print strOne, 'and', strAno, 'can convert'
    else:
        print strOne, 'and', strAno, 'cannot convert'

运行结果:

s x
add
o i
add
n a
add
g o
songbw and xiaosh cannot convert


s x
add
o i
add
n a
add
o m
sono and xiam cannot convert


s x
add
o i
add
n a
add
g m
add
i o
a n
songia and xiamon can convert

26、打印出第n次出现k次的字符

比如给个字符串,a出现了3次,b也出现了3次,假如a先出现,b紧接着出现,那么第一次出现3次的字符就是a,第二次出现3次的字符就是b
示例:
给定字符串:
abcabxyb

The  1 th character that appear  1 times is  c
The  1 th character that appear  2 times is  a
The  1 th character that appear  3 times is  b
The  2 th character that appear  1 times is  x
The  3 th character that appear  1 times is  y

python代码如下:

# -*- coding:utf-8 -*-
# /usr/bin/python

# 参数 strData 是传入的字符串   
# 参数 a_iSeq 是 n   
# 参数 a_iCharSum 是 k  

def charAppearKtimes(strData, a_iSeq, a_iCharSum):
    if strData and a_iSeq > 0 and a_iCharSum > 0:
#         Build a dictionary from character to its occurrence and get max times
        dicChar2Sum = {}
        maxNumChar = 1
        for char in strData:
            if char not in dicChar2Sum:
                dicChar2Sum[char] = 1
            else:
                dicChar2Sum[char] = dicChar2Sum[char] + 1
                if dicChar2Sum[char] > maxNumChar:
                    maxNumChar = dicChar2Sum[char]
        if a_iCharSum <= maxNumChar:
            listlistChar = [[] for i in range(maxNumChar + 1)]
            for char in strData:
                if char in dicChar2Sum:
                    listlistChar[dicChar2Sum[char]].append(char)
                    del dicChar2Sum[char]
            if len(listlistChar[a_iCharSum]) >= a_iSeq:
                return listlistChar[a_iCharSum][a_iSeq - 1]

if __name__ == '__main__':
    strTestData = 'abcabxyb'
    for iSeq in range(4):
        for iCharSum in range(4):
            print 'The ', iSeq, 'th character that appear ', iCharSum, 'times is ', charAppearKtimes(strTestData, iSeq, iCharSum)
    
    

result:

The  0 th character that appear  0 times is  None
The  0 th character that appear  1 times is  None
The  0 th character that appear  2 times is  None
The  0 th character that appear  3 times is  None
The  1 th character that appear  0 times is  None
The  1 th character that appear  1 times is  c
The  1 th character that appear  2 times is  a
The  1 th character that appear  3 times is  b
The  2 th character that appear  0 times is  None
The  2 th character that appear  1 times is  x
The  2 th character that appear  2 times is  None
The  2 th character that appear  3 times is  None
The  3 th character that appear  0 times is  None
The  3 th character that appear  1 times is  y
The  3 th character that appear  2 times is  None
The  3 th character that appear  3 times is  None

27、如何用awk计算文件中一组数的平均值?

想想也可悲,好歹linux下的开发,从毕业后就一直在整,用awk计算文件中一组数的平均值,竟然都没写出来,想想工作中其实也用到几次,但每次都偷懒,不去查,事后也不总结。

文件a.txt中数据:

12
33
55
55
77

awk命令:

awk 'BEGIN {total=0} {total+=$0} END{print total/NR}' a.txt
awk '{if(NR==1){total=$0}} {if(NR>1){total+=$0}} END{print total/NR}' a.txt

解释
这两个awk都可以实现该功能,只不过,第二个用了if,看上去稍微复杂一点,但其实awk命令的本质,是由一组类似if的条件组成的,即满足什么条件执行什么动作。

28、层次遍历一棵二叉树,代码实现

这个题,我是做出来的,只是当时用的C++写的,下面是python的代码实现:

# -*- coding:utf-8 -*-
# /usr/bin/python

from collections import deque

class TreeNode:
    def __init__(self, a_iValue):
        self.m_iValue = a_iValue
        self.m_leftTreeNode = None
        self.m_RightTreeNode = None

# Using queue to realize the level traversal of binary tree
class acessTree:
    # the result is put into the third parametern a_listData
    def levelTraversal(self, a_classTreeNode, a_listData):
        if a_classTreeNode:
            queue = deque([])
            queue.append(a_classTreeNode)
            while queue:
                classTmpTreeNode = queue.popleft()
                if classTmpTreeNode.m_leftTreeNode:
                    queue.append(classTmpTreeNode.m_leftTreeNode)
                if classTmpTreeNode.m_RightTreeNode:
                    queue.append(classTmpTreeNode.m_RightTreeNode)
                # print 'in code', classTmpTreeNode.m_iValue
                a_listData.append(classTmpTreeNode.m_iValue)

    # list is returned directly
    def improveLevelTraversal(self, a_classTreeNode):
        listData = []
        if a_classTreeNode:
            queue = deque([])
            queue.append(a_classTreeNode)
            while queue:
                classTmpTreeNode = queue.popleft()
                if classTmpTreeNode.m_leftTreeNode:
                    queue.append(classTmpTreeNode.m_leftTreeNode)
                if classTmpTreeNode.m_RightTreeNode:
                    queue.append(classTmpTreeNode.m_RightTreeNode)
                # print classTmpTreeNode.m_iValue
                listData.append(classTmpTreeNode.m_iValue)
        return listData


if __name__ == '__main__':
    insAcessTree = acessTree()

    # #use case one
    listData = []
    classRootNode = TreeNode('65')
    insAcessTree.levelTraversal(classRootNode, listData)
    print 'case one result:'
    print 'edition', listData
    print 'improve edition', insAcessTree.improveLevelTraversal(classRootNode)

    # use case two
    classRootNode = TreeNode('65')
    classOtherNode = TreeNode('37')
    classRootNode.m_leftTreeNode = classOtherNode
    listData = []
    insAcessTree.levelTraversal(classRootNode, listData)
    print 'case two result:'
    print 'edition', listData
    print 'improve edition', insAcessTree.improveLevelTraversal(classRootNode)


    # use case three
    listData = []
    classRootNode = TreeNode('65')
    classOtherNode = TreeNode('37')
    classRootNode.m_RightTreeNode = classOtherNode
    insAcessTree.levelTraversal(classRootNode, listData)
    print 'case three result:'
    print 'edition', listData
    print 'improve edition', insAcessTree.improveLevelTraversal(classRootNode)

    # use case four
    listData = []
    classRootNode = TreeNode('65')
    classOtherNode = TreeNode('37')
    classRootNode.m_leftTreeNode = classOtherNode
    classOtherNode = TreeNode('42')
    classRootNode.m_RightTreeNode = classOtherNode
    classOtherNode = TreeNode('55')
    classRootNode.m_leftTreeNode.m_leftTreeNode = classOtherNode
    insAcessTree.levelTraversal(classRootNode, listData)
    print 'case four result:'
    print 'edition', listData
    print 'improve edition', insAcessTree.improveLevelTraversal(classRootNode)

result:

case one result:
edition ['65']
improve edition ['65']
case two result:
edition ['65', '37']
improve edition ['65', '37']
case three result:
edition ['65', '37']
improve edition ['65', '37']
case four result:
edition ['65', '37', '42', '55']
improve edition ['65', '37', '42', '55']

解析:

当时之所以写接口 improveLevelTraversal ,好像是因为,接口 levelTraversal ,不知道为什么放数据放不到a_listData 中,但后来放进去了,因为那时不知道list 是可变对象,相当于c++ 中的引用传递,理论上是一定能把数据放进去的,如果接口没写错的话

29、访问链表中的倒数第k个元素

这个题,并不是我实际面试中遇到的,之所以写这个题,是因为,之前也遇到过考察链表,但是要用python写,那时都不会啊,所以才从剑指offer上挑了个,感觉有代表性的链表的题,然后用python实现,它的想法其实很简单:

用2个指针,第一个指针先走k步,第二个指针不动,然后两个指针一起走,当第一个指针走到头的时候,第二个指针指向的结点,就是要访问的结点,然后 python 代码实现如下:

# -*- coding:utf-8 -*-
# /usr/bin/python

# idea:
# Use two pointers, the first one goes K steps first, the second one doesn't move,
# and then the two pointers go together. When the first pointer comes to the end,
# the node pointed to by the second pointer is the node to be accessed
class listNode:
    def __init__(self, a_value):
        self.m_value = a_value
        self.m_next = None


class acessNode:
    def visitReverseNode(self, a_nodeRoot, a_iBack):
        if a_nodeRoot and a_iBack > 0:
            firstNode = a_nodeRoot
            secNode = a_nodeRoot
            for i in range(a_iBack - 1):
                if secNode:
                    secNode = secNode.m_next
            while secNode and secNode.m_next:
                secNode = secNode.m_next
                firstNode = firstNode.m_next
             #if secNode is None,a_iBack has exceeded the length           of the list,
            # so no need to return
            if secNode:
                return firstNode.m_value


if __name__ == '__main__':
    testAcess = acessNode()
    #use case 1
    #build list A B C D E
    nodeTmp = nodeHead = listNode('A')

    nodeFresh = listNode('B')
    nodeTmp.m_next = nodeFresh
    nodeTmp = nodeTmp.m_next

    nodeFresh = listNode('C')
    nodeTmp.m_next = nodeFresh
    nodeTmp = nodeTmp.m_next

    nodeFresh = listNode('D')
    nodeTmp.m_next = nodeFresh
    nodeTmp = nodeTmp.m_next

    nodeFresh = listNode('E')
    nodeTmp.m_next = nodeFresh
    nodeTmp = nodeTmp.m_next

    #     test
    print 'use case 1 :list is ABCDE'
    for k in range(8):
        print 'The ', k, '-th ', 'inverse node is ', testAcess.visitReverseNode(nodeHead, k)

    # use case 2
    # build list A
    nodeHead = listNode('A')

    #     test
    print 'use case 2 :list is A'
    for k in range(3):
        print 'The ', k, '-th ', 'inverse node is ', testAcess.visitReverseNode(nodeHead, k)


    # use case 3
    nodeHead = None
    # test
    print 'use case 3 :list is None'
    for k in range(2):
        print 'The ', k, '-th ', 'inverse node is ', testAcess.visitReverseNode(nodeHead, k)

result:

use case 1 :list is ABCDE
The  0 -th  inverse node is  None
The  1 -th  inverse node is  E
The  2 -th  inverse node is  D
The  3 -th  inverse node is  C
The  4 -th  inverse node is  B
The  5 -th  inverse node is  A
The  6 -th  inverse node is  None
The  7 -th  inverse node is  None
use case 2 :list is A
The  0 -th  inverse node is  None
The  1 -th  inverse node is  A
The  2 -th  inverse node is  None
use case 3 :list is None
The  0 -th  inverse node is  None
The  1 -th  inverse node is  None

30、求数据塔,从上到下的所有路径中,结点和最大的那个和,不用求路径(编程题)

数据塔实例如下:

A、如何把数据塔传递给函数?
我是用的一个list的list,如上图所示数据塔,是这么存储的:
[[10], [11, 12], [13, 4, 5], [7, 9, 8, 2], [5, 14, 9, 42, 55]]

B、解决此问题的核心思想是什么?

用的是动态规划,定义3个函数:

CurNode(level, index) 表示在level 层,索引是index的结点的值,level从0开始表示数据塔的第几层,index是level层的结点的索引,也是从0开始

CurMax(level, index) 表示,假如路径的最后一个结点是CurNode(level, index)时的和的最大值,它计算的伪代码如下:

if 0 == index:
    curMax(level, index) = curMax(level-1, index) + curNode(level, index)
elif lastIndex == index:
    curMax(level, index) = curMax(level-1, index-1) + curNode(level, index)
else:
    curMax(level, index) = max(curMax(level-1, index), curMax(level-1, index-1)) + curNode(level, index)

其中,lastIndex表示当前层的最后一个索引,那么到最后一层的和的最大值lastMax就如下公式

lastMax=\underset{0\le index\le lastInedex}{max}{curMax(lastLevel, index)}

其中,lastLevel表示数据塔的最后一层,则python 代码如下:

#!/usr/bin/python
# -*- encoding: utf-8

def curMax(a_listlistData, a_iLevel, a_iIndex):
    if a_listlistData and a_iLevel >=0 and a_iIndex >=0 and a_iIndex < len(a_listlistData[a_iLevel]):
        if a_iLevel:
            if 0 == a_iIndex:
                return curMax(a_listlistData, a_iLevel - 1, a_iIndex) + a_listlistData[a_iLevel][a_iIndex]
            elif len(a_listlistData[a_iLevel]) - 1 == a_iIndex:
                return curMax(a_listlistData, a_iLevel - 1, a_iIndex - 1) + a_listlistData[a_iLevel][a_iIndex]
            else:
                return max(curMax(a_listlistData, a_iLevel - 1, a_iIndex - 1),
                           curMax(a_listlistData, a_iLevel - 1, a_iIndex)) + a_listlistData[a_iLevel][a_iIndex]
        else:
            return a_listlistData[a_iLevel][a_iIndex]

def findMaxPathValue(a_listlistData):
    tempMax = None
    if a_listlistData:
        tempMax = curMax(a_listlistData, len(a_listlistData)-1, 0)
        # print tempMax
        for i in range(len(a_listlistData[-1])):
            if curMax(a_listlistData, len(a_listlistData)-1, i) > tempMax:
                tempMax = curMax(a_listlistData, len(a_listlistData)-1, i)
    return tempMax



if __name__ == '__main__':
    # use case 1
    listlistData = []
    listTemp = [10]
    listlistData.append(listTemp)
    listTemp = [11, 12]
    listlistData.append(listTemp)
    listTemp = [13, 4, 5]
    listlistData.append(listTemp)
    listTemp = [7, 9, 8, 2]
    listlistData.append(listTemp)
    listTemp = [5, 14, 9, 42, 55]
    listlistData.append(listTemp)
    print 'use case 1 is ', listlistData,\
    'and resuslt is ', findMaxPathValue(listlistData)


    # use case 2
    listlistData = []
    print 'use case 2 is ', listlistData,\
    'and resuslt is ', findMaxPathValue(listlistData)

    # use case 3
    listlistData = []
    listTemp = [10]
    listlistData.append(listTemp)
    print 'use case 3 is ', listlistData, \
    'and resuslt is ', findMaxPathValue(listlistData)

以上是递归代码,但是它多次计算curMax(),随着塔的层次变高,算重复的curMax()会越来越多,所以计算会越来越慢,一下是非递归 python 代码:

#!/usr/bin/python
# -*- encoding: utf-8

def findMaxPathValue(a_listlistData):
    if a_listlistData:
        listlistMaxValue = []
        for listData in a_listlistData:
            listlistMaxValue.append([0 for i in range(len(listData))])

        for level, listData in enumerate(a_listlistData):
            for index, data in enumerate(listData):
                curDataLen = len(listData)
                if level - 1 >= 0:
                    if 0 == index:
                        listlistMaxValue[level][index] = listlistMaxValue[level - 1][index]
                    elif curDataLen - 1 == index:
                        listlistMaxValue[level][index] = listlistMaxValue[level - 1][index - 1]
                    else:
                        listlistMaxValue[level][index] = max(listlistMaxValue[level - 1][index - 1], listlistMaxValue[level - 1][index])
                listlistMaxValue[level][index] = listlistMaxValue[level][index] + data
        return max(listlistMaxValue[level])


if __name__ == '__main__':
    # use case 1
    listlistData = []
    listTemp = [10]
    listlistData.append(listTemp)
    listTemp = [11, 12]
    listlistData.append(listTemp)
    listTemp = [13, 4, 5]
    listlistData.append(listTemp)
    listTemp = [7, 9, 8, 2]
    listlistData.append(listTemp)
    listTemp = [5, 14, 9, 42, 55]
    listlistData.append(listTemp)
    print 'use case 1 is ', listlistData,\
    'and resuslt is ', findMaxPathValue(listlistData)

    # use case 2
    listlistData = []
    print 'use case 2 is ', listlistData,\
    'and resuslt is ', findMaxPathValue(listlistData)

    # use case 3
    listlistData = []
    listTemp = [10]
    listlistData.append(listTemp)
    print 'use case 3 is ', listlistData, \
    'and resuslt is ', findMaxPathValue(listlistData)

31、能不能简单说下密度聚类?

1)一个代表是DBSCAN
a、\varepsilon邻域内样本的个数,是否达标,确定该样本是否为核心对象,将核心对象及其包含的样本作为一个簇
b、然后根据密度相连去合并其它的对象,直到无法再合并

由上述算法可知:

a、每个簇至少包括一个核心对象

b、非核心对象可以是簇的一部分,构成簇的边缘

c、包含过少对象的簇被认为是噪声


问题:

计算\varepsilon邻域是否包含某个样本时,是用的欧式距离吗,还是各种算距离的都可以啊?

我自己的答案是,各种算距离的都可以啊

2)另一个代表是密度最大化算法

先说2个基本的概念,一个是\rho_i,一个是\delta_i

\rho_i是样本的密度,简单来说,该样本周围的点多,那肯定密度就大啊,比如可以用高斯相似性来计算这个密度

一种计算密度的方法如下:

\rho _i=\sum_j\chi(d_{ij}-d_c)\\

其中

\chi(x)= \left \{\begin{array}{cc} 1, & x<0\\ 0, & otherwise \end{array}\right.

i是我们要计算密度的那个点,d_c是截断距离,我们预设的值,d_{ij}表示j点到i点的距离,显然,距离小于d_c,则密度值加1,也即\rho_i表示到点i的距离小于d_c的点的个数,所以d_c的选择必须是稳健的,一种推荐做法是使得,平均每个点的邻居数是点总数的百分之一到百分之二。

\delta_i是一个距离,表示比该样本密度大的最近距离的那个样本,所对应的距离就是\delta_i

那么显然,针对每一个样本,都有一个\rho_i 和 \delta_i,则\rho_i\delta_i 存在如下关系:

\rho_i\delta_iresult
泯然众人矣,都是簇中心
异常点
样本需要合并到某簇中心

A、针对每一个样本,计算\rho_i and \delta_i

B、可以根据某一阈值,确定出哪些样本是簇类中心,哪些样本是异常值

C、排除异常点和簇类中心,遍历剩下所有的点,距离哪个簇类中心近,就把样本放到哪一个簇里

问题:

\rho_i多大算大,\delta_i多大算大,这里肯定存在一个阈值,不知道这个阈值是如何选的,因为要据此确定簇类中心和异常点啊

3)因为 kmeans 是基于距离的聚类算法,因此它只能发现“类圆形”(凸)的聚类,这是kmeans算法的缺点,由上面可知,密度聚类是可以发现任意形状的聚类的,但计算复杂度可能比较大,需要建立空间索引来降低计算量

32、能不能简单说下层次聚类?

分为凝聚式的层次聚类和分裂式的层次聚类
1)凝聚式的层次聚类
        其实本质上还是计算簇与簇之间的相似性,只不过第一轮是,每个样本是一个簇,然后相似就合并,我猜测每一层只合并一次,比如说,某一层,挑出最相似的2个簇进行合并,直到满足簇的个数为止,而计算簇相似性的方法有很多,比如最小距离、最大距离、平均距离、mse的变化等


2)分裂式的层次聚类

        分裂式的层次聚类和凝聚式的层次聚类,过程刚好反过来,第一轮,相当于所有的样本都是属于一个簇的,然后,比如用最大的欧式距离进行分裂,我的理解是,比如找到簇中,欧式距离最大的两个点,然后以这两个点为中心,将这一个簇分成2个簇,依次类推,直到满足簇的个数为止

33、能不能简单说下谱聚类?

1)得到相似度矩阵W,并做一个简单的变换,本来自己和自己的相似度应该为1,我们把它弄成0,这样有一个好处,这一行的和,就是该样本和其它样本相似性的和
2)得到矩阵D,它是一个对角阵,对角线上的值,是相似度矩阵W的每一行的和,然后其它元素都是0
3)D-W 得到拉式矩阵,也即拉普拉斯矩阵,记为L
4)拉普拉斯矩阵,有几种变换,一种是两边都乘以D的-1/2,也即对称拉普拉斯矩阵,一种是左边乘以D逆,也即随机游走拉普拉斯矩阵
5)   对拉普拉斯矩阵求特征值和特征向量
6)特征值由小到大排列,重新生成一个矩阵,然后每一行就是样本对应的特征,那么如何生成矩阵呢?  

       首先特征值对应的特征向量是列向量,然后根据特征值有小到大进行横向堆叠,就生成了矩阵。


7)如果要聚k类的话,取每一行的前k个值,喂给kmeans就可以了

34、能不能简单说下 pca,是怎么降维的吗?

假如有m个样本,每个样本有n维的特征,如何利用 pca ,要把n维的特征降到 n^{'}维?

1)求样本集的协方差矩阵的特征值和对应的特征向量

协方差矩阵是怎么计算呢?

        样本的任意一个特征可以看成一个随机变量,那么m个样本,对应的这个特征上的值,可以看做是在这个随机变量上的m次采样,其实也就是,n个特征表示n个随机变量,那么协方差矩阵就是要求,n个随机变量相互之间的协方差。

       首先对每一个原始样本x^{(i)}进行标准化和中心化处理得到 x^{(i)'},公式如下:

x^{'(i)}=x^{(i)}-\frac{1}{m}\sum_{j=1}^{m}{x^{(j)}},  其中x^{(i)}表示原始的第i个样本,x^{'(i)}表示中心化后的第i个样本,简单说来,就是每个样本值减去样本的均值

        接着对处理后的样本横向堆叠,得到n行m列的矩阵X{'},那么,\frac{1}{m-1}X^{'}X^{'T}即为样本集的协方差矩阵,但实际\frac{1}{m-1}并不影响特征向量,对特征值进行了同比缩放,也不影响特征值之间的大小关系,所以实际操作中,对X^{'}X^{'T}求特征值和特征向量就可以了

2)对n个特征值进行从大到小的排序,并将前n^{'}个大的特征值对应的特征向量,进行横向堆叠,组成矩阵W,也即W是n行n^{'}列的矩阵

3)用z^{(i)}=W^{T}x^{(i)}对原始的样本x^{(i)}进行降维,其中x^{(i)}是n行1列,则降维后的样本就变成了n^{'}行1列

35、kmeans 的样本为什么要符合方差相同的高斯分布,效果会好一点呢?

        因为 kmeans 是在如下假定的情况下,利用 MLE 推导出来的:

a、每个样本都符合高斯分布,且方差一样 

b、同一个簇里的样本的均值是一样的,不同簇里的样本的均值是不一样的

        具体推导如下,首先高斯分布的概率密度函数如下:

f(x)=\frac{1}{\sqrt{2\pi}\sigma}exp({-\frac{(x-\mu)^2}{2\sigma^2}}) \\

         然后,由如上假定,假设样本是由 K 个簇组成的,写出MLE的公式如下:

f(\mu_1,\mu_2,...,\mu_j,...,\mu_K)=\prod_{j=1}^{K}{\prod_{i=1}^{N_j}{\frac{1}{\sqrt{2\pi}\sigma}exp({-\frac{(x_i-\mu_j)^2} {2\sigma^2}})}}\\ J(\mu_1,\mu_2,...,\mu_j,...\mu_K)=\sum_{j=1}^{K}{\sum_{i=1}^{N_j}{lg(\frac{1}{\sqrt{2\pi}\sigma}exp({-\frac{(x_i-\mu_j)^2} {2\sigma^2}})})}\\ =\sum_{j=1}^{K}{\sum_{i=1}^{N_j}{lg(\frac{1}{\sqrt{2\pi}\sigma}){-\frac{(x_i-\mu_j)^2} {2\sigma^2}}}}\\ =\sum_{j=1}^{K}{\sum_{i=1}^{N_j}{lg(\frac{1}{\sqrt{2\pi}\sigma})}}-\frac{1}{2\sigma^2}\sum_{j=1}^{K}{\sum_{i=1}^{N_j}{(x_i-\mu_j)^2}}

因为在整个过程中,\sigma 是不变的,被认为是常量,所以接下来求极值,即对 \mu_j求导就可以了

\frac{\partial J}{\partial \mu_j}=\frac{1}{\sigma^2}\sum_{i=1}^{N_j}{(x_i-\mu_j)}=0\\

\mu_j=\frac{1}{N_j}\sum_{i=1}^{N_j}{x_j}

我理解,这里和梯度下降的关系如下:  

         我们之所以梯度下降,是因为 \mu_j不好求,但是又kmeans 算法知,当我得到一个新簇之后,我的\mu_j不需要梯度下降,直接一步到位,用公式

\mu_j=\frac{1}{N_j}\sum_{i=1}^{N_j}{x_j}

更新即可

         而且,之前邹博老师,也用代码验证过,如果造的数据,方差相等,聚类的各种指标都非常好,方差只要不一样,效果立刻就下来了

工程相关:
1、用 c++ 手写堆排序

堆排序的步骤(以升序为例):
a、初始建大顶堆,时间复杂度是o(n)
b、把堆顶元素和数组最后一个元素互换
c、针对数组的前n-1个元素,对堆顶元素进行向下调整,这个时间复杂度是o(logn)
d、将堆顶元素和倒数第二个元素互换,然后针对前n-2个元素,对堆顶元素向下调整
e、依次类推

以下代码类似一次向下调整,它实际是初始建堆的一小步,第一个参数是需要排序的数组,第二个参数表示,这个索引对应结点的左右子树都是堆,实现的功能是,把以此索引对应结点为根的树调整成堆

-*- coding:utf-8 -*-
# /usr/bin/python

def test(listTest):
    listTest[0] = 10

def adjustDown(listNeedSort, curNodeIndex):
    # iFinalPlaceVal = listNeedSort[curNodeIndex]
    # iLength = len(listNeedSort)

    # iTempMaxIndex = iLeftNodeIndex = 2 * curNodeIndex + 1
    # iRightNodeIndex = 2 * curNodeIndex + 2
    # if iRightNodeIndex < iLength and listNeedSort[iRightNodeIndex] > listNeedSort[iLeftNodeIndex]:
    #     iTempMaxIndex = iRightNodeIndex
    
    # while listNeedSort[iTempMaxIndex] > iFinalPlaceVal:
    #     listNeedSort[curNodeIndex] = listNeedSort[iTempMaxIndex]
    #     curNodeIndex = iTempMaxIndex
    #     if curNodeIndex <= n/2 - 1:
    #         iTempMaxIndex = iLeftNodeIndex = 2 * curNodeIndex + 1
    #         iRightNodeIndex = 2 * curNodeIndex + 2
    #         if iRightNodeIndex < iLength and listNeedSort[iRightNodeIndex] > listNeedSort[iLeftNodeIndex]:
    #             iTempMaxIndex = iRightNodeIndex
    #     else:
    #         break
    iLength = len(listNeedSort)
    iFinalPlaceVal = listNeedSort[curNodeIndex]
    iTempMaxIndex = curNodeIndex
    while curNodeIndex <= iLength/2 - 1 and curNodeIndex == iTempMaxIndex:
        iTempMaxIndex = iLeftNodeIndex = 2 * curNodeIndex + 1
        iRightNodeIndex = 2 * curNodeIndex + 2
        if iRightNodeIndex < iLength and listNeedSort[iRightNodeIndex] > listNeedSort[iLeftNodeIndex]:
            iTempMaxIndex = iRightNodeIndex

        if listNeedSort[iTempMaxIndex] > iFinalPlaceVal:
            listNeedSort[curNodeIndex] = listNeedSort[iTempMaxIndex]
            curNodeIndex = iTempMaxIndex

    listNeedSort[curNodeIndex] = iFinalPlaceVal
    
if __name__ == "__main__":
    listTest = [1, 8, 6, 3]
    adjustDown(listTest, 0)
    print listTest

当然这不是完整的堆排序,写完了,接着补充呗

堆排序的适用场景:
堆排序对记录数少的文件并不提倡使用,对记录数很多的文件,还是有效的,因为堆排序的时间主要耗在初始建堆和不断调整堆顶元素进行排序的过程

堆排序相比快速排序的优势:
堆排序在最坏的情况下,时间复杂度也是o(nlogn),这是它相对于快速排序的最大优点,除此之外,它只需要一个存记录的辅助空间用于进行交换
 

2、找到最小的 k 个数(搜索方向的)
3、求连续子数组的最大和
4、解释一下什么是IO多路复用

       要解释IO多路复用,我们先解释一下阻塞IO、非阻塞IO、然后再引到IO多路复用。
       先解释下IO,IO就是input and output,我们知道unix下一切皆文件,那么要想实现信息之间的交流,实际就是往文件中写入数据,再从文件中读取数据的过程,对于文本文件,很好理解,比如c语言,fopen一个文件会返回一个文件描述符fd,我们就可以调用此文件描述符进行读写操作,而进程间通信实际是一样的,创建一个socket,同样会返回一个文件描述符fd,然后就可以对这个描述符进行发送和接收的操作了。

       拿进程间通信的读取数据,也即recvfrom系统调用来说,阻塞IO就是,当socket上没有数据时,进程在调用recvfrom接口时会卡住,它的缺点是显然的,进程卡住了,就什么都不能做了,当然可以在服务端为每一个连接,开启一个线程来解决这个问题,这样任何一个线程卡住,都不会阻塞整个进程,显然在有成千上万个连接过来时,进程可能就崩了,而且线程多的话,响应也会变慢,当然还可以用线程池或者连接池的方法来解决这个问题,总之对于小规模的服务请求,这种方法是可行的,但是对于大规模的服务请求,多线程或者连接池就会遇到瓶颈。

       非阻塞IO是,可以通过设置socket是非阻塞的模式,在当socket上没有数据时,调用recvfrom接口时会立刻返回,EWOULDBLOCK错误,而不是一直卡在那里,不得不说进程是可以去做一些其它事情了,但是需要不断的循环调用recvfrom接口,这样会有2个缺点,一个是cpu占用率会很高,在机器性能一般的情况下,很容易卡死,这也是为什么,有时需要加上sleep的原因,另外会有延迟,因为内核数据可能早早的已经准备好,但此时还没来得及调用 recvfrom接口,导致系统的吞吐量也会下降,所以在实践中是不怎么推荐非阻塞IO的,但其实这种方式的本质是,我们的进程在监控套接字上是否有数据到来,而实际操作系统为我们提供了更为高效的监控接口,比如select/poll/epoll,可以检测多个连接是否活跃,这就是所谓的IO多路复用了,而且多路复用也是阻塞的模型,只不过用的是select/poll/epoll 接口来阻塞,只有当有可用的socket时,服务端这边才会响应,去接收消息。

       接下来对比一下多线程模式和select/epoll的优缺点:
       如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

参考文章:

并发编程(IO多路复用) - cai128118* - 博客园

IO 多路复用 是什么意思_百度知道


5、堆栈和内存的本质区别是什么?
6、在问你写的项目时,问你是否用过连接池,进程通信那一部分,用的是长连接还是短连接
7、给定一个字符串,以最快速度找到最长的不重复子串
8、你说你熟悉进程通信,你使用过的进程通信都有哪些?
9、使用过 hash 桶吗?
10、项目上有一个数据接收模块,问连接数和并发数有多少?
12、生产者消费者模型可以解决什么问题啊?
14、static 的使用场景,那你说全局的 static 和 类里面的 static 有什么区别?引申,static_cast的应用场景呢?

15、手写快排

c++ 版

#include<iostream>
using std::cout;

void quickSort(int *a_pData, int a_iNum);

int main()
{
	//test case 1
	{
		int arrayData[2] = {5, 3};
		cout << "Raw data\n";
		for(int i = 0; i < 2; ++i)
		{
			cout << arrayData[i] << '\t';
		}
		cout << '\n';
		quickSort(arrayData, 2);
		cout << "Sorted data\n";
		for(int i = 0; i < 2; ++i)
		{
			cout << arrayData[i] << '\t';
		}
		cout << '\n';
	}

	
	//test case 2
	{
		int arrayData[1] = {5};
		cout << "Raw data\n";
		for(int i = 0; i < 1; ++i)
		{
			cout << arrayData[i] << '\t';
		}
		cout << '\n';
		quickSort(arrayData, 1);
		cout << "Sorted data\n";
		for(int i = 0; i < 1; ++i)
		{
			cout << arrayData[i] << '\t';
		}
		cout << '\n';
	}


	//test case 3
	{
		int arrayData[5] = {5, 3, 8, 1, 10};
		cout << "Raw data\n";
		for(int i = 0; i < 5; ++i)
		{
			cout << arrayData[i] << '\t';
		}
		cout << '\n';
		quickSort(arrayData, 5);
		cout << "Sorted data\n";
		for(int i = 0; i < 5; ++i)
		{
			cout << arrayData[i] << '\t';
		}
		cout << '\n';
	}

}

//Quick Sort
void quickSort(int *a_pData, int a_iNum)
{
	if(NULL == a_pData)
	{
		return;
	}
	if(a_iNum <= 1)
	{
		return;
	}

	int iLeft = 0;
	int iRight = a_iNum - 1;
	int iTemp = a_pData[0];

	while(iLeft < iRight)
	{
		while(a_pData[iRight] > iTemp && iRight > iLeft)
		{
			--iRight;
		}
		a_pData[iLeft] = a_pData[iRight];
		
		while(a_pData[iLeft] < iTemp && iRight > iLeft)
		{
			++iLeft;
		}
		a_pData[iRight] = a_pData[iLeft];
	}
	a_pData[iLeft] = iTemp;

	quickSort(a_pData, iLeft);
	quickSort(a_pData + iLeft + 1, a_iNum - iLeft - 1);

}

后来对比了下,严蔚敏老师写的数据结构的书中快排的代码,感觉老师的代码更清晰一点:

1、代码结构,分为了2个接口一个Partition接口,负责将数组分开并返回分开的位置,一个QSort接口,调Partition接口用来快排,感觉结构很清晰

2、传参,传的一个数组的指针,一个low值和一个high值,这样数组的指针位置可以不用动,代码更好写,也更清晰,而我只传了一个长度,就需要不断的去变换数组指针的位置,不好啊

python 版

# /usr/bin/python
# -*- encoding:utf-8 -*-

def quickSort(a_pData):
	if a_pData:
		iLeft = 0
		iRight = len(a_pData) - 1
		iTemp = a_pData[0]
		while iLeft < iRight:
			while iRight > iLeft and a_pData[iRight] > iTemp:
				iRight = iRight - 1
			a_pData[iLeft] = a_pData[iRight]
			while iRight > iLeft and a_pData[iLeft] < iTemp:
				iLeft = iLeft + 1
			a_pData[iRight] = a_pData[iLeft]
		a_pData[iLeft] = iTemp
		quickSort(a_pData[0:iLeft])
		quickSort(a_pData[iLeft+1:len(a_pData)])
			
if __name__ == '__main__':
    # test case 1
	arrayData = [5, 3]
	print 'Raw data\n', arrayData
	quickSort(arrayData)
	print 'Sorted data\n', arrayData
	# test case 2
	arrayData = [5, 3, 8, 1, 10]
	print 'Raw data\n', arrayData
	quickSort(arrayData)
	print 'Sorted data\n', arrayData
	# test case 3
	arrayData = [5]
	print 'Raw data\n', arrayData
	quickSort(arrayData)
	print 'Sorted data\n', arrayData

16、比如我要上一个10级的台阶,我1次可以走1阶、3阶或者5阶,问我要上完这10级台阶,有多少种方法,这是一个coding 题

        面试时,当时的想法是用动态规划,但是并没有想出来,后来我是由隐马尔可夫模型的维特比算法得到的启发,它用的也是动态规划,具体思路如下:
        我们以上10级台阶,跨一步用的台阶数是{1, 3, 5}为例:   
        假设 crossStepMethodSum(10) 表示上10级台阶的方法总数,crossStepFactor(1, 10) 表示上10级台阶且最后一步是上1个台阶的方法总数,crossStepFactor(3, 10) 表示上10级台阶且最后一步是上3个台阶的方法总数,crossStepFactor(5, 10) 表示上10级台阶且最后一步是上5个台阶的方法总数,那么显然

crossStepMethodSum(10) = crossStepFactor(1, 10) + crossStepFactor(3, 10) + crossStepFactor(5, 10),

也就是说,只要求出来 crossStepFactor(1, 10)、crossStepFactor(3, 10) 和 crossStepFactor(5, 10),这个问题就解决了,而 crossStepFactor(1, 10) = crossStepMethodSum(9) = crossStepFactor(1, 9) + crossStepFactor(3, 9) + crossStepFactor(5, 9),即 crossStepFactor(1, 10) = crossStepFactor(1, 9) + crossStepFactor(3, 9) + crossStepFactor(5, 9),

crossStepFactor(3, 10) 和 crossStepFactor(5, 10) 同理,具体c++代码实现如下:

c++ 代码:

#include<iostream>
using std::cout;

int crossStepFactor(int a_iLastStepNum, int a_iTotalStep);
int crossStepMethodSum(int a_iTotalStep);
int main()
{
	//test that last step is 1
	cout << "test that last step is 1:\n";
	for(int i=-2; i<10; ++i)
	{
		cout << "LastStepNum = 1" << ",TotalStep = " << i << ",Method Sum = " << crossStepFactor(1, i) << '\n';
	}
	cout << '\n';
		
	//test that last step is 3
	cout << "test that last step is 3:\n";
	for(int i=-2; i<10; ++i)
	{
		cout << "LastStepNum = 3" << ",TotalStep = " << i << ",Method Sum = " << crossStepFactor(3, i) << '\n';
	}
	cout << '\n';

	//test that last step is 5
	cout << "test that last step is 5:\n";
	for(int i=-2; i<10; ++i)
	{
		cout << "LastStepNum = 5" << ",TotalStep = " << i << ",Method Sum = " << crossStepFactor(5, i) << '\n';
	}
	cout << '\n';

	//final test
	cout << "final test:\n";
	for(int i=-2; i<10; ++i)
	{
		cout << "TotalStep = " << i << ",Method Sum = " << crossStepMethodSum(i) << '\n';
	}
}

//the total number of methods to go up a_iTotalStep steps and the last step is to go up a_iLastStepNum step
int crossStepFactor(int a_iLastStepNum, int a_iTotalStep)
{
	if(a_iLastStepNum <= 0)
	{
		cout << "The number of steps in the last step must be greater than 0" << '\n';
		return 0;
	}
	if(a_iTotalStep <= 0)
	{
		cout << "Total steps must be greater than 0" << '\n';
		return 0;
	}
	if(a_iTotalStep < a_iLastStepNum)
	{
		return 0;
	}
	else if(a_iTotalStep == a_iLastStepNum)
	{
		return 1;
	}

	return crossStepFactor(1, a_iTotalStep - a_iLastStepNum) + crossStepFactor(3, a_iTotalStep - a_iLastStepNum) + crossStepFactor(5, a_iTotalStep - a_iLastStepNum);
}

//Indicates the total number of methods to climb a_iTotalStep steps
int crossStepMethodSum(int a_iTotalStep)
{
	if(a_iTotalStep <= 0)
	{
		cout << "Total steps must be greater than 0" << '\n';
		return 0;
	}
	return crossStepFactor(1, a_iTotalStep) + crossStepFactor(3, a_iTotalStep) + crossStepFactor(5, a_iTotalStep);
}

    在整理上面的思路时,又有了新的想法,或许更简单一点,由上面很容易得出:
    crossStepMethodSum(10) = crossStepMethodSum(9) + crossStepMethodSum(7) + crossStepMethodSum(5)

由此写出python代码如下:

# /usr/bin/python
# -*- encoding:utf-8 -*-
import time
 
# Indicates the total number of methods to climb a_iTotalStep steps
def crossStepMethodSum(a_iTotalStep):
	if a_iTotalStep < 0:
		return 0
	if 0 == a_iTotalStep:
		return 1
	if 1 == a_iTotalStep:
		return 1
	return  crossStepMethodSum(a_iTotalStep - 1) + \
crossStepMethodSum(a_iTotalStep - 3) + \
crossStepMethodSum(a_iTotalStep - 5)

def improve_cross_step_method_sum(total_steps, cur_steps):
    if cur_steps < 0:
        return 0
    if 0 == total_steps:
        return 0
    if 0 == cur_steps:
        return 1
    if 1 == cur_steps:  
        return 1
    return  improve_cross_step_method_sum(total_steps, cur_steps - 1) + \
improve_cross_step_method_sum(total_steps, cur_steps - 3) + \
improve_cross_step_method_sum(total_steps, cur_steps - 5)
    
    

def cross_step_method_sum(steps_num):
    before_cur_methods = [1, 1, 1, 2, 3]
    if steps_num <= 0:
        return 0
    elif steps_num > 0 and steps_num < 5:
        return before_cur_methods[steps_num]
    
    cur_steps = 4
    while cur_steps < steps_num:
        cur_methods = before_cur_methods[0] + \
        before_cur_methods[2] + \
        before_cur_methods[4]
        before_cur_methods.pop(0)
        before_cur_methods.append(cur_methods)
        cur_steps = cur_steps + 1
    
    return cur_methods
 
if __name__ == '__main__':
	iStep = -2
	start = time.time()
	while iStep < 10:
		print ('TotalStep = ', iStep, ',Method Sum = ',  crossStepMethodSum(iStep))
		iStep = iStep + 1
	end = time.time()    
	print (end - start)
    
	print ('==================\n')        
	iStep = -2    
	start = time.time()    
	while iStep < 10:
		print ('TotalStep = ', iStep, ',Method Sum = ',  improve_cross_step_method_sum(iStep, iStep))
		iStep = iStep + 1
	end = time.time()    
	print (end - start)

	print ('==================\n')        
	iStep = -2    
	start = time.time()    
	while iStep < 10:
		print ('TotalStep = ', iStep, ',Method Sum = ',  cross_step_method_sum(iStep))
		iStep = iStep + 1
	end = time.time()    
	print (end - start)

       执行结果:   

TotalStep =  -2 ,Method Sum =  0
TotalStep =  -1 ,Method Sum =  0
TotalStep =  0 ,Method Sum =  1
TotalStep =  1 ,Method Sum =  1
TotalStep =  2 ,Method Sum =  1
TotalStep =  3 ,Method Sum =  2
TotalStep =  4 ,Method Sum =  3
TotalStep =  5 ,Method Sum =  5
TotalStep =  6 ,Method Sum =  8
TotalStep =  7 ,Method Sum =  12
TotalStep =  8 ,Method Sum =  19
TotalStep =  9 ,Method Sum =  30
0.0023381710052490234
==================

TotalStep =  -2 ,Method Sum =  0
TotalStep =  -1 ,Method Sum =  0
TotalStep =  0 ,Method Sum =  0
TotalStep =  1 ,Method Sum =  1
TotalStep =  2 ,Method Sum =  1
TotalStep =  3 ,Method Sum =  2
TotalStep =  4 ,Method Sum =  3
TotalStep =  5 ,Method Sum =  5
TotalStep =  6 ,Method Sum =  8
TotalStep =  7 ,Method Sum =  12
TotalStep =  8 ,Method Sum =  19
TotalStep =  9 ,Method Sum =  30
0.003069162368774414
==================

TotalStep =  -2 ,Method Sum =  0
TotalStep =  -1 ,Method Sum =  0
TotalStep =  0 ,Method Sum =  0
TotalStep =  1 ,Method Sum =  1
TotalStep =  2 ,Method Sum =  1
TotalStep =  3 ,Method Sum =  2
TotalStep =  4 ,Method Sum =  3
TotalStep =  5 ,Method Sum =  5
TotalStep =  6 ,Method Sum =  8
TotalStep =  7 ,Method Sum =  12
TotalStep =  8 ,Method Sum =  19
TotalStep =  9 ,Method Sum =  30
0.0010647773742675781

       我对比了下,两份代码的计算结果是一样的,当然这方法是我想的,或许还有更好的办法,但是像这种递归的方法,在阶梯树足够大的时候,计算肯定会很慢,在实践中应用的话,还是需要写一个非递归的代码,而且对于接口 crossStepMethodSum 存在一个问题

       递归接口 crossStepMethodSum 是有点问题的,在 TotalStep 是0的时候, Method Sum 应该是0,但有点不太好改,因为,在递归时,需要要求,TotalStep =  0 ,Method Sum =  1,所以可能需要传递一个总的阶梯数,一直不变,我修改的接口是 improve_cross_step_method_sum   

       我也写了非递归的接口,递归和非递归,时间上区别太大了,我测试了下,同样是计算到阶梯是40,递归用了40s,非递归用了不到1s,所以说,从某种意义上说,递归算法,根本没法用,小数据量还行

17、常用的 mysql 索引都有哪些?是怎么存储的?使用场景是什么?

18、kafak 的选举机制有了解吗?

19、了解B+树吗,它和二叉树有什么区别?

20、二维数组如何动态分配和释放?

先说2个基本的概念:一级指针和二级指针

一级指针:
指针变量里面存的是地址,但这个地址里面已经存的是正常的数据了
二级指针:
指针变量里存的也是地址,但是这个地址里面存的还是个地址,然后,在这个地址里面存的才是真实的数据了,就拿下面的例子来说,变量ppData中存了个地址,但是我根据这个地址拿到的又是另外一个地址,因为,我不是做了这么一个操作吗:ppData[i] = new int[5],然后根据那个另外的地址,才能拿到数据啊

code example:

#include<iostream>

using std::cout;

int main()
{
        int **ppData = new int *[10];
        for(int i=0; i< 10; i++)
        {
                ppData[i] = new int[5];
        }

        for(int i=0; i< 10; i++)
        {
                delete[] ppData[i];
        }
        delete[] ppData;

}

最后补充一下:

1、对于静态的二维数组,可以如下初始化:int array2[][3] = {1,2,3,4,5,6};

2、当时那个面试官,用一行给我写了个二维数组的动态分配,后来查了些资料,也没找到一行动态分配二维数组的,当时有点被面试官吊打,有点懵逼了,只能连连说是(现在已经不记得是怎么写的了),不过二维数组的释放内存空间,没写出来,真的是不因该啊

21、hive 根据时间段如何统计?

因为我其实并没有接触过大数据,所以下面用的oracle聚合,但觉得本质是一样的:

select to_char(cti_start_time, 'yyyymmddhh') as DATATOHOUR, count(*)
   from QN2192.ENT_RECORD_BX_TABLE_H_201708
  group by to_char(cti_start_time, 'yyyymmddhh')
 having to_char(cti_start_time, 'yyyymmddhh') > '2017080808'

这个其实很完美,group by 时,把时间格式化到了小时,就相当于按小时统计了,having 是对group by 的过滤条件又进行了过滤,然后想到了,在工作中,我们是怎么统计的

select obj_id,
        max(obj_name),
        start_time,
        skill_id,
        area_id,
        sum(decode(stat_name, 'AN_TL_OB_AN', field_value, 0)),
        sum(decode(stat_name, 'AT_RN', field_value, 0)),
        sum(decode(stat_name, 'AT_TL_IB_RN', field_value, 0)),
        sum(decode(stat_name, 'AN_TL_IB_RN', field_value, 0)),
        sum(decode(stat_name, 'AT_RDY', field_value, 0)),
        sum(decode(stat_name, 'AT_OWK', field_value, 0)),
        sum(decode(stat_name, 'AT_TK', field_value, 0)),
        sum(decode(stat_name, 'AT_ACW', field_value, 0)),
        sum(decode(stat_name, 'AT_TL_IB_CNS_TK', field_value, 0)),
        sum(decode(stat_name, 'AT_LCK', field_value, 0)),
        sum(decode(stat_name, 'AN_TL_OB_AB', field_value, 0)),
        sum(decode(stat_name, 'AN_TL_OB_RN', field_value, 0)),
        sum(decode(stat_name, 'AT_TL_OB_AN', field_value, 0)),
        sum(decode(stat_name, 'AT_TL_IT_CNS_TK', field_value, 0)),
        sum(decode(stat_name, 'AT_TL_IT_AN', field_value, 0)),
        sum(decode(stat_name, 'AT_TL_OB_CNS_TK', field_value, 0)),
        sum(decode(stat_name, 'AT_TL_OB_RN', field_value, 0))
   from "QN2192".S_D_5
  where start_time = to_date('2018-12-05', 'YYYY-MM-DD')
    and stat_name in ('AN_TL_OB_AN',
                      'AT_RN',
                      'AT_TL_IB_RN',
                      'AN_TL_IB_RN',
                      'AT_RDY',
                      'AT_OWK',
                      'AT_TK',
                      'AT_ACW',
                      'AT_TL_IB_CNS_TK',
                      'AT_LCK',
                      'AN_TL_OB_AB',
                      'AN_TL_OB_RN',
                      'AT_TL_OB_AN',
                      'AT_TL_IT_CNS_TK',
                      'AT_TL_IT_AN',
                      'AT_TL_OB_CNS_TK',
                      'AT_TL_OB_RN')
  group by obj_id, obj_name, start_time, skill_id, area_id;

重点解释下 decode,是oracle提供的一个函数,以decode(stat_name, 'AT_TL_IB_CNS_TK', field_value, 0)) 为例吧,如果stat_name字段的值是 AT_TL_IB_CNS_TK,就取 field_value字段的值,否则取0进行累加

22、说一说左连接、右连接和内连接?

内连接:获取2张表的公共部分
sql:
select * from books a inner join articles b on a.title=b.title
左连接:以左表为基础,在这里就是以books表为基础,articles表中没有的补空就ok了啊
select * from books a left join articles b on a.title=b.title
右连接刚好和左连接反过来啊

参考文章:
https://www.cnblogs.com/zhaoyini/p/join.html

23、找到最长不重复子串  

#include<iostream>
using std::cout;


int getLongUnrepeatSub(char *a_pCharHead);
int main()
{
	{
		char *pTest = "";
		cout << "Longest unrepeated substring of empty string is " << getLongUnrepeatSub(pTest) << '\n';
		cout << '\n';
	}

	{
		char *pTest = NULL;
		cout << "Longest unrepeated substring of empty string is " << getLongUnrepeatSub(pTest) << '\n';
		cout << '\n';
	}

	{
		char *pTest = "mbada";
		cout << "Longest unrepeated substring of " << pTest << " is ";
		int iMaxLength = getLongUnrepeatSub(pTest);
		cout << "length is " << iMaxLength << " \n";
		cout << '\n';
	}

	{
		char *pTest = "mbadamiq";
		cout << "Longest unrepeated substring of " << pTest << " is ";
		int iMaxLength = getLongUnrepeatSub(pTest);
		cout << "length is " << iMaxLength << " \n";
		cout << '\n';
	}

	{
		char *pTest = "mbaqckdamiq";
		cout << "Longest unrepeated substring of " << pTest << " is ";
		int iMaxLength = getLongUnrepeatSub(pTest);
		cout << "length is " << iMaxLength << " \n";
		cout << '\n';
	}
	return true;
}

int getLongUnrepeatSub(char *a_pCharHead)
{
	if(!a_pCharHead || a_pCharHead[0] == '\0')
	{
		return 0;
	}
	// Build a hash table, the ASSC code of the letter is the index, and the value is the position of the letter in the string
	char *arrayHashTable[256] = {NULL};
	// pCurMaxStart stores the start index of the longest substring
	// pCurMaxEnd stores the end index of the longest substring
	// pTmpStart stores the start index of the longest substring with pTmpEnd as the end character
	char *pCurMaxStart = a_pCharHead, *pCurMaxEnd = a_pCharHead, *pTmpStart = a_pCharHead, *pTmpEnd = a_pCharHead;
	for(pTmpEnd = a_pCharHead; *pTmpEnd != '\0'; ++pTmpEnd)
	{
		if(NULL != arrayHashTable[*pTmpEnd] && arrayHashTable[*pTmpEnd] >= pTmpStart)
		{
			pTmpStart = arrayHashTable[*pTmpEnd] + 1;
		}
		arrayHashTable[*pTmpEnd] = pTmpEnd;
		if(pTmpEnd - pTmpStart > pCurMaxEnd - pCurMaxStart)
		{
			pCurMaxStart = pTmpStart;
			pCurMaxEnd = pTmpEnd;
		}

	}
	//For test
	for(char *pTest = pCurMaxStart; pTest != pCurMaxEnd; ++pTest)
	{
		cout << *pTest;
	}
	cout << *pCurMaxEnd;
	cout << '\n';
	return pCurMaxEnd - pCurMaxStart + 1;
}

运行结果: 

(base) ➜  Desktop ./a.out
Longest unrepeated substring of empty string is 0

Longest unrepeated substring of empty string is 0

Longest unrepeated substring of mbada is mbad
length is 4

Longest unrepeated substring of mbadamiq is damiq
length is 5

Longest unrepeated substring of mbaqckdamiq is mbaqckd
length is 7

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值