【问题标题】:How to check for repeated characters within a string in c如何在c中检查字符串中的重复字符
【发布时间】:2020-07-03 23:13:52
【问题描述】:

我正在尝试创建一个程序来检查命令行参数字符串中的重复字符。假设该字符串仅包含 26 个字符,并且所有字符都必须是字母顺序。但是,字符串中不能有任何重复的字符,每个字母字符只能出现一次。我想出了程序的前两个部分,但我不知道如何检查重复的字符。我真的可以使用一些帮助和解释。

#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>

int main (int argc, string argv[])
{
    if (argc != 2)
    {
        printf("Usage: ./substitution key\n");
        return 1;
    }
    else
    {
        int len = strlen(argv[1]);
        if (len != 26)
        {
            printf("Key must contain 26 characters.\n");
            return 1;
        }
        else
        {
            for (int i = 0; i < len; i++)
            {
                if (!isalpha(argv[1][i]))
                {
                    printf("Usage: ./substitution key\n");
                    return 1;
                }
            }
        }
    }
}

【问题讨论】:

  • 您需要 26 个计数器,每个字母对应一个,以便计算每个字母出现的频率。执行此计数后,您必须验证所有 26 个计数器的值是否为 1(这意味着每个字母恰好出现一次)。为了分配这 26 个计数器,您可以声明 26 个单独的局部计数器变量或一个包含 26 个元素的数组。
  • 保留一个地图并将其初始化为零。对于每个字符,检查它是否以前找到过。如果未找到,则将值增加到 1,否则重复此字符。
  • @AsifMujtaba:如果你指的是std::map,请注意这个问题被标记为C,而不是C++。当然,也可以在 C 中实现一个映射,但是对于这个简单的问题来说,这似乎是一项过多的工作。
  • 实际上可以通过数组来完成。我的逻辑是使用 hashMap。谢谢指正。
  • @Patrick Dankyi 大写和小写字母,例如“a”和“A”是不同的字母吗?

标签: c cs50


【解决方案1】:

以下是解决方案的基础知识:

开始时,初始化一个标志表:char Seen[UCHAR_MAX+1] = {0};。在此数组中,当且仅当字符 c 已经出现时,Seen[c] 才会为真(非零)。 (获取UCHAR_MAX#include &lt;limits.h&gt;。)

处理每个字符时,将其复制到unsigned charunsigned char c = argv[1][i];。然后将其转换为大写:c = toupper(c);。 然后测试是否已经看过:

if (Seen[c])
    Report error...

如果是新的,请记住它已被看到:Seen[c] = 1;

这就是所有必要的。

注意事项:

  • 如果已知 A 到 Z 在字符集中是连续的,就像它们在 ASCII 中一样,则可以将 char Seen[UCHAR_MAX+1] 简化为 char Seen['Z'-'A'+1] 并使用 Seen[c-'A'] 进行索引。 (实际上,一个较弱的条件就足够了:A 是 value 中最小的大写字符,Z 是最大的。)
  • 不要试图使用unsigned char c = toupper(argv[1][i]),因为toupper 是为unsigned char 值定义的,char 值可能超出范围(负)。
  • 在一个深奥的 C 实现中,charint 一样宽,但它们都不存在,UCHAR_MAX+1 的计算结果为零。但是,如果发生这种情况,编译器应该发出警告。

【讨论】:

  • 我只是把这个编码起来看看。十分优雅。让我想起了 python 字典。竖起大拇指!感谢分享!
【解决方案2】:

根据问题,预期输入是 26 个字符,每个字符只出现 1 次。

根据Andreas Wenzel 的想法,我为每个角色添加了计数器。以防发现任何字符丢失或重复。这是我的解决方案:

int main(int argc, char *argv[])
{
    const int OFFSET = 'A';

    if (argc != 2)
    {
        return 1;
    }
    if (strlen(argv[1]) != 26)
    {
        printf("Key must contain 26 characters.\n");
        return 1;
    }

    unsigned char *key = argv[1];
    int count[26] = {0}; // array of all zero

    // Check for any invalid character 
    for (int i = 0, n = strlen(key); i < n; i++)
    {
        if (!isalpha(key[i]))
        {
            printf("Key contains invalid characters.\n");
            return 1;
        }
        count[toupper(key[i]) - OFFSET]++;
    }
    // check for duplicate character of 
    for (int i = 0; i < 26; i++)
    {
        if (count[i] != 1) 
        {
            printf("Key shouldn't contain duplicate characters\n");
            return 1;
        }
    }
)

【讨论】:

  • 请注意,对于非默认的locales,函数isalpha 可能会为不在'a''z''A'Z 范围内的字符返回非零值' .例如,当区域设置为某些欧洲语言时,isalpha 将为'ü' 的字符代码返回非零值,在我正在使用的字符集中其值为252。函数toupper 可能会将此字符更改为'Ü',它在我正在使用的字符集中具有220 的值。使用此值,您将越界访问count
  • 另外值得注意的是,ISO C 标准不保证字母'A''Z' 是连续存储的。它只对数字'0''9' 做出这样的保证。因此,您的程序将使用连续存储字母的字符集(例如 ASCII),但不适用于 EBCDIC,它不会这样做。
  • @AndreasWenzel 非常感谢您的 cmets!我已根据您的建议编辑了答案。
  • 我现在已删除您已解决的我的 cmets,因为它们不再相关(因为您已解决问题)。我没有删除两个 cmets,因为它们仍然相关。但是,正如我在已删除的 cmets 中所述,这些剩余的问题可能不值得修复。假设您的程序仅用于与 ASCII 兼容的字符编码可能是安全的,因此您可以假设 'A''Z' 是连续存储的。如果您确定使用的是默认语言环境,则语言环境问题可能也不值得修复。
【解决方案3】:

我假设这是来自 cs50,我现在正在处理这个问题......我所做的是我使用了一个简单的布尔函数。我使用了两个循环,第一个循环代表引用的第一个字符,在嵌套循环中,我遍历所有其他字符,如果 i == k 则返回 false。

bool no_repeat(string arg_letters)
{
   for (int i = 0, j = strlen(arg_letters); i < j; i++)
   {
      for (int k = i+1; k < j; k++)
      {
         if (arg_letters[i] == arg_letters[k])
         {
            return false;
         }

      }
   }
   return true;
}

【讨论】:

    【解决方案4】:

    您可以使用strchr() 来检测给定字符是否在字符串的其余部分中重复:

    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char *argv[]) {
        if (argc != 2) {
            printf("Usage: ./substitution key\n");
            return 1;
        } else {
            char *key = argv[1];
            int len = strlen(key]);
            if (len != 26) {
                printf("Key must contain 26 characters.\n");
                return 1;
            } else {
                for (int i = 0; i < len; i++) {
                    unsigned char c = key[i];
                    if (!isalpha(c)) {
                        printf("The key must contain only letters.\n");
                        return 1;
                    }
                    if (strchr(key + i + 1, c) {
                        printf("The key must not contain duplicate letters.\n");
                        return 1;
                    }
                }
            }
        }
        return 0;
    }
    

    或者,您可以使用一组指标来一次性执行检查。如果您想忽略字符大小写,则首选此方法。

    #include <ctype.h>
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        if (argc != 2) {
            printf("Usage: ./substitution key\n");
            return 1;
        } else {
            char *key = argv[1];
            unsigned char seen['Z' + 1 - 'A'] = { 0 };
            int i;
            for (i = 0; key[i] != '\0'; i++) {
                unsigned char c = key[i];
                if (!isalpha(c)) {
                    printf("The key must contain only letters.\n");
                    return 1;
                }
                if (seen[toupper(c) - 'A']++ != 0) {
                    printf("The key must not contain duplicate letters.\n");
                    return 1;
                }
            }
            if (i != 26) {
                printf("Key must contain all 26 letters.\n");
                return 1;
            }
        }
        return 0;
    }
    

    【讨论】:

    • 这个解决方案有一个time complexityO(n^2),而它可以在O(n) 中解决,正如this solution 演示的那样。
    • @AndreasWenzel:时间复杂度并不是真正的二次方:指令总数是有界的,因为输入字符串正好有 26 个字符:可能使用更少指令的更精细的解决方案,但不一定值得努力...请注意,关于大小写的问题有些模棱两可,并且您指向的解决方案不能移植到执行字符集中字母不连续的系统。在使用 EBCDIC 的系统上,此代码具有未定义的行为。
    • 是的,你说得对,我指出的解决方案不适用于 EBCDIC(实际上我已经在对该答案的评论中指出了这一点)。但是,this other solution 可以与 EBCDIC 一起使用,并且时间复杂度为 O(n),但它需要 256 个字节的数据,必须初始化为 0。因此,您可能是对的,使用 O(n) 算法可能不是值得,当n 限制为 26 时。
    • @AndreasWenzel:添加了另一种一次性的便携式方法。绝对更好:)
    • @AndreasWenzel:我使用了一个小数组,仍然对目标字符集做出假设,但恐怕 C 标准不要求 A 是最小的大写字母和 Z最大的...该代码适用于 ASCII 和 EBCDIC,但是对于奇异字符集似乎需要一个完整的 256 字节数组,这无论如何都不符合 OP 的要求。
    【解决方案5】:

    您可以考虑使用strchr()。我从https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_72/rtref/strchr.htm得到一个例子

    #include <stdio.h>
    #include <string.h>
    
    #define SIZE 40
    
    int main(void) {
      char buffer1[SIZE] = "computer program";
      char * ptr;
      int    ch = 'p';
    
      ptr = strchr(buffer1, ch);
      printf("The first occurrence of %c in '%s' is '%s'\n",
              ch, buffer1, ptr );
    
    }
    
    /*****************  Output should be similar to:  *****************
    The first occurrence of p in 'computer program' is 'puter program'
    */
    

    【讨论】:

    • 使用strchr是浪费;需要O(n^2)时间,但问题中的问题可以在O(n)时间内解决。
    【解决方案6】:

    有很多方法可以做到这一点,但我喜欢的方法是先对字符串进行排序,然后遍历它并将字符串删除或复制到另一个字符串/数组,你可以这样做

    s = "bedezdbe"
    

    排序后

    s = "bbddeeez"
    

    检查一下

    if(s[i-1]!=s[i])
    

    【讨论】:

    • 排序需要 O(n log n) 时间,但问题中的问题可以在 O(n) 时间内解决。
    • 正如我所说“有很多方法可以做到,但我喜欢的一种是”
    【解决方案7】:

    我假设您还没有学习数据结构和算法。如果是这样,我会进行二进制插入排序,当您发现重复时,您只需打印错误消息并返回 1,就像您对其余错误所做的那样。

    也就是说,现在你可能已经想不通了。所以在这种情况下,只需使用蛮力方法而不考虑优化。

    1. 从第一个字符开始,遍历字符串的其余部分以查看是否匹配。

    2. 如果匹配,错误信息

    3. 如果没有匹配,则重复下一个字符。

    下面的代码通过提到的这种蛮力方法检查是否有任何重复字符。当然,您需要根据您的代码对其进行调整。

    #include <stdio.h>
    #include <string.h>
    #include <stdbool.h>
    
    bool itrepeats(char c, char *restofstring, int sublen){
        for(int j=0; j<sublen; j++){
            if(c==*restofstring) return true;
            restofstring++;
        }
        return false;
    }
    
    int main (int argc, char *argv[])
    {
        int len = strlen(argv[1]);
        char *substr = argv[1];
        
        //loop through all characters
        for(int i=0;i<len;i++){
            //get a character
            char c = argv[1][i];
            //start of substring after c
            substr++;
            //length of that substring
            int substrlen = len-(i+1);
            //check for c in rest of string
            if(itrepeats(c, substr, substrlen)){
                printf("%c repeats, not allowed!\n",c);
                return 1;
            }
            else printf("all good!\n");
        }
    }
    

    【讨论】:

      【解决方案8】:

      您可以首先对字符串中的字符进行排序,以便任何重复的字母彼此相邻。因此,在排序后,使用包含当前扫描字符的变量扫描已排序的字符串。如果您扫描的下一个字符与您扫描的最后一个字符相同,则您有一个重复字符

      【讨论】:

      • "字符必须按字母顺序排列。" - 你指的是什么?第一个命令行参数?还是 ASCII 表?
      • 排序需要 O(n log n) 时间,但问题中的问题可以在 O(n) 时间内解决。
      • eejakobowski - OP 在哪里说它们必须按字母顺序排序?他只是说输入必须是字母顺序且不重复。
      • Eric - 是的,我在上面看到了你的答案,我自己试过了,很惊喜。
      • @ChumbiChubaGo 我将“所有字符都必须按字母顺序”误读为“输出必须按字母顺序”。无论如何,这仍然是一个有效的解决方案,并且需要的工作量最少
      【解决方案9】:

      我以前做过,所以这是我对这个问题的解决方案: 每次我们在字符串中找到当前 argv[1] 的字符时,我们都会给计数器加 1

      在argv[1]正常的正常情况下,计数器最后应该是26。

      但是如果有重复的字符,counter最后应该大于26。

      int counter = 0;
      for (int i = 0; i < len; i++)
              {
                  char argu = argv[1][i];
                  // make sure that the key doesnt accept duplicated characters in key.
                  for (int k = 0; k < len; k++)
                  {
                      if (argu == argv[1][k])
                      {
                          counter++;
                      }
                      if (counter > 26)
                      {
                          printf("Opps! no duplicated characters!\n");
                          return 1;
                      }
                  }
              }
      

      【讨论】:

      • 问题表明没有重复的字符是期望的结果。因此,在这种情况下打印"Oops" 似乎并不合适,因为这意味着发生了错误。 "Opps" 应该是 "Oops" 的意思吗?
      • 此解决方案效率相当低,因为它总共需要 676 (26^2) 次比较(time complexityO(n^2)),而 this solution 只需要 26 次比较(时间复杂度为 @ 987654328@).
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-17
      • 1970-01-01
      • 2015-01-22
      • 2016-02-12
      • 2018-10-17
      • 1970-01-01
      • 2011-11-15
      相关资源
      最近更新 更多