现在的位置: 首页 > 综合 > 正文

百度面试题–5只蚂蚁走木棍问题的非递归解法(Java调试通过)

2017年11月28日 ⁄ 综合 ⁄ 共 4192字 ⁄ 字号 评论关闭

致歉:之前贴的代码有bug。

题目描述:

   有一根27厘米的细木杆,在第3厘米、7厘米、11厘米、17厘米、23厘米这五个位置上各有一只蚂蚁。木杆很细,不能同时通过一只蚂蚁。开始时,蚂蚁的头朝左还是朝右是任意的,它们只会朝前走或调头,但不会后退。当任意两只蚂蚁碰头时,两只蚂蚁会同时调头朝反方向走。假设蚂蚁们每秒钟可以走一厘米的距离。编写程序,求所有蚂蚁都离开木杆的最小时间和最大时间。

当然,这道题目可以不用编程做,注意到“两只蚂蚁会同时调头朝反方向走”,也就是一秒钟两只蚂蚁完成调头和反方向爬行两个动作,可以理解为蚂蚁穿越了对方毫无影响地原方向前进(或者将两只蚂蚁互换来看待),那么

最大时间就取决于两头的蚂蚁的方向:

如果23厘米处的蚂蚁向原点爬行离开木杆,需用时23秒;如果3厘米处的蚂蚁向27厘米处爬行离开木杆,需用27-3=24秒,所以最大用时为24秒。

最小时间就取决于中间蚂蚁朝两头跑所用时间:

如果处于小于等于11厘米处的蚂蚁向原点爬行离开木杆,需用时11秒;如果大于11厘米处的蚂蚁向27厘米处爬行离开木杆,需用27-17=10秒,所以都爬出最小用时为11秒。

但如果面试官要求编写程序,那就老实编程吧,以下是一个Java编写的程序,以及编程思路。

思路与建模

  • 将木杆看成0至27的坐标, 5只蚂蚁分别在3, 7, 11, 17, 23的位置
  • 创建enum Direction{ Left, Right}
  • 创建Ant类, 蚂蚁可以爬行(walk), 掉头(shift),  以及记录是否已经爬出的标记.
  • 创建Controller类, 给定蚂蚁的初始方向, Controller类可以计算出在这种初始方向下蚂蚁全部爬出需要的时间.
  • 创建Simulator类, 它给出蚂蚁初始方向的所有组合, 并使用Controller执行得到所有时间, 选择最大值和最小值.

蚂蚁的初始方向不是向左就是向右,可以用二进制表示。5只蚂蚁, 2^5=32个组合,所以用0-31之间的整数的二进制刚好可以表示蚂蚁的初始方向。比如24的二进制为11000,分别与二进制数00001, 00010, 00100, 01000, 10000相与,结果不为零的蚂蚁方向向右,可得第四、五只蚂蚁方向向右。

实现

在Model包内,包含Ant类

package cn.dfeng.model;
/**
 * 蚂蚁
 * @author dfeng
 *
 */
public class Ant {
	private int position; //蚂蚁的位置
	Direction direction;  //爬行方向
	private boolean out = false; //是否已经爬出

	public Ant(int p, Direction dir){
		this.position = p;
		this.direction = dir;
	}

	/**
	 * 蚂蚁行走
	 */
	public void walk(){
		if(direction == Direction.Right){
			position++;
		}else{
			position--;
		}
	}
	
	/**
	 * 蚂蚁调头
	 */
	public void shift(){
		this.direction = (this.direction == Direction.Left) ? Direction.Right : Direction.Left;
	}
	
	public int getPosition() {
		return this.position;
	}
	
	public Direction getDirection() {
		return this.direction;
	}
	
	public boolean isOut() {
		return out;
	}

	public void setOut(boolean out) {
		this.out = out;
	}
}

在Controller包内

package cn.dfeng.control;

import java.util.ArrayList;
import java.util.Arrays;

import cn.dfeng.model.Ant;
import cn.dfeng.model.Direction;

/**
 * 控制器
 * @author dfeng
 *
 */
public class Controller {

	public static final int MAX_LENGTH = 27; 
	
	/*
	 * 蚂蚁初始位置
	 */
	private static final int[] POSITIONS = {3, 7, 11, 17, 23};
	
	private int online_number = 5; //还在木杆上的蚂蚁数,初始为5
	
    private long timer = 0; //计时器
    
    ArrayList<Ant> ants;
    
