力扣原题,java代码
解法一:广度优先
暴力求解思想:枚举所有的情况,依次取出手中的球,将之插入到桌面不同位置。故而每次迭代中,我们只需要记录两个变量即可,即当前桌面球的信息以及手中球的信息。
初始化队列信息:
Queue<String[]> q = new LinkedList<>(); q.add(new String[]{board,hand});
当桌面上的球全部被移除时,游戏结束返回使用球的数量;
当手中的球全部使用完,但桌面上的球仍旧未被移除完,此种情况不可行。
如果穷举所有情况,代码一定会超时,故而可以采取一些减枝策略:
1.对于手中的球而言,每次插入时,相同颜色的球只需考虑一次
2.插入球时只需考虑两种情况
①若插入的球与后一位颜色相同,则可以将球插入,之后与该球颜色相同的连续位置不再考虑球的插入(例如当board = "RBBWWBB"时,将颜色为B的球插入到board时,将球插入到索引位置1, 2, 3的情况是一样的,所以我们只需靠将球插入到1的情况即可,另外2种可以跳过,节省运行时间)
②若插入的球与后一位颜色不一致,那么桌面上球插入的后一位和前一位球一定要一致或者球插入的位置就是桌面球位置的最前端(例如当board = "RRWWRRBBRR", hand = "WB"时,先将B颜色的球插入到第一组"RR"之间使得board = "RBRWWRRBBRR",再将W小球插入到"WW"前面,这样就可以使得board上全部的小球被移除。将B防止在"RR"之间阻隔了两组"RR"相遇,造成四个R被移除,而剩余的两个R无法被移除的情况)
为了更进一步的减少计算时间,我们还可以在程序中加入记忆搜索和A*算法。
记忆搜索:对于每种情况,桌面上球的信息需要全部考虑,但手中球的信息我们只需要考虑不同颜色球的个数即可。故而每种情况我们用一个字符串来记录,将当前已知的所有情况存储在HashMap中,并记录当前情况花费球的个数,若后期探索过程中遇到同样的情况,则只需保留花费球数量最少的情况即可。
A*算法:队列中每种情况除了记录桌面球的信息和手中球不同颜色球的数量外,还需要记录当前花费球的数量cur。引入A*算法后,需要再添加一个信息,即将当前board中所有的小球移除,预计花费的代价eva。使用优先队列存储每种情况的信息,优选探索cur+eva值最小的情况,当board中所有球被移除时,此时的cur即是我们要求的最少球数量,因为队列中其他情况的cur+eva皆大于当前情况的cur,这说明,其他情况最少需要花费球的数量都大于当前情况花费球的数量,故而当前情况花费球的数量就是最小的!(备注:当手中的球一定无法将当前桌面的球全部移除时,这种情况不再探索)
class Solution {
class Node {
String b;
int[] colorNumber;
int cur; // 当前代价
int eva; // 预期代价
Node(String b, int[] colorNumber, int cur, int eva) {
this.b = b;
this.colorNumber = colorNumber.clone();
this.cur = cur;
this.eva = eva;
}
}
// 消除当前Board上所有彩球,最少需要的代价
public int evaluate(String b, int[] colorNumber) {
int[] bx = new int[5];
for (int i = 0; i < b.length(); i++) {
bx[cn.get(b.charAt(i))]++;
}
int count=0;
for (int i = 0; i < 5; i++) {
if(bx[i]>0 && bx[i]<3) {
if(colorNumber[i] + bx[i] < 3) {
return -1; // 不可行
} else {
count += 3-bx[i];
}
}
}
return count;
}
HashMap<Character,Integer> cn; // RYBGW 01234
public int findMinStep(String board, String hand) {
// 升序
PriorityQueue<Node> q = new PriorityQueue<>((a, b)->(a.cur+a.eva)-(b.cur+b.eva));
int n = hand.length(); // 球的总数
int[] colorNumber = new int[5]; // 五种颜色的球的数量
char[] color = new char[]{'R','Y','B','G','W'};
cn = new HashMap<>();
for (int i = 0; i < 5; i++) {
cn.put(color[i],i);
}
for (int i = 0; i < hand.length(); i++) {
colorNumber[cn.get(hand.charAt(i))]++;
}
q.add(new Node(board, colorNumber,0,evaluate(board,colorNumber)));
HashMap<String,Integer> map = new HashMap<>(); // 已有的状态
map.put(merge(board,colorNumber),0);
while (!q.isEmpty()) {
Node node = q.poll();
char[] b = node.b.toCharArray();
int len = b.length; // 桌面上球剩余数量
int cur = node.cur+1;
int[] h = node.colorNumber;
for (int i = 0; i < 5; i++) {
char c = color[i];
if(h[i] <= 0) {
continue;
}
int[] hnew = h.clone(); hnew[i]--;
int k=0;
while (k<len) {
if(c != b[k] && (k==0 || b[k-1] != b[k])) {
k++;
continue;
}
String bnew = insert(node.b,c,k);
int eva = evaluate(bnew,hnew);
if(bnew.equals("")) {
return cur;
}
if(eva >= 0) {
String state=merge(bnew,hnew);
if(!map.containsKey(state) || map.get(state) > cur) {
q.add(new Node(bnew,hnew,cur,eva));
map.put(state,cur);
}
}
while (k<len && c == b[k++]);
}
}
}
return -1;
}
public String merge(String a, int[] colorNumber) {
String state = a;
for (int i = 0; i < 5; i++) {
state += colorNumber[i];
}
return state;
}
// 将球c插入位置i
public String insert(String board, char c, int k) {
int n = board.length();
board = board.substring(0,k)+c+board.substring(k,n);
if(board.length() > 2) {
board = eliminate(board,k,k);
}
return board;
}
// 消除
public String eliminate(String board, int i, int j) {
int n = board.length();
char c = board.charAt(i);
while (i-1>=0 && c == board.charAt(i-1)) {
i--;
}
while (j+1<n && c == board.charAt(j+1)) {
j++;
}
if(j-i>1) {
board = board.substring(0,i)+board.substring(j+1,n);
if(board.length() > 2) {
i = i>0?i:1;
board = eliminate(board,i-1,i-1);
}
}
return board;
}
}