问题:
- 1 、 为什么离开现在的公司
应届生。
- 2 、 你最满意和最不满意的任务是什么?为什么?
让人厌烦的机械化工作。(以及让人厌烦的写不出脚本自动化这些破事的自己)
机械化,就是说,花了时间就有结果。不能体现我的聪明才智。==
- 3 、 你的爱好是什么?
把不错的电影一遍一遍看,提取出音频一遍遍听。
- 4 、 有 github / bitbucket 等账号优先考虑
GitHub:district10
技术问题:
以思路为主,简洁易懂。
- 1 、 八皇后问题(你要真从网上抄,我也没治不是么)
才复习了这个。
首先不能同行,那就直接用
C[i]
(i=[0,7)
,每个 i 都不一样)来存储每一行的列的位置。比如C[2]=5
代表皇后点位为(3, 6)
。不能同列,可以用循环检测是否和其他皇后冲突。
不能同斜线,可以用循环检测是否和其他皇后冲突。两种情况:
dx == dy
dx == -dy
优化 1:把 8 皇后的放置看成一个过程,第一个皇后,随意放,第二个皇后,只要和第一个皇后不冲突,随意放,以此,八个皇后都放上了,就是一个 valid 的情况。
这样冲突的判断不必要和全部其他皇后检测,只需要看之前的皇后。
优化 2:列、主对角线、副对角线编码,存起来,占了一个坑,就把那个坑标记好。
这样,就不用通过循环来判断是否冲突了。只要检测一个标志位。
这个标志位设计方案怎么都可以,比如像这样:
主对角线 副对角线 y - x ?dx == dy y + x ? dx == -dy (dx+dy==0) +----------------------------------> y +-----------------------------------> y | 0 1 2 3 4 5 6 7 | 0 1 2 3 4 5 6 7 | -1 0 1 2 3 4 5 6 | 1 2 3 4 5 6 7 8 | -2 -1 0 1 2 3 4 5 | 2 3 4 5 6 7 8 9 | -3 -2 -1 0 1 2 3 4 | 3 4 5 6 7 8 9 10 | -4 -3 -2 -1 0 1 2 3 | 4 5 6 7 8 9 10 11 | -5 -4 -3 -2 -1 0 1 2 | 5 6 7 8 9 10 11 12 x | -6 -5 -4 -3 -2 -1 0 1 x | 6 7 8 9 10 11 12 13 | -7 -6 -5 -4 -3 -2 -1 0 | 7 8 9 10 11 12 13 14 V V
代码如下:
void search( int cur ) { if( cur == n ) { ++tot; // 打印结果的代码放在这里 } else { for( int i = 0; i < n; ++i ) { // vis: visited, vis[0] -> col, vis[1] -> minor diag, vis[2] -> major diag if( !vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n] ) { C[cur] = i; // col y-x=0 y+x=0 vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 1; search( cur+1 ); vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 0; // 改回来 } } } }
其中 vis 是一个二维数组,
vis[0][i]
表示第 i 列是否被占坑(1 是有人,0 是没人),vis[1][i]
表示副对角线(上面右图),vis[2][i]
表示主对角线(上面左图,因为 cur-i 可能为负(图中下三角部分),所以,要加上 n=8 )。如果想看看每个可能情况的放置,在上面
// 打印结果的代码放在这里
处加上:printf( "solve #%d:", tot ); for( int i = 0; i < n; ++i ) { printf( " %d", C[i] ); } printf( "\n" );
输出如下:
solve #1: 0 4 7 5 2 6 1 3 solve #2: 0 5 7 2 6 3 1 4 solve #3: 0 6 3 5 7 1 4 2 solve #4: 0 6 4 7 1 3 5 2 ... solve #89: 7 1 3 0 6 4 2 5 solve #90: 7 1 4 2 0 6 3 5 solve #91: 7 2 0 5 1 4 6 3 solve #92: 7 3 0 2 5 1 6 4
或者,你要一个视觉化的表示,上面的代码可以这么来:
printf( "-- Solve #%03d --\n", tot ); for( int i = 0; i < n; ++i ) { printf( "+-+-+-+-+-+-+-+-+\n" ); for( int j = 0; j < n; ++j ) { printf( "|%c", C[i] == j ? 'X' : ' ' ); } printf( "|\n" ); } printf( "-----------------\n\n" );
打印出来就是这样:(如果看得不够“方”,那是字宽和行高的问题,就怪不得我了)
-- Solve #092 -- +-+-+-+-+-+-+-+-+ | | | | | | | |X| +-+-+-+-+-+-+-+-+ | | | |X| | | | | +-+-+-+-+-+-+-+-+ |X| | | | | | | | +-+-+-+-+-+-+-+-+ | | |X| | | | | | +-+-+-+-+-+-+-+-+ | | | | | |X| | | +-+-+-+-+-+-+-+-+ | |X| | | | | | | +-+-+-+-+-+-+-+-+ | | | | | | |X| | +-+-+-+-+-+-+-+-+ | | | | |X| | | | -----------------
- 2 、 给定一个正整数,判断这个正整数是否是质数。请给出代码或者伪代码
质数只能靠筛选(它就这么定义的,也没有人发现什么别的特性可以替代这个定义,还能加速判断)。
唯一的优化就是,你要判断 n 是否是质数,你不用从 1 到 n-1 一个一个判断他们是否互质,只要判断到 sqrt(n) 就可以了。这里到底是 sqrt(n) 还是 sqrt(n)-1,sqrt(n)+1,sqrt(n-1),sqrt(n) + 1?对于这种 off-by-one 的潜在 bug,我们分析一下:
看看 n = 9,sqrt(9) = 3:
- 首先,我们要判断 3,所以 sqrt(n)-1、sqrt(n-1) 错;
- 其次,我们不用判断 4,因为 32 == 9,4 比 3 大,如果有个数乘以 4 等于 9,那它小于 3,那我们已经判断过了啊!所以没必要 sqrt(n)+1;
- …
这么分析起来是不是有点意思,但很累?实际情况下,我都是 trail-and-error 的。只有这么几种情况,一个个试呗。而且,我们把代码放松点。不优化那么“严肃”,问题就简单多了。代码如下
bool isPrime( int n ) { int ceil = sqrt(n); for( int i = 2; i <= ceil; ++i ) { if( 0 == n%i ) { return false; } } return true; }
可能的优化:
- 用一个表,直接从里面读取结果(是不是质数)。适合于需要判断很多次,又不担心内存的情况;
- 每次计算后把结果保存起来。适合于可能对一些数字要经常判断,又不想占用很多内存的情况;
如果你计算能力绰绰有余,要什么优化。
- 3 、 现在有一个双向链表,每一个 node 的结构如下,
{ name: 唯一标识, left: leftNode, right: rightNode }
请用代码或者伪代码描述,给定一个 name ,和一个双向链表 dlink ,找到这个对应的结点并删除。
我还没有复习到双向链表。假设你这么定义的 dlink:
typedef node dlink; // 然后用 leftNode 指向头,rightNode 指向尾
我画个示意图,大致如下(天知道我为什么这么喜欢画 ascii 示意图):
首节点的 left 指向自己,末节点的 right 指向自己。 dlink 的 left 指向首节点,如果没有 node,就指向自己。right 情况类似。 +-----------+ | | +-------+ +--------+ +-----v--+ | +----> node +-------> node +------> node +--------+ ^ +--+ <-------+ <------+ <--------+ | | +-^-----+ +--------+ +--------+ | | | | | | +----+ | | | | | | +---------+ | +---+ dlink +----------------------------------------+ | | +---------+
那我们的函数接口为
bool delNode( dlink *dl, char *name )
,大概可以写成这样:bool delNode( dlink *dl, char *name ) { if( !dl || !name ) { return false; } node *n = dl->left; while( n && strcmp(n->name, name) != 0 ) { if( n->right == n ) { return false; } n = n->right; } if( !n ) { return false; } // 现在 n 就是我们要删除的节点了,那就把它删掉吧 node *pre = n->left; node *next = n->right; // 根据我们的定义,left 和 right 都不可能为 null // 但实际编码的过程中,我还是 assert 一下 assert( pre && next && "this line won't fail.\n" ); if( pre == n ) { // 这是第一个节点 dl->left = next; next->left = next; free( n ); // free( pre ) return true; } if( next == n ) { // 这是最后一个节点 pre->right = pre; dl->right = pre; free( n ); // free( next ) return true; } // 走到这一步,说明这是一个中庸的节点 pre->right = next; next->left = pre; free( n ); return true; }
- 4 、 请简述用户从浏览器浏览一个网页背后数据流转的流程
既然说数据流转,我就不说什么 DNS 查询的部分了。
对于用户而言,数据主要是网页的 html、js 以及 css 文件。
访问一个 url,如果以
blahblah.html
结尾,那就会- 如果 cache 里有这个网页而且没有过期,那就直接用本地的;
- 否则,请求这个网页。数据便从服务器那段传过来,可能是一个静态页面,服务器直接回传;也可能是一个动态生成的页面,需要临时生成一个页面。
现在用户拿到了 html,然后浏览器解析它,发现里面有引入别的 css 文件,js 文件,以及图片等等。于是浏览器再看,是否已经下载了?是否过期?
能用本地的就用本地的,不能就发出请求去获取。
于是浏览器一边获取数据,一边显示了出来。
- 5 、 请简述 git 多人协作的流程
首先明确 git 是一个非中心的版本控制系统。所以每个 repo(repository)的副本(姑且这么称呼),都是包含完整的历史信息(除非你 clone 的时候用了
--depth 1
之类的东西)。但非中心的方式协作起来不方便。因为你要知道别人的副本在哪个服务器(当然也可以是本机),哪个目录。所以通常都有一个中心化的位置,大家一起在这里存取。
以 GitHub 为例,新建一个 repo 后,克隆到本地,本地的 master 分支和 GitHub 上的 origin/master 联系了起来。
每次修改前,要先 pull(或者你喜欢手动 fetch 然后 merge),然后本地修改,然后提交到 origin/master,以和其他人同步。 pull 了之后可能有冲突,要解决 conflicts。解决好了没问题,再 push 到 GitHub。
如果大家都往 master 分支同步,可能会把这个分支搞得很乱。所以我们都是新建一个 dev 分支,搞定了一些事情,再切到 master 分支,把 dev 分支的内容 merge 进来。
对于一些小的 bug fix,或者新的 feature,也最好从 dev 分支 checkout 一个新的小分支,处理完了再合并到 dev。
git 是没有权限控制的。所以 GitHub 上只有一个团队的人才能直接修改 origin 上的 branches,其他人要提交 PR(Pull Requests)。
[北京][15k-18k] 创业公司求 Java 开发工程师,基于微信平台。我们可以给到的:广阔的发展空间,独当一面的机会,我全部的技术相关经验。 - V2EX
写完了发现你们要的是 Java 工程师……好吧,我只能说:毕业之前我可以学得不错的。
我也曾瞟过一本英文的 Pro Java8。