2.求最长无重复字符子串和求最长回文子串的算法

        最近算法题目做的不是很多,难度到了medium想快速写出一些完爆其他小伙伴的算法难度太大了,今天就记录两个比较难想和理解的算法题目,说实话,即使现在我只是看代码还是很难理解,但我尝试用自己的话表达出来,看看我的理解怎么样?

        首先第一个算法题目是求最长无重复字符子串的问题,LeetCode上原题目是这样:


        这个问题忽一看,最容易想到的解法就是,找出字符串中所有的子串,排除掉有重复字符的子串,然后取长度最长的子串的长度。这种做法虽然简单好理解,但是它的时间复杂度很高,有O(n^2)。

        那么怎么样才能通过一次扫描来找出结果呢?注意题目中只需找出最长子串的长度,并不需要返回该最长子串,所以我们只需知道该子串的开始位置和结束位置即可。       我先贴上python代码,再来根据代码说一下思路: 

class Solution(object):
    def lengthOfLongestSubstring(self, s):
        """
        :type s: str
        :rtype: int
        """
        maxlen,first,dict = 0,0,{}
        for i in range(0,len(s)):
            if len(s[first:i+1]) != len(set(s[first:i+1])):
                first = dict[s[i]]+1   
            else:
                maxlen = max(maxlen,i-first+1)    
            dict[s[i]] = i
        return maxlen

        首先我先声明变量maxlen,用来记录最长子串的长度,first用来记录子串的第一个位置,即开始位置,再声明一个字典dict,字典的用处之后说明。首先来扫描字符串,first此时为0,如果从first到当前扫描位置的子串中没有重复字符,即没有进入if语句,此时的子串符合条件,所以更新maxlen的值,并同时用字典记录该字符对应的位置,该字符作为key。这里为什么要用字典记录这个数据呢?听我继续分析,如果当前扫描位置出现了重复字符,换句话说,即first到当前扫描位置之前还出现了扫描位置的字符,此时怎么办呢?这里我们就需要用到字典来取出此前重复字符的位置,first更新为该字符的位置+1,这样我们又可以保证从first到当前扫描位置中没有重复字符了。此时的子串长度显然要小于或等于之前的子串,没有必要更新maxlen,但是需要更新字典中该字符的位置,这样直到扫描结束我们就可以获得maxlen了。

        这种做法虽然是O(n)的时间复杂度,但是提交后的runtime一直不理想,我分析了下因为我在比较该子串中使用了len(list)与len(set)来比较是否有重复字符,由于用来判断的子串中最多只会出现一对重复字符,这样判断会很没有效率,那么应该怎样改进呢?经过思考,既然我已经用dict记录了已经出现的字符,那么怎么用其判断当前扫描的字符,在first之后到当前扫描位置之前还出现过呢?其实很简单,只要判断字典中是否有当前字符的key,如果有该key代表该字符出现过,如果该key对应的value值大于等于了first,这样就代表了出现重复字符了。不知道我这样说大家能理解么?下面是改进之后的代码,思路一样,只是判断条件作了改变,runtime大大降低了:

class Solution(object):
    def lengthOfLongestSubstring(self, s):
        """
        :type s: str
        :rtype: int
        """
        first,maxlen,dict = 0,0,{}
        for i in range(len(s)):
            if s[i] in dict and first <= dict[s[i]]://这个判断条件很关键
                first = dict[s[i]] + 1
            else:
                maxlen = max(maxlen, i - first + 1)
            dict[s[i]] = i
        return maxlen 


        下面我们来看第二个算法题目,求一个字符串中的最长回文子串,原题目是这样:

        首先,什么是回文子串,就是正着读和倒着读一模一样,比如abcdcba。如果该子串本身就是回文的,当然是最长的回文子串了。如何找出一个字符串中的最长回文子串呢?大体上有四种思路,在这篇文章中作者已经把前三种介绍的很清晰,另外前三种方法的思路也比较容易理解,这里我主要说明一下第四种方法,也是最叼炸天的一种思路,名为Manacher算法,关于这个算法我看了好多文章,这篇文章中是讲解的是最通俗易懂好理解的。请大家看完之后再接着看我的分析,先贴一下我的Python代码:

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        cs = '#'.join('${}!'.format(s))
        n = len(cs)
        lenL = [0]*n
        maxright,maxcenter,maxlen,index = 1,1,0,0
        for i in xrange(1,n-1):
            if maxright > i:
                lenL[i] = min(lenL[2*maxcenter-i], maxright-i)
            else:
                lenL[i] = 1
            while cs[i-lenL[i]] == cs[i+lenL[i]]:
                lenL[i] = lenL[i]+1
            if i+lenL[i]>maxright:
                maxright = lenL[i]+i
                maxcenter = i
            if lenL[i]>maxlen:
                maxlen = lenL[i]
                index = i
        return cs[index+1-maxlen:index+maxlen].replace('#','')     
       cs是将原串处理过后的字符串,字符串的串首和串尾分别加上$和!字符防止越界。用lenL记录以每个字符为中心的最长回文串的半径,得出lenL之后找出最大值和最大值对应的index,可以获得最长回文子串,再反处理回去,去掉#号即可,下面就是具体怎样求lenL这个数组了。

       首先声明了maxright,maxcenter两个变量,maxright记录当前已经扫描过的字符中,以这些字符为中心的所有回文串里的右端点中的最大值+1后的值,即回文串的右边界。maxcenter则相应代表右端点最大的回文串的中心字符的位置了。接下来进行扫描字符串,判断maxright与当前索引的大小关系,为什么要判断这个呢,在这里主要利用了回文串的一大性质就是正读和倒读一模一样,所以

       如果maxright>当前索引i,那么代表当前扫描的字符必然存在于一个以maxcenter为中心的回文串里面。那么我们可以找到i关于maxcenter对称的位置j,这里必然也是当前扫描的字符,为什么要找到这个位置呢,是为了避免重复判断回文串。因为找到该位置j后,我们可以从lenL里拿到该位置的回文半径,就可以判断以该位置为中心的最大回文串是否被包含在以maxcenter为中心的最大回文串中,如果在,根据回文串的性质,我们可以直接得出lenL[i] = lenL[j],如果没有被包含,那么代表lenL[i]至少大于等于maxright-i,因为以j为中心的最大回文串与以maxcenter为中心的最大回文串相交部分肯定是回文的,对应到i的位置,就代表lenL[i]>=maxright-i。这样我们需要继续扫描之后的字符看是否是回文的。这就是整个算法最核心之处:lenL[i] = min(lenL[2*maxcenter-i], maxright-i)。

       如果maxright<=i,则只能继续扫描以该字符为中心的左右两边的位置来判断回文串了。

       整个过程中需要不断更新maxright和maxcenter,以及最后所需要的maxlen,index等值。扫描完成后进行处理就可以得到最长回文子串了,不知道我这样讲,有没有讲清楚其中的思路?




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值