在java中查找字符串中字符频率的有效方法:O(n)

Posted

技术标签:

【中文标题】在java中查找字符串中字符频率的有效方法:O(n)【英文标题】:Efficient way to find Frequency of a character in a String in java : O(n) 【发布时间】:2011-09-07 02:43:06 【问题描述】:

在最近的一次采访中,我被要求编写以下程序。 找出给定字符串中频率最小的字符? 因此,我尝试通过使用 charAt 迭代字符串并将字符作为键存储在 HashMap 中,并将出现次数作为其值。 现在我必须在 Map 上进行迭代以找到最低的元素。

有没有更有效的方法来做到这一点,因为我想显然上面的方法太密集了。

更新和另一种解决方案

经过一些思考过程和答案,我认为这可能是 O(n) 的最佳时间。 在第一次迭代中,我们必须逐个字符地遍历字符串,然后将它们的频率存储在特定位置的数组中(字符是 int),同时有两个临时变量保持最少的计数和相应的字符。因此,当我转到下一个字符并将其频率存储在 arr[char] = arr[char]+1; 同时我将检查临时变量的值是否大于该值,如果是,则临时变量将是这个值,char 也将是这个值。这样我想我们不需要第二次迭代来找到最小的并且我猜也不需要排序

.... 笏说?或者更多解决方案

【问题讨论】:

你的运行时间是 O(2n) = O(n)。你能做的最好的事情是 O(n)。也许你可以摆脱第二次迭代,但就是这样。 第二次迭代是不变的。该算法很好,但我建议使用数组而不是 HashMap,这样应该更有效。 查看类似的 SO 问题:Get mode value in java 最佳答案符合您的建议。 @Kevin .. 是的 .. 如果它是一个排序映射,第二次迭代可以是 O(1) 以找到最少或最高出现的字符 ... 这在O(n + m) 中运行,其中n 是字符串的长度,m 是唯一字符的数量。我想知道是否有办法减少其中一个术语。 【参考方案1】:

我会使用数组而不是哈希映射。如果我们仅限于 ascii,则只有 256 个条目;如果我们使用 Unicode,则为 64k。无论哪种方式都不是不可能的尺寸。除此之外,我看不出你如何改进你的方法。我正在尝试一些巧妙的技巧来提高效率,但我想不出任何办法。

在我看来,答案几乎总是一个完整的字符列表:所有那些使用零次的字符。

更新

这可能接近于 Java 中最有效的方法。为方便起见,我假设我们使用的是纯 Ascii。

public List<Character> rarest(String s)

  int[] freq=new int[256];

  for (int p=s.length()-1;p>=0;--p)
  
    char c=s.charAt(p);
    if (c>255)
      throw new UnexpectedDataException("Wasn't expecting that");
    ++freq[c];
  
  int min=Integer.MAX_VALUE;
  for (int x=freq.length-1;x>=0;--x)
  
    // I'm assuming we don't want chars with frequency of zero
    if (freq[x]>0 && min>freq[x])
      min=freq[x];
  
  List<Character> rares=new ArrayList<Character>();
  for (int x=freq.length-1;x>=0;--x)
  
    if (freq[x]==min)
      rares.add((char)x);
  
  return rares;

任何保持列表按频率排序的努力都会效率低下,因为每次检查一个字符时都必须重新排序。

任何对频率列表进行排序的尝试都将变得更加低效,因为对整个列表进行排序显然比只选择最小值要慢。

对字符串进行排序然后计数会更慢,因为排序会比计数更昂贵。

从技术上讲,最后创建一个简单的数组比创建一个 ArrayList 更快,但 ArrayList 使代码的可读性略高。

可能有一种方法可以更快地做到这一点,但我怀疑这接近于最佳解决方案。我当然有兴趣看看是否有人有更好的主意。

【讨论】:

@Jay 一个数组可能没问题,但是在第二次迭代中,为了找到实际答案,SortedHashMap wud 再次将数组的复杂性降低到 1,否则你必须迭代才能找到最小值。 . 说什么? @Thomas:嗯,好点,直到阅读你的帖子我才知道。一项小研究表明,Java“char”仍然只有 16 位,但他们现在添加了额外的函数来细读字符串并将代码点作为 int 而不是 char 返回。好吧,如果我们需要支持最新版本的 Unicode,我们需要一个更大的数组。假设每个条目都是一个 int,那仍然只有 400k,并非不可能,尽管它开始变大了。我想如果 Unicode 7 支持 32 位值,那么数组方法开始变得不切实际。 @whataheck:我不熟悉“SortedHashMap”。我在 Javadocs 中没有看到它。我想这是来自比我更新版本的 Java 的东西,或者你是从 3rd 方库中获取的。无论哪种方式,如果它在每次更新值时重新排序地图,这将比检查最小值一次要慢得多。事实上,即使它只在你完成后排序一次,对整个地图进行排序仍然需要更多的工作才能找到最小值。 @Jay 你对我在我的问题中提出的解决方案有什么看法......? 使用HashMap,你的意思是?这是一个合理的解决方案。它比基于数组的解决方案使用更少的内存,并且避免了 Unicode 6 的大字符集的问题。另一方面,它比基于数组的解决方案慢。我拼凑了一个测试程序,尝试了这两种方法,HashMap 解决方案比基于数组的解决方案花费了大约 8 倍的时间。如果性能不是主要问题可能会更好,因为它更干净一些。【参考方案2】:

我认为您的方法在理论上是最有效的 (O(n))。但实际上它需要相当多的内存,而且可能非常慢。

将字符串转换为 char 数组,对数组进行排序,然后使用简单的循环计算频率可能更有效(至少它使用更少的内存)。但是,理论上它的效率较低(O(n log n)),因为排序(除非您使用更有效的排序算法)。

测试用例:

import java.util.Arrays;

public class Test 

    public static void main(String... args) throws Exception 
        //        System.out.println(getLowFrequencyChar("x"));
        //        System.out.println(getLowFrequencyChar("bab"));
        //        System.out.println(getLowFrequencyChar("babaa"));
        for (int i = 0; i < 5; i++) 
            long start = System.currentTimeMillis();
            for (int j = 0; j < 1000000; j++) 
                getLowFrequencyChar("long start = System.currentTimeMillis();");
            
            System.out.println(System.currentTimeMillis() - start);
        

    

    private static char getLowFrequencyChar(String string) 
        int len = string.length();
        if (len == 0) 
            return 0;
         else if (len == 1) 
            return string.charAt(0);
        
        char[] chars = string.toCharArray();
        Arrays.sort(chars);
        int low = Integer.MAX_VALUE, f = 1;
        char last = chars[0], x = 0;
        for (int i = 1; i < len; i++) 
            char c = chars[i];
            if (c != last) 
                if (f < low) 
                    if (f == 1) 
                        return last;
                    
                    low = f;
                    x = last;
                
                last = c;
                f = 1;
             else 
                f++;
            
        
        if (f < low) 
            x = last;
        
        return (char) x;
    


【讨论】:

让我们看看谁能比这更快:-) 您对我在问题中提出的解决方案有何看法...? 查看我上面的评论:更新后的答案不起作用。如果你不相信我,请执行它。【参考方案3】:

查找字符串中字符频率的过程非常简单。 答案见我的代码。

import java.io.*;
public class frequency_of_char

    public static void main(String args[])throws IOException
    
        BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
        int ci,i,j,k,l;l=0;
        String str,str1;
        char c,ch;
        System.out.println("Enter your String");
        str=in.readLine();
        i=str.length();
        for(c='A';c<='z';c++)
        
            k=0;
            for(j=0;j<i;j++)
            
                ch=str.charAt(j);
                if(ch==c)
                    k++;
            
            if(k>0)
            System.out.println("The character "+c+" has occured for "+k+" times");
        
    

【讨论】:

复杂度需要为 O(n) .....以上代码的复杂度为 O(n^2),根据讨论议程,这不是一种有效的方法【参考方案4】:

我会这样做,因为它涉及的代码行数最少:

你想知道频率的字符:“_” 字符串“this_is_a_test”

String testStr = "this_is_a_test";
String[] parts = testStr.split("_"); //note you need to use regular expressions here
int freq = parts.length -1;

如果字符串以相关字符开头或结尾,您可能会发现奇怪的事情,但我会留给您测试。

【讨论】:

【参考方案5】:

必须遍历 HashMap 不一定是坏事。这只会是O(h),其中h 是HashMap 的长度——唯一字符的数量——在这种情况下总是小于或等于n。例如"aaabbc"h = 3 代表三个唯一字符。但是,由于h 严格小于可能的字符数:255,它是恒定的。所以,你的大哦将是O(n+h),实际上是O(n),因为h 是不变的。我不知道有什么算法可以得到更好的 big-oh,你可以尝试进行一些特定于 java 的优化,但是这里说的是我编写的一个简单算法,它可以找到频率最低的 char。它从输入 "aaabbc" 返回 "c"

import java.util.HashMap;
import java.util.Map;

public class ***Question 

public static void main(String[] args) 
    // TODO Auto-generated method stub

    System.out.println("" + findLowestFrequency("aaabbc"));



public static char findLowestFrequency(String input) 

    Map<Character, Integer> map = new HashMap<Character, Integer>();

    for (char c : input.toCharArray())

        if (map.containsKey(c))
            map.put(c, map.get(c) + 1);
        else
            map.put(c, 0);

    char rarest = map.keySet().iterator().next();

    for (char c : map.keySet())

        if (map.get(c) < map.get(rarest))
            rarest = c;

    return rarest;




【讨论】:

以上是关于在java中查找字符串中字符频率的有效方法:O(n)的主要内容,如果未能解决你的问题,请参考以下文章

在Java中使用哈希映射来查找字符串中字符的频率[关闭]

在 O(1) 空间和 O(n) 时间中查找 2 个字符串是不是是字谜

[算法总结] 13 道题搞定 BAT 面试——字符串

在随机生成的整数列表中查找所有模式及其出现频率的最有效方法

字符串字符串查找 ( 蛮力算法 )

[MSTL] lc451. 根据字符出现频率排序(STL+哈希表)