Самый быстрый алгоритм, чтобы проверить, является ли число pandigital? - PullRequest
27 голосов
/ 21 марта 2010

Pandigital number - это число, которое содержит цифры 1. длина номера.
Например, 123, 4312 и 967412385.

Я решил много задач Project Euler, но проблемы 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 пандигитальных чисел. Мое среднее время составляло 500 миллисекунд.

Я использую Java, но вопрос открыт для любого языка.

UPDATE
Ответ @andras пока что лучший, но ответ @Sani Huttunen вдохновил меня на добавление нового алгоритма, который почти совпадает с @ andras.

Ответы [ 18 ]

20 голосов
/ 21 марта 2010

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:

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;
}

Для перечисления чисел в указанном вами диапазоне достаточно генерации перестановок.

Следующее не является ответом на ваш вопрос в строгом смысле, так как оно не выполняет проверку. Он использует универсальную реализацию перестановок, не оптимизированную для этого особого случая - он по-прежнему генерирует необходимые 720 перестановок за 13 мс (разрывы строк могут быть перепутаны):

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();
    }
}
12 голосов
/ 21 марта 2010

Это мое решение:

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 секунды в моей (довольно медленной) системе.

7 голосов
/ 21 марта 2010

Вы можете улучшить две вещи:

  • Вам не нужно использовать набор: вы можете использовать логический массив с 10 элементами
  • Вместо преобразования в строку,используйте деление и операцию по модулю (%), чтобы извлечь цифры.
6 голосов
/ 04 марта 2013

Использование битового вектора для отслеживания найденных цифр является самым быстрым необработанным методом. Есть два способа улучшить это:

  1. Проверьте, делится ли число на 9. Это необходимое условие, чтобы быть пандигитальным, поэтому мы можем исключить 88% чисел заранее.

  2. Используйте умножения и сдвиги вместо делений, если ваш компилятор не сделает этого за вас.

Это дает следующее, которое запускает тестовый тест примерно за 3 мс на моей машине. Он правильно идентифицирует 362880 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;
}
3 голосов
/ 21 марта 2010

Зачем искать, когда их можно сделать?

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()
3 голосов
/ 21 марта 2010

Мое решение включает в себя суммы и продукты. Это на C # и работает около 180 мс на моем ноутбуке:

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;
}
2 голосов
/ 21 марта 2010

TheMachineCharmer прав.По крайней мере, для некоторых проблем лучше перебрать все пандигиталии, проверяя каждую из них на предмет соответствия критериям проблемы.Тем не менее, я думаю, что их код не совсем верен.

Я не уверен, какой из этих вариантов лучше в этом случае: публикация нового ответа или редактирование ихВ любом случае, вот модифицированный код Python, который я считаю правильным, хотя он не генерирует пандигиталии 0-к-n.

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()
2 голосов
/ 21 марта 2010

J делает это красиво:

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

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

Но медленно. Я буду пересматривать. На данный момент тактирование составляет 4,8 секунды.

EDIT:

Если это просто между двумя установленными числами, 123456789 и 123987654, то это выражение:

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

Запускается через 0,23 секунды. Это примерно такой же быстрый стиль грубой силы, как и в J.

1 голос
/ 19 сентября 2010

У меня есть решение для генерации чисел Pandigital с использованием StringBuffers в Java. На моем ноутбуке мой код работает в общей сложности 5 мс. Из этого только 1 мс требуется для генерации перестановок с использованием StringBuffers; оставшиеся 4 мс требуются для преобразования этого StringBuffer в int [].

@ 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));
    }
}

Этот код также можно использовать для генерации всех чисел Пандигитала (кроме нуля). Просто измените вызов создания объекта на

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

Это означает, что префикса нет, и перестановки должны начинаться с «1» и продолжаться до тех пор, пока длина чисел не станет равной 9.

Ниже приведены измерения времени для разных длин. alt text @andras: Можете ли вы попробовать и запустить свой код для генерации девятизначных чисел Pandigital? Сколько времени это займет?

1 голос
/ 21 марта 2010

Вы можете добавить:

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

Это приведет к короткому замыканию многих ваших вычислений, так как он вернет false, как только будет найден дубликат, поскольку add () возвращает false в этом случае.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...