内容会持续更新,有错误的地方欢迎指正,谢谢!
递归和迭代
递归的代码简洁但性能不如迭代,面试时,可根据题目的特点和面试官的要求选择如何实现。深入理解递归(一) 深入理解递归(二)
排序和查找
重点掌握 二分查找、归并排序、快速排序
旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
法一:顺序查找,即从头到尾遍历数组,时间复杂度为O(n),但没利用旋转数组的特性,所以达不到要求。
法二:二分查找的应用(难度较大)。先判断原数组是不是一个递增的数组,如果不是,则设三个变量,P1指向第一个元素,P2指向最后一个元素,Mid是中间数。如下图,Mid会指向5,由于5大于3,则令P1指向Mid所指的元素5,P2不变,Mid指向1,此时,1小于5,1又小于2,则令P2指向Mid所指的元素1。此时,P1和P2指向相邻的两个元素,P2指向的数字就是最小值。
但还未考虑完全,如下图,此特殊情况:P1、P2、Mid指向的三个数字都相等,则只能用法一来解决之。
最终整合之后的代码:
int minNumberInRotateArray(vector<int> rotateArray)
{
if(rotateArray.empty())
return 0;
int frontPoint=0,backPoint=rotateArray.size()-1,midPoint=0;
while(rotateArray[frontPoint]>=rotateArray[backPoint])
{
if(backPoint-frontPoint==1)
{
midPoint=backPoint;
break;
}
midPoint=(backPoint+frontPoint)/2;
//特殊情况时(即所指的三个数字都相等时),用法一,即顺序查找
if(rotateArray[midPoint]==rotateArray[frontPoint]&&
rotateArray[frontPoint]==rotateArray[backPoint])
return MidInOrder(rotateArray,frontPoint,backPoint);
//正常情况时,用法二
if(rotateArray[midPoint]>=rotateArray[frontPoint])
{
frontPoint=midPoint;
}else if(rotateArray[midPoint]<=rotateArray[backPoint])
{
backPoint=midPoint;
}
}
return rotateArray[midPoint];
}
海量数据排序
实现一个排序算法,要求时间复杂度为O(n),你有什么方法吗?
分析:多和面试官交流,比如问清楚对什么数据排序,面试官会告诉你:打算对公司所有员工的年龄排序,公司总共几万名员工。你再问:也就是说这些数据是在一个较小的范围内的,可以使用辅助空间吗?面试官回你:可以使用常量大小的辅助空间。那么问题已经很清楚了。
解答:由于年龄是在一定范围内的,即0~99,故申请一个长度为100的内存空间,每个元素用于记录其对应年龄出现的次数,也就是22岁出现一次,a[22]就加1。
回溯法
若在二维数组(通常表现为迷宫或棋盘)上搜索路径,我们可以用回溯法,回溯法很适合用递归的代码来实现。
通过递归这种代码结构来实现回溯这个目的。回溯法可以被认为是一个有过剪枝的DFS过程。DFS和BFS算法
动态规划和贪婪算法
动态规划:若求最优解,并且该问题可分解为多个有互相联系的子问题,我们就可以用DP。先推出递推式,便可用自上而下的递归思路去实现;但为了避免不必要的重复计算,我们用自上而下的迭代思路去实现,也就是把子问题的最优解先求出来并用数组(一维或二维)保存下来,接下来利用这些子问题的解来求解大问题的解。动态规划
贪婪算法:步步为赢
动态规划之剪绳子
给你一根长度为n的绳子,请把绳子剪成m段 (m和n都是整数,n>1并且m>1)每段绳子的长度记为k[0],k[1],…,k[m].请问k[0]k[1]…*k[m]可能的最大乘积是多少?例如,当绳子的长度为8时,我们把它剪成长度分别为2,3,3的三段,此时得到的最大乘积是18。其中,每一小段的绳子长度都为整数。
分析:求最优解,且子问题之间互相有依赖,即考察DP。
为避免子问题的重复计算,我们从下往上依次存储子问题的最优解。即:从上往下分析问题,从下往上求解问题。
对于第一刀,有n-1种选择,故有递推式:f(n)=max{f(n-i)*i},其中i=[1,n-1]
注意:
- 绳子剪成 i x (n-i) 和 (n-i) x i 是相同的,故可省掉一半的该运算;
- 不符合切割条件的输入n(即n<2),以及输入n为2、3时的结果,因为题中规定m>1。
代码:
int maxProduct(int length)
{
if (length < 2) return 0;
if (length == 2) return 1;
if (length == 3) return 2;
//子问题的最优解存储在products数组里,索引为1、2、3时对应的值为1、2、3
int* products = new int[length + 1];//多申请一位,方便使用
products[0] = 0;
products[1] = 1;
products[2] = 2;
products[3] = 3;
int max = 0;
for (int i = 4; i <= length; ++i)
{
max = 0;
for (int j = 1; j <= i / 2; ++j)
{
int product = products[j] * products[i - j];
if (max < product)
max = product;
products[i] = max;//从4往length依次存储子问题的最优解
}
}
max = products[length];
delete[] products;
return max;
}
贪婪算法之剪绳子
题目同上,只是解法不同。
贪婪决策:当n>=5时,应尽可能多地剪长度为3的绳子;当剩下长度为4的绳子时,则把其剪成两段长度为2的绳子。当剩下长度为小于3的绳子,直接将其乘上去即可。
int maxProduct(int length)
{
if (length < 2) return 0;
if (length == 2) return 1;
if (length == 3) return 2;
int timesOf3 = length / 3;//长度为3的绳子的段数
if (length - timesOf3 * 3 == 1)//结果为1就表明,肯定会剩长度为4的绳子
timesOf3--;
int timesOf2 = (length - timesOf3 * 3) / 2;//长度为2的绳子的段数,timesOf2为0或2
int result = pow(3, timesOf3) * pow(2, timesOf2);//最终结果
return result;
}
通常,面试官会让面试者证明该策略的正确性:
当n>=5时,3(n-3)>=2(n-2)
当n==4时,2*2>1*3
即证!
位运算
把数字表示为0和1后的操作,即对二进制数据的操作,总共只有:与&、或|、异或^、左移<<、右移>>这五种。其中,异或的话,只有0^1或1^0为1;左移的话,n=n<<1表示将n的二进制数左移一位,可用于代替乘法。
另外,一个整数减去1后 再和 原来的整数 做与运算,得到的结果相当于把原来的整数的最右边的1变成0。举例:1100。1100减去1得到1011,1100和1011做与运算,得到1000,也就是把原整数最右边的1变成了0。比如:统计1100中有多少个1,就可以用这种方法,每做一次与运算,count就+1,直到原整数变为0000。
很多的二进制问题,都可以用这种思路来解决。