面试经典
求n!后面的连续的0
题型:
对于n!, 求结果末尾有多少个零?
提示:
记住一个思想:
每隔1个5,出现1个0,比如5,10,15,20;
每隔1个5 * 5,会多出现1个0,比如25,50;
每隔1个5 * 5 * 5,会多出1个0,比如125;
依此类推。
举例:
n = 25:
求25!后面的0;
25!是1~25依次相乘,因此列出
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25;
5,10,15,20,25在5这个层面贡献出了1个0;
此外,25在25这个层面贡献出了1个0。
所以如果我们要计算n!后面有多少个0,可以通过计算 n / 5 + n / 52 + … + n / 5 k(5k < n && 5K+1 > n)
所以25!后面会连续拥有5 + 1 = 6个0.
//根据上面的思想
int trailingZeroes(int n) {
int zeros = 0;
while(n / 5){
n = n / 5;
zeros += n;
}
return zeros;
}
实现平方根函数
提供思路:
模拟sqrt函数其实质就是去求f(x) = x2 - A的零点。
而求零点最常用的就是零点定理,令[a,b]为包含待求根的区间。其中a = 0(f(0) >> 0一定成立);b = A + 1/4((A + 1/4)2一定大于A)[证明见上面的链接]
树状数组详解
二叉树的翻转
给定一棵二叉树,将其翻转[左子树与右子树对调]
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输入:
4
/ \
7 2
/ \ / \
9 6 3 1
void revertBTree(Tree *root){
if(root == NULL) return;
swap(root->left,root->right);
revertBTree(root->right);
revertBTree(root->left);
}
判断镜像树(K叉树)
判断一段K叉树是否是镜像树(左右对称)
4
/ \
3 3
/ | \ / | \
9 4 1 1 4 9
typedef struct Tree{
int s = 0x80000000;
vector<struct Tree> children;
}KTree;
bool isNULL(KTree kT){
return kT.s == 0x80000000; //用这个最小的负数来表示是否为空其实是有缺陷的。
}
bool isSymmetric(KTree left,KTree right){
if(isNULL(left) == true && isNULL(right) == true) return true;
if((isNULL(left) == true && isNULL(right) != true) || (isNULL(left) != true && isNULL(right) == true) ) return false;
if(left.children.size() != right.children.size()) return false;
if(left.s != right.s) return false;
int k = right.children.size();
for(int i = 0,j = k - 1;i < k,j >= 0;i ++,j --){
if(isSymmetric(left.children[i],right.children[j]) == false) return false;
}
return true;
}
其实本题还有判断二叉树是否对称的情况
请参考算法刷题
以及如何判断两棵树是否一摸一样
请参考Traversing two trees in parallel
求第n个斐波拉契数列:
题目的含义是这样的:
给定一个整数I。求第I个斐波拉契数列是多少?
vector<int> v1;//本题因为不清楚输入n的范围,而直接申请0x7fffffff这么大的空间会导致系统直接运行时错误。因此本题借鉴了记忆化搜索的观点。
v1.push_back(0);
v1.push_back(1);
int qiufeibo(int n){
if(v1.size() >= n + 1){//因为已经生成过了v1[n],没必要再计算一遍
return v1[n];
}
for(int i = v1.size() - 1;i < n;i ++){
int c = v1[i] + v1[i - 1];
v1.push_back(c);
}
return v1[n];
}
快速幂
快速幂的概念比较简单
比如如何计算a1023,直接让1023个a相乘着实划不来。
可以考虑a1023 = a1 * a2 * a4 * a8 * a16 * a32 * a64 * a 128 * a256 * a512(仅做了10次乘法即可得到结果。效率确实提升很快)
理论依据(1023(10) = 1111111111(2))
int M[10011];
string s;
void kuaisumi(int n){
s = "";
do{
s += to_string(n % 2);
n = n / 2;
}while(n);
reverse(s.begin(),s.end());
}
void init(int n){
M[0] = 1;
M[1] = n;
int i = 2;
for(;(MAX / n) >= M[i - 1];i ++){//(MAX / n) >= M[i - 1]:防止越界
if((i & 1) == 0){ //判断奇偶 i如果为奇数则i的二进制表示最后一位是1.
M[i] = M[i / 2] * M[i / 2];
continue;
}
kuaisumi(i);
int pivot = 1;
M[i] = 1;
for(int j = 0;j < s.length();j ++,pivot *= 2){
if(s[j] == '0') continue;
M[i] *= M[pivot];
}
}
}
找出重叠区间中的最大区间数
题目描述
给定多个可能重叠的区间,找出重叠区间的最大区间个数。
输入为:[1,5],[10,15],则输出为1;
输入为:[1,2],[2,3],[3,4],[4,5],则输出为2(重叠区间相互之间都要有交集);
对上面的用例解释一下:[1,2]和[2,3]是重叠的,重叠的区间有[1,2]和[2,3]。共有两个区间。而最大重叠的区间中最大区间个数是2.
输入为:[1,7],[2,5],[3,4],[8,15],[9,17],[20,25],则输出为3。
给个思路:
先定义结构体
struct Interval{
int type;//type为0表示起点,type为1表示终点
int val;//区间的起点和终点的坐标
}
用至少(2 * n)大小的结构体数组盛入我们需要的数据
然后排序,排序原则:
根据val由小到大排序,然后如果val值相同,则type为0的在前。
排序实现:
通过重载operator < 和 调用sort()实现
cur = 0;//cur是当前区间重合个数,当退出当前重合的区间时,cur一定等于0
max = 0x80000000;
之后,遇到type为0的,则cur++,遇到type为1的,则cur--;
一轮循环结束[事实上应该是cur++执行之后],统计cur和当前max的大小,如果cur>max则max = cur;
注意本题与这道题的区分
贪心算法之活动安排问题
本题之所以会放在这里
完全是因为今日头条的一道面试题
算法:
系统日志, 记录了进程号、启动时间秒级、退出时间秒级
一天的数据。
问,这一天中, 同时运行的最大进程数是多少?
1 9:00:00 9:30:00
2 10:00:00 21:00:00
3 13:00:00 14:00:00
读者可以类比一下,这也会是我们程序员以后工作中可能会用到的。毕竟实际意义明显
最长回文子串
题目描述
输入一个字符串,求该字符串的最长回文子串
输入实例:banana
输出实例:anana第一种思路:
首先枚举子串起点i,然后枚举子串终点j。(其实质就是枚举子串)。然后再判断选择的子串是不是回文。算法复杂度O(n3)
第二种思路:
从字符串的中点来枚举。然后从中点向两边扩散。然后以此类推。
算法复杂度分析:O(n2)
第三种思路:
动态规划:(令dp[i][j]表示S[i]至S[j]所表示的子串是否是回文子串,是则为1,不是为0)
在假定i+1…j-1之间的字符串是回文的情况下,查看当左右边分别向外扩展一个字符的情况是否为回文。
d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] str[i] == str[j] 0 str[i] != str[j] dp[i][j]= \begin{cases} dp[i + 1][j - 1]& \text{str[i] == str[j]}\\ 0& \text{str[i] != str[j]} \end{cases} dp[i][j]={dp[i+1][j−1]0str[i] == str[j]str[i] != str[j]
算法复杂度分析:O(n2)
第四种思路:
Manacher算法 [博主因为时间原因和能力有限,尚未搞懂,请读者自行百度]
第三种思路源代码:
string longestPalindrome(string s)
{
if(s == "") return s;
vector<vector<int> > dp;
for(int i = 0;i < s.length();i ++){
vector<int> v2;
v2.clear();
dp.push_back(v2);
for(int j = 0;j < s.length();j ++){
dp[i].push_back(0);
}
}
for(int i = 0;i < s.length();i ++){
dp[i][i] = 1;
if(s[i] == s[i + 1]){
dp[i][i + 1] = 1;
}
}
int tmpi,tmpj;
for(int l = 3;l < s.length();l ++){ //子串的长度
for(int i = 0;(i + l - 1) < s.length();i ++){ //i是起点,i + l - 1是终点
int j = i + l - 1;
if(s[i] == s[j]){
if(dp[i + 1][j - 1] == 1){
dp[i][j] = dp[i + 1][j - 1];
tmpi = i;
tmpj = j;
}
}else{
dp[i][j] = 0;
}
}
}
return s.substr(tmpi, tmpj - tmpi + 1);
}
不能由数组中子集的数字之和形成的最小数字
题目描述:
给定一个有序数组,请给出不能由数组中的任意一个子集中的所有元素之和形成的最小的数字
输入:
[1,2,3,8,9,10]
输出:
7
思路描述:
res初始化为1, i是指针,初始化为0
1.arr[i] <= res:res = res + arr[i];
2.arr[i] > res:return res;//res即为所求思路解释:
假设res = 1 + arr[0] + … + arr[i - 1] ;//前i个arr[]数组元素得不到最小的值(注意:数组下标从0开始)
如果arr[i]>res, 则res即为所求,这个应该很容易理解。
如果arr[i]<=res, 那么res是前i 个数组元素得不到的最小的值,那么res - arr[i]必然是我们通过前i个元素可以得到的值,否则就会有矛盾。那么[res-arr[i],res)都是可以通过数组的前i个元素可以得到的,那么如果我们对这区间里的所有的数都加上arr[i]的话,那么[res,res + arr[i])则都是可以通过前i + 1个元素可以得到的值,那么我们现在只需要考虑res + arr[i]是否是我们所求的最小值,而是否是我们所求的最小值是需要通过下一轮迭代才能确定。
找出不在数组中的最小的那个数字所遇到的问题
题目描述:
给定一个数组,找出不在数组中的最小的那个数字。
思路描述:本题可以采用类似于二分查找+分治法的套路
首先取1~n的中间值m=(1+n)/2,
然后利用快排中的partition函数的思想将整个数组分成2部分。
A1中都是小于或等于m的数字,A2中都是大于m的数字。
如果A1的长度小于m,那么说明缺少的数字应该出现在A1中,否则在A2中,具体推导过程作者说的很详细,这里就不在赘述了。
二叉树中的最大路径和
题目描述:
寻找二叉树中的权值和最大的路径,请注意,路径不一定通过根结点
示例输入:
10
/ \
2 10
/ \ \
20 1 -25
/ \
2 4
示例输出:
42[解释:该路径为(20-2-10-100)]
思路解释:
本题是考虑到对于每个结点,有三种情况:
1.最大总和路径包括当前结点
2.最大总和在左边路径
3.最大总和在右边路径
本题采用递归的思想解决:
首先, 分别计算左右子树的最大路径和(沿某条路径的和)和最大结点和(该子树的满足题意的最大和)
其次, 比较左右子树的最大路径和,取最大路径和作为本树的最大路径和
最后, 将左右子树的最大路径和以及根结点相加得到本树的最大结点和,与当前的最大值相比较,取最大者。
void dfs(Btree b,int& maxNode,int& maxPath){
if(b == NULL){
maxNode = 0;
maxPath = 0;
return;
}
int templeft1,tempright1;
dfs(b->lchild,templeft,templeft1);
dfs(b->rchild,tempright,tempright1);
maxPath = (tempright1 > templeft1 ? tempright1 : templeft1) + b->val;
int temp = tempright1 + templeft1 + b->val;
if(maxNode < temp){
maxNode = temp;
}
}
全排列
全排列的题目大家应该都很清楚,因此在此处不赘述。思路也就写在代码里面了
int a[1001];
int mark[1001] = {0};
void quanpailie(int n,int count1){
if(count1 == n){
for(int i = 0;i < count1;i ++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i = 1;i <= n;i ++){
if(mark[i] == 0){
a[count1] = i;
mark[i] = 1;
quanpailie(n,(count1 + 1));//本人写成了quanpailie(n,(++count1)),导致我原本认为很简单的全排列问题搞了有45分钟
mark[i] = 0;//恢复现场,方便下一轮迭代。
}
}
}
其实全排列稍微有点难度的就是可重复元素的全排列,在此处先打个样,之后再有空再补充。
琐碎零星的知识点(需要重点注意)
1.最简n真分数的问题等价于最大公约数
2.多使用puts();自动补充’\n’
3.八皇后问题,将穷举(八重循环)改为递归,需要重点关注
4.得到数组大小:sizeof(arr) / sizeof(arr[0])*
5.字符串替换可以用队列接收,通过拆分字符串
6.若计算只包含加法、减法和乘法的整数表达式除以正整数n的余数,可以在每步计算之后对n取余,结果不变。
7.在很多情况下,最好在做一件事之前检查是不是可以做,而不是做完之后再后悔。因为“悔棋”往往比较麻烦。
//比较常用的一个小知识点
int i = 0;
int j = !i;
printf("%d\n",j); //此时的j输出为1.
1.Find the Difference
题目很简单,但是如何将时间复杂度由O(n2)下降为O(n1)确实困扰了我
直到看见这个新奇的思路,充分理解了字符在计算机的存储方式(ascill码)与字符的关系
int s_count = 0;
int t_count = 0;
for(unsigned int i=0; i<s.length(); i++)
{
s_count += (int)s[i];
t_count += (int)t[i];
}
t_count += (int) t[t.length()-1]; //for the extra last char
return (char)(t_count - s_count);
2. Missing Number
本题用hash可以直接做出来,但是题目最后有一个要求是希望在O(1)时间内完成。这个困扰到了我。
参看了大神的答案,有了想法:
假设有N个数,那么有N个数组,中间缺失了一位,那么必然有a[N - 1] = N;(数组下标从0开始)
因此[0,1,2,…,N]中间缺失了一个数字
那么(0 ^ a[0]) ^ (1 ^ a[1]) ^ (x ^ a[x]) ^ . … ^ (N - 1 ^ a[N - 1]) ^ N [假设x(0<=x<N)
那么根据上面的异或式子,最后必然结果是缺失的那个数字
int missingNumber(int[] nums) {
int missing = nums.length;
for (int i = 0; i < nums.length; i++) {
missing ^= i ^ nums[i];
}
return missing;
}