微软2016年4月实习生笔试第三题-Demo Day题解

来源:互联网 发布:中国社会发展数据库 编辑:程序博客网 时间:2024/06/01 10:09

这道题选择动态规划做,说实话,一开始看到动态规划,我是懵逼的,对于我等渣渣,还啥都没学呢。可是大神做出来了,为了赶上脚步,看懂他的代码,于是花了几天的时间,看动态规划。现在将所理解的动态规划加以总结:

我们常会遇到最优化决策问题,比如经典最长公共子序列(不同于公共子串),背包问题,斐波那契数列等等,它们的共同点是:1、都要求最优解;2、都有重复子问题;3、都有最优子结构。如果查百度百科或维基百科,都会有这些字眼,因为这是利用动态规划解决某一问题的原因。最优决策应该容易理解,我们要求的问题就是一个优化问题;重复子问题简单说就是,我们要解决的这个大问题,可以划分为需要重复解决的小问题,比如求x,y的最长公共子序列,我们可以求x[i]和y[j]的最长公共子序列,而对于每一个递增的i和j就是不断重复求解子问题的过程,直到x,y的长度N,M;而最优子结构呢,就是要每一个子问题都有最优解,这样,我们才能利用不断求子问题的最优解,来求整个大问题的最优解。

而当我们使用动态规划来解决问题的时候,我们需要一个基本的算法思路:我们之所以用动态规划来解决问题,是因为它的备忘法比传统记忆法要优越,即将每次计算子问题的最优解,用一个二维表记录下来c[i][j],其中行代表所处的子问题状态,列代表该状态下的最优解。这样,当进行下一状态计算的时候,如果需要前面已经计算过的值,只需要从备忘表中取出来即可,避免了重复运算。

以求x,y两序列的最长公共子序列为例:

