leetcodeday5 –最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = “babad”
输出:”bab”
解释:”aba” 同样是符合题意的答案。
示例 2:

输入:s = “cbbd”
输出:”bb”
示例 3:

输入:s = “a”
输出:”a”
示例 4:

输入:s = “ac”
输出:”a”
 

提示:

1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成

思路:根据回文串的特点: 回文串中心向两边的元素对称,遍历str每个元素,以该元素为中心,判断i-1和i+1是否一致,如果一致,继续判断i-2和i+2,知道不一致或者达到边界停止,并记录substr长度,但是还必须考虑回文串中心是两个相同的元素的情况。

# [5] 最长回文子串
#

# @lc code=start
class Solution:
    def longestPalindrome(self, s: str) -> str:
        length=len(s)
        mx=""
        if length ==1:
           return s
        if length ==2 and s[0]==s[1]:
            return s
        elif length ==2 and s[0]!=s[1]:
            return s[0]
#对于回文串中心是单个元素
        for i in range(length):
            start =end = i
            if i ==0: mx=s[0]
            else: 
                for j in range(i):
                    start -=1
                    end +=1
                    #if start>=0 and end<length: 
                    #   print(i-j-1,i+j+1)
                    if start>=0 and end<length and s[i-j-1]==s[i+j+1]:

                        mx=s[i-j-1:i+j+1+1] if 2*j+3>len(mx) else mx
                    else:break
    
#对于回文串中心是相邻两个元素的情况: 
        for i in range(length):
            end = i
            start =i-1
            if i ==0: continue
            if i ==1 and s[0]==s[1]: 
                
                mx=s[0:2] if 2>len(mx) else mx
                print(mx)
                continue
            elif s[i]==s[i-1]: 
                mx=s[i-1:i+1] if 2>=len(mx) else mx
                print(mx)
                for j in range(i):
                    start -=1
                    end +=1
                    
                    if start>=0 and end<length and s[i-1-j-1]==s[i+j+1]:

                        mx=s[i-1-j-1:i+j+1+1] if 2*j+4>len(mx) else mx
                    else:break
            else:continue
        return mx                  
               
# @lc code=end

再来看看官方解法:

方法一:动态规划
思路与算法

对于一个子串而言,如果它是回文串,并且长度大于 22,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab” 是回文串,那么“ababa” 一定是回文串,这是因为它的首尾两个字母都是“a”。

class Solution:
def longestPalindrome(self, s: str) -> str:
    n = len(s)
    if n < 2:
       return s    
    max_len = 1
    begin = 0
    # dp[i][j] 表示 s[i..j] 是否是回文串
    dp = [[False] * n for _ in range(n)]
    for i in range(n):
        dp[i][i] = True

    # 递推开始
    # 先枚举子串长度
    for L in range(2, n + 1):
        # 枚举左边界,左边界的上限设置可以宽松一些
        for i in range(n):
            # 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
            j = L + i - 1
            # 如果右边界越界,就可以退出当前循环
            if j >= n:
                break

            if s[i] != s[j]:
                dp[i][j] = False 
            else:
                if j - i < 3:
                    dp[i][j] = True
                else:
                    dp[i][j] = dp[i + 1][j - 1]

            # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
            if dp[i][j] and j - i + 1 > max_len:
                max_len = j - i + 1
                begin = i
    return s[begin:begin + max_len]

方法二:中心扩展算法

思路与算法

我们仔细观察一下方法一中的状态转移方程:

可以发现,所有的状态在转移的时候的可能性都是唯一的。也就是说,我们可以从每一种边界情况开始「扩展」,也可以得出所有的状态对应的答案。

边界情况即为子串长度为 11 或 22 的情况。我们枚举每一种边界情况,并从对应的子串开始不断地向两边扩展。如果两边的字母相同,我们就可以继续扩展,例如从 P(i+1,j-1)P(i+1,j−1) 扩展到 P(i,j)P(i,j);如果两边的字母不同,我们就可以停止扩展,因为在这之后的子串都不能是回文串了。

聪明的读者此时应该可以发现,「边界情况」对应的子串实际上就是我们「扩展」出的回文串的「回文中心」。方法二的本质即为:我们枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。我们对所有的长度求出最大值,即可得到最终的答案

class Solution:
   def expandAroundCenter(self, s, left, right):
       while left >= 0 and right < len(s) and s[left] == s[right]:
             left -= 1
             right += 1
       return left + 1, right - 1
