【算法】Longest Duplicate Substring 最长重复子串

Longest Duplicate Substring 最长重复子串

题目

给出一个字符串,得出该字符串的最长重复字串,如 “abcabcabc” 的最长重复字串,是 “abcabc”

解题思路

  1. 通过Rabin-Karp算法,可以加速字符串比对
    • 简单来说,就是将每个字符映射为一个数值
    • 然后设计一个hash算法,由于只考虑小写字母,所以以小写字母的个数26作为进制,每遍历一个字符就将字符值进位,防止溢出用一个较大数进行模运算
    • 算出第一个子串的hash值
    • 计算第二个子串的hash值时,用第一个子串的值减去第一个字母值,在加上后一个字母值,就是第二个子串的hash值
    • 这样通过利用前一个子串hash值,而不用重新每个字符都换算,就减少了对比时间
    • 比对时,由于不同子串的hash值有小概率重复,所以即使hash值相同,也要依次比对子串再能判断是否真的相同
  2. 一个字符串若有长度为m的重复字串,则一定存在长度为n(n<m)的重复字串,这样通过折半法,就可以缩短找到最长长度的时间

代码实现

    class func LongestDuplicateSubstringSolution(_ S:String) -> String{
//        字符串长度
        let len = S.count
//        字符串长度小于2时无重复字串
        if len < 2 {
            return ""
        }
//        26进制
        let factor = 26;
//        设一个较大的模
        let powV : Double = pow(2, 20)
        let module = Int(powV)
        var nums = [Int]()
//        将字符串中的字符转化成ASCII码存入数组
        for sub in S {
            let ch = sub.asciiValue
            nums.append(Int(ch!))
        }
//        初始化重复字串的起始位置
        var start = -1
//        用二分差法,找到最大重复字串的起始位置以及长度
        var low = 1
        var high = len
        while low != high {
//            将长度折半
            let L = (high - low) / 2 + low
//            初始化临时起始位置
            var location = -1
//            初始化hashmap,key是hash值,value是Int数组的数组,用于存放子字符的位置和长度,这里之所以用了数组,而不是直接存放信息,是因为有可能发生两个不同字符串算出的hash值相同的情况,所以即使hash值相同,也要遍历数组确认是否真的是相同字符串
            var hashMap = [Int:[[Int]]]()
//            hash值变量
            var tmp = 0;
//            用于记录进位了几次
            var aL = 1;
//            将第一个折半长度为L的子串的hash值和位置长度算出来并存入hashmap中
            for j in 0 ..< L {
                tmp = (tmp * factor + nums[j]) % module
                aL = (aL * factor) % module
            }
            hashMap[tmp]=[[0,L]]
//            从第二个开始计算hash值
            for j in 1 ... len - L {
//                减去前一位
                tmp = (tmp * factor - nums[j-1] * aL % module + module) % module
//                加上后一位
                tmp = (tmp + nums[j+L-1]) % module
//                如果hashmap的key中已经存在hash值,则遍历数组,逐一对比子串
                if hashMap.keys.contains(tmp) {
//                    用于是否真的重复的标识
                    var isContain = false
//                    获取当前子串
                    let sIndex = S.index(S.startIndex, offsetBy: j)
                    let eIndex = S.index(S.startIndex, offsetBy: j + L)
                    let str = String(S[sIndex ..< eIndex])
//                    获取当前hash值对应的数组
                    let locArr = hashMap[tmp]
//                    遍历数组
                    for locs in locArr!{
//                        获取数组元素对应的子串
                        let sIndex = S.index(S.startIndex, offsetBy: locs[0])
                        let eIndex = S.index(S.startIndex, offsetBy: locs[0] + locs[1])
                        let strInMap = String(S[sIndex ..< eIndex])
//                        对比子串,若相同,将位置赋予临时起始位置,并跳出遍历循环
                        if str == strInMap {
                            location = j
//                            真的重复,将isContain标识置为true
                            isContain = true
                            break
                        }
                    }
//                    如果并未真的重复,则将现位置长度存入数组
                    if(!isContain){
                        var locArr = hashMap[tmp]
                        locArr?.append([j,L])
                    }else{
//                        若真的重复,则跳出检验当前长度L子串是否重复的循环,这样位置获取的就是第一个重复字串的位置
                        break
                    }
                }else{
//                    如果hashmap的key中不含有hash值,则新创建key-value
                    hashMap[tmp] = [[j,L]]
                }
            }
//            如果临时起始位置发生改变,说明此次循环找到了重复的长度为L的子串,将铲毒下线+1,并将起始位置赋值
            if ( location != -1) {
                low = L + 1
                start = location
            }else{
//                临时起始位置未发生改变,说明此次循环没有重复的长度为L的子串,则将长度上限设为L
                high = L
            }
        }
//        若起始位置未发生改变,说明没有找到重复的子串
        if start == -1 {
            return ""
        }
//        否则,依据位置和长度,返回重复的子串
        let startIndex = S.index(S.startIndex, offsetBy: start)
        let endIndex = S.index(S.startIndex, offsetBy: start+low-1)
        return String(S[startIndex ..< endIndex])
    }

PS: 该代码没能通过LeetCode的时间限制检验,待完善

代码地址:https://github.com/sinianshou/EGSwiftLearning

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值