今天的字符串相关题目,感觉不同语言写法差异大,因为python有很多字符串可以直接调用的库,很方便。但是带来的困惑就是,想到用库后通过后,再用不想调库的方法去写C感觉特别麻烦,而且好久没写C了。想的是二刷的时候学一下C++,用C++一点一点写。一刷就先这样,看懂随想录的就可以。还发现python相关数据结构的操作不是很熟悉,搜索了一些好的博客供以后参考。
LeetCode344-反转字符串
题目描述:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
输入:s = [“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]
题目链接:https://leetcode.cn/problems/reverse-string/
解题思路
- 双指针法
1 双指针法
- 定义两个指针
- 一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
left, right = 0, len(s) - 1
# 该方法已经不需要判断奇偶数,经测试后时间空间复杂度比用 for i in range(right//2)更低
# 推荐该写法,更加通俗易懂
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
class Solution(object):
def reverseString(self, s):
"""
:type s: List[str]
:rtype: None Do not return anything, modify s in-place instead.
"""
for i in range(len(s)/2):
temp = s[i];
s[i] = s[len(s)-1-i];
s[len(s)-1-i] = temp;
2 心得体会
字符串反转互换前后元素即可,简单的题目。
LeetCode541-反转字符串Ⅱ
题目描述:给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
输入:s = “abcdefg”, k = 2
输出:“bacdfeg”
题目链接:https://leetcode.cn/problems/reverse-string-ii/
解题思路
- 分段解法
- 讨论范围
1 分段解法
- 在遍历字符串的过程中,只要让 i + = ( 2 ∗ k ) i += (2 * k) i+=(2∗k),即 i i i 每次移动 2 ∗ k 2 * k 2∗k 就可以,然后判断是否需要有反转的区间
python写法1
class Solution:
def reverseStr(self, s: str, k: int) -> str:
"""
1. 使用range(start, end, step)来确定需要调换的初始位置
2. 对于字符串s = 'abc',如果使用s[0:999] ===> 'abc'。字符串末尾如果超过最大长度,则会返回至字符串最后一个值,这个特性可以避免一些边界条件的处理。
3. 用切片整体替换,而不是一个个替换.
"""
def reverse_substring(text):
left, right = 0, len(text) - 1
while left < right:
text[left], text[right] = text[right], text[left]
left += 1
right -= 1
return text
res = list(s)
for cur in range(0, len(s), 2 * k):
res[cur: cur + k] = reverse_substring(res[cur: cur + k]) # cur+k如果超出list的index范围也没事,相当于res[cur:]
return ''.join(res) # 将list转为str返回,用join
python写法2:切片
class Solution:
def reverseStr(self, s: str, k: int) -> str:
# Two pointers. Another is inside the loop.
p = 0
while p < len(s):
p2 = p + k
# Written in this could be more pythonic.
s = s[:p] + s[p: p2][::-1] + s[p2:] # 步长为负,从start->end倒着切,start、end均为空,表示所有
p = p + 2 * k
return s
2 讨论范围
- 每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符
class Solution(object):
def reverseStr(self, s, k):
"""
:type s: str
:type k: int
:rtype: str
"""
s = list(s);
if len(s) < k:
for i in range(len(s)/2):
temp = s[i];
s[i] = s[len(s)-1-i];
s[len(s)-1-i] = temp;
else:
count = 1;
while(len(s) >= 2*k*count):
for i in range((2*count-2)*k, (4*count-3)*k/2): # [0, ((0k+1k)/2)),[2k, ((2k+3k)/2))...
# 即[反转起点, 要反转区间的中值)
temp = s[i];
s[i] = s[(2*count-1)*k-1-i+(count-1)*2*k]; # 这里要加上(n-1)*2k, 因为要达到-0,-1这样的效果,直接-i是减多了,要加回来
s[(2*count-1)*k-1-i+(count-1)*2*k] = temp;
count += 1;
if len(s) - 2*k*(count-1) < k:
for i in range((2*count-2)*k, ((2*count-2)*k+len(s))/2):
temp = s[i];
s[i] = s[len(s)-1-i+(count-1)*2*k];
s[len(s)-1-i+(count-1)*2*k] = temp;
elif len(s) - 2*k*(count-1) < 2 * k:
for i in range((2*count-2)*k, (4*count-3)*k/2):
temp = s[i];
s[i] = s[(2*count-1)*k-1-i+(count-1)*2*k];
s[(2*count-1)*k-1-i+(count-1)*2*k] = temp;
s = "".join(s);
return s;
注意:当需要固定规律一段一段去处理字符串的时候,要想想在for循环的表达式上做做文章!!!
按照讨论来写这道题真的是痛苦经历了 : (
真的别太荒谬TwT
3 心得体会
这道题没有想到以2k作为循环分段来写,自己设置计数器讨论,越讨论边界越蒙,需要举例子在脑子里面跑,越跑越糊涂,太荒谬了。
以后分段的题目一定先考虑for循环一段一段处理!
还有就是python字符串的特有操作:切片分割等不是很熟练,一般都转化为list做,要再看看切片的操作。python字符串相关操作:https://blog.csdn.net/weixin_37600771/article/details/83856577
剑指offer05-替换空格
题目描述:请实现一个函数,把字符串 s 中的每个空格替换成"%20"
输入:s = “We are happy.”
输出:“We%20are%20happy.”
题目链接:https://leetcode.cn/problems/ti-huan-kong-ge-lcof/
解题思路
- 普通解法
- 双指针法
1 普通解法
- 开辟新数组,遇到空格就给新数组%20,不遇到空格就直接复制
python写法1:转list
class Solution(object):
def replaceSpace(self, s):
"""
:type s: str
:rtype: str
"""
s = list(s);
for i in range(len(s)):
if s[i] == ' ':
s[i] = '%20';
s = "".join(s);
return s;
python写法2:字符串做
class Solution(object):
def replaceSpace(self, s):
"""
:type s: str
:rtype: str
"""
res = ""
for ch in s:
if ch ==" ":
res += "%20";
else:
res += ch;
return res;
2 双指针法
- 扩充数组到每个空格替换成"%20"之后的大小
- 从后向前替换空格,也就是双指针法
- i指向新长度的末尾,j指向旧长度的末尾
- 不需要额外开辟空间 (感觉只有cpp没开辟,看了C和python都申请了新空间复制一份s
python写法1
class Solution:
def replaceSpace(self, s: str) -> str:
counter = s.count(' ')
res = list(s)
# 每碰到一个空格就多拓展两个格子,1 + 2 = 3个位置存’%20‘
res.extend([' '] * counter * 2)
# 原始字符串的末尾,拓展后的末尾
left, right = len(s) - 1, len(res) - 1
while left >= 0:
if res[left] != ' ':
res[right] = res[left]
right -= 1
else:
# [right - 2, right), 左闭右开
res[right - 2: right + 1] = '%20'
right -= 3
left -= 1
return ''.join(res)
python写法2:切片
class Solution:
def replaceSpace(self, s: str) -> str:
# method 1 - Very rude
return "%20".join(s.split(" "))
# method 2 - Reverse the s when counting in for loop, then update from the end.
n = len(s)
for e, i in enumerate(s[::-1]):
print(i, e)
if i == " ":
s = s[: n - (e + 1)] + "%20" + s[n - e:]
print("")
return s
注意:
很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
好处:
Ⅰ. 不用申请新数组。
Ⅱ. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。(记得以前学C时候写过类似题目,我就是从前向后每次都向后移动。
3 心得体会
看了标准答案后感觉除了cpp是在原数组上面操作,其他的像是python转list也是开辟新空间,c也开辟了一块新的空间,包括python切片这个操作也是开辟新空间复制一份s。所以我感觉这个直接开辟一个新的字符串那样写法也行,虽然就没什么技术含量了。但是平时练习的话写一下,熟悉一下这个思路,双指针+从后向前遍历也很好,二刷的时候写这个算法。
python list操作:https://blog.csdn.net/hu1258123819/article/details/91895869
LeetCode151-翻转字符串里的单词
题目描述:给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s 中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
输入:s = “the sky is blue”
输出:“blue is sky the”
题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/
解题思路
- 调库解法
- 不调库解法
1 调库解法
- 使用split库函数,分隔单词,然后定义一个新的string字符串,最后再把单词倒序相加
class Solution(object):
def reverseWords(self, s):
"""
:type s: str
:rtype: str
"""
l = s.split(" ", -1);
res = "";
while "" in l:
l.remove("");
for i in range(len(l)/2):
temp = l[i];
l[i] = l[len(l)-1-i];
l[len(l)-1-i] = temp;
for str in l:
res += str + " ";
res = res.strip();
return res;
2 不调库解法
- 移除多余空格
- 将整个字符串反转
- 将每个单词反转
- 先整体反转再局部反转
python写法1
class Solution:
#1.去除多余的空格
def trim_spaces(self, s):
n = len(s)
left = 0
right = n-1
while left <= right and s[left] == ' ': #去除开头的空格
left += 1
while left <= right and s[right] == ' ': #去除结尾的空格
right = right-1
tmp = []
while left <= right: #去除单词中间多余的空格
if s[left] != ' ':
tmp.append(s[left])
elif tmp[-1] != ' ': #当前位置是空格,但是相邻的上一个位置不是空格,则该空格是合理的
tmp.append(s[left])
left += 1
return tmp
#2.翻转字符数组
def reverse_string(self, nums, left, right):
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
return None
#3.翻转每个单词
def reverse_each_word(self, nums):
start = 0
end = 0
n = len(nums)
while start < n:
while end < n and nums[end] != ' ':
end += 1
self.reverse_string(nums, start, end-1)
start = end + 1
end += 1
return None
#4.翻转字符串里的单词
def reverseWords(self, s): #测试用例:"the sky is blue"
l = self.trim_spaces(s) #输出:['t', 'h', 'e', ' ', 's', 'k', 'y', ' ', 'i', 's', ' ', 'b', 'l', 'u', 'e'
self.reverse_string(l, 0, len(l)-1) #输出:['e', 'u', 'l', 'b', ' ', 's', 'i', ' ', 'y', 'k', 's', ' ', 'e', 'h', 't']
self.reverse_each_word(l) #输出:['b', 'l', 'u', 'e', ' ', 'i', 's', ' ', 's', 'k', 'y', ' ', 't', 'h', 'e']
return ''.join(l) #输出:blue is sky the
python写法2
class Solution:
def reverseWords(self, s: str) -> str:
# method 1 - Rude but work & efficient method.
s_list = [i for i in s.split(" ") if len(i) > 0]
return " ".join(s_list[::-1])
# method 2 - Carlo's idea
def trim_head_tail_space(ss: str):
p = 0
while p < len(ss) and ss[p] == " ":
p += 1
return ss[p:]
# Trim the head and tail space
s = trim_head_tail_space(s)
s = trim_head_tail_space(s[::-1])[::-1]
pf, ps, s = 0, 0, s[::-1] # Reverse the string.
while pf < len(s):
if s[pf] == " ":
# Will not excede. Because we have clean the tail space.
if s[pf] == s[pf + 1]:
s = s[:pf] + s[pf + 1:]
continue
else:
s = s[:ps] + s[ps: pf][::-1] + s[pf:]
ps, pf = pf + 1, pf + 2
else:
pf += 1
return s[:ps] + s[ps:][::-1] # Must do the last step, because the last word is omit though the pointers are on the correct positions,
拓展优化:快慢指针移除空格
- 与数组移除元素那道题目类似
- 快指针用来扫字符串确认元素是否需要保留,若需要,则赋给慢指针,若不需要,则快指针继续向前走
- 慢指针用来指向要更新的位置
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0; //整体思想参考https://programmercarl.com/0027.移除元素.html
for (int i = 0; i < s.size(); ++i) { //
if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); //slow的大小即为去除多余空格后的大小。
}
3 心得体会
自己写的时候用了python内置的库,写完想了一下要是用C怎么写,感觉自己能写出来去掉空格的逻辑,但是先整体反转再局部反转这个算法并没有想到,二刷的时候可以写写。
切片写法还是比较简洁,如果用python,切片要熟练。
去掉空格的方法,随想录里面的快慢指针移除空格要再看看。
剑指Offer58-II. 左旋转字符串
题目描述:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
输入: s = “abcdefg”, k = 2
输出: “cdefgab”
题目链接:https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/
解题思路
- 普通解法
- 切片法
- 不额外申请空间解法
1 普通做法
- 开辟新的数组去存前n个,然后将n至末尾的移动到前面,再将前n个添在后面
class Solution(object):
def reverseLeftWords(self, s, n):
"""
:type s: str
:type n: int
:rtype: str
"""
s = list(s);
l = [];
for i in range(n):
l.append(s[i]);
for i in range(len(s)-n):
s[i] = s[n+i];
for i in range(len(s)-n, len(s)):
s[i] = l[i-len(s)+n];
return "".join(s);
2 切片法
- 切片重组即可
# 方法一:可以使用切片方法
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
return s[n:] + s[0:n]
3 不额外申请空间解法
- 反转区间为前n的子串
- 反转区间为n到末尾的子串
- 反转整个字符串
- 先局部反转再整体反转
# 方法二:也可以使用上文描述的方法,有些面试中不允许使用切片,那就使用上文作者提到的方法
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
s = list(s)
s[0:n] = list(reversed(s[0:n]))
s[n:] = list(reversed(s[n:]))
s.reverse()
return "".join(s)
# 方法三:如果连reversed也不让使用,那么自己手写一个
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
def reverse_sub(lst, left, right):
while left < right:
lst[left], lst[right] = lst[right], lst[left]
left += 1
right -= 1
res = list(s)
end = len(res) - 1
reverse_sub(res, 0, n - 1)
reverse_sub(res, n, end)
reverse_sub(res, 0, end)
return ''.join(res)
# 同方法二
# 时间复杂度:O(n)
# 空间复杂度:O(n),python的string为不可变,需要开辟同样大小的list空间来修改
#方法四:考虑不能用切片的情况下,利用模+下标实现
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
new_s = ''
for i in range(len(s)):
j = (i+n)%len(s)
new_s = new_s + s[j]
return new_s
4 心得体会
先用python切片做的,然后考虑了下C怎么写,又写了新开辟数组去装前n个,移动后len-n个向前,再在结尾加上前n个即可。但是这个写法不如方法四取模方便。
总结
字符串本来写前会觉得是比较简单的章节,结果由于题目调库切片写的容易,但是换成C麻烦这个问题,感觉写博客总结会有很多,所以博客放在第二天写了(耽误了不少时间,惭愧TwT),因为还要看懂随想录的代码。二刷的时候不要调库写了,按照算法自己来写。