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

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

我们常会遇到最优化决策问题,比如经典最长公共子序列(不同于公共子串),背包问题,斐波那契数列等等,它们的共同点是: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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值