Swift-从字符串匹配看普通算法与KMP算法

最近在leetcode上刷题,当然,是用swift,中间的辛酸经历就不提了,不得不说swift在便利性上的确十分强大,但其效率也的确相较C++、JAVA等显得相对低下,在这里不得不吐槽leetcode的Time Limit Exceeded魔咒似乎并不随着语言环境的不同而有所改变,每当看着Top solutions上一些C++、JAVA信徒用同样的算法打败了Time Limit Exceeded魔咒而我却一次又一次在魔咒面前止步不得不说我感到我的心在滴血。

然而就是又一次在刷题苦于ime Limit Exceeded魔咒时看到了KMP算法,当即被晦涩难懂的理论砸的懵逼了,在好半天的理解加题目应用才逐渐明白过来。

KMP算法的原理可以看这篇,在这么多篇的博文中这篇算是我看过最浅显易懂的了。

我只是将KMP算法的原理通过实际题目以自我的理解剖析一遍。


算法题是这样的:
Repeated Substring Pattern

Given a non-empty string check if it can be constructed by taking a substring of it and appending multiple copies of the substring together. You may assume the given string consists of lowercase English letters only and its length will not exceed 10000.

Example 1:

Input: "abab"
Output: True

Explanation: It's the substring "ab" twice.

Example 2:

Input: "aba"
Output: False

Example 3:

Input: "abcabcabcabc"
Output: True

Explanation: It's the substring "abc" four times. (And the substring "abcabc" twice.)

简单明了的算法题,给你一个不为空的字符串,你需要通过算法得出这个字符串中是否有字符成循环关系,返回布尔值。

简单的审题我们可以写成这样的伪代码。

//for循环套for循环。
//外层的循环从字符串的0下标开始截取每一个字符组成字符串。
//内层的循环从外层字符串的长度+1开始循环,截取之后的每个字符组成字符串并与外层的可变字符进行比较
//定义一个变量,当内层字符串与外层字符串相同时+=1,若变量在跳出内层循环时的值与原始字符串的长度 / 外层字符串的长度 - 1(外层字符串的长度) 的值相等则输出True,否则输出False。

通过伪代码你即可看出这是多繁复的写法了,当然我们可以通过加上一些条件进行筛选而减少步骤,例如

//若外层字符串与内层字符串长度不相等则不进行比较
//若外层字符串长度 % 原始字符串长度不为0则不进行内部循环

等等。
这些筛选条件的确让内部操作减少但也让代码变得更加繁琐,而且就算这样也没抵挡地住Time Limit Exceeded魔咒。

那么,换一种思考法,我们通过截取字符串并让它循环增殖再与原字符串比较,如此,是否更高效?

func repeatedSubstringPattern(_ str: String) -> Bool {
    for i in (1...str.characters.count/2).reversed(){
        if str.characters.count % i == 0 {
            let s = str.substring(to: str.index(str.startIndex, offsetBy: i))
            var s2 = ""
            let m = str.characters.count / s.characters.count
            for _ in 0..<m{
                s2.append(s)
            }
            if s2 == str { return true }
        }
    }
    return false
}

这种方法比起第一种拥有更简洁的代码,并且,其效率相较于第一种也显得高上一节(你看外部for循环都砍一半了,内部for循环更是只有m次)。
不得不说Swift的某些字符串操作手法简直是天怒人怨,截取字符串要写成这样

            let s = str.substring(to: str.index(str.startIndex, offsetBy: i))

如果要获得某一个具体位置的字符更是要如此

        let sc = str.characters[str.characters.index(str.characters.startIndex, offsetBy: i)]

不要说 for c in s.characters,两个字符串比较时还是要加个变量记录位置ORZ

然而,终究还是抵挡不住魔咒,虽然第二种方法效率高上一截,但依然抵挡不住魔咒,(顺便一提同样的代码我写了份C++的直接通过了……)
于是在Top solutions我找到了KMP算法

func repeatedSubstringPattern1(_ str: String) -> Bool {
    //KMP算法
    let length = str.characters.count
    //i表示的是下一需要比较的字符串的下标
    var i = 1,j = 0
    var next = [Int](repeating:0, count:length+1)
    while i < length {
        if str.characters[str.characters.index(str.characters.startIndex, offsetBy: i)] == str.characters[str.characters.index(str.characters.startIndex, offsetBy: j)] {
            //若位置i的字符与j匹配的上则对下一个位置的字符进行比较
            //同时,对next数组(即最大公共长度数组进行更新),其下标中的数值即是当前的最大公共长度,即j+=1后的值
            i+=1
            j+=1
            next[i] = j
        } else if j == 0 {
            //若公共长度为一,这种情况可能出现在一开始,也就是字符一开始没有匹配上的时候,亦可能出现在匹配过程中因为匹配不上而不断切割公共长度,直到公共长度为0时。
            //此时,将需要比对的字符向后移动一位进行匹配,也就是i+=1
            i+=1
        } else {
            //当字符不匹配而公共长度又不为0时则不断切割最大公共长度,即前移j在字符串中的位置,也就是将之前保存的next容器中的最大公共长度进行切割,获取j的历史值
            j = next[j]
        }
    }
    return  next[length] > 0 && next[length] % (length - next[length]) == 0
}

哈哈哈哈!龟儿子,就这样也想难倒我!

……


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容