近期笔试小结(附数据库工程师面试准备)
《网易 0912》
也不知道在网易招聘官网怎么投了个数据库管理工程师的,不过还是认真的做了其笔试题。
选择题不说了,两道编程题难度一般,需细心!
1.买苹果
下面都是比较直接的做法,其实可以考虑使用背包模型去做。
#include<iostream> #include<vector> using namespace std; int main() { int n; while(cin>>n){ int result=0; if(n<6) { cout<<-1<<endl; return 0; } while(n>0) { if(n%8==0) { result+=n/8; break; } else { n-=6; if(n>=0) result++; } } if(n>=0) cout<<result<<endl; else cout<<-1<<endl; } return 0; }
或(由于范围比较小可以直接枚举6个和8个的袋数维护最小值即可。时间复杂度:O(n^2/48) ):
#include <iostream> #include <cstdio> using namespace std; int main(){ int n, res = 10000; while(cin >> n){ for(int i = 0; i <= 20; i++){ for(int j = 0; j <= 20; j++){ if(i * 6 + j * 8 == n){ res = min(res,(i + j)); } } } if(res == 10000) res = -1; cout << res << endl; } return 0; }
2.最大奇约数
题意:
定义函数f(x)为x的最大奇数约数,x为正整数,例如f(44) = 11.现在给出一个N,需要求出f(1) + f(2) + f(3) + ... + f(N)
例如: N = 7,则f(1) + f(2) + f(3) + f(4) + f(5) + f(6) + f(7) = 1 + 1 + 3 + 1 + 5 + 7 = 21.
分析:易知奇数的最大奇约数是其自身, 而偶数的最大奇约数是是除去所有偶因子后的那个奇数。最直观的想法就是挨个遍历求各个数的最大奇约数的和。
然而很明显,时间复杂度满足不了要求
#include <iostream> using namespace std; int main() { long long N; while(cin >> N){ long long res = 0; for (long long i = 1; i <= N; ++i) { int temp = i; while (temp % 2 == 0) { temp /= 2; } res += temp; } cout << res << endl; } return 0; }
需要找规律进行改进,记F(N)=f(1) + f(2) + f(3) + ... + f(N),观察会发现,
如果N为奇数,则f(2)+f(4)+...+f(N-1)=f(1) + f(2) + f(3)+...+f((N-1)/2)
那么F(N) = 1 + 3 + 5 + ....+N + f(2) + f(4)+...+f(N-1)=F((N-1)/2)+(N+1)*(N+1)/4
如果N为偶数,则F(N)=1+3+5+...+(N-1)+f(2)+...+f(N)=F(N/2)+N*N/4
#include<iostream> using namespace std; long long sum(long long n) { if (n == 1) { return 1; } if (n % 2 == 0) { return sum(n / 2) + n * n / 4; } else { return sum((n-1)/2) + (n+1)*(n+1)/4; } } int main() { int N; while(cin >> N){ cout << sum(N) << endl; } }
也可以将奇偶统一可以写出更简洁的代码,如下:
#include <iostream> using namespace std; long long n; long long sum(long long n){ if(n == 0) return 0; return (long long)( (n + 1) / 2) * ( (n + 1) / 2) + sum(n / 2); } int main(){ while(cin >> n){ cout << sum(n) << endl; } return 0; }
这样每次计算问题规模减半,时间复杂度为:O(log(n))
3.聚簇索引的特点
4.设计一个简单的论坛系统的数据库
这个其实网上有很多比较好的参考资料了:
论坛数据库的设计(写得很好)
总结几种常用的论坛设计方法:
- 分割思想:
(1)数据库切分:用户库、主题库、回复库
(2)数据表水平切分:用户库1-n、主题库1-n、回复库1-n (比如按时间分)
(3)分布式数据库:每台计算机中都有DBMS的一份完整拷贝副本,并具有自己局部的数据库,位于不同地点的许多计算机通过网络互相连接,共同组成一个完整的、全局的大型数据库。
(4)论坛功能可以进行分隔,不同的服务器负责不同的功能
(5)用主从数据库,master是写, slave是读
(6)把内容与其它信息分开,好处就是可以让每个表的文件最小化,对数据库操作压力会减小,这样保证每张表数据量很小,操作速度会快,也可以在这里使用缓存
- 索引:
针对是否建立索引有着一定的分歧:
个人觉得建立索引还是很有必要的。理由如下:
(1)建立索引可以加快检索速度,对于论坛读和写的比例相差很大,用户体验当然是读多写少,所以综合考虑还是要用索引,而且是加在常用的读关键字上。
(2)索引之所以会降低更新的速度,是因为更新还包括对索引的更新,从更新帖子10万左右,这句话是说,我们可能对发帖标题,发帖内容,回复标题,回复内容这4个字段做更新。需要注意的是,这四个字段并不是用来建立表连接的字段,为了优化查询速度我们不会在这四个字段上建立索引,所以从这道题目出发,我们建立的索引不会影响更新帖子的性能。只要被索引的列(例如回复表的标题ID)不被频繁更新,即使索引所在地行的其它列被频繁update,索引也不会被更新从而产生性能消耗,一张表一天30万次的索引更新,因它引起的性能消耗小到即使数据库安装在奔腾3单核CPU下都能轻松承担下来。
(3)对于更新的速度慢的问题,我们有解决的方法,你提交更新了后,前台可以让程序返回一个正确结果,后台开个线程异步慢慢跟新数据库就是了,反正更新成功的前提就是假设数据库连接永远正确并处于可靠状态。在数据库和用户之间建立一个缓冲区。(如,将更新的数据放到内存中,达到一定数量的时候再统一更新数据库。假如以100条为例,一旦内存中达到100条数据量将这100条数据统一入库。减少insert操作)
- 缓冲:
读的时候的缓冲:缓存路由表
主题缓存表(这个取每个区的前面100条记录),一般来说负载最大的就是主题的第一页,所以缓存表是个小表。
另外使用hibernate,在数据库上面加了一层缓存。
生成静态页,缓存最热,最新的帖子。
对于经常更新的数据都设计成单独表 ,这样可以最大程度的利用hibernate缓存
缓存常用的数据和表,利用缓存来将经常被访问的帖子留在内存中,为每条缓存的记录添加一个访问时间,如果长时间没被访问就从缓存中删除掉,
避免内存过大,每次用户看帖的时候,首先检索缓存中时候有需要的帖子,没有的话再访问数据库,然后将数据库返回的帖子信息存储到缓存中。
写的时候的缓冲:数据库和用户之间建立缓存,将更新的数据放在内存中,异步操作的。所有的写贴操作 放到一个队列然后批量执行插入数据库操作。
预估计的缓冲:假如用户第一次打开某标题,那将此标题的相关的前100条数据缓存到客户断。这样避开对数据库的直接查询,减少数据库压力。
- 代码优化
尽量避免表的连接约束通过代码来实现约束 例如用户id的验证在用户登录时验证这样就可以把帖子表的用户id外键去掉这样就成了单表操作、查询 而连接可以通过触发来实现这样最多是查询了3个表而不是连接中的笛卡尔笛卡尔积 回复表的查询限定每次查询的记录数例如限定10条其它的通过点击触发来操作"注代码优化容易出现bug 原因有些开发工具本身有优化"
- 数据库性能调优
尽量用硬件来代替软件优化 原则就是能用硬件的尽量用硬件 比如磁盘阵列 RAID0 有条件用RAID10 加大内存 .避免小表上建索引 对论坛来说数据帖子和回复不是很重要 可以定期删除一些垃圾帖子 楼主说的几百万条记录的论坛对现在的数据库管理系统和计算机来说永不着刻意的优化,定期维护打包备份数据库就可以了
提高速度的关键:
(1)建立合理的索引并在查询时充分利用;
(2)避免使用关联,这样避免整表扫描;使用关联不如多次使用主键查询来的快;
(3)一些处理的功能尽可能放到内存中来做,比如组织主题和回复;
(4)海量缓存(使用静态页面也是个不错的做法)
(5)定期对表进行转储
数据库面试相关准备题
4.存储过程和函数的区别是什么?
5.什么是数据库事务?
6.游标的作用是什么,如何知道游标已经到了最后?
7.触发器
8.什么叫SQL注入式攻击,如何防范?
9.解释聚簇索引和非聚簇索引之间的区别?
10.SELECT INTO 和 INSERT INTO SELECT 两种表复制语句
11.SQL 中GO的作用
每个被GO分隔的语句都是一个单独的事务,一个语句执行失败不会影响其它语句执行。
12.SQL中的case when then else end用法
14.数据库中最常用的是哪两种并发控制协议?
15.
-----------------------------------
《新美大0911》
1.收红包问题
题意:在一个桌子上放了若干个数值不等的红包,围成一个圈,现在让你选取若干个红包,要求相邻的两个红包不能同时选取,编程求出选取红包所得钱数的最大值;
输入:
2
1,2,3
1,2,3,4
输出:
3
6
分析:
按顺序依次选取红包,则每个位置都有两种状态,选取和不选取,最终要使红包钱数最大化,可以用动态规划的思想来解决。在动态规划中,每一步的选择都是前面所有步骤的总结,每一步选择最优从而使最终方案最优。DP算法的这种本质决定了问题必须是线性结构,不过本问题是环状结构,第一个红包(指逻辑上的第一个,其实环上的任何一个红包都可以作为第一个)的状态不仅会影响第二个红包的状态,还会影响最后一个红包的状态,那么要先将环状结构转化为线性结构,具体的实现方式是:
(1)选取第一个红包的所有方案:选取第一个红包,从而第二个红包和最后一个红包都不能选取,剩余红包的状态不确定;
(2)不选取第一个红包的所有方案:不选取第一个红包,那么剩下的红包状态都不确定。
若桌上某个位置的红包被选取,那么其后一个位置的红包就不能选取;若某一位置的红包未选取,其后一个位置的红包就可以选取,也可以不被选取。DP转移方程:
-> dp[i+1][0]=max(dp[i][0],dp[i][1]);
-> dp[i+1][1]=dp[i][0] ;
其中i表示红包的索引,第二维中的0表示不选取第i个红包,1表示选取第i个红包,即第二维用来表示红包的两种状态;
而dp[i][0]表示从红包0到红包i中选取红包,并且不选取红包i,得到的红包钱数最大值;dp[i][1]表示从红包0到红包i中选取红包,并且选取红包i,所得到的红包钱数最大值。---算法时间复杂度为O(n)
#include <iostream> #include <vector> #include <algorithm> using namespace std; vector<int> Hbao; int Choose(int begin, int end) { if (end < begin) return 0; int dp[2], temp; dp[0] = dp[1] = 0; for (int i = begin; i <= end; i++) { temp = dp[0]; dp[0] = max(dp[0], dp[1]); dp[1] = temp + Hbao[i]; } return max(dp[0], dp[1]); } int main() { int t, n, val, res; cin >> t; while (t--) { cin >> val; Hbao.push_back(val); while (cin.get() == ','){ cin >> val; Hbao.push_back(val); } n = Hbao.size(); //1.选取红包0,然后对红包2到红包n-2进行DP res = Hbao[0] + Choose(2, n - 2); //2.不选取红包0,然后对红包1到红包n-1进行DP res = max(res, Choose(1, n - 1)); cout << res << endl; Hbao.clear(); } return 0; }
可参考:http://blog.csdn.net/lrgdongnan/article/details/52506373