def longestPalindrome(self, s: str) -> str:
    start, end = 0, 0
    for i in range(len(s)):
        left1, right1 = self.expandAroundCenter(s, i, i)
        left2, right2 = self.expandAroundCenter(s, i, i + 1)
        if right1 - left1 > end - start:
            start, end = left1, right1
        if right2 - left2 > end - start:
            start, end = left2, right2
    return s[start: end + 1]

方法三:Manacher 算法
还有一个复杂度为 O(n) 的 Manacher 算法。然而本算法十分复杂,一般不作为面试内容。这里给出,仅供有兴趣的同学挑战自己。

为了表述方便,我们定义一个新概念臂长,表示中心扩展算法向外扩展的长度。如果一个位置的最大回文字符串长度为 2 * length + 1 ,其臂长为 length。

下面的讨论只涉及长度为奇数的回文字符串。长度为偶数的回文字符串我们将会在最后与长度为奇数的情况统一起来。

思路与算法

在中心扩展算法的过程中,我们能够得出每个位置的臂长。那么当我们要得出以下一个位置 i 的臂长时,能不能利用之前得到的信息呢?

答案是肯定的。具体来说,如果位置 j 的臂长为 length,并且有 j + length > i,如下图所示:

当在位置 i 开始进行中心拓展时,我们可以先找到 i 关于 j 的对称点 2 * j – i。那么如果点 2 * j – i 的臂长等于 n,我们就可以知道,点 i 的臂长至少为 min(j + length – i, n)。那么我们就可以直接跳过 i 到 i + min(j + length – i, n) 这部分,从 i + min(j + length – i, n) + 1 开始拓展。

我们只需要在中心扩展法的过程中记录右臂在最右边的回文字符串,将其中心作为 j,在计算过程中就能最大限度地避免重复计算。

那么现在还有一个问题:如何处理长度为偶数的回文字符串呢?

我们可以通过一个特别的操作将奇偶数的情况统一起来:我们向字符串的头尾以及每两个字符中间添加一个特殊字符 #,比如字符串 aaba 处理后会变成 #a#a#b#a#。那么原先长度为偶数的回文字符串 aa 会变成长度为奇数的回文字符串 #a#a#,而长度为奇数的回文字符串 aba 会变成长度仍然为奇数的回文字符串 #a#b#a#,我们就不需要再考虑长度为偶数的回文字符串了。

注意这里的特殊字符不需要是没有出现过的字母,我们可以使用任何一个字符来作为这个特殊字符。这是因为,当我们只考虑长度为奇数的回文字符串时,每次我们比较的两个字符奇偶性一定是相同的,所以原来字符串中的字符不会与插入的特殊字符互相比较,不会因此产生问题。

class Solution:
    def expand(self, s, left, right):
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return (right - left - 2) // 2

    def longestPalindrome(self, s: str) -> str:
        end, start = -1, 0
        s = '#' + '#'.join(list(s)) + '#'
        arm_len = []
        right = -1
        j = -1
        for i in range(len(s)):
            if right >= i:
                i_sym = 2 * j - i
                min_arm_len = min(arm_len[i_sym], right - i)
                cur_arm_len = self.expand(s, i - min_arm_len, i + min_arm_len)
            else:
                cur_arm_len = self.expand(s, i, i)
            arm_len.append(cur_arm_len)
            if i + cur_arm_len > right:
                j = i
                right = i + cur_arm_len
            if 2 * cur_arm_len + 1 > end - start:
                start = i - cur_arm_len
                end = i + cur_arm_len
        return s[start+1:end+1:2]

leetcodeday6 –Z 字形变换

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:”PAHNAPLSIIGYIR”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);
 

示例 1:

输入:s = “PAYPALISHIRING”, numRows = 3
输出:”PAHNAPLSIIGYIR”


示例 2:
输入:s = “PAYPALISHIRING”, numRows = 4
输出:”PINALSIGYAHRPI”
解释:
P I N
A L S I G
Y A H R
P I
示例 3:

输入:s = “A”, numRows = 1
输出:”A”
 

提示:

1 <= s.length <= 1000
s 由英文字母(小写和大写)、’,’ 和 ‘.’ 组成
1 <= numRows <= 1000

第一次尝试:分别求奇数和偶数列的对应的字符串下标

假设:numrows=5,对应位置如果没有元素,设为-1

