编程面试的10大算法概念汇总

来源:互联网 发布:android 查看端口号 编辑:程序博客网 时间:2024/05/01 07:24

以下是在编程面试中排名前10的算法相关的概念,我会通过一些简单的例子来阐述这些概念。由于完全掌握这些概念需要更多的努力,因此这份列表只是作为一个介绍。本文将从Java的角度看问题,包含下面的这些概念:

1. 字符串
2. 链表
3. 树
4. 图
5. 排序
6. 递归 vs. 迭代
7. 动态规划
8. 位操作
9. 概率问题
10. 排列组合

1. 字符串

如果IDE没有代码自动补全功能,所以你应该记住下面的这些方法。

1
2
3
4
5
6
toCharArray()// 获得字符串对应的char数组
Arrays.sort() // 数组排序
Arrays.toString(char[] a)// 数组转成字符串
charAt(intx) // 获得某个索引处的字符
length() // 字符串长度
length // 数组大小

2. 链表

在Java中,链表的实现非常简单,每个节点Node都有一个值val和指向下个节点的链接next。

1
2
3
4
5
6
7
8
9
classNode {
    intval;
    Node next;
 
    Node(intx) {
        val = x;
        next =null;
    }
}

链表两个著名的应用是栈Stack和队列Queue。

栈:

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
26
27
28
classStack{
    Node top;
 
    publicNode peek(){
        if(top !=null){
            returntop;
        }
 
        returnnull;
    }
 
    publicNode pop(){
        if(top ==null){
            returnnull;
        }else{
            Node temp =new Node(top.val);
            top = top.next;
            returntemp;   
        }
    }
 
    publicvoid push(Node n){
        if(n !=null){
            n.next = top;
            top = n;
        }
    }
}

队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
classQueue{
    Node first, last;
 
    publicvoid enqueue(Node n){
        if(first ==null){
            first = n;
            last = first;
        }else{
            last.next = n;
            last = n;
        }
    }
 
    publicNode dequeue(){
        if(first ==null){
            returnnull;
        }else{
            Node temp =new Node(first.val);
            first = first.next;
            returntemp;
        }  
    }
}

3. 树

这里的树通常是指二叉树,每个节点都包含一个左孩子节点和右孩子节点,像下面这样:

1
2
3
4
5
classTreeNode{
    intvalue;
    TreeNode left;
    TreeNode right;
}

下面是与树相关的一些概念:

  1. 平衡 vs. 非平衡:平衡二叉树中,每个节点的左右子树的深度相差至多为1(1或0)。
  2. 满二叉树(Full Binary Tree):除叶子节点以为的每个节点都有两个孩子。
  3. 完美二叉树(Perfect Binary Tree):是具有下列性质的满二叉树:所有的叶子节点都有相同的深度或处在同一层次,且每个父节点都必须有两个孩子。
  4. 完全二叉树(Complete Binary Tree):二叉树中,可能除了最后一个,每一层都被完全填满,且所有节点都必须尽可能想左靠。

译者注:完美二叉树也隐约称为完全二叉树。完美二叉树的一个例子是一个人在给定深度的祖先图,因为每个人都一定有两个生父母。完全二叉树可以看成是可以有若干额外向左靠的叶子节点的完美二叉树。疑问:完美二叉树和满二叉树的区别?(参考:http://xlinux.nist.gov/dads/HTML/perfectBinaryTree.html

4. 图

图相关的问题主要集中在深度优先搜索(depth first search)和广度优先搜索(breath first search)。

下面是一个简单的图广度优先搜索的实现。

1) 定义GraphNode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
classGraphNode{
    intval;
    GraphNode next;
    GraphNode[] neighbors;
    boolean visited;
 
    GraphNode(intx) {
        val = x;
    }
 
    GraphNode(intx, GraphNode[] n){
        val = x;
        neighbors = n;
    }
 
    publicString toString(){
        return"value: "+this.val;
    }
}

