【问题标题】:How to generalize my algorithm to detect if one string is a rotation of another如何概括我的算法以检测一个字符串是否是另一个字符串的旋转
【发布时间】:2016-12-28 22:33:41
【问题描述】:

所以我一直在经历各种问题来复习即将到来的面试,我遇到的一个问题是确定两个字符串是否相互旋转。显然,我不是第一个解决这个问题的人。事实上,我确实发现我解决这个问题的想法似乎与this question 中采用的方法相似。

完全披露:我在 Math SE 上确实有一个 related question,它从更数学的角度关注属性(尽管值得注意的是,我试图在此背后制定想法的方式最终由于某些原因是不正确的那里有解释)。

这就是想法(这类似于链接问题中采用的方法):假​​设您有一个字符串abcd 和旋转cdab。显然,cdab 都是 cdab 的子字符串,但如果将它们连接在一起,则会得到 abcd

所以基本上,旋转只是需要将子字符串从字符串的末尾移动到开头(例如,我们通过将 cd 从字符串的末尾移动到字符串的开头来从 abcd 构造 cdab )。

我想出了一种在非常有限的情况下工作的方法(如果两个子字符串都由连续的字母组成,就像他们在示例中所做的那样),但否则它会失败(我给出了一个通过和失败的例子代码下方的案例和输入/输出)。我试图弄清楚是否有可能(甚至值得)尝试修复它以在一般情况下工作。

    public bool AreRotations(string a, string b)
    {
        if (a == null)
            throw new ArgumentNullException("a");
        else if (b == null)
            throw new ArgumentNullException("b");
        else if (a.Trim().Length == 0)
            throw new ArgumentException("a is empty or consists only of whitespace");
        else if (b.Trim().Length == 0)
            throw new ArgumentException("b is empty or consists only of whitespace");

        // Obviously, if the strings are of different lengths, they can't possibly be rotations of each other
        if (a.Length != b.Length)
            return false;

        int[] rotationLengths = new int[a.Length];

        /* For rotations of length -2, -2, -2, 2, 2, 2, the distinct rotation lengths are -2, 2
         * 
         * In the example I give below of a non-working input, this contains -16, -23, 16, 23
         * 
         * On the face of it, that would seem like a useful pattern, but it seems like this
         * could quickly get out of hand as I discover more edge cases
         */
        List<int> distinctRotationLengths = new List<int>();
        for (int i = 0; i < a.Length; i++)
        {
            rotationLengths[i] = a[i] - b[i];

            if (i == 0)
                distinctRotationLengths.Add(rotationLengths[0]);
            else if (rotationLengths[i] != rotationLengths[i - 1])
            {
                distinctRotationLengths.Add(rotationLengths[i]);
            }
        }

        return distinctRotationLengths.Count == 2;
    }

现在是示例输入/输出:

        StringIsRotation rot = new StringIsRotation();

        // This is the case that doesn't work right - it gives "false" instead of "true"
        bool success = rot.AreRotations("acqz", "qzac");

        // True
        success = rot.AreRotations("abcdef", "cdefab");

        // True
        success = rot.AreRotations("ablm", "lmab");

        // False, but should be true - this is another illustration of the bug
        success = rot.AreRotations("baby", "byba");

        // True
        success = rot.AreRotations("abcdef", "defabc");

        //True
        success = rot.AreRotations("abcd", "cdab");

        // True
        success = rot.AreRotations("abc", "cab");

        // False
        success = rot.AreRotations("abcd", "acbd");

        // This is an odd situation - right now it returns "false" but you could
        // argue about whether that's correct
        success = rot.AreRotations("abcd", "abcd");

是否有可能/值得挽救这种方法并让它仍然是 O(n),或者我应该只使用我链接到的帖子中描述的方法之一? (请注意,这实际上不是生产代码或作业,纯粹是为了我自己的学习)。

编辑:基于 cmets 进一步澄清,这里实际上有两个问题 - 首先,这个算法是否可以修复?其次,它是否甚至值得修复它(或者我应该尝试另一种方法,如答案中描述的方法或我链接到的其他问题)?我想到了一些可能的修复方法,但它们都涉及不优雅的特殊情况推理或使该算法 O(n^2),这两者都会首先扼杀算法的要点。

【问题讨论】:

  • 你能按字母顺序对两个字符串中的所有字符进行排序并进行比较吗?
  • @apocalypse 所有旋转都是字谜,但并非所有字谜都是旋转。在字符串轮换中,顺序很重要。
  • 在您的示例中,您有 success = rot.AreRotations("abcd", "cdab"); = true 和 false!还有你到底在问什么?修复你的算法,或者什么是最好的算法?
  • 我不明白,为什么 rot.AreRotations("baby", "byba") 是假的?它旋转了 2 个点。
  • @user3589054 很抱歉造成混乱,这实际上是错误的说明,我会澄清评论

