算法65----字符串

题目:

  1. 判断两个字符串是否互为变形词
  2. 字符串中数字子串的求和
  3. 去掉字符串中连续出现k个0的子串
  4. 判断两个字符串是否互为旋转词
  5. 将整数字符串转成整数值
  6. 替换字符串中连续出现的指定字符串
  7. 字符串的统计字符串
  8. 判断字符数组中是否所有的字符都只出现过一次
  9. 在有序但含有空的数组中查找字符串
  10. 字符串的调整与替换
  11. 翻转字符串
  12. 数组中两个字符串的最小距离
  13. 添加最少字符使字符串整体都是回文字符
  14. 括号字符串的有效性和最长有效长度
  15. 公式字符串求值
  16. 0左边必有1的二进制字符串数量
  17. 拼接所有字符串产生字典顺序最小的大写字符串
  18. 找到字符串的最长无重复字符子串
  19. 找到被指的新类型字符
  20. 最小包含子串的长度
  21. 回文最少分割数
  22. 字符串匹配问题
  23. 字典树(前缀树)的实现

、判断两个字符串是否互为变形词

题目:给定两个字符串str1和str2,如果str1和str2中出现的字符种类一样且每种字符出现的次数也一样,则str1和str2互为变形词。
请实现函数判断两个字符串是否互为变形词。
举例:
str1=”123″, str2=”231″, 返回true;
str1=”123″, str2=”2331″,返回false。

思路:

 1. 首先比较两个字符串的长度,长度不同肯定是false。

 2. 如果长度相同,新建一个字典,用以存储每个字符出现次数。

 3. 遍历str1,在str1 中出现一次就加1,遍历str2,在str2 中出现一次就减1,最后遍历完str2没有出现负值,就返回true。

代码:

from collections import Counter
def IsDeformation(str1,str2):
    if not str1 or not str2 or len(str1) != len(str2):
        return False
    countstr1 = Counter(str1)
    for s2 in str2:
        if s2 in countstr1:
            countstr1[s2] -= 1
            if countstr1[s2] < 0:
                return False
        else:
            return False
    return True
str1 = '1234'
str2 = '2313'
IsDeformation(str1,str2)

 二、字符串中数字子串的求和

 给定一个字符串str,求其中全部数字串所代表的数字之和

  1. 忽略小数点,“ A1.3 ” 表示的数字就是包含两个数字 1 和 3

  2. 紧贴数字的左边出现 “-”,其连续出现的数量如果为奇数,就视为 负,如果为偶数,就视为 正 “ A-1BC–23” 表示的是 -1 和 23

思路:时间复杂度是O(N),空间复杂度是O(1)

首先定义三个变量, res表示目前的累加和,num表示当前收集到的数字,布尔型变量flag表示将num加到res中,num是正还是负.

代码:

def numSum(arr):
    if not arr:
        return 0
    num , res = 0 , 0
    flag = 1
    i = 0
    while i < len(arr):
        while i < len(arr) and arr[i] == '-':
            flag *= -1
            i += 1
        while i<len(arr) and arr[i].isdigit():
            num = num*10 + int(arr[i])
            i += 1
        if i<len(arr) and not arr[i].isdigit():
            i += 1
        if num:
            res += flag*num 
        num ,flag = 0 , 1
    return res
arr = 'A1.3'
numSum(arr)
a="A-1BC--23"
numSum(a)

 三、去掉字符串中连续出现k个0的子串

给定一个字符串str和一个整数k,如果str中正好有连续的k个’0’字符出现时,把k个连续的’0’字符去除,返回处理后的字符串。

举例:

str=”A00B”,k=2,返回”AB”;

str=”A0000B000″,k=3,返回”A0000B”。

思路:

 采用count记录连续0的个数,若count==k,则将str连续的0删除。

代码:

def removeKzeros(arr,k):
    if not arr or k == 0:
        return arr
    count , i = 0 , 0
    while i < len(arr):
        if arr[i] != '0':
            i += 1
        while i < len(arr) and arr[i] == '0':
            count += 1
            i += 1
        if count and count == k:
            arr = arr[:i-count]+arr[i:]
        count = 0
    return arr
           
