2013/03/29 腾讯马拉松复试第2题源码。

题目描述:

考虑一个3*3方格游戏:

游戏的目标是通过移动,让相同颜色的块形成一个连通块(相邻是指两个块有边相邻,相邻不算)

移动规则如下:选择一行(),向左右(上下)移动一格,方格从一边划出,则从对应的另外一边划入,像履带一样。

如选择第一行向右边移动,最右边的那格会移动到最左边。

游戏中还有一些方格被固定住,这些方格没办法移动(如下图的第三行第二列)

 

假设现在告诉你初始状态,请问你最少需要几步才能达到目标?

 (Time Limit:2000/1000MS(Java/Others)    Memory Limit:65535/32768K(Java/Others))

 

Input

第一行一个整数 T代表接下去有 T组数据;

每组数据由 3*3的模块组成,每个模块表示的小正方形是由上下左右四个小三角形组成;

每个模块有 5个字符,前四个字符分别表示组成正方形的上下左右四个小三角形的颜色,

五个字符表示该格子能否移动(0表示能移动,1表示不能移动).

[T echnicalSpecification]

0<T<100

代表颜色的字符一定是RGBO的其中一个;

代表能否移动移动的字符一定是 0或者 1

Output

首先输出 case,接着输出最小的移动步数使得游戏达到目标状态( sample)

数据保证有解.

SampleInput

2

GGGG0 GGGG0 GGGG0

OGOO0 GGGG0 OGOO0

OOOO0 OGGG1 OOOO0

RRRR0 OOOO0 OOOO0

OOOO0 OOOO0 OOOO0

OOOO0 OOOO0 RRRR0

SampleOutput

Case #1: 5

Case #2: 2

 

Java源码:

import java.util.Scanner;
import java.util.Set;
import java.util.HashSet;
import java.util.Deque;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.HashMap;

/**
 * 2013/03/29 腾讯马拉松复试第2题。
 * 问题的解决通过对移动步长的递增穷举来完成。移动路径的查找则是利用了包含状态检测的DFS算法来实现。
 * @author Christopher
 *
 */
public class Main {
 public static void main(String[] args){
  Scanner in = new Scanner(System.in);
  int dataGroup = in.nextInt();//数据组数
  Square[] squareGroup = new Square[dataGroup];//存储每一组数据
  //读入数据并初始化
  for(int i = 0; i < dataGroup; i++){
   squareGroup[i] = new Square();
  }
  //计算并输出结果
  for(int i = 1; i <= dataGroup; i++){
   System.out.println("Case #" + i + ": " + squareGroup[i-1].moveToGoal());
  }
  
 }
 
 /**
  * 该类是对题目中由9个小正方形组成的大正方形的抽象表示。
  * @author Christopher
  *
  */
 static class Square{
  private String[][] square = new String[3][3];
  private String[][] squareCopy = new String[3][3];//square的副本
  private int[][] squareState = new int[3][3];/*记录原来的小正方形序号(0-9)现在在矩阵中的位置,
  以此来表示Square的状态*/
  private Set<Character> colorSet = new HashSet<Character>();//记录该Square含有的颜色
  private Set<Integer> nodeSet = new HashSet<Integer>();/*记录每一种颜色的某一个小三角形的序号,
  第(i,j)号小正方形的四个小三角形序号依次为4*(3*i+j)--4*(3*i+j)+3*/
  private Map<String, Integer> stateMap = new HashMap<String, Integer>();
  /* 记录每一个已经到达过的状态,以便广度遍历时使用。每一个状态由当前Square状态和到达当前状态所需的
   * 最小步数组成。*/
  private String[] cmd = {"R0", "R1", "R2", "L0", "L1", "L2",
    "U0", "U1", "U2", "D0", "D1", "D2"};
  private boolean[] canDo = {true, true, true, true, true, true,
     true, true, true, true, true, true};//记录相应的命令是否能够执行
  private int step = 0;
  private int len = 0;
  
