【问题标题】:Complexity for generating permutations of a string生成字符串排列的复杂性
【发布时间】:2016-11-24 02:22:29
【问题描述】:

我试图弄清楚生成给定字符串的所有排列的代码的复杂性(来自Cracking the Coding Interview book)是O(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!。

我错过了什么?

【问题讨论】:

  • 我不知道我是否正确,但我认为它是 O((N+1)!) ?

标签: java algorithm time-complexity permutation


【解决方案1】:

我认为getPerms的时间复杂度是O((n + 1)!)

我们用T(n)表示getPerms的运行时间,其中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)!)

【讨论】:

  • @Down Voter,请解释拒绝投票的原因,以便我改进答案
【解决方案2】:

如果您正在研究这个,最好学习通用解决方案,而不仅仅是示例中介绍的实现。 Sedgewick 做了我所知道的最好的分析。我在课堂上教它。

https://www.cs.princeton.edu/~rs/talks/perms.pdf

generate 函数每次调用的复杂度为 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 展示的另一种方法是堆,它只是一个交换排列而不是 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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-21
    • 1970-01-01
    • 1970-01-01
    • 2018-09-11
    • 2018-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多