网易有道面试总结
第一次实习生面试给了网易有道,总结一下面试的经验,方便日后回顾。有道的一面和二面主要都是就算法和项目展开,并没有对基础知识进行过多的提问,可以看出在面试官也没有准备的情况下,手写算法一定是面试着重考察的,而算法所占的比重取决于面试官准备的程度。
- 连续最大子数组
- 连续最大子数组,并求得起始位置
- 连续子数组和最小
- 连续子数组求绝对值和最小
- 最大存水量
- 收集雨水
- 直方图中找最大矩形
连续最大子数组
第一题十分简单,求解连续最大子数组,不多说,使用动态规划按照递推公式即可得解。
题目解析:当然这道题也存在着某些细节上的问题,例如对于子数组是否能够为空的问题,当数组全部都是负数的时候,如果数组能够为空,那么连续最大的子数组和为0,如果不能够为空,那么连续最大的子数组的和为最大的那个负数。
在代码中的体现如下:
public static int getMaxSum(int[] a){
int length=a.length;
int max=a[0];//如果可以为空那么这里赋值为0,不能为空赋值为a[0]
int nmax=0;
for(int i=0;i<length;i++){
nmax=Math.max(nmax+a[i], a[i]);
if(nmax>max){
max=nmax;
}
}
return max;
其实动态规划中要做的就是找到能够标示当前i状态的表达式,以及怎么从第i个状态推演出第i+1个状态的表达式。首先第一步,以这个题为例,因为要求连续的最大子数组,那么第i个状态下的最大值不是第i-1个状态下的最大值加上a[i],就是a[i]本身(是a[i]本身意味着之前的累加对a[i]做出了负贡献,需要从a[i]这个位置重新开始),在这个过程中不断的存储最大值。
连续最大子数组起始位置
利用ArrayList存储index,对于第i个状态如果是nmax+a[i]较大,则直接将i put进List,否则将list清除,再将iput进。
public static int[] getIndex(int[] a){
List<Integer> indexs=new ArrayList<Integer>();
int[] re=new int[2];
int length=a.length;
int nmax=0;
int max=a[0];
for(int i=0;i<length;i++){
nmax=Math.max(nmax+a[i], a[i]);
if(nmax==a[i]){
indexs.clear();
indexs.add(i);
}
else{
indexs.add(i);
}
if(nmax>=max){
max=nmax;
re[0]=indexs.get(0);
re[1]=indexs.get(indexs.size()-1);
}
}
return re;
}
连续子数组的和最小
该题目和连续子数组最大和没有什么区别,都是使用动态规划的方法,只不过把每一个状态由求最大变成求最小,即在第i+1个状态下比较第i个状态加上a[i]的和与a[i]的大小关系,取较小的那一个作为当前状态的值。
public static int getMin(int[] a){
int length=a.length;
int min=0;
int nmin=a[0];
for(int i=1;i<length;i++){
nmin=Math.min(nmin+a[i], a[i]);
if(nmin<min){
min=nmin;
}
}
return min;
}
连续子数组和的绝对值最小
当sum[i+1]为最优解的时候不能保证它的子问题sum[i]也是最优解,因此不能够满足动态规划的解题要求。
当出现绝对值最小的时候可以考虑使用前缀数组的方法。
给一数组A,
前缀和数组:新建一数组B,数组中每一项B[i]保存A中[0…i]的和;
我们试着求数组a[n]的前缀和数组b[n + 1],即
b[0] = 0
b[1] = a[0] + b[0]
b[2] = a[1] + b[1]
…
b[n] = a[n - 1] + b[n - 1]
可以看出,a[i] + a[i + 1] + … + a[i + k] = b[i + k + 1] - b[i],这样我们可以把求数组a的连续子数组和的绝对值的最小值的问题,转化为求前缀数组b中两两之差的绝对值最小值,然后我们对数组b进行排序,然后比较相邻两个元素差的绝对值,最终结果可能为index从0到i,或者为从j到i两种情况。时间复杂度方面,数组a转换为数组b需要O(n)的时间,数组b排序需要O(nlog(n))的时间,比较数组b相邻两个元素差的绝对值需要O(n)的时间,因此整体的时间复杂度为O(nlog(n))。
public static int getAbsMin(int[] a){
int length=a.length;
int[] b=new int[length];
b[0]=a[0];
for(int i=1;i<length;i++){
b[i]=b[i-1]+a[i];
}
int min=Math.abs(b[0]);
int nmin=0;
for(int i=1;i<length;i++){
nmin=Math.min(Math.abs(b[i]-b[i-1]), Math.abs(b[i]));
if(min>nmin){
min=nmin;
}
}
return min;
}
最大盛水容器
最大盛水容器解析
题目解析:对于盛水容器主要取决于左右边缘的高度,以及左右边缘之间的距离(一旦左右边缘确定,中间的长度对于盛水量是没有影响的)
public static int maxWater(int[] length){
int len=length.length;
int maxArea=0;
int begin=0;
int end=len-1;
while(begin<end){
maxArea=Math.max(maxArea, (end-begin)*Math.min(length[begin], length[end]));
if(length[begin]<length[end]){
begin++;
}else{
end--;
}
}
return maxArea;
}
收集雨水
题目解析:要求得存储的雨水量首先要明确雨水的存储与哪些条件有关,对于位置i,它能够存储的雨水量与它左侧最高的海拔以及它右侧最高的海拔还有它自身的海拔高度有关,所以该问题可以转化为求其左侧最高的海拔以及右侧最高的海拔的过程,以空间换时间的思想,使用一个数组把每个位置上Math.min(左侧最高海拔,右侧最高海拔)存储起来。
public int trapRainWater(int[] heights) {
int len=heights.length;
int[] bound=new int[len];
int leftMax=0;
int area=0;
int rightMax=0;
for(int i=0;i<len;i++){
bound[i]=leftMax;
leftMax=Math.max(leftMax, heights[i]);
}
for(int j=len-1;j>=0;j--){
bound[j]=Math.min(rightMax, bound[j]);
rightMax=Math.max(rightMax, heights[j]);
if(bound[j]>heights[j]){
area=area+(bound[j]-heights[j]);
}
}
return area;
}
使用Stack进行解题,可以实现只扫描一次就能求解
使用Stack解题的过程与左右各扫描一次的解题过程略有不同,左右各扫描一次是把每一个位置当做一个求解单位,找到左右两侧的最大值,进行单元格的求解。而使用Stack在遍历的过程中只要是当前位置小于栈顶位置,就把当前位置入栈,直到找到大于栈顶元素的位置,将栈顶位置出栈,如果此时栈内无元素,那么相当于左侧不存在挡板,构不成水槽,如果此时栈内有元素,那么栈内元素则作为左侧挡板,当前元素作为右侧挡板求解存水量。
“`
int length=heights.length;
Stack index=new Stack();
int i=0;
int area=0;
while(i