$$\begin{bmatrix} 0 & -1 &8 & -1 & 16 \\ 1 & 7 & 9 & 15 & 17 \\ 2 & 6 & 10 & 14 & 18 \\ 3 & 5 & 11 & 13 & 19 \\ 4 & -1 & 12 & -1 & 20 \end{bmatrix}$$

因为一般是对行操作,所以先将其转置:

$$\begin{bmatrix} 0 & 1 &2 & 3 & 4 \\ -1 & 7 & 6 & 5 & -1 \\ 8 & 9 & 10 & 11 & 12 \\ -1 & 15 & 14 & 13 & -1 \\ 16& 17 & 18 & 19 & 20 \end{bmatrix}$$

先求偶数行,因为每行首元素:值为 nextstart+(numRows-1)*2 ,因此每一行的值为List[i]=[j for j in range(nextstart,nextstart+numRows)]

奇数行:为上一行的尾元素的顺延,奇数行首尾元素为-1:

List[i][0]=-1
List[i][numRows-1]=-1
List[i][1:numRows-1]=list(range(List[i-1][numRows-1]+1,List[i-1]

接下来转置回来,将二维转一维,然后去掉数组中的-1和值大于序列长度的位置:

List=[i for item in List for i in item if i!=-1 and i<length]

最后根据一维数组对应位置的值就是新字符串对应下标的字符

for j in range(length):
            string[j]=s[List[j]]
        return "".join(string) 

其中numrows=1的 情况要单独考虑

# @lc code=start
class Solution:
    def convert(self, s: str, numRows: int) -> str:
        length=len(s)
        if length<=numRows or numRows==1 :
            return s

        row=(length//((numRows-1)*2)+1)*2
        List=[[-1 for m in range(numRows)] for l in range(row)]
        nextstart=0
    #第i行
        for i in range(row):
#如果是偶数行:

            if(i % 2) == 0:
                List[i]=[j for j in range(nextstart,nextstart+numRows)]
                nextstart=nextstart+(numRows-1)*2
            else:
                List[i][0]=-1
                List[i][numRows-1]=-1
                List[i][1:numRows-1]=list(range(List[i-1][numRows-1]+1,List[i-1][numRows-1]+1+numRows-2))[::-1]
        List=list(list(i) for i in zip(*List))
        List=[i for item in List for i in item if i!=-1 and i<length]
        string=list(range(length))
        for j in range(length):
            string[j]=s[List[j]]
        return "".join(string) 
            

改进:

方法一:按行排序
思路

通过从左向右迭代字符串,我们可以轻松地确定字符位于 Z 字形图案中的哪一行。

算法

我们可以使用 \(\text{min}( \text{numRows}, \text{len}(s))\) 个列表来表示 Z 字形图案中的非空行。

从左到右迭代 s,将每个字符添加到合适的行。可以使用当前行和当前方向这两个变量对合适的行进行跟踪。

只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zigzag-conversion
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

c++ 函数进阶

  • 内联函数
  • 引用变量
  • 如何按引用传递参数
  • 默认参数
  • 函数重载指函数参数的数目和类型 不同,不能通过返回类型区别两个同名函数。函数重载不可以根据返回类型区分
  • 函数模板具体化

1、内联函数

常规函数和内联函数之间主要的不同是在于c++编译器如何将他们组合到程序中。

定义内联函数:

  • 在函数声明前 加上关键字 inline
  • 在函数定义前加上关键字 inline

c语言使用宏来实现内联函数功能:

#define SQUARE(X) X*X

2、引用变量

int & rate = rats

引用是变量的别名,主要作用是用于函数的形参,通过将引用变量用作参数,函数将使用原始数据,而不是副本。

c++使用&来声明引用,int & 表示指向int的引用

函数默认参数

char * left(const char *str,int n =1)

参数n的默认值为1,如果不传递n,就默认为一,否则传递的值将覆盖1

函数重载:

void print(const char *str,int width);
void print(double d,int width)
void print(long l, int width)

c++允许定义函数名相同的函数,但必须是输入参数的数量和类型不同,如果只是返回类型相同,c++认为这两个是一个函数。

函数模板: template <typename Anytype> 可以理解为 anyname就是我们的一种数据类型,根据实际情况函数中可以使用不同的数据类型

定义函数模板:

template <typename Anytype>
void Swap(Anytype &a,Anytype &b)
{
       函数体
}
or


template <class  Anytype>
void Swap(Anytype &a,Anytype &b)
{
       函数体
}

调用:直接 使用 Swap(int x,int y)

类模板:

template<typenameT>
classStack
{
}