簡體   English   中英

在不使用額外數據結構和小寫字符假設的情況下確定一個字符串具有所有唯一字符

[英]Determining a string has all unique characters without using additional data structures and without the lowercase characters assumption

這是 Gayle Laakmann McDowell 在Cracking the Coding Interview一書中提出的問題之一:

實現一個算法來確定一個字符串是否包含所有唯一字符。 如果不能使用額外的數據結構怎么辦?

作者寫道:

我們可以通過使用位向量來減少我們的空間使用量。 我們將假設,在下面的代碼中,字符串只是小寫的'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 字符。

有沒有和作者一樣高效的解決方案(或者接近於作者一樣高效的解決方案)?

相關問題:

對於 asccii 字符集,您可以用 4 個 long 表示 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;
}

您可以使用以下代碼為 un​​icode 字符生成類似方法的主體:

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);
}

你只需要一行……實際上不到一行:

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

這使用否定前瞻來斷言每個字符在字符串的后面不會重復。

這種方法的時間復雜度為 O(n^2),因為對於輸入中的所有 n 個字符,后面的所有字符(其中有 n 個)都進行相等性比較。

我認為我們需要對“附加數據結構”有一個通用且實用的定義。 直觀地說,我們不想將每個標量整數或指針都稱為“數據結構”,因為這與禁止“附加數據結構”毫無意義。

我建議我們從 big-O 符號借用一個概念:“附加數據結構”是一種隨着數據集的大小而增長的結構。

在本例中,OP 引用的代碼似乎有 O(1) 的空間要求,因為位向量恰好適合整數類型。 但正如 OP 所暗示的那樣,問題的一般形式實際上是 O(N)。

一般情況的解決方案的一個示例是使用兩個指針和嵌套循環來簡單地將每個字符相互比較。 空間要求是 O(1) 但時間要求是 O(N^2)。

下面的算法怎么樣?

腳步:

將字符串轉換為小寫。

循環遍歷字符串中的每個字符

設置變量數據 = 0

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

使用 mask = 1 << offset 設置該位置的標志

如果按位 AND 返回 true,則一個字符正在重復(掩碼和數據),因此在此處中斷。

否則,如果我們還沒有看到字符重復,則通過按位或通過執行 data = data | 為該字符設置位。 面具

繼續直到字符結束。

沒有任何額外數據結構的單行解決方案:

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

我將此答案作為想法或建議發布。 我不確定這個解決方案的運行時間復雜度的真實性(但我確實認為有效時間復雜度不應該超過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;
}
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 +

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM