我试图弄清楚如何生成给定字符串的所有排列的代码(来自Cracking the Coding Interview book)的复杂性为O(n!)。
我知道这是最好的复杂性,因为我们有n!排列,但我想以编码方式理解它,因为并非每个执行此操作的算法都是O(n!)。
守则:
import java.util.*;
public class QuestionA {
public static ArrayList<String> getPerms(String str) {
if (str == null) {
return null;
}
ArrayList<String> permutations = new ArrayList<String>();
if (str.length() == 0) { // base case
permutations.add("");
return permutations;
}
char first = str.charAt(0); // get the first character
String remainder = str.substring(1); // remove the first character
ArrayList<String> words = getPerms(remainder);
for (String word : words) {
for (int j = 0; j <= word.length(); j++) {
String s = insertCharAt(word, first, j);
permutations.add(s);
}
}
return permutations;
}
public static String insertCharAt(String word, char c, int i) {
String start = word.substring(0, i);
String end = word.substring(i);
return start + c + end;
}
public static void main(String[] args) {
ArrayList<String> list = getPerms("abcde");
System.out.println("There are " + list.size() + " permutations.");
for (String s : list) {
System.out.println(s);
}
}
}
这是我一直以来的想法,直到现在: 在任何函数调用中,可用的单词数是(n-1);假设我们在一个剩余长度(n-1)的地方。现在在所有这些(n-1)个单词的所有可能位置插入第n个元素需要(n-1)*(n-1)个时间。
所以跨执行,应该是(n-1)^ 2 +(n-2)^ 2 +(n-3)^ 2 + .... 2 ^ 2 + 1 ^ 2操作,其中我不要以为是n!。
我在这里想念的是什么?
答案 0 :(得分:1)
我认为getPerms
的时间复杂度为O((n + 1)!)
。
我们将getPerms
的运行时间表示为T(n)
,其中n
是输入的长度。
=============================================== ====================
两个if
分支和行char first = str.charAt(0)
需要O(1)
次。以下行需要O(n)
时间:
String remainder = str.substring(1); // remove the first character
下一行需要时间T(n - 1)
:
ArrayList<String> words = getPerms(remainder);
现在我们考虑嵌套for-loops
的运行时间。外for-loop
的大小为(n-1)!
:
for (String word : words) {
内部for-loop
的大小为n + 1
:
for (int j = 0; j <= word.length(); j++) {
insertCharAt
的复杂性也是O(n)
。
因此,嵌套for-loops
的总运行时间为(n + 1) * (n - 1)! * O(n) = O((n + 1)!)
。
因此我们有以下关系:
T(n) = T(n - 1) + O(n) + O((n + 1)!) = T(n - 1) + O(n) + O((n + 1)!) = (T(n - 2) + O(n - 1) + O(n!) + O(n) + O((n + 1)!) = T(n - 2) + ( O(n - 1) + O(n) ) + ( O(n!) + O((n + 1)!) ) = ... = O(n2) + (1 + ... + O(n!) + O((n + 1)!) ) = O((n + 1)!)
答案 1 :(得分:1)
如果你正在研究这个,那么最好学习一般的解决方案,而不仅仅是你的例子中提供的实现。 Sedgewick做了我所知道的最好的分析。我在课堂上教它。
https://www.cs.princeton.edu/~rs/talks/perms.pdf
生成函数的每次调用的复杂性是O(n)。因此成本是O(n!)。
您提供的代码效率极低。有一个巨大的常数因素,因为你创建了许多字符串对象和数组,这是你可以用Java做的最低效的事情之一。
如果您只想浏览所有排列,请置换单个实体,不要生成列表。这是一个更快的实现:
public class Permute {
private int[] a;
private void swap(int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public Permute(int n) {
a = new int[n];
for (int i = 0; i < n; i++)
a[i] = i+1;
this.generate(n);
}
public void generate(int N) {
// System.out.println("generate(" + N + ")");
if (N == 0) doit();
for (int c = 0; c < N; c++) {
// System.out.print("Swapping: " + c + "," + N);
swap(c, N-1); //swap(0, 7)
generate(N-1);
// System.out.print("Swapping: " + c + "," + N);
swap(c, N-1);
}
}
public void doit() {
for (int i = 0; i < a.length; i++)
System.out.print(a[i] + " ");
System.out.println();
}
public static void main(String[] args) {
Permute p = new Permute(4);
}
}
Sedgewick显示的另一种方法是Heaps,每个排列只有一个交换而不是2.这是一个C ++实现:
#include <vector>
#include <iostream>
using namespace std;
class Heaps {
private:
vector<int> p;
public:
Heaps(int n) {
p.reserve(n);
for (int i = 0; i < n; i++)
p.push_back(i+1);
generate(n);
}
void doit() {
cout << "doit size=" << p.size() << '\n';
for (int i = 0; i < p.size(); i++)
cout << p[i];
cout << '\n';
}
void generate(int N) {
// cout << "generate(" << N << ")\n";
if (N == 0)
doit();
for (int c = 0; c < N; c++) {
generate(N-1);
swap(p[N % 2 != 0 ? 0 : c], p[N-1]);
}
}
};
int main() {
Heaps p(4);
}