最快的算法来检查一个数字是否是pandigital?

时间:2010-03-20 21:43:46

标签: java c# algorithm

Pandigital数字是一个包含数字1..number长度的数字 例如123,4312和967412385。

我已经解决了许多Project Euler问题,但Pandigital问题总是超过一分钟规则。

这是我的pandigital功能:

private boolean isPandigital(int n){
    Set<Character> set= new TreeSet<Character>();   
    String string = n+"";
    for (char c:string.toCharArray()){
        if (c=='0') return false;
        set.add(c);
    }
    return set.size()==string.length();
}

创建自己的函数并使用此方法进行测试

int pans=0;
for (int i=123456789;i<=123987654;i++){
    if (isPandigital(i)){
         pans++;
    }
}

使用此循环,您应该获得720个pandigital数字。我的平均时间是500毫秒。

我正在使用Java,但问题是对任何语言都开放。

更新
@andras答案到目前为止是最好的时间,但是@Sani Huttunen的回答激发了我添加一个新的算法,这个算法几乎和@andras一样。

18 个答案:

答案 0 :(得分:20)

C#,17毫秒,如果你真的想要检查

class Program
{
    static bool IsPandigital(int n)
    {
        int digits = 0; int count = 0; int tmp;

        for (; n > 0; n /= 10, ++count)
        {
            if ((tmp = digits) == (digits |= 1 << (n - ((n / 10) * 10) - 1)))
                return false;
        }

        return digits == (1 << count) - 1;
    }

    static void Main()
    {
        int pans = 0;
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 123456789; i <= 123987654; i++)
        {
            if (IsPandigital(i))
            {
                pans++;
            }
        }
        sw.Stop();
        Console.WriteLine("{0}pcs, {1}ms", pans, sw.ElapsedMilliseconds);
        Console.ReadKey();
    }
}

对于与基数10中的Wikipedia definition一致的检查:

const int min = 1023456789;
const int expected = 1023;

static bool IsPandigital(int n)
{
    if (n >= min)
    {
        int digits = 0;

        for (; n > 0; n /= 10)
        {
            digits |= 1 << (n - ((n / 10) * 10));
        }

        return digits == expected;
    }
    return false;
}

要枚举您给出的范围内的数字,生成排列就足够了。

以下不是严格意义上的问题的答案,因为它没有实施检查。它使用了一个未针对这种特殊情况进行优化的通用排列实现 - 它仍然在13ms内生成所需的720个排列(换行符可能会混乱):

static partial class Permutation
{
    /// <summary>
    /// Generates permutations.
    /// </summary>
    /// <typeparam name="T">Type of items to permute.</typeparam>
    /// <param name="items">Array of items. Will not be modified.</param>
    /// <param name="comparer">Optional comparer to use.
    /// If a <paramref name="comparer"/> is supplied, 
    /// permutations will be ordered according to the 
    /// <paramref name="comparer"/>
    /// </param>
    /// <returns>Permutations of input items.</returns>
    public static IEnumerable<IEnumerable<T>> Permute<T>(T[] items, IComparer<T> comparer)
    {
        int length = items.Length;
        IntPair[] transform = new IntPair[length];
        if (comparer == null)
        {
            //No comparer. Start with an identity transform.
            for (int i = 0; i < length; i++)
            {
                transform[i] = new IntPair(i, i);
            };
        }
        else
        {
            //Figure out where we are in the sequence of all permutations
            int[] initialorder = new int[length];
            for (int i = 0; i < length; i++)
            {
                initialorder[i] = i;
            }
            Array.Sort(initialorder, delegate(int x, int y)
            {
                return comparer.Compare(items[x], items[y]);
            });
            for (int i = 0; i < length; i++)
            {
                transform[i] = new IntPair(initialorder[i], i);
            }
            //Handle duplicates
            for (int i = 1; i < length; i++)
            {
                if (comparer.Compare(
                    items[transform[i - 1].Second], 
                    items[transform[i].Second]) == 0)
                {
                    transform[i].First = transform[i - 1].First;
                }
            }
        }

        yield return ApplyTransform(items, transform);

        while (true)
        {
            //Ref: E. W. Dijkstra, A Discipline of Programming, Prentice-Hall, 1997
            //Find the largest partition from the back that is in decreasing (non-icreasing) order
            int decreasingpart = length - 2;
            for (;decreasingpart >= 0 && 
                transform[decreasingpart].First >= transform[decreasingpart + 1].First;
                --decreasingpart) ;
            //The whole sequence is in decreasing order, finished
            if (decreasingpart < 0) yield break;
            //Find the smallest element in the decreasing partition that is 
            //greater than (or equal to) the item in front of the decreasing partition
            int greater = length - 1;
            for (;greater > decreasingpart && 
                transform[decreasingpart].First >= transform[greater].First; 
                greater--) ;
            //Swap the two
            Swap(ref transform[decreasingpart], ref transform[greater]);
            //Reverse the decreasing partition
            Array.Reverse(transform, decreasingpart + 1, length - decreasingpart - 1);
            yield return ApplyTransform(items, transform);
        }
    }