  public Square(){
   Scanner in = new Scanner(System.in);
   for(int i = 0; i < square.length; i++){
    for(int j = 0; j < square[i].length; j++){
     square[i][j] = in.next();
     squareCopy[i][j] = square[i][j];
     squareState[i][j] = 3 * i + j;
     if(colorSet.size() < 4){
      for(int k = 0; k < 4; k++){//记录颜色的种类
       if(colorSet.add(square[i][j].charAt(k)))
        nodeSet.add(Integer.valueOf(4*(3*i+j)+k));
      }
     }
    }
   }
  }
  
  /**
   * 移动到目标状态,返回最小移动的步数。
   * @return
   */
  public int moveToGoal(){
   if(isConnected()) return 0;
   //计算canDo
   for(int i = 0; i < square.length; i++){
    for(int j = 0; j < square[i].length; j++){
     if(square[i][j].charAt(4) == '1'){
      for(int k = 0; k < cmd.length; k++){
       if(cmd[k].equals("R" + i) || cmd[k].equals("L" + i) ||
         cmd[k].equals("U" + j) || cmd[k].equals("D" + j)){
        canDo[k] = false;
       }
      }
     }
    }
   }

   //穷举法移动square
   for(len = 1; len < Integer.MAX_VALUE; len++){//对步数的长度进行穷举
    int result = recursiveMove();
    if(result != -1){
     return result;
    }
    else{
     reset();
     continue;
    }
   }
   return -1;
  }
  
  /**
   * 深度优先递归穷举,穷举的层数由len确定。
   * @return - 找到结果返回当前的步数(也就是当前的层数),否则返回-1。
   */
  private int recursiveMove(){
   for(int i = 0; i < cmd.length; i++){
    if(canDo[i]){
     move(cmd[i]);
     step++;
     String state = currentState();
     Integer value = stateMap.get(state);//获取与state对应的键值
     if(value != null){//确实已经包含该状态
      if(value > step){//需要更新对应的值
       stateMap.put(state, value);
      }
      else{//不需要更新,说明当前路径应舍弃
       backTrack(cmd[i]);
       step--;
       continue;
      }
     }
     else{//当前状态还没有遇到过
      stateMap.put(state, value);
     }
     
     if(step == len){
      if(isConnected()){
//       System.out.println("cmd = " + cmd[i]); //这句和下一个输出是为了倒序打印移动路径
       return step;
      }
      else{
       backTrack(cmd[i]);
       step--;
       continue;
      }
     }
     
     int res = recursiveMove();
     if(res != -1){
//      System.out.println("cmd = " + cmd[i]); //这句和上一个输出是为了倒序打印移动路径
      return res;
     }
     else{
      backTrack(cmd[i]);
      step--;
      continue;
     }
    }
    else continue; 
   }
   return -1;
   
  }
  
  /**
   * 判断该Square是否连通。
   * @return - 连通返回true,否则返回false。
   */
  private boolean isConnected(){
   int nodeSum = 0;
   //广度优先遍历计算每一种颜色所在的区域的结点个数之和
   Deque<Integer> dq = new ArrayDeque<Integer>(36);//3 * 3 * 4
   Set<Integer> nSet = new HashSet<Integer>(36);
   for(Integer node : nodeSet){
    dq.add(node);
    while(!dq.isEmpty()){
     Integer n = dq.pop();
     nSet.add(n);
     //计算对应小正方形的i,j和在小正方形中的序号k
     int i, j, k;
     k = n % 4;
     j = (n - k) / 4 % 3;
     i = ((n - k) / 4 - j) / 3;
     if(k == 0){
      if(i > 0 && square[i-1][j].charAt(1) == square[i][j].charAt(k)
        && !nSet.contains(n-11)){
       dq.add(n-11);
      }
      if(square[i][j].charAt(2) == square[i][j].charAt(k)
        && !nSet.contains(n+2))
       dq.add(n+2);
      if(square[i][j].charAt(3) == square[i][j].charAt(k)
        && !nSet.contains(n+3))
       dq.add(n+3);
     }
     else if(k == 1){
      if(i < 2 && square[i+1][j].charAt(0) == square[i][j].charAt(k)
        && !nSet.contains(n+11)){
       dq.add(n+11);
      }
      if(square[i][j].charAt(2) == square[i][j].charAt(k)
        && !nSet.contains(n+1))
       dq.add(n+1);
      if(square[i][j].charAt(3) == square[i][j].charAt(k)
        && !nSet.contains(n+2))
       dq.add(n+2);
     }
     else if(k == 2){
      if(j > 0 && square[i][j-1].charAt(3) == square[i][j].charAt(k)
        && !nSet.contains(n-3)){
       dq.add(n-3);
      }
      if(square[i][j].charAt(0) == square[i][j].charAt(k)
        && !nSet.contains(n-2))
       dq.add(n-2);
      if(square[i][j].charAt(1) == square[i][j].charAt(k)
        && !nSet.contains(n-1))
       dq.add(n-1);
     }
     else{// k == 3
      if(j < 2 && square[i][j+1].charAt(2) == square[i][j].charAt(k)
        && !nSet.contains(n+3)){
       dq.add(n+3);
      }
      if(square[i][j].charAt(0) == square[i][j].charAt(k)
        && !nSet.contains(n-3))
       dq.add(n-3);
      if(square[i][j].charAt(1) == square[i][j].charAt(k)
        && !nSet.contains(n-2))
       dq.add(n-2);
     }
    }//while
    nodeSum += nSet.size();
    nSet.clear();
   }
   return nodeSum == 36;
  }
  