    /**
     * 指定蚂蚁初始方向, 创建控制器
     * @param directions 蚂蚁初始方向
     */
    public Controller(Direction[] directions){
    	ants = new ArrayList<Ant>();
    	/*
         * 创建初始蚂蚁队列
         */
    	for( int i = 0; i < POSITIONS.length; i++ ){
    		Ant ant = new Ant(POSITIONS[i], directions[i]);
    		ants.add(ant);
    	}
    }
    
    public long start(){
    	/*
    	 * 在木杆上的蚂蚁数为0即全部爬出
    	 */
    	while(online_number > 0){
    		shiftAnts();
    		moveAnts();
    		timer++;
    		analyzeAntStatus();
    	}
    	return timer;
    }
    
    //移动还在杆上的蚂蚁
    private void moveAnts() {
    	for (Ant ant : ants) {
    		if (!ant.isOut())
    			ant.walk();
    	}
    }
    
    //分析蚂蚁的状态
    private void analyzeAntStatus() {
    	for (Ant ant : ants) {
    		if (ant.isOut()) {
    			continue;
    		}
    		if ((ant.getPosition() == MAX_LENGTH) ||
    				(ant.getPosition() == 0)) {
    			ant.setOut(true);
    			online_number--;
    			System.out.println("Ant " + ants.indexOf(ant) + " is out at " + timer);
    		}
    	}
    }
    
    //移动前检查杆上的蚂蚁是否需要调头
    //使用了检查一串字符串中是否有重复字符的算法,空间换时间
    private void shiftAnts(){
    	short[] flags = new short[28];
    	Arrays.fill(flags, (short)-1);
    	
    	for (int i = 0; i < ants.size(); i++) {
    		Ant ant = ants.get(i);
    		if (ant.isOut()) {
    			continue;
    		}
    		short result = flags[ant.getPosition()];
			if(result == -1) {
				flags[ant.getPosition()] = (short)i; //如果还没有蚂蚁在这个位置,就记下这只蚂蚁的序号。
			} else { //如果已经有了,两只蚂蚁调头
				ant.shift();
				ants.get(result).shift();
				System.out.println("Ant " + i + " and ant " + result + " shift at " + timer);
			}
    	}
    }
}

在Root包内

package cn.dfeng;

import cn.dfeng.control.Controller;
import cn.dfeng.model.Direction;

public class Simulator {

	private static final int[] DIRECTIONS = {0x01, 0x02, 0x06, 0x08, 0x10}; //它们的二进制分别是00001, 00010, 00100, 01000, 10000,你知道为什么使用它们了吧
	
	private long longest = 0;
	private long shortest = Long.MAX_VALUE;
	
	/**
	 * 开始模拟
	 */
	public void simulate() {
		for (int i = 0; i < 32; i++) {
			Controller con = new Controller(this.getDirections(i));
			long time = con.start();
			if( time > longest ){
				longest = time;
			}
			if( time < shortest ){
				shortest = time;
			}
			System.out.println( " Time: " + time );
		}
	}

	/*
	 * 创建蚂蚁初始位置
	 */
	private Direction[] getDirections(int seed) {
		Direction[] dirs = new Direction[5];
		for(int i = 0; i < DIRECTIONS.length; i++) {
			if((DIRECTIONS[i] & seed) == 0) {
				dirs[i] = Direction.Left;
			} else {
				dirs[i] = Direction.Right;
			}
		}
		
		System.out.println( "Round: " + Integer.toBinaryString(seed));

		return dirs;
	}

	/**
	 * 打印结果
	 */
	public void getResult() {
		System.out.printf("Longest time %d.\nShortest Time: %d", longest,
				shortest);
	}

	public static void main( String[] args ){
		Simulator sim = new Simulator();
		
		sim.simulate();
		
		sim.getResult();
	}
}

评析

蚂蚁的初始方向不是向左就是向右,可以用二进制表示。5只蚂蚁, 2^5=32个组合,所以用0-31之间的整数的二进制刚好可以表示蚂蚁的初始方向。比如24的二进制为11000,分别与二进制数00001, 00010, 00100, 01000, 10000相与,结果不为零的蚂蚁方向向右,可得第四、五只蚂蚁方向向右。有了初始方向和初始位置, 就可以使用Controller模拟并比较了.

用最好的算法判断蚂蚁是否应该调头也是一个需要思考的地方,它与判断一个字符串中是否有重复字符的面试题颇为相似,可以使用空间换时间的思路快速解决。

抱歉!评论已关闭.