标签: c# string algorithm


【解决方案1】:

假设第一个字符串是 S,第二个是 S',显然如果它们的长度不同,那么我们输出它们不是彼此的旋转。创建一个字符串 S''=SS。实际上是 S 与自身的串联。然后如果 S,S' 是彼此的旋转,我们通过KMP 算法在O(n) 中找到 S'' 中的子字符串 S',否则我们输出它们不是彼此的旋转。顺便说一句,如果您正在寻找一种快速实用的算法,那么请使用 Boyer Moore 算法而不是 KMP。

为了更明确地解决这个问题,我想说的是,对于这种字符串匹配问题的特殊情况,我不希望有一个简单的算法。所以考虑到这个背景,我认为对你的算法进行简单的修改是行不通的。事实上,字符串匹配算法领域非常发达。如果有比 KMP 或基于后缀树的算法等更简单的算法,那么对于这种特殊情况,我仍然认为研究这些通用算法会有所帮助。

【讨论】:

  • 你能写C#代码吗? var s1 = "abc"; var s2 = "cba"; var result = (s1 + s1).Contains(s2); 结果是 false。没事吧?
  • @StepUp,cba 不是 abc 的旋转。所以答案是正确的。
【解决方案2】:

这样的东西有用吗?:

private bool IsRotation(string a, string b)
{
    if (a.Length != b.Length) { return false; }
    for (int i = 0; i < b.Length; ++i)
    {
        if (GetCharactersLooped(b, i).SequenceEqual(a))
        {
            return true;
        }
    }
    return false;
}

private IEnumerable<char> GetCharactersLooped(string data, int startPos)
{
    for (int i = startPos; i < data.Length; ++i)
    {
        yield return data[i];
    }
    for (int i = 0; i < startPos; ++i)
    {
        yield return data[i];
    }
}

附:对于 abcd = abcd,这将返回 true,因为您可以将其视为一个完整的旋转。如果不需要,请在第一个函数中将循环的开始从 0 更改为 1。

【讨论】:

    【解决方案3】:

    如果您只是在寻找一种方法来检查一个字符串是否是另一个字符串的旋转,这是您链接的函数的 C# 实现(据我所知,这是解决此问题的最快方法特殊问题):

    bool IsRotation(string a, string b)
    {
        if (a == null || b == null || a.Length != b.Length)
            return false;
    
        return (a + a).Contains(b);
    }
    

    如果您要求对您的算法提供反馈,我不确定我是否理解您的算法试图做什么。您似乎正试图通过将char 值的差异存储在字符串中并查看它们是否总和为0 来检测旋转?或者,如果唯一差异列表包含镜像对(对 (x,y) 其中x = -y)?或者仅仅是如果唯一差异的数量是偶数?还是我在您的描述中完全遗漏的其他内容?

    我不确定您所做的是否可以概括,只是因为它在很大程度上取决于单词中的字符,以至于它可能无法充分检查它们的呈现顺序。即使你可以,这也只是一个学术练习,因为上述方法将比你的方法更快、更有效。

    【讨论】:

    • 您的答案的第一部分只是将我的答案简单地解释为 C# 代码。您答案的第二部分更像是评论而不是答案的一部分。因此,我认为您应该删除此答案并将其转为评论。
    • @SaeedAmiri 我的答案的第一部分是他的问题中链接到的函数 OP 的 C# 实现。由于该问题已在 Java 中实现(并且您的答案根本不包含代码),我相信它仍然是相关的。我的回答的第二部分提出了问题,但也解决了 OP 提出的问题,即是否可以推广 his 对字符串旋转问题的方法。顺便说一句,根本没有解决这个问题,所以如果有任何问题需要编辑或删除,那是你的。 (为了记录,不,我的答案不是基于你的。)
    • BTW 请记住,永远不要出于恶意而投反对票! (就像你现在所做的那样)。
    • @SaeedAmiri 我并不是出于恶意投反对票。我已经在决定是否投反对票并发表评论,并决定不打扰,因为你的回答得到的票数显然是少数,而且当时我还有其他事情要做。但是你的评论让我回过神来,所以我决定完成我已经在考虑做的事情。
    • @StepUp 看看你想要做什么。 "abc" + "abc" = "abcabc"。该字符串是否包含"cba"
    猜你喜欢
    • 2017-04-30
    • 1970-01-01
    • 2013-05-12
    • 2019-05-11
    • 2011-02-02
    • 2010-10-13
    相关资源
    最近更新 更多