arr = 'A00B'
k = 2
removeKzeros(arr,k)
arr="A0000B000"
k=3
removeKzeros(arr,k)

 四、判断两个字符串是否互为旋转词

如果一个字符串str,把字符串str前面任意的部分挪到后面形成的字符串叫做str的旋转词。
如str=”12345″,str的旋转词有”12345″、”23451″、”34512″、”45123″、”51234″。
给定两个字符串a和b,请判断a和b是否互为旋转词。
举例:
a=”cdab”,b=”abcd”,返回true;
a=”1ab2″,b=”ab12″,返回false;
a=”2ab1″,b=”ab12″,返回true。
要求:
如果a和b长度不一样,那么a和b必然不互为旋转词,可以直接返回false。
当a和b长度一样,都为N时,要求解法的时间复杂度为O(N)。

思路:

将两个b拼接在一起赋值给c,查看c中是否包含字符串a,若包含,则返回true;否则返回false。

代码:

def isRotation(a,b):
    if len(a) != len(b):
        return False
    c = b+b
    return (a in c)

a = 'cdab'
b = 'abcd'
isRotation(a,b)
        

 五、将整数字符串转成整数值

给定一个字符串str,如果str符合日常书写的整数形式,并且属于32位整数的范围,返回所代表的整数值,否则返回0。
eg
str = “123”,返回123.
str = “023”,因为“023”不符合日常的书写习惯,所以返回0.
str = “A23”,返回0;
str = “0”,返回0;
str= “2147483647”,返回2147483647.
str = “2147483648”,因为溢出了,所以返回0;
str = “-123”,返回-123;

思路:

空字符串输入、正负符号、非法字符、整型溢出【最难处理】

  1.  检查日常书写,非法字符
    • 第一个既不是负号,也不是数字的情况,如:‘A12’
    • 第一个是负号,但是整个字符串的长度只有1,或者负号后面跟个0的情况,如‘-“或者”-012“
    • 以0开头,而且整个字符串的长度大于1,如:‘012”
    • 从第二个开始依次遍历字符串,一旦出现不是数字的情况立即返回FALSE
  2. 字符转数字操作
    • 字符串为空或者字符串的长度为0
    • 字符串中存在不合法的字符
    • 第一个字符是否为负号的情况

处理整数溢出:

当发生溢出时,取最大或最小的int值。即大于正整数能表示的范围时返回MAX_INT:2147483647;小于负整数能表示的范围时返回MIN_INT:-2147483648。

我们先设置一些变量:

  • sign用来处理数字的正负,当为正时sign > 0,当为负时sign < 0
  • n存放最终转换后的结果
  • c表示当前数字

处理溢出:

  • 如果我们要转换的字符串是”2147483697″,那么当我扫描到字符’9’时,判断出214748369 > MAX_INT / 10 = 2147483647 / 10 = 214748364(C语言里,整数相除自动取整,不留小数),则返回0;
  • 如果我们要转换的字符串是”2147483648″,那么判断最后一个字符’8’所代表的数字8与MAX_INT % 10 = 7的大小,前者大,依然返回0。

代码:

#判断是否为合法
def isValid(s):
    if s[0] != '-' and not s[0].isdigit():
        return False
    elif s[0] == '-' and (len(s) == 1 or s[1] == '0'):
        return False
    elif s[0] == '0' and len(s) > 1:
        return False
    for i in range(len(s)):
        if not s[i].isdigit():
            return False
    return True
def convert(s):
    #判断为空
    if not s:
        return 0
    if not isValid(s):
        return 0
    sign = -1 if s[0] == '-' else 1
    q = 214748364 #-2^31 // 10
    maxr = 7
    res , cur = 0 , 0 
    start = 0 if sign == 1 else 1
    for i in range(start,len(s)): 
        cur = int(s[i])
        if res > q or res == q and cur > maxr:
            return 0
        res = res * 10 + cur 
    if sign and res == 2147483648:
        return 0
    return res * sign
