[LeetCode] 5, the longest palindrome substring. Difficulty Level: Moderate. There are many solutions, which are worth deliberating.

[LeetCode] 5, the longest palindrome substring. Difficulty Level: Moderate.

1. Topic

insert image description here

2. My answer: double pointer traverse from the beginning

Idea: Use double pointers (double for loop) to enumerate all substrings, judge whether they are palindrome strings, and save the longest palindrome string. Time complexity is O(n 3 ): double pointer O(n 2 ), judging whether the substring is a palindrome O(n)

2.1 Cycle of Violence: Time Limit Exceeded

A direct brute force loop would exceed the time limit because the time complexity is too high. But the code is correct:

class Solution:
    def judgePalindrome_(self,s:str)->bool:
        if len(s)==1:
            return True
        length=len(s)
        for i in range(0,length//2+1):
            if s[i]!=s[length-i-1]:
                return False
        return True

    def longestPalindrome(self, s: str) -> str:
        if len(s)<=1:
            return s
        
        # 判断s是否为单一元素字符串,如"aaaaaaaaaa"。若不使用该判断,会超出时间限制。
        if s==s[0]*len(s):
            return s

        # 记录下maxLen和相应的起始位置
        maxLen=1   
        startIndex=0

        # 双指针
        length=len(s)
        for i in range(0,length-1):
            subStr=s[i]
            for j in range(1,length-i):
                subStr+=s[i+j]
                if self.judgePalindrome_(subStr):
                    if len(subStr)>=maxLen:
                        maxLen=len(subStr)
                        startIndex=i
        return s[startIndex:startIndex+maxLen]

2.2 Optimized violent loop: Although there is no timeout, the efficiency is very low

Since we only need to find the longest substring, the code in 2.1 can be optimized. We only need to judge whether the substring whose length is greater than the current maxLen is a palindromic substring, because the substring whose length is smaller than maxLen cannot be the longest even if it is a palindromic substring, so there is no need to judge.

The optimized code is as follows:

class Solution:
    def judgePalindrome_(self,s:str)->bool:
        if len(s)==1:
            return True
        length=len(s)
        for i in range(0,length//2+1):
            if s[i]!=s[length-i-1]:
                return False
        return True

    def longestPalindrome(self, s: str) -> str:
        if len(s)<=1:
            return s
        
        # 判断s是否为单一元素字符串,如"aaaaaaaaaa"。若不使用该判断,会超出时间限制。
        if s==s[0]*len(s):
            return s

        # 记录下maxLen和相应的起始位置
        maxLen=1   
        startIndex=0

        # 双指针
        length=len(s)
        for i in range(0,length-1):
            subStr=s[i]
            for j in range(1,length-i):
                subStr+=s[i+j]
                # 只判断长度大于 maxLen 的子串即可
                if len(subStr) > maxLen and self.judgePalindrome_(subStr):
                    if len(subStr)>=maxLen:
                        maxLen=len(subStr)
                        startIndex=i
        return s[startIndex:startIndex+maxLen]

Results of the:

执行用时:9136 ms, 在所有 Python3 提交中击败了 4.98% 的用户
内存消耗:16 MB, 在所有 Python3 提交中击败了 49.25% 的用户

The execution results are terrible. In fact, even a little more trial use cases will time out. So this question does not work with double pointers.

3. Double pointer center diffusion method (traverse from the center of the string)

Using the symmetry of the palindrome substring, use the left and right double pointers to spread to both sides with each position as the center. The time complexity is O(n 2 )

code:

class Solution:
    def expend_(self,s,left,right):
        length=len(s)
        while left>=0 and right<length and s[left]==s[right]:
            left-=1
            right+=1
        # while循环会导致 left-=1 和 right+=1 多执行一次
        return left+1, right-1

    def longestPalindrome(self, s: str) -> str:
        if len(s)<=1:
            return s
        if len(s)==2:
            if s[0]==s[1]:
                return s
            else:
                return s[0]

        length=len(s)
        begin=0
        end=0
        for i in range(length-1):
            # 以奇数长度中心开始扩展, 此时初始扩展中心为 left=right=i
            left1,right1=self.expend_(s,i,i)
            # 以偶数长度中心开始扩展, 此时初始扩展中心分别为 left=i,right=i+1
            left2,right2=self.expend_(s,i,i+1)

            if right1-left1>end-begin:
                begin=left1
                end=right1
            if right2-left2>end-begin:
                begin=left2
                end=right2
        return s[begin:end+1]  

Key idea: Use a function to implement two cases of central diffusion for odd-length and even-length strings.

Results of the:

执行用时:340 ms, 在所有 Python3 提交中击败了 89.70% 的用户
内存消耗:16.1 MB, 在所有 Python3 提交中击败了 45.40% 的用户

4. Dynamic programming method

For a substring, if it is a palindrome and its length is greater than 2, it is still a palindrome after removing the first and last two letters. Therefore, this question involves repeated calculations, so the judgment result of whether the shorter substring is a palindrome can be saved in advance, and the previous results can be used to reduce the amount of calculation when judging longer substrings.

4.1 My wrong answer: Forward traversal of strings

Here is the answer I wrote, which evaluates wrongly for one test case:

输入:"aaaaa"
输出:"aaaa"
预期结果:"aaaaa"

code:

class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s)<=1:
            return s
        if len(s)==2:
            if s[0]==s[1]:
                return s
            else:
                return s[0]
        
        # 记录最长回文子串的长度和起始下标
        maxLen=1
        startIndex=0

        length=len(s)
        dp=[[False]*length for _ in range(length)]
        # dp[i][j]表示s[i:j+1]是否为回文串

        # 初始状态
        for i in range(length-1):
            dp[i][i]=True    # 单个字符 s[i:i+1]=s[i] 一定是回文串
            if s[i]==s[i+1]:
                dp[i][i+1]=True    # 判断长度为2的子串是否为回文串
                if maxLen<2:
                    maxLen=2
                    startIndex=i
        
        for i in range(length-2):
            for j in range(i+2,length):
                # 状态转移方程, 前提条件是 len(s[i:j+1])>2
                dp[i][j]=(dp[i+1][j-1]==True and s[i]==s[j])
                if dp[i][j]==True and j-i+1>maxLen:
                    maxLen=j-i+1
                    startIndex=i
        return s[startIndex:startIndex+maxLen]              

Analysis of the cause of the error:

Obviously, for a string whose input is s="aaaaa", the longest substring is dp[0][4]=s[0:4+1]="aaaaa"

But when the program calculates dp[0][4]=(dp[1][3]==True and s[1]==s[3]), the loop of outer i only loops i=0, i= The case of 1 has not been traversed, so dp[1][3]=False, the program execution result is dp[0][4]=False; but obviously dp[0][4]=True is correct.

The fundamental reason is that in the case of traversing strings sequentially, the result of i cannot be used when traversing i+1, and the result of i+1 cannot be used when traversing i, which leads to the loss of the meaning of the dynamic programming method.

4.2 The correct answer: reverse traversal of the string

In order for a longer string to take advantage of the calculation results of a shorter string, the string must be traversed in reverse. code show as below:

class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s)<=1:
            return s
        if len(s)==2:
            if s[0]==s[1]:
                return s
            else:
                return s[0]
        
        # 记录最长回文子串的长度和起始下标
        maxLen=1
        startIndex=0

        length=len(s)
        dp=[[False]*length for _ in range(length)]
        # dp[i][j]表示s[i:j+1]是否为回文串

        # 初始状态
        for i in range(length-1):
            dp[i][i]=True    # 单个字符 s[i:i+1]=s[i] 一定是回文串
            if s[i]==s[i+1]:
                dp[i][i+1]=True    # 判断长度为2的子串是否为回文串
                if maxLen<2:
                    maxLen=2
                    startIndex=i
        
        # i 一定要逆向遍历
        for i in range(length-3,-1,-1):
            for j in range(i+2,length):
                # 状态转移方程, 前提条件是 len(s[i:j+1])>2
                dp[i][j]=(dp[i+1][j-1]==True and s[i]==s[j])
                if dp[i][j]==True and j-i+1>maxLen:
                    maxLen=j-i+1
                    startIndex=i
        return s[startIndex:startIndex+maxLen]              

Five, judging the various writing methods of palindrome substrings

Method 1: Use a for loop to iterate through half the length of the string.

This way of writing needs to consider whether to distinguish whether the length of the string is odd or even (in fact, there is no need to distinguish), which is inconvenient to write. code show as below:

def judgePalindrome_(self,s:str)->bool:
    if len(s)==1:
        return True
    length=len(s)
    for i in range(0,length//2+1):
        if s[i]!=s[length-i-1]:
            return False
    return True

Method 1: Use left and right double pointers to traverse from the head and tail respectively, and stop the loop when the two pointers meet.

This way of writing is concise and clear, and the way of writing is simple. You don't need to think about whether you need to distinguish whether the length of the string is odd or even. code show as below:

def judgePalindrome_(self,s:str)->bool:
    if len(s)==1:
          return True
 	left=0
    right=len(s)-1
    while left<right:
        if s[left]!=s[right]:
            return False
        left+=1
        right-=1
    return True

Guess you like

Origin blog.csdn.net/qq_43799400/article/details/130919572