    #region Overloads

    public static IEnumerable<IEnumerable<T>> Permute<T>(T[] items)
    {
        return Permute(items, null);
    }

    public static IEnumerable<IEnumerable<T>> Permute<T>(IEnumerable<T> items, IComparer<T> comparer)
    {
        List<T> list = new List<T>(items);
        return Permute(list.ToArray(), comparer);
    }

    public static IEnumerable<IEnumerable<T>> Permute<T>(IEnumerable<T> items)
    {
        return Permute(items, null);
    }

    #endregion Overloads

    #region Utility

    public static IEnumerable<T> ApplyTransform<T>(
        T[] items, 
        IntPair[] transform)
    {
        for (int i = 0; i < transform.Length; i++)
        {
            yield return items[transform[i].Second];
        }
    }

    public static void Swap<T>(ref T x, ref T y)
    {
        T tmp = x;
        x = y;
        y = tmp;
    }

    public struct IntPair
    {
        public IntPair(int first, int second)
        {
            this.First = first;
            this.Second = second;
        }
        public int First;
        public int Second;
    }

    #endregion
}

class Program
{

    static void Main()
    {
        int pans = 0;
        int[] digits = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Stopwatch sw = new Stopwatch();
        sw.Start();
        foreach (var p in Permutation.Permute(digits))
        {
            pans++;
            if (pans == 720) break;
        }
        sw.Stop();
        Console.WriteLine("{0}pcs, {1}ms", pans, sw.ElapsedMilliseconds);
        Console.ReadKey();
    }
}

答案 1 :(得分:12)

这是我的解决方案:

static char[][] pandigits = new char[][]{
        "1".toCharArray(),
        "12".toCharArray(),
        "123".toCharArray(),
        "1234".toCharArray(),
        "12345".toCharArray(),
        "123456".toCharArray(),
        "1234567".toCharArray(),
        "12345678".toCharArray(),
        "123456789".toCharArray(),
};
private static boolean isPandigital(int i)
{
    char[] c = String.valueOf(i).toCharArray();
    Arrays.sort(c);
    return Arrays.equals(c, pandigits[c.length-1]);
}

在我(相当慢的)系统上以0.3秒的速度运行循环。

答案 2 :(得分:7)

你可以改进两件事:

  • 您不需要使用集合:您可以使用包含10个元素的布尔数组
  • 使用除法和模运算(%)来提取数字,而不是转换为字符串。

答案 3 :(得分:6)

使用位向量来跟踪找到的数字似乎是最快的原始方法。有两种方法可以改善它:

  1. 检查数字是否可被9整除。这是pandigital的必要条件,因此我们可以预先排除88%的数字。

  2. 如果您的编译器没有为您执行此操作,请使用乘法和移位而不是除法。

  3. 这给出了以下内容,它在我的机器上运行测试基准测试约3ms。它可以正确识别36000080个9位数的泛数字数字,介于100000000和999999999之间。

    bool IsPandigital(int n)
    {
        if (n != 9 * (int)((0x1c71c71dL * n) >> 32))
            return false;
    
        int flags = 0;
        while (n > 0) {
            int q = (int)((0x1999999aL * n) >> 32);
            flags |= 1 << (n - q * 10);
            n = q;
        }
        return flags == 0x3fe;
    }
    

答案 4 :(得分:3)

我的解决方案涉及总和和产品。 这是在C#中,在我的笔记本电脑上运行大约180ms:

static int[] sums = new int[] {1, 3, 6, 10, 15, 21, 28, 36, 45};
static int[] products = new int[] {1, 2, 6, 24, 120, 720, 5040, 40320, 362880};

static void Main(string[] args)
{
  var pans = 0;
  for (var i = 123456789; i <= 123987654; i++)
  {
    var num = i.ToString();
    if (Sum(num) == sums[num.Length - 1] && Product(num) == products[num.Length - 1])
      pans++;
  }
  Console.WriteLine(pans);
}

protected static int Sum(string num)
{
  int sum = 0;
  foreach (char c in num)
    sum += (int) (c - '0');

  return sum;
}

