我一直试图解决这个面试问题,要求对一个字符串进行随机播放,以免相邻的两个字母相同 例如,
ABCC - > ACBC
我想到的方法是
1)迭代输入字符串并存储(字母,频率) 在一些集合中配对
2)现在通过拉出我们没有提取的最高频率(即> 0)字母来构建结果字符串
3)每当我们拉信时更新(减少)频率
4)如果所有字母的频率为零,则返回结果字符串
5)如果我们只剩下一个频率大于1的字母,则返回错误
通过这种方法,我们可以为最后一个保存更珍贵(不太频繁)的字母。但为了实现这一点,我们需要一个集合,让我们可以有效地查询密钥,同时有效地按值对其进行排序。像this这样的东西可以工作,除非我们需要在每次检索字母后对集合进行排序。
我假设是Unicode字符。
关于使用什么样的集合的任何想法?还是另一种方法?
答案 0 :(得分:12)
您可以按频率对字母进行排序,将排序后的列表分成两半,然后依次从两半中取出字母来构造输出。这只需要一种。
示例:
ACABBACAB
AAABBBCC
AAAB
+ BBCC
ABABACBC
如果最高频率的字母数超过字符串长度的一半,则问题无法解决。
答案 1 :(得分:1)
为什么不使用两个数据结构:一个用于排序(如堆)和一个用于密钥检索,如字典?
答案 2 :(得分:1)
接受的答案可能会产生正确的结果,但可能不是这个采访脑筋急转弯的“正确”答案,也不是最有效的算法。
简单的答案是采用基本排序算法的前提,并改变循环谓词以检查邻接而不是大小。这确保了“排序”操作是唯一需要的步骤,并且(像所有良好的排序算法一样)可以完成最少量的工作。
下面是一个类似于插入排序的c#示例,为了简单起见(尽管许多排序算法可以进行类似的调整):
string NonAdjacencySort(string stringInput)
{
var input = stringInput.ToCharArray();
for(var i = 0; i < input.Length; i++)
{
var j = i;
while(j > 0 && j < input.Length - 1 &&
(input[j+1] == input[j] || input[j-1] == input[j]))
{
var tmp = input[j];
input[j] = input[j-1];
input[j-1] = tmp;
j--;
}
if(input[1] == input[0])
{
var tmp = input[0];
input[0] = input[input.Length-1];
input[input.Length-1] = tmp;
}
}
return new string(input);
}
标准插入排序的主要变化是该函数必须向前看和向后看,因此需要回绕到最后一个索引。
最后一点是这种类型的算法优雅地失败,提供的结果是连续字符最少(在前面分组)。
答案 3 :(得分:1)
由于我以某种方式被说服了,可以将副手注释扩展为完整算法,因此我将其写为答案,它必须比一系列不可编辑的注释更具可读性。
实际上,该算法非常简单。基于以下观察,如果我们对字符串进行排序,然后将其分为两个等长的一半,如果字符串的长度为奇数,则加上中间字符,那么除非没有任何区别,否则这两个半部分中的对应位置必须彼此不同解。这很容易看到:如果两个字符相同,那么它们之间的所有字符也一样,总共⌈n/2⌉+1
个字符。但是,只有任何单个字符的实例不超过⌈n/2⌉
个实例,解决方案才有可能。
所以我们可以进行如下操作:
下面的示例代码是C ++,因为我没有方便测试的C#环境。它还以两种方式进行了简化,这两种方式都很容易解决,但要以使算法晦涩为代价:
如果在交织中的某个时刻,算法遇到一对相同的字符,则应停止并报告失败。但是,在下面的示例实现中,它的界面过于简单,无法报告失败。如果没有解决方案,则下面的函数将返回错误的解决方案。
OP建议该算法应使用Unicode字符,但是正确处理多字节编码的复杂性似乎并未添加任何有用的解释算法。所以我只用了单字节字符。 (在C#和C ++的某些实现中,没有足够宽的字符类型来容纳Unicode代码点,因此,星体平面字符必须用代理对表示。)
#include <algorithm>
#include <iostream>
#include <string>
// If possible, rearranges 'in' so that there are no two consecutive
// instances of the same character.
std::string rearrange(std::string in) {
// Sort the input. The function is call-by-value,
// so the argument itself isn't changed.
std::string out;
size_t len = in.size();
if (in.size()) {
out.reserve(len);
std::sort(in.begin(), in.end());
size_t mid = len / 2;
size_t tail = len - mid;
char prev = in[mid];
// For odd-length strings, start with the middle character.
if (len & 1) out.push_back(prev);
for (size_t head = 0; head < mid; ++head, ++tail)
// See explanatory text
if (in[tail] != prev) {
out.push_back(in[tail]);
out.push_back(prev = in[head]);
}
else {
out.push_back(in[head]);
out.push_back(prev = in[tail]);
}
}
}
return out;
}
答案 4 :(得分:1)
您可以通过使用优先级队列来做到这一点。 请找到以下说明。 https://iq.opengenus.org/rearrange-string-no-same-adjacent-characters/
答案 5 :(得分:0)
这是一种概率方法。算法为:
10)从输入字符串中选择一个随机字符。
20)尝试将所选的char插入输出字符串中的任意位置。
30)如果由于与同一字符相邻而无法插入,请转至10。
40)从输入字符串中删除所选的字符,然后转到10。
50)继续直到输入字符串中没有更多的字符,或者失败的尝试太多。
public static string ShuffleNoSameAdjacent(string input, Random random = null)
{
if (input == null) return null;
if (random == null) random = new Random();
string output = "";
int maxAttempts = input.Length * input.Length * 2;
int attempts = 0;
while (input.Length > 0)
{
while (attempts < maxAttempts)
{
int inputPos = random.Next(0, input.Length);
var outputPos = random.Next(0, output.Length + 1);
var c = input[inputPos];
if (outputPos > 0 && output[outputPos - 1] == c)
{
attempts++; continue;
}
if (outputPos < output.Length && output[outputPos] == c)
{
attempts++; continue;
}
input = input.Remove(inputPos, 1);
output = output.Insert(outputPos, c.ToString());
break;
}
if (attempts >= maxAttempts) throw new InvalidOperationException(
$"Shuffle failed to complete after {attempts} attempts.");
}
return output;
}
不适用于长度超过1000个字符的字符串!
更新:这是一种更为复杂的确定性方法。算法为:
public static IEnumerable<T> ShuffleNoSameAdjacent<T>(IEnumerable<T> source,
Random random = null, IEqualityComparer<T> comparer = null)
{
if (source == null) yield break;
if (random == null) random = new Random();
if (comparer == null) comparer = EqualityComparer<T>.Default;
var grouped = source
.GroupBy(i => i, comparer)
.OrderByDescending(g => g.Count());
var piles = Enumerable.Range(0, 3).Select(i => new Pile<T>()).ToArray();
foreach (var group in grouped)
{
GetSmallestPile().AddRange(group);
}
int totalCount = piles.Select(e => e.Count).Sum();
if (piles.Any(pile => pile.Count > (totalCount + 1) / 2))
{
throw new InvalidOperationException("Shuffle is impossible.");
}
piles.ForEach(pile => Shuffle(pile));
Pile<T> previouslySelectedPile = null;
while (totalCount > 0)
{
var selectedPile = GetRandomPile_WeightedByLength();
yield return selectedPile[selectedPile.Count - 1];
selectedPile.RemoveAt(selectedPile.Count - 1);
totalCount--;
previouslySelectedPile = selectedPile;
}
List<T> GetSmallestPile()
{
List<T> smallestPile = null;
int smallestCount = Int32.MaxValue;
foreach (var pile in piles)
{
if (pile.Count < smallestCount)
{
smallestPile = pile;
smallestCount = pile.Count;
}
}
return smallestPile;
}
void Shuffle(List<T> pile)
{
for (int i = 0; i < pile.Count; i++)
{
int j = random.Next(i, pile.Count);
if (i == j) continue;
var temp = pile[i];
pile[i] = pile[j];
pile[j] = temp;
}
}
Pile<T> GetRandomPile_WeightedByLength()
{
var eligiblePiles = piles
.Where(pile => pile.Count > 0 && pile != previouslySelectedPile)
.ToArray();
Debug.Assert(eligiblePiles.Length > 0, "No eligible pile.");
eligiblePiles.ForEach(pile =>
{
pile.Proximity = ((totalCount + 1) / 2) - pile.Count;
pile.Score = 1;
});
Debug.Assert(eligiblePiles.All(pile => pile.Proximity >= 0),
"A pile has negative proximity.");
foreach (var pile in eligiblePiles)
{
foreach (var otherPile in eligiblePiles)
{
if (otherPile == pile) continue;
pile.Score *= otherPile.Proximity;
}
}
var sumScore = eligiblePiles.Select(p => p.Score).Sum();
while (sumScore > Int32.MaxValue)
{
eligiblePiles.ForEach(pile => pile.Score /= 100);
sumScore = eligiblePiles.Select(p => p.Score).Sum();
}
if (sumScore == 0)
{
return eligiblePiles[random.Next(0, eligiblePiles.Length)];
}
var randomScore = random.Next(0, (int)sumScore);
int accumulatedScore = 0;
foreach (var pile in eligiblePiles)
{
accumulatedScore += (int)pile.Score;
if (randomScore < accumulatedScore) return pile;
}
Debug.Fail("Could not select a pile randomly by weight.");
return null;
}
}
private class Pile<T> : List<T>
{
public int Proximity { get; set; }
public long Score { get; set; }
}
此实现可以拖延数百万个元素。我并不完全相信,混响的质量和以前的概率实现一样完美,但是应该接近。