显示所有可能的数字,其数字按升序排列

时间:2014-03-12 11:33:09

标签: algorithm

编写一个程序,可以显示给定两个数字之间的所有可能数字,其数字按升序排列。

例如: -

  1. 输入:5000到6000

    Output: 5678 5679 5689 5789
    
  2. 输入:90到124

    Output: 123 124
    
  3. 蛮力方法可以使其计入所有数字并检查每一个数字。但我想要的方法可以跳过一些数字,并且可以带来比O(n)更小的复杂性。是否存在可以为此问题提供更好方法的任何此类解决方案?

5 个答案:

答案 0 :(得分:2)

我提供Python解决方案。它是有效的,因为它只考虑相关的数字。基本思路是向上计数,但处理溢出的方式有所不同。虽然我们通常将溢出数字设置为0,但我们将它们设置为前一个数字+1。请查看内联评论以获取更多详细信息。你可以在这里玩它:http://ideone.com/ePvVsQ

def ascending( na, nb ):
    assert nb>=na
    # split each number into a list of digits
    a = list( int(x) for x in str(na))
    b = list( int(x) for x in str(nb))

    d = len(b) - len(a)

    # if both numbers have different length add leading zeros 
    if d>0:
        a = [0]*d + a  # add leading zeros
    assert len(a) == len(b)
    n = len(a)


    # check if the initial value has increasing digits as required,
    # and fix if necessary
    for x in range(d+1, n):
        if a[x] <= a[x-1]:
            for y in range(x, n):
                a[y] = a[y-1] + 1
            break

    res = [] # result set

    while a<=b:
        # if we found a value and add it to the result list
        # turn the list of digits back into an integer
        if max(a) < 10:
            res.append( int( ''.join( str(k) for k in a ) ) )

        # in order to increase the number we look for the
        # least significant digit that can be increased
        for x in range( n-1, -1, -1): # count down from n-1 to 0
            if a[x] < 10+x-n:
                break
        # digit x is to be increased
        a[x] += 1
        # all subsequent digits must be increased accordingly
        for y in range( x+1, n ):
            a[y] = a[y-1] + 1

    return res

print( ascending( 5000, 9000 ) )

答案 1 :(得分:1)

听起来像Project Euler的任务。这是C ++中的解决方案。它不短,但它简单有效。哦,嘿,它使用backtracking

// Higher order digits at the back
typedef std::vector<int> Digits;

// Extract decimal digits of a number
Digits ExtractDigits(int n)
{
  Digits digits;

  while (n > 0)
  {
    digits.push_back(n % 10);
    n /= 10;
  }

  if (digits.empty())
  {
    digits.push_back(0);
  }

  return digits;
}

// Main function
void PrintNumsRec(
  const Digits& minDigits,  // digits of the min value
  const Digits& maxDigits,  // digits of the max value
  Digits& digits,  // digits of current value
  int pos,         // current digits with index greater than pos are already filled
  bool minEq,      // currently filled digits are the same as of min value
  bool maxEq)      // currently filled digits are the same as of max value
{
  if (pos < 0)
  {
    // Print current value. Handle leading zeros by yourself, if need
    for (auto pDigit = digits.rbegin(); pDigit != digits.rend(); ++pDigit)
    {
      if (*pDigit >= 0)
      {
        std::cout << *pDigit;
      }
    }
    std::cout << std::endl;
    return;
  }

  // Compute iteration boundaries for current position
  int first = minEq ? minDigits[pos] : 0;
  int last = maxEq ? maxDigits[pos] : 9;

  // The last filled digit
  int prev = digits[pos + 1];

  // Make sure generated number has increasing digits
  int firstInc = std::max(first, prev + 1);

  // Iterate through possible cases for current digit
  for (int d = firstInc; d <= last; ++d)
  {
    digits[pos] = d;

    if (d == 0 && prev == -1)
    {
      // Mark leading zeros with -1
      digits[pos] = -1;
    }

    PrintNumsRec(minDigits, maxDigits, digits, pos - 1, minEq && (d == first), maxEq && (d == last));
  }
}

// High-level function
void PrintNums(int min, int max)
{
  auto minDigits = ExtractDigits(min);
  auto maxDigits = ExtractDigits(max);

  // Make digits array of the same size
  while (minDigits.size() < maxDigits.size())
  {
    minDigits.push_back(0);
  }

  Digits digits(minDigits.size());

  int pos = digits.size() - 1;

  // Placeholder for leading zero
  digits.push_back(-1);

  PrintNumsRec(minDigits, maxDigits, digits, pos, true, true);
}