LCS(x,y,i,j)if(x,y!=null){  then if(x[i]=y[j]){    c[i,j]=LCS(x,y,i-1,j-1)+1;  }else {  c[i,j]=max{LCS(x,y,i-1,j), LCS(x,y,i,j-1)}return c[i,j];
以上为算法导论里求解最长公共子序列的伪代码。其中c[i][j]这个二维数组用来记录,x[i]和y[j]的LCS长度,初始值i=0,j=0时,c=0

因此,动态规划解题的算法复杂度为O(m*n)。但是我们需要注意,虽然动态规划的备忘法很好的解决了需要重复利用的值的问题,但是它是以牺牲空间为代价,来储存这些二维表的,如果所需计算的规模过于庞大,我们就要考虑空间溢出问题。对于一个可能与前N个阶段相关的问题,建立数组Data[0..N],其中各项为前面N个阶段的保存数据。这样不采用这种内存节约方式时对于阶段k的访问只要对应成对数组Data中下标为k mod (N+1)的单元的访问就可以了。这种处理方法对于程序修改的代码很少,速度几乎不受影响,而且需要保留不同的阶段数也都能很容易实现。


然后了解了动态规划的知识后,就可以来解这道题:

这道题实质是利用不断将empty改成blocked或将blocked改成empty,来改变机器人运行的方向,达到终点。其中的特殊情况为:起点终点、第一行第一列、最后一行最后一列;这些地方机器人的方向或改单元的障碍物为确定值,需要单独拿出来讨论。其余为中心部分。总体思路为:求到某一单元需要改变的单元数=min{其上单元备忘值,其左单元备忘值}+自身是否为障碍物(b则加1)

大神的代码


import java.util.Scanner;public class DemoDay{public int minChange(char[][] grids) {    //传入图,b为障碍物,.为无障碍物,起点和终点默认为.int m = grids.length;             //行数int n = grids[0].length;          //列数        int[][] a = new int[m][n];        //记录从起点到达该单元所需修改的单元数目最小值        int[][] right = new int[m][n];            /*记录除起点、终点、最后一行、最后一列以外的单元的当前运动方向,0为向右,1为向下,2为任意方向         * (如果从上方和左方到达该单元所需改变的单元数目相同,则可从任意方向到达该单元)*/                //只有一行,返回所有障碍物数目        if(m==1){        int count = 0;        for(int i=1;i<n;i++){        if(grids[0][i]=='b'){        count++;        }        }        return count;        }        //只有一列,返回所有障碍物数目        if(n==1){        int count = 0;        for(int i=1;i<m;i++){        if(grids[i][0]=='b'){        count++;        }        }        return count;        }        //列数大于1且行数大于1        /*         * 动态规划,一层一层遍历该图,每个单元遍历一次,时间复杂度为m*n         * 每遍历到一个单元,计算到该单元所需改变的单元数目最小值         */        for(int i=0;i<m;i++){//一行一行记录每一状态的最优解,知道求到最终状态a[m][n]            for(int j=0;j<n;j++){            //初始化起点            if(i==0 && j==0){            a[i][j] = 0;            }else                //初始化第一排(第一排的方向必向右),每一个状态只受前一状态的左侧状态影响和当前状态的影响            if(i==0){            a[i][j] = a[i][j-1] + (grids[i][j]=='b'?1:0);            right[i][j] = 0;            }else            //初始化第一列(第一列的方向必向下)            if(j==0){            //起点下面的一个单元(只有当起点右边的单元为1障碍物时,方向才会向下)            if(i==1 && grids[i-1][j+1] != 'b'){            a[i][j] = 1;//将起点右侧一个单元从empty换位blocked,因为change grids是双向的,题目只要求最小转换数,并不是只算从blocked到empty的            }else{            //其他情况(方向必向下,不受第二列单元影响)            a[i][j] = a[i-1][j] + (grids[i][j]=='b'?1:0);            }            right[i][j] = 1;//第一列的方向必为下            }else            //终点(取上方和左方单元中最小的那个值)            if(i==m-1 && j==n-1){            a[i][j] = Math.min(a[i-1][j], a[i][j-1]);            }else            //到达最后一行(最后一行的所有单元,其左边的单元无论方向向右还是向下,都会改变为向右),此时最后一行不包括第一列,因为前面已经讨论过了第一列,用else则把第一列的已经排除在外了            if(i==m-1){            int top = a[i-1][j];            int left = a[i][j-1];            //上方的单元方向向右,且右上方单元不为障碍物(此时需要改变上方单元的运动方向为向下,即需要修改右上角单元为障碍物)            if(right[i-1][j]==0 && grids[i-1][j+1] != 'b'){            top++;            }            a[i][j] = Math.min(top, left) + (grids[i][j]=='b'?1:0);            right[i][j] = 0;            }else            //到达最后一列(最后一列的所有单元,其上边的单元无论方向向右还是向下,都会改变为向下)            if(j==n-1){            int top = a[i-1][j];            int left = a[i][j-1];            //左方的单元方向向下,且左下方单元不为障碍物(此时需要改变上方单元的运动方向为向右,即需要修改左下角单元为障碍物)            if(right[i][j-1]==1 && grids[i+1][j-1] != 'b'){            left++;            }            a[i][j] = Math.min(top, left) + (grids[i][j]=='b'?1:0);            right[i][j] = 1;            }else{            //在图的中心部分时            int top = a[i-1][j];            int left = a[i][j-1];            //上方的单元方向向右,且右上方单元不为障碍物(此时需要改变上方单元的运动方向为向下,即需要修改右上角单元为障碍物)            if(right[i-1][j]==0 && grids[i-1][j+1] != 'b'){            top++;            }            //左方的单元方向向下,且左下方单元不为障碍物(此时需要改变上方单元的运动方向为向右,即需要修改左下角单元为障碍物)            if(right[i][j-1]==1 && grids[i+1][j-1] != 'b'){            left++;            }            if(top<left){            a[i][j] = top + (grids[i][j]=='b'?1:0);            right[i][j] = 1;            }            else            if(left<top){            a[i][j] = left + (grids[i][j]=='b'?1:0);            right[i][j] = 0;            }else{//如果两边一样活left<top,统一将方向设置为向右0,不可以,这样运行wr            a[i][j] = left + (grids[i][j]=='b'?1:0);            right[i][j] = 2;            }            }            }        }        return a[m-1][n-1];    }public static void main(String []args){DemoDay demo = new DemoDay();Scanner in = new Scanner(System.in);        while(in.hasNext()) {//判断一开始有没有输入,可以去掉        int N = in.nextInt();        int M = in.nextInt();        String delet = in.nextLine();//废弃的流        char[][] grid = new char[N][M];        for(int i=0;i<N;i++){//二维数组读入grids,一行一行读,每一行读的string的char为列元素        String line = in.nextLine();        for(int j=0;j<M;j++){        grid[i][j] = line.charAt(j);        }        }        System.out.println(demo.minChange(grid));        }}}


0 0
原创粉丝点击