s = '2147483637'
convert(s)
                

 六、替换字符串中连续出现的指定字符串

给定三个字符串str、from和to,已知from字符串中无重复字符,把str中所有from的子串全部替换成to字符串,对连续出现from的部分要求只替换成一个to字符串,返回最终的结果字符串

  举例:

    str=”123abc”,from=”abc”,to=”4567″,返回”1234567″

    str=”123″,from=”abc”,to=”456″,返回”123″

    str=”123abcabc”,from=”abc”,to=”X”,返回”123X”

思路:

先将str中含from的都替换成0*len(from),然后将不等于0的用cur暂存,遇到0则 res + cur + to。

 

把str看作字符类型的数组,首先把str中from部分所有位置的字符编码设为0(即空字符),如”12abcabca4″,from=”abc”,处理后str=[‘1′,’2′,0,0,0,0,0,0,’a’,’4′]。
具体步骤如下:
1 生成整数变量match,标识目前匹配到from字符串的什么位置,初始时match=0;
2 从左到右遍历str中每个字符,假设当前遍历到str[i];
3 若str[i]==from[match],若match是from最后一个字符的位置,说明在str中发现了from字符串,则从i位置向前的M个位置,都把字符编码设为0,M为from的长度,设置完成后令match=0;若match不是from最后一个字符的位置,则match++。继续遍历str的下一个字符;
4 若str[i]!=from[match],说明匹配失败,令match=0,即回到from开头重新匹配。继续遍历str的下一个字符;

代码:

def replace(s,f,to):
    if not s or f == None:
        return s
    arr = list(s)
    j = 0
    for i in range(len(s)):
        if s[i] == f[j]:
            if j == len(f)-1:
                s = s[:i-j] + '0' * len(f) + s[i+1:]
                # s[i-j+1:i+1] = '0' * len(f)
                j = 0
            else:
                j += 1
        else:
            j = 0
    res = ''
    cur = ''
    for i in range(len(s)):
        if s[i] != '0':
            cur = cur + s[i]
        if s[i] == '0' and (i == 0 or s[i-1] != '0'):
            res = res + cur + to
            cur = ''
    if cur:
        res = res + cur
    return res
s = '12abcabc3'
f = 'abc'
to = 'X'
replace(s,f,to)

 七、字符串的统计字符串

《算法65----字符串》

思路:

一个变量count和一个结果变量res,若s[i] == s[i+1],count +=1 ,否则count= 1

代码:

def getCountString(s):
    if not s:
        return s
    count = 1
    res = ''
    for i in range(len(s)-1):
        if s[i] == s[i+1]:
            count+=1
        else:
            res = res + '_' + s[i]+'_'+str(count)
            count = 1
    if count == 1:
        res = res + '_' + s[-1] + '_' + '1'
        
    return res[1:]
s = 'aaabbadddffc'
getCountString(s)

 8、判断字符数组中是否所有的字符都只出现过一次

《算法65----字符串》

要求1:采用字典来实现,比较简单。

要求2:考察排序。

  1. 先将chas排序,排序后相同的字符就放在一起,易判断有没有重复字符。
  2. 重点选择什么排序算法,保证空间复杂度为O(1)且时间复杂度较小。
  • 时间O(N)、空间O(1)的没有排序算法可以做到。
  • 时间O(NlogN):归并【其实递归也需要辅助空间】、快排【空间最低O(logN)】、希尔【时间不稳定,可能会变成O(N2)】、堆排序【可以】。
  • 结果选择堆排序,但要用非递归来实现,递归需要额外的空间。

 9、在有序但含有空的数组中查找字符串

10、字符串的调整与替换【倒着复制】

《算法65----字符串》

《算法65----字符串》

 

原问题思路:

  • 遍历第一遍:得到两个信息,chas的左半区有多大,记为len,左半区的空格有多少,记为num。

可知最终长度为len+2*num,替换字母为%20.

  • 从右往左遍历第二遍:遇到字母复制到新长度最后位置,遇到空格则加入02%。

《算法65----字符串》

 补充问题思路:

