分割字符串的所有方法
Posted
技术标签:
【中文标题】分割字符串的所有方法【英文标题】:All ways to partition a string 【发布时间】:2016-08-29 15:47:35 【问题描述】:我正在尝试找到一种有效的算法来获得对字符串进行分区的所有方法
例如对于给定的字符串 'abcd' => 'a' 'bcd' 'a' 'b' 'cd' 'a' 'b' 'c' 'd' 'ab' 'cd' 'ab' 'c' 'd' 'abc' 'd' 'a', 'bc', 'd
任何语言都会受到赞赏
提前致谢!
【问题讨论】:
在编码的快速和容易程度或运行速度方面是否有效?还有字符串的最大长度吗?随着字符串长度的增加,它会产生成倍增加的结果,并且您会很快遇到内存不足的错误。 我的目标是在这个算法中输入单个单词,我希望它在速度方面高效,但我很想看到两种不同的方法:) 不单独列出“abcd”可能是设计使然,但我认为您错过了“a”、“bc”、“d”。 是的,谢谢你! 【参考方案1】:问题分析
在每对相邻的字符之间,可以决定是否剪切。对于大小为 n 的字符串,有 n-1 个位置可以剪切或不剪切,即有两种可能。因此,大小为 n 的字符串可以按 2n-1 种方式进行分区。
输出由 2n-1 个分区组成,每个分区有 n 个字符加上分隔符。所以我们可以将输出大小描述为 f(n) = 2n-1 * n + s(n) 其中 s(n) ≥ 0 占分区分隔符和行分隔符。
因此,仅由于输出大小,解决此问题的算法必须具有指数运行时间或更糟:Ω(2n)。
(0 ≤ c * 2n = ½ * 2n = 2n-1 ≤ 2n -1 * n ≤ f(n) 对于所有 n≥k 具有正常数 c=½, k=1)
解决方案
我选择将分区表示为整数。 cutpoints
中的每一位决定是否在字符i
和i+1
之间进行切换。要遍历所有可能的分区,我们只需要遍历0
和2^(n-1) - 1
之间的所有整数。
示例:对于长度为 4 的字符串,我们遍历 0
和 2^3 - 1
或 0
和 7
或二进制:000
和 111
之间的所有整数。
# (python 2 or 3)
def all_partitions(string):
for cutpoints in range(1 << (len(string)-1)):
result = []
lastcut = 0
for i in range(len(string)-1):
if (1<<i) & cutpoints != 0:
result.append(string[lastcut:(i+1)])
lastcut = i+1
result.append(string[lastcut:])
yield result
for partition in all_partitions("abcd"):
print(partition)
内存使用情况:
我认为我的解决方案在 Python 3 中使用了 O(n)
内存。一次只生成一个分区,它被打印出来并且不再被引用。这当然会改变,如果您保留所有结果,例如通过将它们存储在列表中。
在 Python 2 中,将 range
替换为 xrange
,否则所有可能的 cutpoints
都将存储在一个列表中,因此需要指数级的内存。
JavaScript 解决方案
// ES6 generator
function* all_partitions(string)
for (var cutpoints = 0; cutpoints < (1 << (string.length - 1)); cutpoints++)
var result = [];
var lastcut = 0;
for (var i = 0; i < string.length - 1; i++)
if (((1 << i) & cutpoints) !== 0)
result.push(string.slice(lastcut, i + 1));
lastcut = i + 1;
result.push(string.slice(lastcut));
yield result;
for (var partition of all_partitions("abcd"))
console.log(partition);
使用 NodeJS v4.4.3 测试(免责声明:我之前没有使用过 NodeJS)。
【讨论】:
我尝试移植这是 javascript : function* all_partitions(string) var result, lastcut, i, cutpoints; for (cutpoints = 0; cutpoints @Ben:我认为您的运算符优先级有问题,需要使用更多括号。我将用 JS 解决方案更新我的答案... @johnLate 你是如何用 O(2^n) 替换 O(2^(n-1)*n) 的?对于 n > 2,后者不是受前者的限制吗? @arjunkhera 你说得对,我在简化时很草率。我已经更新了我的答案。 @arjunkhera 我已经考虑了更多,并在问题分析中切换到 Big-Omega 而不是 Big-O,因为此时我们对下限而不是上限感兴趣。 【参考方案2】:GeeksforGeeks 为这个问题提供了一个很好解释的解决方案:
对于字符串abcd
,将有 2^(n-1) 个,即 8 个分区。
(a)(b)(c)(d)
(a)(b)(cd)
(a)(bc)(d)
(a)(bcd)
(ab)(c)(d)
(ab)(cd)
(abc)(d)
(abcd)
解决方案的关键在于recursion
打印所有排列。
维护两个参数——要处理的下一个字符的索引和到目前为止的输出字符串。我们从要处理的下一个字符的索引开始,将由未处理的字符串形成的子字符串附加到输出字符串并递归剩余的字符串,直到我们处理整个字符串。
// Java program to find all combinations of Non-
// overlapping substrings formed from given
// string
class GFG
// find all combinations of non-overlapping
// substrings formed by input string str
static void findCombinations(String str, int index,
String out)
if (index == str.length())
System.out.println(out);
for (int i = index; i < str.length(); i++)
// append substring formed by str[index,
// i] to output string
findCombinations(str, i + 1, out +
"(" + str.substring(index, i+1) + ")" );
// driver program
public static void main (String[] args)
// input string
String str = "abcd";
findCombinations(str, 0, "");
时间复杂度为 O(2^n)
这是文章的链接:http://www.geeksforgeeks.org/print-ways-break-string-bracket-form/
【讨论】:
【参考方案3】:我只是想为遇到这个问题的任何人发布一个简单的递归解决方案。可能不是最好的方法,但这对我来说更容易理解和实施。如果我错了,请纠正我。
def party(s:str, P:list, res:list) -> None :
"""Recursively generates all partitions of a given string"""
res.append(P+[s])
for i in range(1,len(s)):
party(s[i:],P+[s[:i]],res)
res = []
party("abcd",[],res)
print(res)
"""
[['abcd'], ['a', 'bcd'], ['a', 'b', 'cd'], ['a', 'b', 'c', 'd'],
['a', 'bc', 'd'], ['ab', 'cd'], ['ab', 'c', 'd'], ['abc', 'd']]
"""
它的工作原理如下: 给定一个字符串或它的子字符串,我们可以在它的每个字符创建两半之后拆分。 说:“abc”可以分为["a","bc"],["ab","c"]
我们将第一部分保存在中间分区P
和
对另一半递归调用party
。
因为两半一起形成一个完整的分区,我们将其保存到res
。
示例:
最初:s = "abc" 是一个有效的分区,保存到 res。
recr call: s = "bc", P = ["a"] ,所以 P +[s]= ["a","bc"] 也是有效的,保存到res
。
继续拆分“bc”。 P = ["a","b"], s="c" 所以 P + [s] 也是有效的。等等……
recr call 3: s = "c", P = ["ab"], 所以P + [s] =["ab","c"] 也是有效的,保存到res
工作:
tests = ["abc","abcd","a"]
for t in tests:
res = []
party(t,[],res)
print(f't -> res \n')
"""Output
abc -> [['abc'], ['a', 'bc'], ['a', 'b', 'c'], ['ab', 'c']]
abcd -> [['abcd'], ['a', 'bcd'], ['a', 'b', 'cd'], ['a', 'b', 'c', 'd'],
['a', 'bc', 'd'], ['ab', 'cd'], ['ab', 'c', 'd'], ['abc', 'd']]
a -> [['a']]
"""
【讨论】:
我发现这比位移更容易理解。【参考方案4】:这是一种通过利用内置迭代器最大限度地减少开发人员时间的解决方案。对于答案本身不是很大的问题规模,它应该相当快。
在字符串的分区和潜在切点的子集之间存在一一对应的关系。如果字符串的长度是n
,那么有n-1
可以剪切字符串的位置。一种直接的方法是遍历这样的子集,并且对于每个这样的子集,以这种方式对字符串进行切片。这是一种使用标准模块itertools
的Python方法:
import itertools
def multiSlice(s,cutpoints):
k = len(cutpoints)
if k == 0:
return [s]
else:
multislices = [s[:cutpoints[0]]]
multislices.extend(s[cutpoints[i]:cutpoints[i+1]] for i in range(k-1))
multislices.append(s[cutpoints[k-1]:])
return multislices
def allPartitions(s):
n = len(s)
cuts = list(range(1,n))
for k in range(n):
for cutpoints in itertools.combinations(cuts,k):
yield multiSlice(s,cutpoints)
例如:
>>> parts = allPartitions('World')
>>> for p in parts: print(p)
['World']
['W', 'orld']
['Wo', 'rld']
['Wor', 'ld']
['Worl', 'd']
['W', 'o', 'rld']
['W', 'or', 'ld']
['W', 'orl', 'd']
['Wo', 'r', 'ld']
['Wo', 'rl', 'd']
['Wor', 'l', 'd']
['W', 'o', 'r', 'ld']
['W', 'o', 'rl', 'd']
['W', 'or', 'l', 'd']
['Wo', 'r', 'l', 'd']
['W', 'o', 'r', 'l', 'd']
请注意,这种方法会生成['World']
作为'World'
的分区。这对应于使用一组空切点进行切片。我认为这是一个特性而不是一个错误,因为分区的标准数学定义允许将一组分区分成一个部分。如果这不符合您的目的,则修复很容易——只需迭代切割点的非空子集。就上述代码而言,此修复相当于在allPartitions
中添加两个字符:替换
for k in range(n):
通过
for k in range(1,n):
【讨论】:
【参考方案5】:类似于以下内容(未经测试且可能存在错误的 VB.NET 示例)
Function FindAllGroups(s As String) As List(Of List(Of String))
Dim ret As New List(Of List(Of String))
Dim l As New List(Of String)
l.Add(s) 'the whole string unbroken
ret.Add(l) 'first option we return is the whole unbroken string by itself
If s.Length > 1 Then
Dim tmp = FindAllGroups(s.Substring(1)) 'find all the groups for the rest of the string after the first character
For Each l2 in tmp
l = l2.ToList 'Copy it
l.Insert(s.SubString(0,1),0)'insert the first character from this string by itself before this combination for the rest of the string
ret.Add(l)
Next
For Each l2 in tmp
l = l2.ToList 'Copy it
l(0)= s.SubString(0,1) & l(0) 'insert the first character from this string as part of the first element in the list
ret.Add(l)
Next
End If
Return ret
End Function
这基本上可以说我们可以将'abcd'拆分成
'a', 1st option for 'bcd' split
'a', 2nd option for 'bcd' split
...
+
1st option for 'bcd' split with the first element prepended with 'a'
2nd option for 'bcd' split with the first element prepended with 'a'
...
然后计算'bcd',我们只是重复上面的过程,只有
'b', 1st option for 'cd' split
'b', 2nd option for 'cd' split
...
+
1st option for 'cd' split with the first element prepended with 'b'
2nd option for 'cd' split with the first element prepended with 'b'
...
等等。递归地重复。
但是,此代码在运行时并不是特别有效。您可以做的一件事是在函数之外添加一个 Dictionary(Of String, List(Of List(Of String)) ,您可以在其中存储结果的缓存,如果该项目存在于其中, 你从那里返回, 如果没有, 计算它并添加它. 列表也可能不是最有效的, ToList 函数可能不是最快的克隆方式. 但是, 我已经简化了它以便更容易理解也为了节省我的时间!
【讨论】:
【参考方案6】:这是一个相当标准的深度优先搜索(回溯)问题。
void dfs(int startIndex, const string& s, vector<string>& tmp,
vector<vector<string>>& res)
if (startIndex == s.size())
res.push_back(tmp);
return;
for (int i = 1; startIndex + i <= s.size(); ++i)
tmp.push_back(s.substr(startIndex, i));
dfs(startIndex + i, s, tmp, res);
tmp.pop_back();
int main()
vector<vector<string>> res;
vector<string> tmp;
string s = "abcd";
dfs(0, s, tmp, res);
其执行及结果请参考here。
【讨论】:
【参考方案7】:#include <bits/stdc++.h>
using namespace std;
vector<string> ans;
string s;
void solve(int previouscut, int len)
if(previouscut == s.length()) // base case
for(auto str:ans)
cout << str << " " ;
cout << "\n";
return;
if(previouscut+len>s.length()) // boundary case
return;
//cut
ans.push_back(s.substr(previouscut,len));
solve(previouscut + len,1);
ans.pop_back(); //backtrack
// no cut
solve(previouscut, len+1);
int main()
cin >> s;
solve(0,1);
return 0;
https://www.geeksforgeeks.org/substring-in-cpp/#
【讨论】:
您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。以上是关于分割字符串的所有方法的主要内容,如果未能解决你的问题,请参考以下文章