【问题标题】:Kattis Challenge: Phone List, 4-Second Time Limit Exceeded, C#Kattis 挑战:电话清单,超过 4 秒的时间限制,C#
【发布时间】:2015-10-20 21:13:18
【问题描述】:

使用在线自动化测试系统 Kattis,我面临的挑战(在 C# 中)创建一个电话号码列表,然后找出其中任何一个是前缀还是另一个。

(参见:https://ncpc.idi.ntnu.no/ncpc2007/ncpc2007problems.pdf,任务 A)

提供答案相对容易,但无论我如何尝试,我都无法逃脱得到结果:超出时间限制,还有一些进一步的信息表明运行程序需要超过 4 秒(通过自动程序)。

我曾多次尝试从头开始重写它,并且我已经尝试了我可以在互联网上找到的所有现有建议。我发现自己完全不知所措,主要是因为最终我无法确定到底出了什么问题。

This code 是在类似(但未解决)thread 上提出的,但并没有发挥作用 - 但这是迄今为止我见过的最好的:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PhoneList
{
class Program
{
    static void Main(string[] args)
    {
        // Save number of test cases.
        var numTestCases = int.Parse(Console.ReadLine());

        // Do this for each test case.
        for (int i = 0; i < numTestCases; i++)
        {
            // Save number of phone numbers.
            var numPhoneNumbers = int.Parse(Console.ReadLine());

            // Save all phonenumbers in the list.
            var phoneNumbersList = new List<string>();
            for (int j = 0; j < numPhoneNumbers; j++)
            {
                string number = Console.ReadLine().Trim();

                // Add to list.
                phoneNumbersList.Add(number);
            }

            // Write output.
            if (phoneNumbersList.All(n => !phoneNumbersList.Except(new[] { n }).Any(o => o.StartsWith(n))))
            {
                Console.WriteLine("YES");
            }
            else
            {
                Console.WriteLine("NO");
            }
        }
    }
}
}

有什么建议吗?

【问题讨论】:

  • 如果这段代码可以正常工作,这可能属于Code Review
  • 对您的电话号码列表进行排序。然后你只需要比较n项和n-1项,直到找到违反规则的项或到达列表末尾。
  • 是的,这让我通过了第二次测试!但是在第三次测试中仍然触发了 Time Limit Exceeded... 我用 * Check update in OP * 替换了 LINQ 查询 *
  • 在您的if 语句中,使用break 跳出循环。找到一个错误号码后,无需浪费时间检查其余号码。
  • 糟糕,忘记休息是愚蠢的。虽然没有任何区别......仍然卡在程序超时的第三个测试用例中......是的,我发现了两个关于完全相同问题的线程,但没有解决方案。但是它们已经很老了。

标签: c# performance time


【解决方案1】:

您是否尝试在添加所有号码后对电话号码列表进行排序(按字母顺序),然​​后检查第 n+1 项是否以第 n 项开头?

phoneNumbersList.Sort((x,y) => string.Compare(x, y));

var consistent = true;
for (var j = 0; j < phoneNumbersList.Count - 1; ++j)
{
   if (phoneNumbersList[j + 1].StartsWith(phoneNumbersList[j]))
   {
        consistent = false;
        break;
   }
}

Console.WriteLine(consistent ? "YES" : "NO");

【讨论】:

  • 我之前曾尝试使用排序但无济于事,但应用您的建议确实解决了 Time Limit Exceeded 问题。然而,我收到了一个错误的答案,而不是它,这表明代码实际上并没有达到预期的效果..
  • 编辑:稍微调整代码修复了错误的答案响应,让我在 Kattis 测试过程中更进一步(步骤 3/5 而不是 2/5),然后再返回给我同样的 Time Limit Exceeded-response.
  • 他们的测试数据是保密的,所以我真的不知道。我所要做的只是一些示例数据,我最终以出色的成绩通过了这些数据,但示例数据只是他们所做的第一次检查。
【解决方案2】:

并非总是最短和最好的就是最好的 :-) 尝试以下方法:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Samples
{
    class PhoneListProcessor
    {
        const int MaxCount = 10000;
        const int MaxDigits = 10;
        Dictionary<int, bool> phoneNumberInfo = new Dictionary<int, bool>(MaxCount * MaxDigits);
        public bool Process(IEnumerable<string> phoneNumbers, bool readToEnd)
        {
            phoneNumberInfo.Clear();
            using (var e = phoneNumbers.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    if (Process(e.Current)) continue;
                    if (readToEnd)
                    {
                        while (e.MoveNext()) { }
                    }
                    return false;
                }
            }
            return true;
        }
        bool Process(string phoneNumber)
        {
            var phoneNumberInfo = this.phoneNumberInfo;
            int phoneCode = 0;
            int digitPos = 0;
            bool hasSuffix = true;
            while (true)
            {
                phoneCode = 11 * phoneCode + (phoneNumber[digitPos] - '0' + 1);
                bool isLastDigit = ++digitPos >= phoneNumber.Length;
                bool isPhoneNumber;
                if (hasSuffix && phoneNumberInfo.TryGetValue(phoneCode, out isPhoneNumber))
                {
                    if (isPhoneNumber || isLastDigit) return false;
                }
                else
                {
                    phoneNumberInfo.Add(phoneCode, isLastDigit);
                    if (isLastDigit) return true;
                    hasSuffix = false;
                }
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var processor = new PhoneListProcessor();
            int testCount = int.Parse(Console.ReadLine());
            for (int testIndex = 0; testIndex < testCount; testIndex++)
            {
                int count = int.Parse(Console.ReadLine());
                var numbers = Enumerable.Range(0, count).Select(_ => Console.ReadLine());
                bool readToEnd = testIndex + 1 < testCount;
                bool valid = processor.Process(numbers, readToEnd);
                Console.WriteLine(valid ? "YES" : "NO");
            }
        }
    }
}

基本上,它所做的是动态构建并在任务限制和细节下使用dictionary prefix trie 的变体。每个电话号码或前缀都被编码为以 11 为基数且不带零的整数(每个数字都递增,因此我们有 1-10 而不是 0-9),以便区分带前导零的数字和不带前导零的相同数字请求。

【讨论】:

  • 它通过了第 2 次检查,但在第 3 次(共 5 次)失败,因为给出了错误的答案。我不知道他们测试了什么或如何通过了第一次检查。
  • @JuliusMikkelä 好的,试试更新的代码,希望它现在应该可以通过了。
  • @JuliusMikkelä 请不要误会我的意思,我不在乎赚取声誉积分,从性能角度来看,这个难题让我非常感兴趣(一般并与 C/C++ 解决方案相比)。你试过我最新的代码吗(最初有一个无聊的错误)?它通过了吗?结果时间?
【解决方案3】:

我有两个建议:

  1. 不要使用phoneNumbers.Sort(); 按字母顺序排序,而是尝试按长度排序:numbers.Sort((a, b) =&gt; a.Length - b.Length);。直觉是,由于较长的字符串不可能成为较短字符串的前缀,因此我们可以跳过检查这些并节省一些工作(一旦发现不一致就尽早救助很重要)。
  2. 由于电话长度只有 10 位,因此可以对其进行迭代并将每个前缀存储在 HashSet 中。 HashSet 为我们提供了对所有先前计算的前缀的 O(1) 查找时间,因此每个测试用例的时间复杂度为 O(n) 用于检查,O(n log(n)) 用于预先排序成本。

代码如下:

using System;
using System.Collections.Generic;

namespace PhoneList
{
    class Program
    {
        static bool IsConsistent(List<string> numbers)
        {
            numbers.Sort((a, b) => a.Length - b.Length);
            var prefixes = new HashSet<string>();

            foreach (var n in numbers)
            {
                for (int i = 1; i < n.Length; i++)
                {
                    if (prefixes.Contains(n.Substring(0, i)))
                    {
                        return false;
                    }
                }

                prefixes.Add(n);
            }

            return true;
        }

        static void Main(string[] args)
        {
            int testCases = Int32.Parse(Console.ReadLine());

            for (int i = 0; i < testCases; i++)
            {
                int phoneNumbersCount = Int32.Parse(Console.ReadLine());
                var numbers = new List<string>();

                for (int j = 0; j < phoneNumbersCount; j++)
                {
                    numbers.Add(Console.ReadLine());
                }

                Console.WriteLine(IsConsistent(numbers) ? "YES" : "NO");
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 2020-06-14
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    相关资源
    最近更新 更多