从右往左倒着复制,遇到数字直接复制,遇到*不复制,当把所有数字复制完,把左半区全部设置成*即可。

11、翻转字符串

《算法65----字符串》

思路:整体全部翻转,再每个单词翻转

《算法65----字符串》

代码:

def rotateWord(words):
    if words == None or len(words) == 0:
        return
    words = list(words[::-1])
    l , r = -1 , -1
    for i in range(len(words)):
        if words[i] != ' ':
            l = i if (i == 0 or words[i-1] ==' ') else l
            r = i if (i == len(words)-1 or words[i+1] == ' ') else r
        if l != -1 and r != -1:
            # reverse(words,l,r)
            words[l:r+1] = words[l:r+1][::-1]
            l , r = -1 , -1
    return ''.join(words)
    
# def reverse(words,l,r):
#     tmp = ''
#     while l<r:
#         tmp = words[l]
#         words[l] = words[r]
#         words[r] = tmp
#         l += 1
#         r -= 1
words = 'dogs love pigs'
rotateWord(words)

《算法65----字符串》

思路一:三步反转法 :(X^TY^T)^T=YX

《算法65----字符串》

 

12、数组中两个字符串的最小距离

《算法65----字符串》

思路:两个变量分别更新str1和str2的位置,res记录两个变量差的最小值。

《算法65----字符串》

代码:

import sys
def minDistance(strs,str1,str2):
    if not strs or not str1 or not str2:
        return -1
    if str1 == str2:
        return 0
    last1 , last2 , res = -1 , -1 , sys.maxsize
    for i in range(len(strs)):
        if strs[i] == str1:
            if last2 != -1:
                res = min(res,i - last2)
            last1 = i
        if strs[i] == str2:
            if last1 != -1:
                res = min(res,i - last1)
            last2 = i
    return res if res != sys.maxsize else -1
strs = ['3','1','3','3','3','2','3','1']
str1 = '1'
str2 = '2'
minDistance(strs,str1,str2)

进阶问题:采用一个记录来存储结果,查询时间记录为O(1),但生成该记录时间复杂度为O(N2),空间复杂度也为O(N2)。

该记录为一个两层字典,外层字典的value也是一个字典。

《算法65----字符串》

 13、添加最少字符使字符串整体都是回文字符串

《算法65----字符串》

思路:动态规划时间O(N2)

(1)先求出最少需要添加多少个字符串才能补成回文串?

str的长度为N,生成N×N的dp矩阵,dp[i][j]的含义是子串str[i…j]最少添加几个字符可以使str[i…j]整体都是回文串。dp[i][j]的求法如下:

  • 如果i == j,说明此时只有一个字符,本身就是回文串,dp[i][j] = 0。
  • 如果str[i…j]有两个字符,如果这个字符相同dp[i][j] = 0。否则dp[i][j] = 1。
  • 如果str[i…j]多于两个字母,如果str[i] == str[j]。则dp[i][j] = dp[i+1][j-1]。否则,dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1。

(2)根据求得的dp矩阵来获得一种回文结果:【类似最长公共子序列】

dp[0][N-1]的值代表整个字符串最少需要添加几个字符,所以,如果最后的结果记为字符串res,res的长度为 N + dp[0][N-1],然后依次设置res左右两头的字符。

代码:

def getPalindrome(str1):
    def getdp(str1):
        dp = [[0 for i in range(len(str1))] for j in range(len(str1))]
        for j in range(1, len(str1)):
            dp[j-1][j] = 0 if str1[j-1] == str1[j] else 1
            for i in range(j-2, -1, -1):
                if str1[i] == str1[j]:
                    dp[i][j] = dp[i+1][j-1]
                else:
                    dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1
        return dp


    if str1 == None or len(str1) < 2:
        return str1
    dp = getdp(str1)
    res = [0 for i in range(len(str1)+dp[0][len(str1)-1])]
    i = 0
    j = len(str1) - 1
    resl = 0
    resr = len(res) - 1
    while i <= j:
        if str1[i] == str1[j]:
            res[resl] = str1[i]
            res[resr] = str1[j]
            i += 1
            j -= 1
        elif dp[i+1][j] < dp[i][j-1]:
            res[resl] = str1[i]
            res[resr] = str1[i]
            i += 1
        else:
            res[resl] = str1[j]
            res[resr] = str1[j]
            j -= 1
        resl += 1
        resr -= 1
    return ''.join(res)

 进阶问题思路:假设str的长度为N,strlps的长度为M,则整体回文串的长度为2×N – M。整个过程类似 “剥洋葱”。比如:

