如何找到最长的子阵列,其元素(数字)可以重新排列,以形成c#中的回文?

时间:2017-12-29 08:03:03

标签: c#

这是我的代码

using System;

public class Program
{

     private static string GetLongestPalindrome(string input)
    {
        int rightIndex = 0, leftIndex = 0;
        var x = "";
        string currentPalindrome = string.Empty;
        string longestPalindrome = string.Empty;
        for(int currentIndex = 1; currentIndex < input.Length - 1; currentIndex++)
        {
            leftIndex = currentIndex - 1;
            rightIndex = currentIndex + 1;
            while(leftIndex >= 0 && rightIndex < input.Length)
            {
                if(input[leftIndex] != input[rightIndex])
                {
                    break;
                }
                currentPalindrome = input.Substring(leftIndex, rightIndex - leftIndex + 1);
                if(currentPalindrome.Length > x.Length)
                    x = currentPalindrome;
                leftIndex--;
                rightIndex++;
            }
        }
        return x;
    }

    public static void Main()
    {
        Console.WriteLine(GetLongestPalindrome("12345354987"));
    }
}

输出:

input  = 12345354987,output = 345543.

这里最长的子阵列是345354,它可以重新排列形成345543,这是一个回文。 在上面的代码中,我获得了回文号45354.但是在使用C#重新排列345354之后,上面的输入包含最大的回文数345543

3 个答案:

答案 0 :(得分:3)

宝贝步骤......

你的方法做得太多,把它分解成你可以处理的小问题。我们需要解决什么?

  1. 从给定字符串中提取所有可能的子字符串。
  2. 验证给定的字符串是否可以变为单个字符的回文移位位置。
  3. 好的,让我们这样做:

    获取所有子字符串相对容易:

    private static IEnumerable<string> GetAllSubSequences(
        this string s)
        => from start in Enumerable.Range(0, s.Length)
           from length in Enumerable.Range(1, s.Length - start)
           select s.Substring(start, length);
    

    或者如果您更喜欢流利的语法:

    private static IEnumerable<string> GetAllSubSequences(string s)
        => Enumerable.Range(0, s.Length)
                     .SelectMany(start => Enumerable.Range(1, s.Length - start),
                                 (start, length) => s.Substring(start, length));
    

    现在我们需要一个方法来告诉我们一个给定的字符串是否可以转移到回文结构中。嗯......我们怎么能轻易做到这一点?我们可以检查所有可能的转变,但这看起来非常混乱和浪费。

    Palindromes有两种口味;甚至长度的回文123321和不均匀的长度123444321。你看到我们可以利用这两种模式吗?看起来在前一种情况下,每个角色的总数必须是均匀的。在后者中,条件几乎相同,但也必须有一个且只有一个字符总数不均匀。

    好的,让我们实现这个:

    private static bool IsRearrangableIntoPalindrome(
        this IEnumerable<char> characters) 
        =>  characters.Count() % 2 == 0 ?
            //all even
            characters.GroupBy(c => c)
                      .All(g => g.Count() % 2 == 0):
            //one odd
            characters.GroupBy(c => c)
                      .Count(g => g.Count() % 2 != 0) == 1; 
    

    现在,我们只是将所有内容放在一起:

    var str = "12345354987";
    var largestPotentialPalindrome =
        str.GetAllSubSequences()
           .Where(s => s.IsRearrangableIntoPalindrome())
           .OrderBy(s => s.Length)
           .LastOrDefault();
    

    果然,答案是3453549,这是可以转移到回文中的最大子字符串:3459543(等等)。

答案 1 :(得分:1)

我迟到了,因为已经为这个问题提供了一个很好的答案,但是自从我昨天开始研究这个问题并且刚刚结束,我想与社区分享我的方法。

我决定在不使用LINQ并尽可能将代码生成为“传统”的情况下解决此问题。第一个问题是找到一种方法来检测potential palindrome。正如@InBetween和我搜索的其他线程中的许多其他StackExchange用户所说,最简单的方法是:

  • 偶数字符串:所有字符的出现都必须是
  • 奇数字符串:除了一个
  • 之外,所有字符的出现都必须是偶数

这是我的实施:

private static Boolean IsPotentialPalindrome(String input)
{
    Dictionary<Char,Int32> occurrences = new Dictionary<Char,Int32>();
    Int32 inputLength = input.Length;

    for (Int32 i = 0; i < inputLength; ++i)
    {
        Char c = input[i];

        if (occurrences.ContainsKey(c))
            ++occurrences[c];
        else
            occurrences.Add(c, 1);
    }

    if ((inputLength % 2) == 0)
    {
        foreach (Int32 occurrence in occurrences.Values)
        {
            if ((occurrence % 2) != 0)
                return false;
        }
    }
    else
    {
        Boolean oddSpotted = false;

        foreach (Int32 occurrence in occurrences.Values)
        {
            if ((occurrence % 2) != 0)
            {
                if (oddSpotted)
                    return false;
                else
                    oddSpotted = true;
            }
        }

        if (!oddSpotted)
            return false;
    }

    return true;
}

一旦最困难的部分完成,我创建了主要方法来检测最长的潜在回文(包括创建给定输入的所有可能的substrings)。由于可以检测到一个或多个具有最大长度的潜在回文,我选择返回List。这是它:

private static List<String> GetLongestPotentialPalindromes(String input)  
{
    input = input.Trim().ToLowerInvariant();

    List<String> potentials = new List<String>();

    if (input.Length < 2)
        return potentials;

    for (Int32 i = 0; i < input.Length; ++i)  
    {  
        String potential = String.Empty;

        for (Int32 j = i; j < input.Length; ++j)  
        {  
            potential += input[j];

            if ((potential.Length > 1) && !potentials.Contains(potential) && IsPotentialPalindrome(potential))
                potentials.Add(potential);
        }  
    }

    if (potentials.Count < 2)
        return potentials;

    Int32 maximumLength = 0;

    foreach (String potential in potentials)
    {
        Int32 potentialLength = potential.Length;

        if (potentialLength > maximumLength)
            maximumLength = potentialLength;
    }

    List<String> finalPotentials = new List<String>();

    foreach (String potential in potentials)
    {
        if (potential.Length == maximumLength)
            finalPotentials.Add(potential);
    }

    return finalPotentials;
}

我的实施工作演示可以找到here。使用您的输入,它发现两个最长的潜在回文是:

2345354
3453549

答案 2 :(得分:0)

让我们提高效率。一些观察

  • 你可以从字符串中制作回文,如果最多只有一个字符重复奇数次
  • 因此,您只需要字符的二进制直方图 - 如果它重复偶数或奇数次。您可以使用HashSet。当前项目将意味着它重复奇数次。
  • 将字符添加到直方图相当于从中删除,奇数更改为偶数,反之亦然。
  • HashSet直方图有一个很大的优势 - 你可以在恒定时间内测试你有多少奇数项目。
  • 重叠子串的直方图是相似的。您可以添加/删除子字符串中不同的字符,以从另一个中获取一个直方图。您可以使用增量更改来避免一遍又一遍地构建所有内容。
  • 您可以从最长的子串开始测试。当你找到一个可以回文的时候,你不必测试较短的子串,因为你只寻找最长的子串。
  • 不要在没有必要的地方分配大量的物品
  • 如果您的字符串长度为len,其中5个(不同)字符出现奇数次,那么您不必测试其子字符串的时间长于len - 4。仅删除3个字符将留下至少2个奇数字符。本声明可以概括。

代码在这里,根据输入,它可以比基本的暴力方法快几个数量级。

private static IEnumerable<string> GetLongestPotentialPalindromes2(string input)
{
    HashSet<char> odds = new HashSet<char>();
    void AddToSet(char c)
    {
        if (!odds.Add(c)) { odds.Remove(c); }
    }

    for (int len = input.Length; len > 0;)
    {
        odds.Clear();
        for (int i = 0; i < len - 1; ++i)
        {
            AddToSet(input[i]);
        }
        int minOddCount = int.MaxValue;
        for (int start = 0; start <= input.Length - len; ++start)
        {
            AddToSet(input[start + len - 1]);
            int oddCount = odds.Count;
            if (oddCount <= 1) { yield return input.Substring(start, len); }
            minOddCount = Math.Min(minOddCount, oddCount);
            AddToSet(input[start]);
        }
        if (minOddCount <= 1) { yield break; }
        len -= minOddCount - 1;
    }
}