确定字符串具有所有唯一字符,而不使用其他数据结构且没有小写字符假设

时间:2014-01-11 02:25:01

标签: java string algorithm bit-manipulation bitvector

这是Gayle Laakmann McDowell在Cracking the Coding Interview book中的一个问题:

  

实施算法以确定字符串是否具有所有唯一字符。如果您不能使用其他数据结构怎么办?

作者写道:

  

我们可以通过使用位向量来减少空间使用量。我们将在下面的代码中假设字符串只是小写'a''z'。这将允许我们只使用一个int。

作者有这个实现:

public static boolean isUniqueChars(String str) {
    int checker = 0;
    for (int i = 0; i < str.length(); ++i) {
        int val = str.charAt(i) - 'a';
        if ((checker & (1 << val)) > 0)
            return false;
        checker |= (1 << val);
    }
    return true;
}

假设我们摆脱了“字符串只是小写'a''z'”的假设。相反,该字符串可以包含任何类型的字符ASCII字符或Unicode字符。

是否存在一种与作者一样有效的解决方案(或者一种与作者一样高效的解决方案)?

相关问题:

7 个答案:

答案 0 :(得分:5)

对于asccii字符集,你可以代表4个longs中的256位:你基本上手工编写一个数组。

public static boolean isUniqueChars(String str) {
    long checker1 = 0;
    long checker2 = 0;
    long checker3 = 0;
    long checker4 = 0;
    for (int i = 0; i < str.length(); ++i) {
        int val = str.charAt(i);
        int toCheck = val / 64;
        val %= 64;
        switch (toCheck) {
            case 0:
                if ((checker1 & (1L << val)) > 0) {
                    return false;
                }
                checker1 |= (1L << val);
                break;
            case 1:
                if ((checker2 & (1L << val)) > 0) {
                    return false;
                }
                checker2 |= (1L << val);
                break;
            case 2:
                if ((checker3 & (1L << val)) > 0) {
                    return false;
                }
                checker3 |= (1L << val);
                break;
            case 3:
                if ((checker4 & (1L << val)) > 0) {
                    return false;
                }
                checker4 |= (1L << val);
                break;
        }            
    }
    return true;
}

您可以使用以下代码为unicode字符生成类似方法的主体:

static void generate() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 1024; i++) {
        sb.append(String.format("long checker%d = 0;%n", i));
    }
    sb.append("for (int i = 0; i < str.length(); ++i) {\n"
            + "int val = str.charAt(i);\n"
            + "int toCheck = val / 64;\n"
            + "val %= 64;\n"
            + "switch (toCheck) {\n");
    for (int i = 0; i < 1024; i++) {
        sb.append(String.format("case %d:\n"
                + "if ((checker%d & (1L << val)) > 0) {\n"
                + "return false;\n"
                + "}\n"
                + "checker%d |= (1L << val);\n"
                + "break;\n", i, i, i));
    }
    sb.append("}\n"
            + "}\n"
            + "return true;");
    System.out.println(sb);
}

答案 1 :(得分:3)

你只需要一行......实际上不到一行:

if (str.matches("((.)(?!.*\\1))*"))

这使用了负面的预测来断言每个字符在字符串中不会重复。

这种方法的时间复杂度为O(n ^ 2),因为对于输入中的所有n个字符,后面的所有字符(有n个)都是相等的。

答案 2 :(得分:1)

我认为我们需要对“附加数据结构”进行一般性和实用性的定义。直观地说,我们不希望将每个标量整数或指针称为“数据结构”,因为这使得任何禁止“附加数据结构”都无效。

我建议我们从big-O表示法中借用一个概念:“附加数据结构”是随着数据集大小而增长的结构。

在当前情况下,OP引用的代码似乎具有O(1)的空间要求,因为位向量恰好适合整数类型。但正如OP暗示的那样,问题的一般形式实际上是O(N)。

一般情况的解决方案示例是使用两个指针和一个嵌套循环来简单地将每个字符相互比较。空间要求为O(1),但时间要求为O(N ^ 2)。

答案 3 :(得分:0)

以下算法怎么样?

步骤:

将字符串转换为小写。

遍历字符串中的每个字符

设置变量数据= 0

计算字符串中第一个字符的偏移量= ascii值 - 97

使用mask = 1&lt;&lt;设置该位置的标志偏移

如果按位AND返回true,则字符重复(掩码和数据),所以在此处中断。

否则如果我们还没有看到字符重复,则通过执行按位OR来设置该字符的位或者通过执行data = data |掩模

继续直到角色结束。

答案 4 :(得分:0)

没有任何额外数据结构的单行解决方案:

str.chars().distinct().count() == (int)str.length();

答案 5 :(得分:0)

我将此答案作为想法或建议发布。我不确定这个解决方案的运行时间复杂度的真实性(但我确实认为有效时间复杂度不应超过 O(n),但我很高兴知道你们中是否有人想要解释一下。


所以,这个想法是这样的。 我们使用两个指针(我认为它们不属于数据结构的范畴,否则事情会太难)。一个叫fast,另一个叫slow。正如它们的名字一样,快速指针比慢指针遍历得更快(一次 2 个索引)。我们将继续检查这些位置的字符,直到发生以下两种情况之一:

  1. 快速和慢速指向相同的索引(现在比较它们的字符没有意义,因为它们将相等)
  2. s[slow] == s[fast](因为现在不同索引的 2 个字符是相等的,我们可以返回 false)

现在我们只是重置快速指针并让它沿着字符串遍历(一个接一个,不再那么快了吧?)直到它变得等于慢,检查 s[fast] == s[slow] 是否为真,以防它返回假的。


所以,C++中的代码是这样的:

#include <bits/stdc++.h>
using namespace std;

int main() {
  string s;
  cin >> s;
  int n = s.size();
  int slow = 0, fast = 1;
  bool flag = false;
  //   cout << "Running..." << endl;
  while (slow != fast) {
    if (s[slow] == s[fast]) {
      flag = true;
      break;
    }
    // cout << "slow: " << s[slow] << " fast: " << s[fast] << endl;
    slow = (slow + 1) % n;
    fast = (fast + 2) % n;
  }
  fast = 0;
  while (!flag && fast != slow) {
    if (s[slow] == s[fast]) {
      flag = true;
      break;
    }
    fast = (fast + 1) % n;
  }
  if (flag) {
    cout << "Duplicates present." << endl;
  } else {
    cout << "No duplicates present." << endl;
  }
  return 0;
}

答案 6 :(得分:0)

import math
def uniqueCharacters(str):
     
    # Assuming string can have characters
    # a-z this has 32 bits set to 0
    checker = 0
     
    for i in range(len(str)):
        
        bitAtIndex = ord(str[i]) - ord('a')
        
        # If that bit is already set in
        # checker, return False
        if ((bitAtIndex) >= 0):
            
            if ((checker & ((1 << bitAtIndex))) > 0):
                print('duplicate character: '+str[i])
                return False
                 
            # Otherwise update and continue by
            # setting that bit in the checker
            checker = checker | (1 << bitAtIndex)
            
 
    # No duplicates encountered, return True
    return True
 
# Driver Code
if __name__ == '__main__':
     
    input_word = input('Enter string: ')
    input_word = input_word.lower()
    if (uniqueCharacters(input_word)):
        print("The String " + input_word +
              " has all unique characters")
    else:
        print("The String " + input_word +