问题描述:
你正在参与祖玛游戏的一个变种。
在这个祖玛游戏变体中,桌面上有 一排 彩球,每个球的颜色可能是:红色 ‘R’、黄色 ‘Y’、蓝色 ‘B’、绿色 ‘G’ 或白色 ‘W’ 。你的手中也有一些彩球。
你的目标是 清空 桌面上所有的球。每一回合:
从你手上的彩球中选出 任意一颗 ,然后将其插入桌面上那一排球中:两球之间或这一排球的任一端。
接着,如果有出现 三个或者三个以上 且 颜色相同 的球相连的话,就把它们移除掉。
如果这种移除操作同样导致出现三个或者三个以上且颜色相同的球相连,则可以继续移除这些球,直到不再满足移除条件。
如果桌面上所有球都被移除,则认为你赢得本场游戏。
重复这个过程,直到你赢了游戏或者手中没有更多的球。
给你一个字符串 board ,表示桌面上最开始的那排球。另给你一个字符串 hand ,表示手里的彩球。请你按上述操作步骤移除掉桌上所有球,计算并返回所需的 最少 球数。如果不能移除桌上所有的球,返回 -1 。
问题分析:
例1:“RRWWRRBBRR” “WB” >> “RRRRBBRR” “B” >> “RBBRR” “B” >>“RRR” “” >> “” “” 预期结果:2;
例2:“RRYGGYYRRYGGYYRR” “GG” >> “RRYYYRRYGGYYRR” “G” >> “RRRRYGGYYRR” “G” >> “RYGGYYRR” “G” >> “RYYYRR” “” >> “RRR” “” >> “” “” 预期结果:-1?按照例1的思路不是2就行了吗???
例3:“RRYGGYYRRYGGYYRR” “GGBBB” >> “RRYYYRRYGGYYRR” “GBBB” >> “RRRRYGGYYRR” “GBBB” >> “RYGGYYRR” “GBBB” >> “RYYYRR” “” >> “RRR” “BBB” >> “” “BBB” 预期结果: 5???
例4:“RRWWRRBBRR” “WBGGG” >> “RRRRBBRR” “BGGG” >> “RBBRR” “BGGG” >>“RRR” “GGG” >> “” “GGG” 预期结果:2;
通过例2和例3得到,按照作者的意思非要先在中间两个R之间插入一个B,才行,那么这样的话,例4也应该先在中间两个R之间插入一个G,所以它的结果应该是5,例1的结果就应该是-1咯?但是它们的预期结果都是2呀!!!
问题求解:
public class Solution {
public int FindMinStep(string board, string hand)
{
if (string.IsNullOrEmpty(board)) return 0;
Dictionary<string, int> dic = new Dictionary<string, int>();
Queue<string> qu = new Queue<string>();
string s0 = string.Format("{0}-{1}", board, string.Join("", hand.ToCharArray().OrderBy(t => t)));
dic[s0] = 0;
qu.Enqueue(s0);
while (qu.Any())
{
string qs = qu.Dequeue();
int step = dic[qs] + 1;
var split = qs.Split('-');
var barr = split[0].ToCharArray();
var harr = split[1].ToCharArray();
var hc = ' ';
for (int hi = 0; hi < harr.Length; ++hi)
if (harr[hi] != hc)
{
hc = harr[hi];
for (int i = 0; i <= barr.Length; ++i)
{
if (i < barr.Length && hc == barr[i]) continue; // 相同字符默认插到最后面
string s = Place(barr, i, harr, hi);
if (s == "") return step;
if (s != null && !dic.ContainsKey(s)) // s == null表示无解
{
dic[s] = step;
qu.Enqueue(s);
}
}
}
}
return -1;
}
// 在barr的位置bi插入harr的hi的字符
string Place(char[] barr, int bi, char[] harr, int hi)
{
int start = bi;
char hc = harr[hi];
int map = (1 << barr.Length) - 1, fullmap = map;
// 判断是否有发生消除
int combo = 2;
while (combo > 0 && start > 0 && barr[start - 1] == hc)
{
start--;
if (start > 0 && barr[start - 1] == barr[start])
start--;
for (int ci = start + 1; ci < barr.Length; ++ci)
if ((map & (1 << ci)) != 0)
if (barr[ci] == barr[start]) combo++; else break;
if (combo >= 3)
{
for (int ri = start; ri < barr.Length; ++ri)
if ((map & (1 << ri)) != 0)
if (barr[ri] == barr[start])
{
map ^= 1 << ri;
hc = ri + 1 < barr.Length ? barr[ri + 1] : ' ';
}
else break;
combo = 1;
}
else combo = 0;
}
if (map == fullmap)
{
// 没有发生消除的情况
if (harr.Length == 1) return null;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bi; ++i)
sb.Append(barr[i]);
sb.Append(harr[hi]);
for (int i = bi; i < barr.Length; ++i)
sb.Append(barr[i]);
sb.Append('-');
for (int i = 0; i < harr.Length; ++i)
if (i != hi)
sb.Append(harr[i]);
return sb.ToString();
}
else
{
// 有发生消除的情况
if (map == 0) return ""; // 找到最终结果
else if (harr.Length == 1) return null;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < barr.Length; ++i)
if ((map & (1 << i)) != 0)
sb.Append(barr[i]);
sb.Append('-');
for (int i = 0; i < harr.Length; ++i)
if (i != hi)
sb.Append(harr[i]);
return sb.ToString();
}
}
}
问题总结:
老规矩先看一下官方求解思路!
DFS枚举
考虑字符串 handhand 中每一个字符可能放置的位置,枚举 handhand 中每一个字符出现在字符串 boardboard 的位置,添加后要移出字符串 boardboard 中出现 三个或者三个以上 且 颜色相同 的球,同时对已经搜过的状态进行记忆化,也就是存起来。
代码实现上,分为两部分:
如何删除字符串 boardboard 中出现 三个或者三个以上 且 颜色相同 的球:这是一个经典的双指针问题,枚举两个端点i,ji,j,一开始i=ji=j,找到board[i]!=board[j]board[i]!=board[j] 的第一个位置,即区间[i, j-1][i,j−1] 就是 boardboard 连续且相同的字符区间,如果区间长度大于33,就将其删除。
如何枚举字符串 handhand 中每一个字符可能放置在字符串 boardboard 的位置,第一层for枚举字符串handhand 的字符,第二次for枚举字符串boardboard的位置,将字符串handhand 的字符添加到符串boardboard对应的位置中。
几个比较难的样例,大家找不到bug,可以试试。
作者:bianchengxiong
链接:https://leetcode-cn.com/problems/zuma-game/solution/acmjin-pai-ti-jie-dfsmei-ju-bian-cheng-x-30qj/
来源:力扣(LeetCode)