1. 问题描述:
X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色。X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去。如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙。*WWWBBB,其中,W字母表示白色青蛙,B表示黑色青蛙,*表示空杯子。
X星的青蛙很有些癖好,它们只做3个动作之一:
1. 跳到相邻的空杯子里。
2. 隔着1只其它的青蛙(随便什么颜色)跳到空杯子里。
3. 隔着2只其它的青蛙(随便什么颜色)跳到空杯子里。
对于上图的局面,只要1步,就可跳成下图局面:
WWW*BBB
本题的任务就是已知初始局面,询问至少需要几步,才能跳成另一个目标局面。
输入为2行,2个串,表示初始局面和目标局面。
输出要求为一个整数,表示至少需要多少步的青蛙跳。
样例输入
*WWBB
WWBB*
样例输出
2
样例输入
WWW*BBB
BBB*WWW
样例输出
10
2. 思路分析:
① 题目不难理解,为青蛙经过杯子中若干次的跳动从初试状态到达目标状态的过程,一开始是想到使用递归,因为递归就可以尝试所有的可能性,青蛙跳杯子的过程就可以看做是交换数组中元素的过程,将空杯子与其他位置的青蛙进行交换即可,使用递归的话那么可以知道存在三种情况:
1)空杯子可以与左右相邻的杯子调换位置
2)空杯子可以与左右相邻的第二个杯子调换位置
3)空杯子可以与左右相邻的第三个杯子调换位置
对于当前的空杯子的状态那么都会存在六种可能的选择,那么这就对应着六种平行状态,可以使用Set来记录其中之前到达过的状态防止在递归的时候重复对之前走过的状态进行递归,在递归调用完进行回溯,也就是从set中删除掉之前加入的字符串这样才可以进行下一个可能状态的尝试,使用一个全局变量来记录最小值,当到达目标状态的时候那么决定是否更新最小值
② 六个可能的平行状态可以使用一个for循环来模拟,在循环中判断是否可以到达下一个状态,递归的时候活尝试所有的可能,由于存在六个分支所以递归的时候非常慢,提交代码上去超时了,但是可以通过简单的例子知道下面的代码逻辑是正确的,正确的做法是使用宽度优先搜索bfs,因为从分析可以知道求解的是从原始状态到目标状态的最短路径,这个与蓝桥杯分酒的题目是很类似的,bfs最擅长的就是求解原始状态到目标状态的最短路径,而且与dfs一样的是需要使用Set来记录其中的中间状态,对于每个状态都是从上一个状态经过变换得到的,所以步数等于上一个状态的步数加一,所以可以求解出原始状态到目标状态的最短路径
3. 代码如下:
bfs代码:
import java.util.*;
public class Main {
public static class Node{
/*两个属性一个是当前的杯子的状态另外一个是到达当前状态需要的步数*/
private String status;
private int step;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
public Node(String status) {
this.status = status;
}
public Node(String status, int step) {
this.status = status;
this.step = step;
}
}
static Set<String> rec = new HashSet<>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String src = sc.next();
String target = sc.next();
rec.add(src);
bfs(src, target);
}
private static void bfs(String src, String target) {
Queue<Node> queue = new LinkedList<>();
int len = src.length();
/*把一开始的状态加入到队列中*/
queue.add(new Node(src, 0));
while (!queue.isEmpty()){
Node poll = queue.poll();
if (poll.status.equals(target)){
System.out.println(poll.step);
}
char arr[] = poll.status.toCharArray();
int index = -1;
for (int i = 0; i < len; ++i){
if (arr[i] == '*'){
index = i;
break;
}
}
/*尝试可能跳的六个方向*/
for (int i = -3; i <= 3; ++i){
if (index + i >= 0 && index + i < len && i != 0){
swap(arr, index, index + i);
String s = new String(arr);
if (!rec.contains(s)){
/*使用宽搜不用移除掉之前的状态所以可以直接加入因为求解的是最短路径*/
rec.add(s);
queue.add(new Node(s, poll.step + 1));
}
/*恢复之前的状态尝试其他的可能性*/
swap(arr, index, index + i);
}
}
}
}
private static void swap(char src[], int i, int j) {
char c = src[i];
src[i] = src[j];
src[j] = c;
}
}
dfs代码:
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class Main{
static Set<String> rec = new HashSet<>();
/*用来记录到达目标状态的最小值*/
static int min = Integer.MAX_VALUE;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String src = sc.next();
String target = sc.next();
rec.add(src);
//System.out.println(src + " " + target);
dfs(src.toCharArray(), target,0);
System.out.println(min);
}
/*dfs对应着六种平行状态*/
private static void dfs(char src[], String target, int count) {
/*递归出口*/
String s = new String(src);
if (s.equals(target)){
if (min > count) {
min = count;
}
return;
}
int index = -1;
/*先要找出当前的状态的杯子在什么位置*/
for (int i = 0; i < src.length; ++i) {
if (src[i] == '*'){
index = i;
//System.out.println(index);
break;
}
}
/*进行六种操作: 这里可以使用循环来进行操作*/
for (int pos = -3; pos <= 3; ++pos){
/*注意在判断的时候pos不等于0是因为整个是没有任何操作的*/
if (index + pos >= 0 && (index + pos < src.length) && pos != 0){
swap(src, index, index + pos);
/*判断之前是否到达过这样的状态*/
String t = new String(src);
if (!rec.contains(t)){
//System.out.println(t + " " + count);
rec.add(t);
//System.out.println(count);
dfs(src, target, count + 1);
/*之前错误的原因是由于在移除元素的时候出现了问题导致
* 移除的不是下标而是字符串这样才是正确的
* */
rec.remove(t);
}
/*还原为之前的状态*/
swap(src, index, index + pos);
}
}
}
private static void swap(char[] src, int i, int j) {
char c = src[i];
src[i] = src[j];
src[j] = c;
}
}