2) 定义一个队列Queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
classQueue{
    GraphNode first, last;
 
    publicvoid enqueue(GraphNode n){
        if(first ==null){
            first = n;
            last = first;
        }else{
            last.next = n;
            last = n;
        }
    }
 
    publicGraphNode dequeue(){
        if(first ==null){
            returnnull;
        }else{
            GraphNode temp =new GraphNode(first.val, first.neighbors);
            first = first.next;
            returntemp;
        }  
    }
}

3) 用队列Queue实现广度优先搜索

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
publicclass GraphTest {
 
    publicstatic void main(String[] args) {
        GraphNode n1 =new GraphNode(1);
        GraphNode n2 =new GraphNode(2);
        GraphNode n3 =new GraphNode(3);
        GraphNode n4 =new GraphNode(4);
        GraphNode n5 =new GraphNode(5);
 
        n1.neighbors =new GraphNode[]{n2,n3,n5};
        n2.neighbors =new GraphNode[]{n1,n4};
        n3.neighbors =new GraphNode[]{n1,n4,n5};
        n4.neighbors =new GraphNode[]{n2,n3,n5};
        n5.neighbors =new GraphNode[]{n1,n3,n4};
 
        breathFirstSearch(n1,5);
    }
 
    publicstatic void breathFirstSearch(GraphNode root, int x){
        if(root.val == x)
            System.out.println("find in root");
 
        Queue queue =new Queue();
        root.visited =true;
        queue.enqueue(root);
 
        while(queue.first !=null){
            GraphNode c = (GraphNode) queue.dequeue();
            for(GraphNode n: c.neighbors){
 
                if(!n.visited){
                    System.out.print(n +" ");
                    n.visited =true;
                    if(n.val == x)
                        System.out.println("Find "+n);
                    queue.enqueue(n);
                }
            }
        }
    }
}
Output:
1
2
value: 2 value: 3 value: 5 Find value: 5
value: 4


5. 排序

下面是不同排序算法的时间复杂度,你可以去wiki看一下这些算法的基本思想。

AlgorithmAverage TimeWorst TimeSpace冒泡排序n^2n^21选择排序n^2n^21Counting Sortn+kn+kn+kInsertion sortn^2n^2 Quick sortn log(n)n^2 Merge sortn log(n)n log(n)depends

另外,这里有一些实现/演示:: Counting sortMergesortQuicksortInsertionSort

  • 视觉直观感受 7 种常用的排序算法
  • 视频: 6分钟演示15种排序算法

6. 递归 vs. 迭代

对程序员来说,递归应该是一个与生俱来的思想(a built-in thought),可以通过一个简单的例子来说明。

问题: 有n步台阶,一次只能上1步或2步,共有多少种走法。

步骤1:找到走完前n步台阶和前n-1步台阶之间的关系。

为了走完n步台阶,只有两种方法:从n-1步台阶爬1步走到或从n-2步台阶处爬2步走到。如果f(n)是爬到第n步台阶的方法数,那么f(n) = f(n-1) + f(n-2)。

步骤2: 确保开始条件是正确的。

f(0) = 0;
f(1) = 1;

1
2
3
4
5
publicstatic int f(intn){
    if(n <=2)return n;
    intx = f(n-1) + f(n-2);
    returnx;
}

递归方法的时间复杂度是n的指数级,因为有很多冗余的计算,如下:

f(5)
f(4) + f(3)
f(3) + f(2) + f(2) + f(1)
f(2) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)
f(1) + f(0) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)

直接的想法是将递归转换为迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicstatic int f(intn) {
 
    if(n <= 2){
        returnn;
    }
 
    intfirst = 1, second =2;
    intthird = 0;
 
    for(inti = 3; i <= n; i++) {
        third = first + second;
        first = second;
        second = third;
    }
 
    returnthird;
}

对这个例子而言,迭代花费的时间更少,你可能也想看看Recursion vs Iteration

 

7. 动态规划

动态规划是解决下面这些性质类问题的技术:

  1. 一个问题可以通过更小子问题的解决方法来解决(译者注:即问题的最优解包含了其子问题的最优解,也就是最优子结构性质)。
  2. 有些子问题的解可能需要计算多次(译者注:也就是子问题重叠性质)。
  3. 子问题的解存储在一张表格里,这样每个子问题只用计算一次。
  4. 需要额外的空间以节省时间。