  /**
   * 按照命令cmd执行移动。
   * cmd是由"方向"和"行(列)数"组成的长度为2的字符串。
   * @param cmd
   */
  private void move(String cmd){
   if(cmd.charAt(0) == 'R' || cmd.charAt(0) == 'L'){//执行移动的方向
    int row = Integer.parseInt(String.valueOf(cmd.charAt(1)));//执行移动的行(列)号
    String temp = square[row][0];
    int tempState = squareState[row][0];
    if(cmd.charAt(0) == 'R'){
     square[row][0] = square[row][2];
     square[row][2] = square[row][1];
     square[row][1] = temp;
     //状态调整
     squareState[row][0] = squareState[row][2];
     squareState[row][2] = squareState[row][1];
     squareState[row][1] = tempState;
    }
    else{// 'L'
     square[row][0] = square[row][1];
     square[row][1] = square[row][2];
     square[row][2] = temp;
     //状态调整
     squareState[row][0] = squareState[row][1];
     squareState[row][1] = squareState[row][2];
     squareState[row][2] = tempState;
    }
   }
   else{//'U' || 'D'
    int col = Integer.parseInt(String.valueOf(cmd.charAt(1)));
    String temp = square[0][col];
    int tempState = squareState[0][col];
    if(cmd.charAt(0) == 'U'){
     square[0][col] = square[1][col];
     square[1][col] = square[2][col];
     square[2][col] = temp;
     //状态调整
     squareState[0][col] = squareState[1][col];
     squareState[1][col] = squareState[2][col];
     squareState[2][col] = tempState;
    }
    else{// 'D'
     square[0][col] = square[2][col];
     square[2][col] = square[1][col];
     square[1][col] = temp;
     //状态调整
     squareState[0][col] = squareState[2][col];
     squareState[2][col] = squareState[1][col];
     squareState[1][col] = tempState;
    }
   }
  }
  
  /**
   * 按照上一步做cmd移动,移动回上一步。
   * @param cmd
   */
  private void backTrack(String cmd){
   if(cmd.charAt(0) == 'R') move("L" + cmd.charAt(1));
   else if(cmd.charAt(0) == 'L') move("R" + cmd.charAt(1));
   else if(cmd.charAt(0) == 'U') move("D" + cmd.charAt(1));
   else move("U" + cmd.charAt(1));
  }
  
  /**
   * 该Square复原。
   */
  private void reset(){
   stateMap.clear();
   step = 0;
   for(int i = 0; i < square.length; i++){
    for(int j = 0; j < square[i].length; j++){
     square[i][j] = squareCopy[i][j];
     squareState[i][j] = 3 * i + j;
    }
   }
  }
  
  /**
   * 返回当前Square的状态,这个状态用小正方形的序号的当前序列表示。
   * @return
   */
  private String currentState(){
   StringBuilder sb = new StringBuilder();
   for(int i = 0; i < squareState.length; i++){
    for(int j = 0; j < squareState[i].length; j++){
     sb.append(squareState[i][j]).append("|");
    }
   }
   return sb.toString();
  }

 }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值