【问题标题】:Unique numbers in C++ [closed]C ++中的唯一数字[关闭]
【发布时间】:2012-01-29 20:50:03
【问题描述】:

我正在尝试有效地列出 1 到 100 之间的数字。但是我必须去掉具有相同数字的数字。
示例: 根据这条规则,12 与 21 相同 13 是 31 14 是 41 所以 for 循环不会超过相同的数字。
我正在考虑一些技巧,例如获取从 1 到 100 的所有数字,然后删除找到的当前数字的排列。 我问这个的原因是因为在像 100000 这样的大范围内它会失败。 另一个例子: 124 等于 142,241,214,412,421

【问题讨论】:

  • 为什么要去掉相同数字的数字?这是作业题吗?
  • 看起来像一个家庭作业。写代码有什么问题?
  • 不,这不是作业。我正在寻找有效的解决方案,因为我可以自己应用一种幼稚的方法。
  • 您的幼稚解决方案的代码在哪里?
  • 我在想你只需要第 (n) 位大于或等于第 (n-1) 位的数字 - 因为如果你遇到例如 31,你会知道您已经有了较小的数字 13,因此您不需要 31。这有帮助吗?

标签: c++ performance algorithm math


【解决方案1】:

您可以应用递归。这个函数的原型是这样的:

print_digits(int num_of_remaining_digits,int start_from_digit, int current_number);