爬台阶问题完全符合上面的四条性质,因此可以用动态规划法来解决。

1
2
3
4
5
6
7
8
9
10
11
12
publicstatic int[] A = newint[100];
 
publicstatic int f3(intn) {
    if(n <= 2)
        A[n]= n;
 
    if(A[n] >0)
        returnA[n];
    else
        A[n] = f3(n-1) + f3(n-2);//store results so only calculate once!
    returnA[n];
}

8. 位操作

位操作符:

OR (|)AND (&)XOR (^)Left Shift (<<)Right Shift (>>)Not (~)1|0=11&0=01^0=10010<<2=10001100>>2=0011~1=0

获得给定数字n的第i位:(i从0计数并从右边开始)

1
2
3
4
5
6
7
8
publicstatic boolean getBit(int num, int i){
    intresult = num & (1<<i);
 
    if(result ==0){
        returnfalse;
    }else{
        returntrue;
    }

例如,获得数字10的第2位:

i=1, n=10
1<<1= 10
1010&10=10
10 is not 0, so return true;

9. 概率问题

解决概率相关的问题通常需要很好的规划了解问题(formatting the problem),这里刚好有一个这类问题的简单例子:

一个房间里有50个人,那么至少有两个人生日相同的概率是多少?(忽略闰年的事实,也就是一年365天)

计算某些事情的概率很多时候都可以转换成先计算其相对面。在这个例子里,我们可以计算所有人生日都互不相同的概率,也就是:365/365 * 364/365 * 363/365 * … * (365-49)/365,这样至少两个人生日相同的概率就是1 – 这个值。

1
2
3
4
5
6
7
8
9
publicstatic double caculateProbability(int n){
    double x =1;
 
    for(inti=0; i<n; i++){
        x *=  (365.0-i)/365.0;
    }
 
    double pro = Math.round((1-x) *100);
    returnpro/100;

calculateProbability(50) = 0.97

10. 排列组合

组合和排列的区别在于次序是否关键。

如果你有任何问题请在下面评论。

参考/推荐资料:
1. Binary tree
2. Introduction to Dynamic Programming
3. UTSA Dynamic Programming slides
4. Birthday paradox

5. Cracking the Coding Interview: 150 Programming Interview Questions and Solutions, Gayle Laakmann McDowell

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手机螺丝拧圆了怎么办 小螺丝断在孔里怎么办 螺丝断在孔里怎么办 蟑螂咬过的衣服怎么办 遇到会飞的蟑螂怎么办 狗粘了老鼠胶怎么办 家有蟑螂怎么办小窍门 人感染狗的螨虫怎么办 棉拖鞋里面的臭怎么办 卧室老有酸臭味怎么办 养狗屋子有腥味怎么办 房间里进了壁虎怎么办 家中进了只老鼠怎么办 医保卡迟迟不发怎么办 医保断了一个月怎么办 没办离职的社保怎么办 家里吊顶上有老鼠怎么办 房间里进老鼠了怎么办 衣服上有老鼠屎怎么办 如果被老鼠咬了怎么办 儿童被老鼠咬了怎么办 蟑螂爬到衣柜里怎么办 床垫里有老鼠屎怎么办 汽车里进了老鼠怎么办 狗狗感染蜱虫怎么办 脸上有螨虫怎么办才能去除 老鼠被剪丁丁后怎么办 1楼下水道钻老鼠怎么办 月经来了奶水少了怎么办 孕37周霉菌严重怎么办 家里进了飞蚂蚁怎么办 家里进了大蜘蛛怎么办 衣服上有蟑螂卵怎么办 被子上有蟑螂卵怎么办 厨房里的小飞虫怎么办 水果生的小飞虫怎么办 家里的厕所有虫怎么办 人吃了蟑螂药怎么办 静电贴粘不住了怎么办 会飞的蚂蚁咬了怎么办 家里有白蚁怎么办能除根