ARTS_012

"week 12"

Posted by Xion on January 19, 2019      views:

这是耗子叔发起的一个活动,每周为一个周期,需要完成以下内容

  • Algrothm: leetcode算法题目
  • Review: 阅读并且点评一篇英文技术文章
  • Tip/Techni: 学习一个技术技巧
  • Share: 分享一篇有观点和思考的技术文章

[Algrothm] Longest Palindromic Substring

problem

5. Longest Palindromic Substring
Medium

2856

275

Favorite

Share
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example 2:

Input: "cbbd"
Output: "bb"

##

这题是一道经典题目,以前做过,但是时间很久了重新思考下。

先考虑原始方法

分为两类,奇数和偶数

奇数

如果以第i个中对称中点 xxxxxxxxx . 从i两边开始找 i-1 == i+1 直到不满足条件

偶数

如果以第i个和i+1个为对称重点

xxxxxxx ..

从i-1 == i+2直到不满足条件。

这样算法复杂度 = n(1+2+3+…+(n/2)),大约是O(n^3),a最长为1000 1000^3 太大不可以接受。

然后看下计算过程中可以重复利用的部分。 如果,

先试试看,从小的对称开始找,然后增大,直到找不到

answer

最终的解法利用了动态规划,需要注意的是,这里需要分开考虑奇数和偶数的情况,否则就会有问题

class Solution:
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """

        def get_left_and_right(str_length, center_index):
            if str_length % 2 == 0:
                left = center_index - str_length / 2 + 1
                right = center_index + str_length / 2
            else:
                left = center_index - (str_length - 1) / 2
                right = center_index + (str_length - 1) / 2
            return int(left), int(right)

        def check_palindrome(str_length, center_index):
            left, right = get_left_and_right(str_length, center_index)
            if left < 0 or right >= s_length:
                return False
            if s[left] == s[right]:
                return True
            return False

        if s == "":
            return ""

        s_length = len(s)
        length = 1
        candidate_list_map = {'odd': list(range(len(s))),
                              'even': list(range(len(s)))}
        ret_s_map = {'odd': None, 'even': None}
        while True:
            now_status = 'even' if length % 2 == 0 else 'odd'
            if ret_s_map[now_status] is not None:
                length += 1
                continue
                
            final_status = now_status
            tmp_candidate_list = [center_index for center_index in candidate_list_map[now_status] if
                                  check_palindrome(length, center_index)]
            if not tmp_candidate_list:
                _left, _right = get_left_and_right(length - 2, candidate_list_map[now_status][0])
                ret_s_map[now_status] = s[_left: _right + 1]
            if ret_s_map['odd'] is not None and ret_s_map['even'] is not None:
                break
            candidate_list_map[now_status] = tmp_candidate_list
            length = length + 1

        return ret_s_map[final_status]

上面这个太长了,可读性也不是非常好,需要参考其他答案修改一把

solution

tips:

1,将字符串转化为 aba -> a#b#a bbbb -> b#b#b#b

这样就不用分开考虑奇数和偶数了。

实际上正确答案应该用马拉车算法

O(N) 解法(Manacher’s Algorithm)

输入处理

首先将字符串转化为#间隔的,字符串的总长度一定是个奇数

例如:s = “abaaba” 转化为t = “#a#b#a#a#b#c#”

构造数组

然后我们可以构造一个p数列。p[i]表示t[i]字母的回文半径。

对于t = “#a#b#a#a#b#c#” 我们最终希望得到一个这样的数据:

t = # a # b # a # a # b # a #
p = 0 1 0 3 0 1 6 1 0 3 0 1 0

如果有p,我们马上就能找到最长的回文字符串P6=6,即”abaaba”

求解数组

那么如何求解p呢

首先我们需要使用的第一个性质就对称的性质,因为我们发现P6左右是对称的。

假设我们有s, “babcbabcbaccba”

当我们需要求解p[13]的时候,可以利用对称的性质,p[13]=p[9] 即,p[i] = p[c-(i-c)]=p[2c-i]

但是如果i=15的时候就不能这样更新了 下面是例子

如果利用对称原则,p[15]=p[7],但是实际上,p[15]为中心的回文是’a#b#c#b#a’

即半径=4,因为我们可以发现p[7]覆盖的范围是0-14。 但是在考虑对称的时候,实际上我们只能观测到2-11.因此,我们只能考虑这个范围内的取值,即使p[15] = R -i。

综合上面两种情况,在只观察图中前20个元素的情况下,可以得到p[i] = min(p[c2-i],R-i)。

如果我们继续往后考虑更多的元素,只需要判断 t[i+i+p[i]] ==t[i-1-p[i]]即可。 这样就可以得到最终的结果。

####例子: c表示中心 r表示最新的半径 i表示当前更新的值 每次更新p[i]的最后一部操作都是暴力搜索得到p[i]的数值。 但是p[i]可以不从0开始搜索,因为可以利用回文的对称性,设置一个更加近似的起点。 这其实有点动态规划的感觉。

i=1,左右两边不对称,因此p[1]=0
 c
 r
 i
^#a#b#a#a#b#c#$
000000000000000

i = 2,左右两边对称半径为1,因此p[2]=1
最大的对称范围就是r
  c
   r
  i
^#a#b#a#a#b#c#$
001000000000000

此处i=r,不在半径内,直接考虑#元素左右的对称情况即可,这里p[3]=0
  c
   r
   i
^#a#b#a#a#b#c#$
001000000000000

上一个阶段的r已经小于i了,因此直接考虑暴力搜索,可以得到b下的半径为3
    c
       r
    i
^#a#b#a#a#b#c#$
001030000000000

利用对称原则
    c
       r
     i
^#a#b#a#a#b#c#$
001030000000000

    c
       r
      i
^#a#b#a#a#b#c#$
001030100000000

       c
           r
       i
^#a#b#a#a#b#c#$
001030140000000

       c
           r
        i
^#a#b#a#a#b#c#$
001030141000000
       c
           r
         i
^#a#b#a#a#b#c#$
001030141000000
       c
           r
          i
^#a#b#a#a#b#c#$
001030141010000
       c
           r
           i
^#a#b#a#a#b#c#$
001030141010000
            c
             r
            i
^#a#b#a#a#b#c#$
001030141010100
            c
             r
             i
^#a#b#a#a#b#c#$
001030141010100

bonus

这题太简单了,所以不算一个算法, 但是我觉得我觉得解法还比较有风格,因此在这里也写一下。

题目链接

我的解法

时间复杂度每个节点只会便利一次, 空间复杂度等于最大的一层, 代码可读性比较好。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def averageOfLevels(self, root):
        """
        :type root: TreeNode
        :rtype: List[float]
        """
        def mean(layer):
            return sum([node.val for node in layer])*1.0/len(layer)
        def get_next_layer(layer):
            return filter(lambda x:x,
                          reduce(list.__add__,
                                            [[node.left,node.right] for node in layer]))
        now_layer = [root]
        ret = []
        while True:
    
            ret.append(mean(now_layer))
            next_layer = get_next_layer(now_layer)
            
            if not next_layer:
                break
            now_layer = next_layer
        return ret

[Review]

[Tip]

[Share]