打印重复字符的星号算法

3
我是一名有用的助手,可以为您翻译文本。
在面试中,我被问到了这个问题:
给定一个包含输入字符串的数组,按照下面所示的方式显示输出。 输入
INDIA  

Output

INDA  
****  
* 

我遍历了数组,并将每个字符存储为std::map中的键,其值为出现次数。然后我遍历该映射并打印星号,并为每个字符减少映射中的值。

最初,我被要求不使用任何库。我提供了一个需要大量迭代的解决方案。对于每个字符,迭代完整的数组直到索引以查找先前的出现次数等等。 是否有更好的方法,例如更好的复杂度,比如更快的操作,可以实现这一目标?


3
如果你想要提供“更好的方法”,你可能需要更具体地展示你是如何做到的。虽然我认为这可能更适合于codereview.se(或者甚至是codegolf.se,因为不允许使用库)。 - PlasmaHH
1
如果输入字符串是8位(甚至16位)字符集,您可以使用简单的数组或向量来标记遇到的字符。 - Peter G.
3
最初我被要求不使用任何库。这是一个警告信号。公司可能患有“非本土症候群”。明智的公司会先检查新雇员是否喜欢现有的库,然后再测试他们是否能够想出解决方案。 - MSalters
2
@MSalters 这是一个非常好的问题,在短时间内(也许30分钟)可以在面试中解决,这将允许他们展示如何解决问题。允许人们使用库将发现人们对库的了解程度,但这可能不是他们正在寻找的;手动完成可能更好,因为它可以让候选人展示他们编写代码的方式(分解、结构、命名等)。我无法想象任何一家公司真正希望C++开发人员在日常工作中不使用标准库。 - user146043
3
@codeMagic: 为什么这个问题被标记为“已暂停”?这是一个有明确答案的有效问题。由于这是一个算法问题,可能有许多不同的解决方法。这是否是将问题暂停的标准? - cppcoder
显示剩余17条评论
8个回答

5
基本上,您要问的是如何在不使用STL代码的情况下实现map,因为使用某种数据结构来复制map的基本功能几乎是解决这个问题最合理的方法。
有很多方法可以做到这一点。如果您的键(这里是可能的字符)来自一个非常大的集合,而该集合的大多数元素不出现(例如完整的Unicode字符集),则您可能希望使用树或哈希表。这两种数据结构非常重要,具有许多变化和不同的实现方式。周围有很多有关这两种结构的信息和示例代码。
正如@PeterG在评论中所说,如果您将看到的唯一字符来自256个8位字符的集合(例如ASCII或类似的字符集),或者其他类似的受限集合,那么您应该使用由256个int组成的数组,并在其中存储每个字符的计数。

如果你提出了8位字符的解决方案,面试官接下来可能会问如何处理完整的Unicode集。说实话,如果预期输入都是短字符串,那么问题中提出的O(n^2)解决方案可能是最快的。 - Mark Ransom
@MarkRansom 我认为在面试时假设8位字符或短字符串都是错误的。那些做出这种假设的人往往会编写糟糕的代码。至少要声明“首先,我将在简化的假设下工作...”,解决问题,然后放弃假设并解决一般性问题。 - jwg
请注意,输入是一个数组,而不是字符的字符串,@Veritas。无论数组的类型是什么,假设可以有一个具有相同类型键的映射? - jwg
抱歉晚了,我有些困惑。他也需要处理宽字符吗?他将不得不编写一个字符串类来处理输入,然后重新实现一个映射表。这似乎对于面试问题来说太过繁琐了。 - Veritas
这是一个数组!其他所有答案都假设字母表中的字符数量足够少,以至于使用数组就足够了。我认为理解这一点并知道如何实现替代方案,正是面试官可能在寻找的。 - jwg

1
以下代码可以正常工作。我假设您不能使用 std::string,并注意这不考虑溢出,因为我没有使用动态容器。这也假定字符可以用 char 表示。
#include <iostream>

int main()
{
    char input[100];
    unsigned int input_length = 0;
    char letters[100];
    unsigned int num_of_letters = 0;
    std::cin >> input;
    while (input[input_length] != '\0')
    {
        input_length += 1;
    }
    //This array acts like a hash map.
    unsigned int occurrences[256] = {0};
    unsigned int max_occurrences = 1;
    for (int i = 0; i < input_length; ++i)
    {
        if ((occurrences[static_cast<unsigned char>(input[i])] += 1) == 1)
        {
            std::cout<< " " << (letters[num_of_letters] = input[i]) << " ";
            num_of_letters += 1;
        }
        if (occurrences[static_cast<unsigned char>(input[i])] > max_occurrences)
        {
            max_occurrences = occurrences[static_cast<unsigned char>(input[i])];
        }
    }
    std::cout << std::endl;
    for (int row = 1; row <= max_occurrences; ++row)
    {
        for (int i = 0; i < num_of_letters; ++i)
        {

            if (occurrences[static_cast<unsigned char>(letters[i])] >= row)
            {
                std::cout << " * ";
            }
            else
            {
                std::cout << "   ";
            }

        }
        std::cout << std::endl;
    }
    return 0;
}