str = ‘A1BC22D1EF’ , str1 = ‘1221’,先剥1。A—-1BC22D1——EF,1的外壳是left= A,right = EF,则左边补(right逆序+left),右边补(left逆序+right)。即FEA—-1BC22D1——-AEF。

第二层为2,:FEA1—-BC——22——-D—-1AEF,left=BC,right= D。同理。

 

 

 

进阶问题代码:

def getPalindrome2(str1, strlps):
    if str1 == None or len(str1) == 0 or strlps == None or len(strlps) == 0:
        return 
    res = [0 for i in range(2*len(str1)-len(strlps))]
    lstr = 0
    rstr = len(str1)-1
    llps = 0
    rlps = len(strlps)-1
    lres = 0
    rres = len(res)-1
    while llps <= rlps:
        temp1 = lstr
        temp2 = rstr
        while str1[lstr] != strlps[llps]:
            lstr += 1
        while str1[rstr] != strlps[rlps]:
            rstr -= 1
        for i in range(temp1, lstr): 
            res[lres] = str1[i]
            res[rres] = str1[i]
            lres += 1
            rres -= 1
        for i in range(temp2, rstr, -1):
            res[lres] = str1[i]
            res[rres] = str1[i]
            lres += 1
            rres -= 1
        res[lres] = str1[lstr]
        res[rres] = str1[rstr]
        lstr += 1
        rstr -= 1
        lres += 1
        rres -= 1
        llps += 1
        rlps -= 1
    return ''.join(res)

 14、括号字符串的有效性和最长有效长度

《算法65----字符串》

《算法65----字符串》

