题目:
| (1)回文字符串
(2)括号字符串
(3)字符串的求和
(4)字符串反转
(5)字符串替换
(6)字符串删除、排序
(7)字符串的遍历
(8)字符串的匹配 (7)两个字符串
(8)字符串的子串
(9)字符串的全排列(python) |
一、判断两个字符串是否互为变形词
题目:给定两个字符串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;
思路:
空字符串输入、正负符号、非法字符、整型溢出【最难处理】
- 第一个既不是负号,也不是数字的情况,如:‘A12’
- 第一个是负号,但是整个字符串的长度只有1,或者负号后面跟个0的情况,如‘-“或者”-012“
- 以0开头,而且整个字符串的长度大于1,如:‘012”
- 从第二个开始依次遍历字符串,一旦出现不是数字的情况立即返回FALSE
- 字符转数字操作
- 字符串为空或者字符串的长度为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)
七、字符串的统计字符串
思路:
一个变量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、判断字符数组中是否所有的字符都只出现过一次
要求1:采用字典来实现,比较简单。
要求2:考察排序。
- 先将chas排序,排序后相同的字符就放在一起,易判断有没有重复字符。
- 重点选择什么排序算法,保证空间复杂度为O(1)且时间复杂度较小。
- 时间O(N)、空间O(1)的没有排序算法可以做到。
- 时间O(NlogN):归并【其实递归也需要辅助空间】、快排【空间最低O(logN)】、希尔【时间不稳定,可能会变成O(N2)】、堆排序【可以】。
- 结果选择堆排序,但要用非递归来实现,递归需要额外的空间。
9、在有序但含有空的数组中查找字符串
10、字符串的调整与替换【倒着复制】
原问题思路:
- 遍历第一遍:得到两个信息,chas的左半区有多大,记为len,左半区的空格有多少,记为num。
可知最终长度为len+2*num,替换字母为%20.
- 从右往左遍历第二遍:遇到字母复制到新长度最后位置,遇到空格则加入02%。
补充问题思路:
从右往左倒着复制,遇到数字直接复制,遇到*不复制,当把所有数字复制完,把左半区全部设置成*即可。
11、翻转字符串
思路:整体全部翻转,再每个单词翻转
代码:
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)
思路一:三步反转法 :(X^TY^T)^T=YX
12、数组中两个字符串的最小距离
思路:两个变量分别更新str1和str2的位置,res记录两个变量差的最小值。
代码:
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也是一个字典。
13、添加最少字符使字符串整体都是回文字符串
思路:动态规划时间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、括号字符串的有效性和最长有效长度
原问题思路:采用一个变量记录‘)’减去‘(’的差,若当前)-(>0则返回FALSE
补充问题思路:动态规划,时间O(N),空间O(N)
dp[i]表示从0到第i个字符串的最长有效括号子串。
需同时考虑两种情况:
- 包含关系(()),dp[i] = dp[i-1] + 2【若 s [ i ] == ')' ,且 s [ i - dp [i-1] -1 ] == '('】
- 并列关系()(),dp[i] = dp[i] + dp [ i - dp [i-1] -2 ]
15、公式字符串求值
思路:采用栈存储数字和加减符号,乘除在放入栈中已计算出结果。变量pre记录数字。括号就递归。
1、遇到数字:采用pre变量保存。
2、遇到符号:存入栈中,存入之前先把栈中的乘除结果算出来
3、遇到左括号:递归计算
4、遇到右括号:计算栈中的结果。
17、拼接所有字符串产生字典顺序最小的大写字符串
思路:排序本身时间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)
遍历字符串中的每一个元素。借助一个辅助键值对来存储某个元素最后一次出现的下标。用一个整形变量存储当前无重复字符的子串开始的下标。
代码:
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、找到被指的新类型字符
思路:从k-1位置开始向左统计大写字母的数量,根据奇偶性来判断。
代码:
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全部切成回文子串的最小分割数。
思路:动态规划时间O(N2),空间O(N2)
定义动态规划数组dp,dp[i]的含义是子串str[0…i]至少需要切割几次,才能把str[0…i]全部切成回文子串。那么dp[len-1]就是最后的结果。
从左往右依次计算dp[i]的值,i 初始为0,具体计算过程如下:
- 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]的值。
- 根据步骤1的方式,让 j 在 i 到 0 的位置上枚举,那么所有可能中最小值就是dp[i]的值,即dp[i] = min{dp[j-1]+1 (0<= j <= i,且str[j…i]必须是回文串)}。
- 如何快速方便的判断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、字符串匹配问题【动态规划】