protected static int Product(string num)
{
  int prod = 1;
  foreach (char c in num)
    prod *= (int)(c - '0');

  return prod;
}

答案 5 :(得分:3)

  

为什么找到什么时候可以制作它们?

from itertools import *

def generate_pandigital(length):
    return (''.join for each in list(permutations('123456789',length)))

def test():
    for i in range(10):
        print i
        generate_pandigital(i)

if __name__=='__main__':
    test()

答案 6 :(得分:2)

J 做得很好:

    isPandigital =: 3 : 0
        *./ (' ' -.~ ": 1 + i. # s) e. s =. ": y
    )

    isPandigital"0 (123456789 + i. 1 + 123987654 - 123456789)

但慢慢来。我会修改。现在,时钟为4.8秒。

编辑:

如果它只是在两个设定数字123456789和123987654之间,则表达式为:

    *./"1 (1+i.9) e."1 (9#10) #: (123456789 + i. 1 + 123987654 - 123456789)

在0.23秒内运行。这与J中的快速,蛮力风格一样。

答案 7 :(得分:2)

TheMachineCharmer是对的。至少对于某些问题,最好迭代所有的pandigitals,检查每个pandigitals以查看它是否符合问题的标准。但是,我认为他们的代码不太正确。

在这种情况下,我不确定哪种更好的礼节:发布新答案或编辑他们的答案。在任何情况下,这里都是修改后的Python代码,我认为它是正确的,虽然它不会生成0到n的pandigitals。

from itertools import *

def generate_pandigital(length):
    'Generate all 1-to-length pandigitals'
    return (''.join(each) for each in list(permutations('123456789'[:length])))

def test():
    for i in range(10):
        print 'Generating all %d-digit pandigitals' % i
        for (n,p) in enumerate(generate_pandigital(i)):
            print n,p

if __name__=='__main__':
    test()

答案 8 :(得分:1)

您可以添加:

 if (set.add(c)==false) return false;

这会使你的很多计算发生短路,因为一旦发现重复,它就会返回false,因为add()在这种情况下返回false。

答案 9 :(得分:1)

bool IsPandigital (unsigned long n) {
  if (n <= 987654321) {
      hash_map<int, int> m;
      unsigned long count = (unsigned long)(log((double)n)/log(10.0))+1;

      while (n) {
          ++m[n%10];
          n /= 10;
      }

      while (m[count]==1 && --count);

      return !count;
  }

  return false;
}

bool IsPandigital2 (unsigned long d) {
  // Avoid integer overflow below if this function is passed a very long number
  if (d <= 987654321) {
      unsigned long sum = 0;
      unsigned long prod = 1;
      unsigned long n = d;

      unsigned long max = (log((double)n)/log(10.0))+1;
      unsigned long max_sum = max*(max+1)/2;
      unsigned long max_prod = 1;

      while (n) {
          sum += n % 10;
          prod *= (n%10);
          max_prod *= max;
          --max;
          n /= 10;
      }

      return (sum == max_sum) && (prod == max_prod);
  }

答案 10 :(得分:1)

我有一个使用Java中的StringBuffers生成Pandigital数字的解决方案。在我的笔记本电脑上,我的代码总共需要5毫秒才能运行。其中,使用StringBuffers生成排列只需要1ms;将此StringBuffer转换为int []需要剩余的4ms。

@medopal:你能检查一下这段代码对系统的影响吗?

public class GenPandigits 
{

 /**
 * The prefix that must be appended to every number, like 123.
 */
int prefix;

/**
 * Length in characters of the prefix.
 */
int plen;

/**
 * The digit from which to start the permutations
 */
String beg;

/**
 * The length of the required Pandigital numbers.
 */
int len;

/**
 * @param prefix If there is no prefix then this must be null
 * @param beg If there is no prefix then this must be "1"
 * @param len Length of the required numbers (excluding the prefix)
 */
public GenPandigits(String prefix, String beg, int len)
{
    if (prefix == null) 
    {
        this.prefix = 0;
        this.plen = 0;
    }
    else 
    {
        this.prefix = Integer.parseInt(prefix);
        this.plen = prefix.length();
    }
    this.beg = beg;
    this.len = len;
}
public StringBuffer genPermsBet()
{
    StringBuffer b = new StringBuffer(beg);
    for(int k=2;k<=len;k++)
    {
        StringBuffer rs = new StringBuffer();
        int l = b.length();
        int s = l/(k-1);
        String is = String.valueOf(k+plen);
        for(int j=0;j<k;j++)
        {
            rs.append(b);
            for(int i=0;i<s;i++)
            {
                rs.insert((l+s)*j+i*k+j, is);
            }
        }
        b = rs;
    }
    return b;
}

public int[] getPandigits(String buffer)
{
    int[] pd = new int[buffer.length()/len];
    int c= prefix;
    for(int i=0;i<len;i++)
        c =c *10;
    for(int i=0;i<pd.length;i++)
        pd[i] = Integer.parseInt(buffer.substring(i*len, (i+1)*len))+c;
    return pd;
}
public static void main(String[] args) 
{
    GenPandigits gp = new GenPandigits("123", "4", 6);

    //GenPandigits gp = new GenPandigits(null, "1", 6);

    long beg = System.currentTimeMillis();
    StringBuffer pansstr = gp.genPermsBet();
    long end = System.currentTimeMillis();
    System.out.println("Time = " + (end - beg));
    int pd[] = gp.getPandigits(pansstr.toString());
    long end1 = System.currentTimeMillis();
    System.out.println("Time = " + (end1 - end));
    }
}

此代码也可用于生成所有Pandigital数字(不包括零)。只需将对象创建调用更改为

即可
GenPandigits gp = new GenPandigits(null, "1", 9);

这意味着没有前缀,并且排列必须从“1”开始并继续直到数字的长度为9.

以下是不同长度的时间测量值。 alt text @andras:您可以尝试运行代码来生成九位数的Pandigital数字吗?现在几点钟?

答案 11 :(得分:0)

很棒的答案,我的2美分

bool IsPandigital(long long number, int n){

int arr[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, amax = 0, amin;
while (number > 0){

    int rem = number % 10;

    arr[rem]--;

    if (arr[rem] < 0)
        return false;

    number = number / 10;
}

for (int i = 0; i < n; i++){

    if (i == 0)
        amin = arr[i];

    if (arr[i] > amax)
        amax = arr[i];

    if (arr[i] < amin)
        amin = arr[i];
}

if (amax == 0 && amin == 0)
    return true;
else
    return false;

}

答案 12 :(得分:0)

我重构了安德拉斯&#39;回答Swift:

extension Int {

    func isPandigital() -> Bool {

        let requiredBitmask = 0b1111111111;
        let minimumPandigitalNumber = 1023456789;

        if self >= minimumPandigitalNumber {

            var resultBitmask = 0b0;
            var digits = self;

            while digits != 0 {

                let lastDigit = digits % 10;
                let binaryCodedDigit = 1 << lastDigit;

                resultBitmask |= binaryCodedDigit;

                // remove last digit
                digits /= 10;
            }

            return resultBitmask == requiredBitmask;
        }

        return false;
    }
}


1023456789.isPandigital(); // true

答案 13 :(得分:0)

直线前进

boolean isPandigital(int num,int length){
    for(int i=1;i<=length;i++){
        if(!(num+"").contains(i+""))
            return false;
    }
    return true;
}

或者,如果您确定该号码已经是正确的长度

static boolean isPandigital(int num){
    for(int i=1;i<=(num+"").length();i++){
        if(!(num+"").contains(i+""))
            return false;
    }
    return true;
}

答案 14 :(得分:0)

我决定使用这样的东西:

  def is_pandigital(n, zero_full=True, base=10):
    """Returns True or False if the number n is pandigital.

    This function returns True for formal pandigital numbers as well as
    n-pandigital
    """
    r, l = 0, 0
    while n:
        l, r, n = l + 1, r + n % base, n / base
    t = xrange(zero_full ^ 1, l + (zero_full ^ 1))
    return r == sum(t) and l == len(t)

答案 15 :(得分:0)

在Java中

你总是可以生成它们,并将字符串转换为整数,这对于较大的数字来说更快

public static List<String> permutation(String str) {
    List<String> permutations = new LinkedList<String>();
    permutation("", str, permutations);
    return permutations;
}

private static void permutation(String prefix, String str, List<String> permutations) {
    int n = str.length();
    if (n == 0) {
        permutations.add(prefix);
    } else {
        for (int i = 0; i < n; i++) {
            permutation(prefix + str.charAt(i),
                    str.substring(0, i) + str.substring(i + 1, n), permutations);
        }
    }
}

以下代码适用于测试数字pandigitality。

对于你的测试我的测试大约在50毫秒左右。

1-9 PanDigital

public static boolean is1To9PanDigit(int i) {
    if (i < 1e8) {
        return false;
    }
    BitSet set = new BitSet();
    while (i > 0) {
        int mod = i % 10;
        if (mod == 0 || set.get(mod)) {
            return false;
        }
        set.set(mod);
        i /= 10;
    }
    return true;
}

或更一般,1到N,

public static boolean is1ToNPanDigit(int i, int n) {
    BitSet set = new BitSet();
    while (i > 0) {
        int mod = i % 10;
        if (mod == 0 || mod > n || set.get(mod)) {
            return false;
        }
        set.set(mod);
        i /= 10;
    }
    return set.cardinality() == n;
}

只是为了好玩,0到9,由于前导零

,零需要额外的逻辑
public static boolean is0To9PanDigit(long i) {
    if (i < 1e6) {
        return false;
    }
    BitSet set = new BitSet();
    if (i <= 123456789) { // count for leading zero
        set.set(0);
    }
    while (i > 0) {
        int mod = (int) (i % 10);
        if (set.get(mod)) {
            return false;
        }
        set.set(mod);
        i /= 10;
    }
    return true;
}

还用于设置迭代边界:

public static int maxPanDigit(int n) {
    StringBuffer sb = new StringBuffer();
    for(int i = n; i > 0; i--) {
        sb.append(i);
    }
    return Integer.parseInt(sb.toString());
}

public static int minPanDigit(int n) {
    StringBuffer sb = new StringBuffer();
    for(int i = 1; i <= n; i++) {
        sb.append(i);
    }
    return Integer.parseInt(sb.toString());
}

您可以轻松使用此代码生成通用的MtoNPanDigital数字检查器

答案 16 :(得分:0)

#include <cstdio>
#include <ctime>

bool isPandigital(long num)
{
  int arr [] = {1,2,3,4,5,6,7,8,9}, G, count = 9;
  do
  {
    G = num%10;
    if (arr[G-1])
      --count;
    arr[G-1] = 0;
  } while (num/=10);

  return (!count);
}

int main()
{
  clock_t start(clock());

  int pans=0;
  for (int i = 123456789;i <= 123987654; ++i)
  {
    if (isPandigital(i))
      ++pans;
  }

  double end((double)(clock() - start));

  printf("\n\tFound %d Pandigital numbers in %lf seconds\n\n", pans, end/CLOCKS_PER_SEC);

  return 0;
}

简单的实施。在大约140毫秒内强制执行并计算

答案 17 :(得分:0)

在123456789到123987654范围内,这个c#实现比@andras大约快8%,但是在我的测试盒上很难看到它在14ms内运行并且这个运行时间为13ms。

static bool IsPandigital(int n)
{
    int count = 0;
    int digits = 0;
    int digit;
    int bit;
    do
    {
        digit = n % 10;
        if (digit == 0)
        {
            return false;
        }
        bit = 1 << digit;

        if (digits == (digits |= bit))
        {
            return false;
        }

        count++;
        n /= 10;
    } while (n > 0);
    return (1<<count)-1 == digits>>1;
}

如果我们平均100次运行的结果,我们可以得到一个小数点。

public void Test()
{
    int pans = 0;
    var sw = new Stopwatch();
    sw.Start();
    for (int count = 0; count < 100; count++)
    {
        pans = 0;
        for (int i = 123456789; i <= 123987654; i++)
        {
            if (IsPandigital(i))
            {
                pans++;
            }
        }
    }
    sw.Stop();
    Console.WriteLine("{0}pcs, {1}ms", pans, sw.ElapsedMilliseconds / 100m);
}

@andras实现平均为14.4ms,此实现平均为13.2ms

编辑: 似乎mod(%)在c#中很昂贵。如果我们用手动编码版本替换mod运算符的使用,则此实现在100次运行中平均为11ms。

private static bool IsPandigital(int n)
{
    int count = 0;
    int digits = 0;
    int digit;
    int bit;
    do
    {
        digit = n - ((n / 10) * 10);
        if (digit == 0)
        {
            return false;
        }
        bit = 1 << digit;

        if (digits == (digits |= bit))
        {
            return false;
        }

        count++;
        n /= 10;
    } while (n > 0);
    return (1 << count) - 1 == digits >> 1;
}

编辑:在数字计算中积分n / = 10,以提高速度。

private static bool IsPandigital(int n)
{
    int count = 0;
    int digits = 0;
    int digit;
    int bit;
    do
    {
        digit = n - ((n /= 10) * 10);
        if (digit == 0)
        {
            return false;
        }
        bit = 1 << digit;

        if (digits == (digits |= bit))
        {
            return false;
        }

        count++;
    } while (n > 0);
    return (1 << count) - 1 == digits >> 1;
}