1
这很有趣。你不应该使用 stl::map,因为那不是一个哈希表。stl map 是一棵二叉树。unordered_map 实际上是一个哈希表。在这种情况下,我们都不需要。我们可以使用一个简单的字符计数数组。
void printAstr(std::string str){
 int array[256] ;// assumining it is an ascii string
 memset(array, 0, sizeof(array));
 int astrCount = 0;
 for(int i = 0; i < str.length()-1; i++){
     array[(int) str[i]]++;
     if(array[(int) str[i]] > 1) astrCount++;
 }
std::cout << str  << std::endl;
for(int i = 0;  i < str.length()-1;i++) std::cout << "* ";
std::cout << std::endl;
while(astrCount != 0){
   for(int i= 0; i< str.length() - 1;i++){
       if(array[(int) str[i]] > 1){
          std::cout << "* ";
          array[(int) str[i]]--;
          astrCount--;
       }else{
        std::cout << " ";
       }
   }
   std::cout << std::endl;
}

}

非常简单,只需将所有值添加到数组中,然后打印它们出现的次数。

编辑:抱歉刚刚做了一些逻辑更改。现在这个代码可以正常运行。


它从未变成负值。但我确实有一些小的逻辑错误,我刚刚修复了。 - Jay
1
该字符串可能包含负字符,例如 é - M.M
我在注释中说:“//假设是ASCII字符串”。 - Jay
你的注释“_stl map是一棵二叉树_”并不完全准确。标准并没有规定std::map必须实现为二叉树,它只要求具有对数查找复杂度,这就是为什么它通常在底层使用红黑树实现的原因。 - Void
@Void 是的,但是你说“底层有一个红黑树”,所以我也没有错。无论如何,我想要表达的观点是,在可以使用数组作为基本哈希表时,就不需要使用 std::map。 - Jay
@Jay 我的观点是RB树只是一种实现细节,而规范并没有强制要求。你暗示了相反的意思。我们可以无限地争论语义,但我总体上明白你想说什么。我只是在吹毛求疵。 :) - Void

1

这里还有一个例子: 你可以看到它在这里工作。

#include <stdio.h>
int main()
{
    int i,j=0,f=1;
    char input[50]={'I','N','D','I','A','N','A','N'};
    char letters[256]={0};
    int counter[256]={0};
    for(i=0;i<50;i++)
    {
        if(input[i])
         counter[input[i]]++;
         if(counter[input[i]]==1)
         {
            putchar(input[i]);
            letters[j]=input[i];
            j++;
         }    
    }
    putchar('\n');
    while(f)
    {
        f=0;      
        for(i=0;i<j;i++)
            if(counter[letters[i]])
            {
                putchar('*');
                counter[letters[i]]--;
                f=1;
            }
            else
            {
                putchar(' ');
            }
        putchar('\n');  
    }
    return 0;
}

一些字符具有负值。您应确保正确的索引。 - Veritas

1
这个问题标记为 ,但在我看来,回答并不都是很符合 C++ 的风格,不过如果要遵循“不使用任何库”的奇怪要求,编写良好的 C++ 代码可能会相当困难。在我的方法中,我使用了一些很酷的 C++11 特性,比如类内初始化或 nullptr这里 是现场演示和下面的代码:
struct letter_count
{
    char letter = '\0';
    int count = 0;
};

int add(letter_count *begin, letter_count *end, char letter)
{
    while (begin != end)
    {
        if (begin->letter == letter)
        {
            return ++begin->count;
        }
        else if (begin->letter == '\0')
        {
            std::cout << letter; // Print the first appearance of each char
            ++begin->letter = letter;
            return ++begin->count;
        }

        ++begin;
    }

    return 0;
}

int max (int a, int b)
{
    return a > b ? a : b;
}

letter_count *buffer = nullptr;

auto testString = "supergalifragilisticoespialidoso";

int len = 0, index = 0, greater = 0;

while (testString[index++])
    ++len;

buffer = new letter_count[len];

for (index = 0; index < len; ++index)
    greater = max(add(buffer, buffer + len, testString[index]), greater);

std::cout << '\n';

for (int count = 0; count < greater; ++count)
{
    for (index = 0; buffer[index].letter && index < len; ++index)
        std::cout << (count < buffer[index].count ? '*' : ' ');

    std::cout << '\n';
}

delete [] buffer;

由于“不允许使用库”(除了<iostream>?),我避免使用std::pair<char, int>(它本来可以替代letter_count结构体),我们需要编写许多工具程序(如maxstrlen);上述程序的输出结果为:

supergaliftcod
**************
* *******   * 
*     ***   * 
*       *     
        *     
        *     

1
我的通用解决方案是遍历单词并将重复的字符替换为未使用的无意义字符。下面是一个简单的示例,我使用感叹号(!)作为无意义字符(输入可以更加健壮,选择一些不容易键入的字符,禁止在答案中使用无意义字符,进行错误检查等)。遍历后,最后一步是删除无意义字符。问题在于保持星号的同时保留它们所暗示的原始位置。为此,我使用了一个临时字符串来保存字母和一个过程字符串来创建最终输出字符串和星号。
#include <iostream>
#include <string>

using namespace std;

int
main ()
{
  string input = "";
  string tempstring = "";
  string process = "";
  string output = "";
  bool test = false;

  cout << "Enter your word below: " << endl;
  cin >> input;

  for (unsigned int i = 0; i < input.length (); i++)
  { //for the traversed letter, traverse through subsequent letters    
    for (unsigned int z = i + 1; z < input.length (); z++)
    {
        //avoid analyzing nonsense characters
        if (input[i] != '!')    
        {           
          if (input[i] == input[z]) 
          { //matched letter; replace with nonsense character
            input[z] = '!';
            test = true;    //for string management later
          }
        }
    }
    if (test)   
    {
      tempstring += input[i];
      input[i] = '*';
      test = false; //reset bool for subsequent loops
    }
  }

  //remove garbage symbols and save to a processing string
  for (unsigned int i = 0; i < input.size (); i++)
    if (input[i] != '!')
      process += input[i];

  //create the modified output string
  unsigned int temp = 0;
  for (unsigned int i = 0; i < process.size (); i++)
    if (process[i] == '*')
    { //replace asterisks with letters stored in tempstring
      output += tempstring[temp];
      temp++;
    }
    else
      output += process[i];

   //output word with no repeated letters
  cout << output << endl;

  //output asterisks equal to output.length
  for (unsigned int a = 0; a < output.length (); a++)
    cout << "*";

  cout << endl;

  //output asterisks for the letter instances removed
  for (unsigned int i = 0; i < process.size (); i++)      
    if (process[i] != '*')
      process[i] = ' ';

  cout << process << endl << endl;
}

运行代码后我收到的示例输出:

Enter your word below: 
INDIA
INDA
****
*

Enter your word below: 
abcdefgabchijklmnop
abcdefghijklmnop
****************
***

1
如果考虑的字母是固定的,可以分为两步:
  1. 创建一个大小为字母表大小的整数数组A,初始化为所有零。
  2. 创建一个大小为输入长度的布尔数组B,初始化为所有false。
  3. 迭代输入;对于每个字符,增加相应的A内容。
  4. 迭代输入;如果其在B中的值为false,则输出一个字符并将其在B中的值设置为true。最后输出回车符。
  5. 重置B。
  6. 像第4步一样迭代输入,但如果A中该字符的计数为正,则打印一个星号并减少此计数;否则打印一个空格。
  7. 输出回车符;只要输出生成的任何星号,就循环到5。

0

只需使用简单的数组即可对值进行计数。

#include<iostream>
#include<string>

using namespace std;

int main(){

    string s;
    char arr[10000];
    cin>>s;
    int count1[256]={0},count2[256]={0};
    for(int i=0;i<s.size();++i){
        count1[s[i]]++;
        count2[s[i]]++;
    }
    long max=-1;
    int j=0;
    for(int i=0;i<s.size();++i){
        if(count1[s[i]]==count2[s[i]]){ //check if not printing duplicate
            cout<<s[i]; 
            arr[j++]=s[i];
        }
        if(count2[s[i]]>max)
            max=count2[s[i]];
        --count1[s[i]];
    }
    cout<<endl;
    for(int i =1; i<=max;++i){
        for(int k=0;k<j;++k){
            if(count2[arr[k]]){
                cout<<"*";
                count2[arr[k]]--;
            }
            else
                cout<<" ";
        }
        cout<<endl;

    }

}

谁曾经给踩了,请说明理由。 - coder hacker
3
第6行、第7行、第12行、第16行、第17行、第18行、第23行和第25行存在越界访问问题。第14行可能会在一段时间后出现越界访问问题。最后一个循环,结尾处的空格似乎不合适。代码难以阅读,且未经解释。 - M.M
@MattMcNabb,代码完美运行,请在您的机器上尝试并投票。我知道我保持了char数组大小不变,但这只是演示而已,操作者可以自己解决。它对他起作用,那你为什么要费心呢? - coder hacker
在我的系统上,负字符会导致打印出垃圾行(尽管任何事情都可能发生,因为访问数组边界之外是未定义的行为)。 - M.M
1
@TejasPatel在面试场合下,“在你的机器上尝试”不是对你代码的充分解释。 - jwg
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接