编辑:为了完成,我在这里提出我的解决方案(我认为它比 Ben Voigt 和升序输出顺序具有更好的可读性

void print_digits(int num_of_remaining_digits,int start_from_digit, int current_number)
{
  if(num_of_remaining_digits == 0) 
  {
    std::cout << current_number << std::endl;
    return;
  }

  for(int i=start_from_digit;i<=9;i++)
  {
     print_digits(num_of_remaining_digits-1,i,10*current_number+i);
  }
}

这里是测试代码

http://ideone.com/Xm8Mv

这是如何工作的?

它是递归中的经典之一。首先是停止条件。然后是主循环。
主循环从start_from_digit 开始,因为所有生成的数字都将按非降序排列。例如,如果current_number15,它将调用print_digits whith

print_digits(num_of_remaining_digits-1,5,155)
print_digits(num_of_remaining_digits-1,6,156)
print_digits(num_of_remaining_digits-1,7,157)
print_digits(num_of_remaining_digits-1,8,158)
print_digits(num_of_remaining_digits-1,9,159)

在每次调用中,它会检查我们是否到达了结尾 whit num_of_remaining_digits,如果没有,将从使用 current_number 推送为 start_from_digit(第二个参数)的数字继续

【讨论】:

  • 难以置信!谢谢。正是我所需要的
  • 你能详细解释一下吗?
  • 当问题被标记为performance时,最好远离std::cout。否则是一个很好的解决方案。
  • 如果你用 printf 替换 cout,如果你用 Ben Voigt 的代码替换这个代码,它的运行速度会快 3 倍,速度会快 3 倍。
  • 也不应该有 10 个 成为列表的一部分吗?
【解决方案2】:

您正在寻找一些字符 (0..9) 与特定长度 (100=2, 1000=3) 的组合。

看这里Algorithm to return all combinations of k elements from n

【讨论】:

  • 谢谢法比奥,看起来可以帮到我
  • 这绝对是要走的路。
【解决方案3】:

我会通过重载正确的运算符(从我的脑海中认为应该只有less)来编写一个适合您比较需求的类,并使用std::set

【讨论】:

    【解决方案4】:

    我会使用哈希表,像这样

    1) 从导出的数字中推导出一个密钥,使得具有相同数字的数字具有相同的密钥(例如,将数字相加,因此“124”和“142”的密钥为 7,或者取数字(+1),因此“124”和“142”的键为 30 - 数字 0 必须 +1)

    2) 将数字放入由其键索引的哈希表中

    现在,关于您是否已经有一个具有相同数字的数字的测试仅限于哈希表中具有相同键的实体。该算法需要线性存储,其性能取决于您能想出的密钥有多好。

    【讨论】:

      【解决方案5】:
      #include <stdio.h>
      
      size_t enum_canonical(char* begin, char* end, char min, char max)
      {
          if (begin == end) {
              puts(begin);
              putchar('\n');
              return 1;
          }
      
          size_t result_count = 0;
          --end;
          for( *end = min; *end <= max; ++*end )
              result_count += enum_canonical(begin, end, min, *end);
      
          return result_count;
      }
      
      int main(void)
      {
          char buff[7];
          printf("%d results\n", enum_canonical(buff, &(buff[6] = '\0'), '0', '9'));
      }
      

      演示:http://ideone.com/BWGdg

      【讨论】:

      • 非常感谢您的帮助。
      • @rolandbishop:请注意这实际上是相同的approach mentioned by ralu
      • +1 ,但这段代码的运行速度仍然和你的一样快 ideone.com/RTXqQ :)
      • @ralu:当然,但我没有硬编码'9' :-p
      • 我注意到了,同意。您还可以生成字符的排列。我更喜欢你的方法。至少我学到了一些东西。你知道为什么这么慢吗?
      【解决方案6】:

      首先,请注意您的规则不包括 11 的倍数。(为什么?)

      首先生成第一个数字 = 1 的所有 2 位数字。

      现在,生成第一个数字 = 2 的所有 2 位数字,但不要生成任何与第一个列表中的数字匹配的数字。

      重复 3 次,但不要从前两个列表中生成任何数字。

      请注意,对于任何 2 位数字 ab,要使其符合条件,它必须是 a

      在 PASCAL 中,只是因为我觉得很烦:

      var i:integer;  j:integer;
      begin
        for i := 1 to 8 do
          for j := i+1 to 9 do
            println(i*10+j);
      end;
      

      稍后添加

      请注意,您要生成的数字将始终具有严格单调递增的数字。要使数字 2abc 符合条件,请注意 2

      【讨论】:

      • a
      【解决方案7】:

      让我们取 1 到 1000。由于 1000 中有 4 位数字,因此我将 1 打印为 0001,因此根据我的算法,0001、0010、0100、1000 是相同的数字。 0120、0012、0210、0102、0201、0021 也是相同的数字。

      这是程序:

      int main()
      {
          int i=0, j=0, k=0;
      
          while(i<=9)
          {
              int unique=(i*100 + j*10 + k);
              printf("unique number : %3d\n", unique);
      
              if(j==9 && k==9)
              {
                  i++;
                  k=i;
                  j=i;
              }
              else if(k==9)
              {
                  j++;
                  k=j;
              }
              else 
                  k++;
          }
      
      }
      

      【讨论】:

        【解决方案8】:

        好像可以这么简单:

        list = {}
        for (j = 1 to 100)
             if (j is not excluded from list)
                  list += j;
        

        真的,只有if 条件很有趣:需要检查列表项的所有相关属性。

        【讨论】:

          【解决方案9】:

          创建一个函数,该函数接受一个字符串,并返回一个字符串数组,其中包含该字符串中字符的所有可能排列。这并不难,但它可能最容易进行递归。不过,说起来容易做起来难。

          一旦你有了这个函数,它返回数组,你只需遍历数组并删除与数组中的一个共享一个公共数字的indecies。

          【讨论】:

          • 感谢您的回答。我遇到的问题是,当我尝试在上限为 >=5000 万时应用这种方法时,算法的运行速度非常慢。我试过在循环中什么都不做,只是增加一个参数,但即使这样也需要超过 10 秒。
          • 那么你就有问题了,因为这是测试所有可能排列的唯一合理方法。
          【解决方案10】:

          我会使用set 来排列数字的数字:

          std::vector<int> list_unwanted = digit_permutations(number);
          std::unordered_set<int> set_unwanted(begin(list_unwanted), end(list_unwanted));
          

          然后从 0 循环到极限,而不是通过检查它们是否在集合中来添加不需要的数字 set_unwanted:

          std::vector<int> numbers;
          numbers.reserve(limit - set_unwanted.count());
          
          for (int i = 0; i < limit; ++i)
              if (!set_unwanted.count(i))
          

          【讨论】:

            【解决方案11】:

            如果您有一组数字,则该集合的任何排列都不是有效的解决方案,因此首先创建一个函数来确定一组数字是否是另一组的排列。 要获得个位数,您可以递归除以 10,直到获得零值。 如果你把所有的数字都放在一个像[1,2,4]这样的数组中,检查另一个数组是否是另一个集合的排列(只有当它们具有相同的长度时才检查它):

            bool permutation(int *a, int *b, int n) // n leading dimension
            {
                bool result=true, vector[n]={false};
                for(int i=0;i<n;i++)
                {
                    for(int j=0;j<n ;j++)
                    {
                        if(a[i]==b[j])
                            vector[i]=false;
                    }
                }
                for(int i=0;i<n && result;i++)
                    result=(vector[i]==true);   // if vector[i] is false, result is false, is
                                                // not a permutation and the loop ends
                return result;
            }
            

            我没有测试过,但我认为它有效,否则告诉我。 至于将所有数字放入数组中,我认为这很容易。 生成所有数字后,您检查某个数字是否不是已取数字的排列。

            【讨论】:

            • 如果我没有理解错误,它就像一个数字是预先找到的数字的排列一样适用。感谢您的贡献,但我更喜欢尽量不寻找未来的数字。如果发现 124 号循环不应该再次超过 241,214。
            • 我现在意识到这是错误的,如果我有一个数组[1,1,1]和[1,2,3],这个函数返回true。
            【解决方案12】:

            这是我的想法,将每个值的数字放在一组中。将该组用作另一组的键,以跟踪已使用的数字。在我的情况下,我使用一个位字段作为数字的集合,即数字 0 由 1 表示,数字 1 由 2 表示(2 由 4 等)。解释太累了,这里是代码:

            unsigned int get_digits(int v)
            {
              unsigned int rv = 0;
              do
              {
                rv |= 1 << (v % 10);
                v /= 10;
              } while(v);
              return rv;
            }
            
            void unique_ints(int n)
            {
              std::set<unsigned int> used_combinations;
              for(int i = 0; i < n; ++i)
              {
                const unsigned int d = get_digits(i);
                if(used_combinations.find(d) == used_combinations.end())
                {
                  used_combinations.insert(d);
                  // cout or some other way to store the value
                  std::cout << i <<  std::endl;
                }
              }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-05-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多