《算法65----字符串》

 原问题思路:采用一个变量记录‘)’减去‘(’的差,若当前)-(>0则返回FALSE

《算法65----字符串》

 

补充问题思路:动态规划,时间O(N),空间O(N)

dp[i]表示从0到第i个字符串的最长有效括号子串。

需同时考虑两种情况:

  1. 包含关系(()),dp[i] = dp[i-1] + 2【若 s [ i ] == ‘)’ ,且 s [ i – dp [i-1] -1 ] == ‘(‘】
  2. 并列关系()(),dp[i] = dp[i] + dp [  i – dp [i-1] -2 ] 

《算法65----字符串》

15、公式字符串求值

《算法65----字符串》

思路:采用栈存储数字和加减符号,乘除在放入栈中已计算出结果。变量pre记录数字。括号就递归。

1、遇到数字:采用pre变量保存。

2、遇到符号:存入栈中,存入之前先把栈中的乘除结果算出来

3、遇到左括号:递归计算

4、遇到右括号:计算栈中的结果。

 

 

17、拼接所有字符串产生字典顺序最小的大写字符串

《算法65----字符串》

思路:排序本身时间O(NlogN)

假设两个字符分别是a,b。a和b拼起来的字符串表示为a.b,那么如果a.b的字典顺序小于b.a,就把a放在前面,否则把b放在前面。每两两字符之间都按照这个标准进行比较,以此标准排序后,最后串起来的结果就是正确答案。

如 ‘b’ , ‘ba’,’b’和‘ba’排序后,’ba’应与’b’位置交换,‘ba’在前,‘b’在后。

代码:cmp_to_key是因为python3中没有cmp这种用法,取代的。

def lowestString(chas):
    if chas == None or len(chas) == 0:
        return ""
    from functools import cmp_to_key
    chas = sorted(chas, key=cmp_to_key(lambda x,y: 1 if x+y > y+x else -1))
    return ''.join(chas)
chas = ['b','ba','abc','dba']
lowestString(chas)

 

18、找到字符串的最长无重复字符子串

给定一个字符串str,返回str的最长无重复字符子串的长度。

举例:

str = ‘abcd’,返回4.

str = ‘aabcb’,最长无重复字符子串为’abc’,返回3.

要求:

如果str的长度为N,请实现时间复杂度为O(N)的方法。

思路:时间O(N),空间O(N)

遍历字符串中的每一个元素。借助一个辅助键值对来存储某个元素最后一次出现的下标。用一个整形变量存储当前无重复字符的子串开始的下标。

《算法65----字符串》

代码:

def maxUnique(s):
    if s == None or s == '':
        return 0
    dic = {}
    res , start = 0 , -1
    for i in range(len(s)):
        if s[i] in dic:
            start = max(start,dic[s[i]])
        tmp = i - start
        dic[s[i]] = i
        res = max(res,tmp)
    return res
    
s = 'ababcadc'
maxUnique(s)

 19、找到被指的新类型字符

《算法65----字符串》

思路:从k-1位置开始向左统计大写字母的数量,根据奇偶性来判断。

《算法65----字符串》

代码:

def test(s,k):
    if not s or s == '' or k < 0 or k >= len(s):
        return ''
    uNum = 0
    for i in range(k-1,-1,-1):
        if not s[i].isupper():
            break
        uNum += 1
    if uNum % 2 == 1:
        return s[k-1:k+1]
    if s[k].isupper():
        return s[k:k+2]
    return s[k]
s='aaABCDEcNCg'
k = 7
test(s,k)

 

21、回文最少分割数【动态规划】

给定一个字符串str,返回把str全部切成回文子串的最小分割数。

《算法65----字符串》

思路:动态规划时间O(N2),空间O(N2)

定义动态规划数组dp,dp[i]的含义是子串str[0…i]至少需要切割几次,才能把str[0…i]全部切成回文子串。那么dp[len-1]就是最后的结果。

从左往右依次计算dp[i]的值,i 初始为0,具体计算过程如下:

  1. 1、假设 j 处在 0 到 i 之间,如果str[j…i]是回文串,那么dp[i]的值可能是dp[j-1] + 1,其含义是在str[0…i]上,既然str[j…i]是回文串,那么它可以自己作为一个分割的部分,剩下的部分str[0…j-1]继续做最经济的分割,也就是dp[j-1]的值。
  2. 根据步骤1的方式,让 j 在 i 到 0 的位置上枚举,那么所有可能中最小值就是dp[i]的值即dp[i] = min{dp[j-1]+1 (0<= j <= i,且str[j…i]必须是回文串)}。
  3. 如何快速方便的判断str[j…i]是否为回文串?
    • 定义一个二维数组p,如果p[j][i]为True,表示str[j…i]是回文串,否则不是。在计算dp过程中,希望能够同步、快速的计算出矩阵p。
    • p[j][i]如果为True,一定来自以下三种情况:
      • <1> str[j][i]由一个字符组成
        <2> str[j][i]由两个字符组成且两个字符相等
        <3> str[j][i]由多个字符组成,str[j] == str[i]且p[j+1][i-1] == True。
    • 在计算dp数组的过程中,位置i是从左向右依次计算的。而对于每一个i来说,又依次从 i 位置向左遍历所有的位置,以此来决策dp[i]。所以对于p[j][i]来说,p[j+1][i-1]一定已经计算过。

代码:

import sys
#从前往后遍历
def minCut(str1):
    if str1 == None or str1 == "":
        return 0
    N = len(str1)
    p = [[False for i in range(N)] for j in range(N)]
    dp = [0 for i in range(N)]
    for i in range(N):
        dp[i] = sys.maxsize
        for j in range(i, -1, -1):
            if str1[j] == str1[i] and (i-j < 2 or p[j+1][i-1]):
                p[j][i] = True
                dp[i] = min(dp[i], 0 if j-1 == -1 else dp[j-1] + 1)
    return dp[-1]

 22、字符串匹配问题【动态规划】

 

 

 

 

点赞