void main()
{
  PrintNums(53, 297);
}

它使用递归来处理任意数量的数字,但它与嵌套循环方法基本相同。以下是(53, 297)的输出:

056
057
058
059
067
068
069
078
079
089
123
124
125
126
127
128
129
134
135
136
137
138
139
145
146
147
148
149
156
157
158
159
167
168
169
178
179
189
234
235
236
237
238
239
245
246
247
248
249
256
257
258
259
267
268
269
278
279
289

更有趣的问题是计算所有这些数字而不明确计算它。人们可以使用动态编程。

答案 2 :(得分:1)

只有非常有限的数字可以匹配您的定义(最多9位数),这些数字可以非常快速地生成。但是如果你真的需要速度,只需缓存树或生成的列表,并在需要结果时进行查找。

using System;
using System.Collections.Generic;

namespace so_ascending_digits
{
  class Program
  {
    class Node
    {
        int digit;
        int value;
        List<Node> children;

        public Node(int val = 0, int dig = 0)
        {
            digit = dig;
            value = (val * 10) + digit;
            children = new List<Node>();
            for (int i = digit + 1; i < 10; i++)
            {
                children.Add(new Node(value, i));
            }
        }

        public void Collect(ref List<int> collection, int min = 0, int max = Int16.MaxValue)
        {
            if ((value >= min) && (value <= max)) collection.Add(value);
            foreach (Node n in children) if (value * 10 < max) n.Collect(ref collection, min, max);
        }
    }

    static void Main(string[] args)
    {
        Node root = new Node();
        List<int> numbers = new List<int>();
        root.Collect(ref numbers, 5000, 6000);
        numbers.Sort();
        Console.WriteLine(String.Join("\n", numbers));
    }
  }
}

答案 3 :(得分:1)

为什么强力算法的效率可能非常低。

对输入进行编码的一种有效方法是提供两个数字:范围的下端, a ,以及范围中的值的数量, ba-1 。这可以用O(lg a + lg( b - a ))位编码,因为表示数字所需的位数在base-2中,大致等于数字的base-2对数。我们可以将其简化为O(lg b ),因为直观地,如果 b - a 很小,那么 a = O( b ),如果 b - 很大,那么 b - a < / em> = O( b )。无论哪种方式,总输入大小为O(2 lg b )= O(lg b )。

现在,强力算法只检查从 a b 的每个数字,并输出其数字在基数10中的数字按递增顺序排列。该范围内有 b - a + 1个可能的数字。但是,当您根据输入大小表示时,您会发现 b - a + 1 = 2 lg( b - a + 1) = 2 O(lg b ,间隔时间足够大。

这意味着对于输入大小 n = O(lg b ),您可能需要检查最坏情况下的O(2 n )值。

更好的算法

您可以直接生成有效数字,而不是检查区间中的每个可能的数字。这是对如何进行粗略概述。数字 n 可以被认为是一个数字序列 n 1 ... n k ,其中 k 大致是log 10 n

对于 a 和一个四位数的数字 b ,迭代看起来像

for w in a1 .. 9:
  for x in w+1 .. 9:
    for y in x+1 .. 9:
      for x in y+1 .. 9:
         m = 1000 * w + 100 * x + 10 * y + w
         if m < a:
            next
         if m > b:
            exit
         output w ++ x ++ y ++ z  (++ is just string concatenation)

如果 a 的数字少于 b ,则 a 1 可被视为0。

对于较大的数字,您可以想象只需添加更多嵌套for循环。通常,如果 b 具有 d 位,则需要 d = O(lg b )循环,每个最多迭代10次。因此运行时间为O(10 lg b )= O(lg b ),这远远好于O(2 lg b )通过检查每个数字是否已排序来获得运行时间。


我掩盖的另一个细节,实际上 会影响运行时间。如上所述,算法需要考虑生成m所需的时间。在没有详细说明的情况下,您可以假设这最多会增加运行时间O(lg b )的因子,从而产生O(lg 2 b )算法。但是,在每个for循环的顶部使用一些额外的空间来存储部分产品可以节省大量的冗余乘法,从而允许我们保留最初规定的O(lg b )运行时间。

答案 4 :(得分:0)

单向(伪代码):

for (digit3 = '5'; digit3 <= '6'; digit3++)
  for (digit2 = digit3+1; digit2 <= '9'; digit2++)
    for (digit1 = digit2+1; digit1 <= '9'; digit1++)
      for (digit0 = digit1+1; digit0 <= '9'; digit0++)
        output = digit3 + digit2 + digit1 + digit0; // concatenation