混洗一个字符串,使两个相邻的字母不相同

时间:2016-08-26 17:34:31

标签: c# string algorithm data-structures

我一直试图解决这个面试问题,要求对一个字符串进行随机播放,以免相邻的两个字母相同 例如,

ABCC - > ACBC

我想到的方法是

  

1)迭代输入字符串并存储(字母,频率)   在一些集合中配对

     

2)现在通过拉出我们没有提取的最高频率(即> 0)字母来构建结果字符串

     

3)每当我们拉信时更新(减少)频率

     

4)如果所有字母的频率为零,则返回结果字符串

     

5)如果我们只剩下一个频率大于1的字母,则返回错误

通过这种方法,我们可以为最后一个保存更珍贵(不太频繁)的字母。但为了实现这一点,我们需要一个集合,让我们可以有效地查询密钥,同时有效地按值对其进行排序。像this这样的东西可以工作,除非我们需要在每次检索字母后对集合进行排序。

我假设是Unicode字符。

关于使用什么样的集合的任何想法?还是另一种方法?

6 个答案:

答案 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⌉个实例,解决方案才有可能。

所以我们可以进行如下操作:

  1. 对字符串进行排序。
  2. 如果字符串的长度为奇数,则输出中间字符。
  3. 将字符串(如果长度为奇数,则减去中间字符)分成两个等长的两半,并交织这两个半。
  4. 在交织的每个点上,由于这对字符彼此不同(请参见上文),因此它们中的至少一个必须与最后一个字符输出不同。因此,我们首先输出该字符,然后输出另一半的相应字符。

下面的示例代码是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个字符的字符串!


更新:这是一种更为复杂的确定性方法。算法为:

  1. 对元素进行分组并按长度对分组进行排序。
  2. 创建三个空堆的元素。
  3. 将每个组插入一个单独的桩中,始终将最大的组插入到最小的桩中,以使桩的长度差异尽可能小。
  4. 检查是否没有堆的元素总数超过一半,在这种情况下,不可能满足相邻元素不相同的条件。
  5. 随机播放堆。
  6. 开始从桩中屈服元素,每次选择一个不同的桩。
  7. 当符合选择条件的桩多于一个时,请随机选择,并按每个桩的大小进行加权。包含近一半剩余元素的桩应该是更可取的。例如,如果剩余元素为100,并且两个合格的堆分别具有49和40个元素,则第一堆应比第二个堆高10倍(因为50-49 = 1和50-40 = 10)。
    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; }
    }

此实现可以拖延数百万个元素。我并不完全相信,混响的质量和以前的概率实现一样完美,但是应该接近。