2 ^ i * 5 ^ j에서 다음으로 가장 작은 것을 인쇄하십시오. 여기서 i, j> = 0


10

최근에 기술 전화 상영 중에이 질문을 받았지만 제대로 수행되지 않았습니다. 질문은 아래에 그대로 포함되어 있습니다.

{2^i * 5^j | i,j >= 0}정렬 된 컬렉션을 생성하십시오 . 다음으로 작은 값을 계속 인쇄하십시오.

예: { 1, 2, 4, 5, 8, 10...}

"다음으로 작은 것"은 최소 힙이 관련되어 있다고 생각하지만, 실제로 어디로 가야할지 몰랐으며 면접관은 어떠한 도움도 제공하지 않았습니다.

그러한 문제를 해결하는 방법에 대한 조언이 있습니까?


인터뷰는 당신이 일정한 기억 속에서 그것을 요구하기를 원한다고 생각합니다. O (n) 메모리를 사용하면이 작업이 매우 간단합니다. 또는 입력 n의 인코딩 크기가 기록되므로 적어도 O (logn) 메모리를 사용하십시오. 메모리 솔루션의 O (n)은 지수 메모리 솔루션입니다.
정보 : A

답변:


14

문제를 다시 말해 봅시다 : 2와 5 이외의 요소가 없도록 1에서 무한대까지 모든 숫자를 출력하십시오.

아래는 간단한 C # 스 니펫입니다.

for (int i = 1;;++i)
{
    int num = i;
    while(num%2 == 0) num/=2;
    while(num%5 == 0) num/=5;
    if(num == 1) Console.WriteLine(i);
}

킬리안 / QuestionC의 접근 방식은 훨씬 더 성능이 좋습니다. 이 접근 방식의 C # 스 니펫 :

var itms = new SortedSet<int>();
itms.Add(1);
while(true)
{
    int cur = itms.Min;
    itms.Remove(itms.Min);
    itms.Add(cur*2);
    itms.Add(cur*5);
    Console.WriteLine(cur);
}

SortedSet 중복 삽입을 방지합니다.

기본적으로 시퀀스의 다음 숫자가에 있는지 확인하여 작동합니다 itms.

이 접근법이 유효하다는 증거 :
설명 된 알고리즘은 형식으로 숫자를 출력 한 후 2^i*5^j세트에 이제 2^(i+1)*5^j와를 포함하도록 2^i*5^(j+1)합니다. 시퀀스의 다음 숫자가이라고 가정합니다 2^p*5^q. 이전에 출력 된 양식 번호 2^(p-1)*5^(q)또는 2^p*5^(q-1)p 또는 q가 0이 아닌 경우 둘 다 존재해야합니다 . 그렇지 않다면, 2^p*5^q이후 다음 번호가 아닙니다 2^(p-1)*5^(q)2^p*5^(q-1)모두 작다.

두 번째 스 니펫은 O(n)( O(i+j) = O(n)i와 j가 n보다 작기 때문에) n 이 출력 된 숫자의 수인 메모리를 사용 하며 O(n log n)시간 에 따라 n 개의 숫자를 찾습니다 . 첫 번째 스 니펫은 지수 시간으로 숫자를 찾습니다.


1
안녕하세요, 인터뷰 중에 왜 내가 혼란 스러웠는지 알 수 있습니다. 실제로, 제공된 예제는 질문에 설명 된 세트의 출력입니다. 1 = 2^0*5^0, 2 = 2^1*5^0, 4 = 2^2*5^0, 5 = 2^0*5^1, 8 = 2^3*5^0, 10 = 2^1*5^1.
Justin Skiles

사람들은 반복 .Remove().Add()가비지 컬렉터에서 나쁜 행동을 유발하는 것, 또는 일을 알아낼 것인가?
Snowbody

1
@ Snowbody : op의 질문은 알고리즘 질문이므로 다소 관련이 없습니다. 이를 무시하고, 가비지 수집기 오버 헤드보다 훨씬 빠른 문제가되기 때문에 첫 번째 관심사는 매우 큰 정수를 처리해야합니다.
Brian

8

이것은 답변을 아는 것이 도움이되는 일반적인 인터뷰 질문입니다. 내 개인 침대 시트에 관련 항목이 있습니다.

  • 순서대로 3 a 5 b 7 c 형식의 숫자를 생성하려면 1부터 시작하여 가능한 세 개의 후속 작업 (3, 5, 7)을 모두 보조 구조에 채우고 목록에서 가장 작은 숫자 추가하십시오.

다시 말해,이를 효율적으로 해결하려면 추가 정렬 버퍼가있는 2 단계 접근 방식이 필요합니다. 더 자세한 설명은 Gayle McDowell의 Cracking the Coding Interview 에 있습니다.


3

다음은 CPU를 희생하면서 일정한 메모리로 실행되는 답변입니다. 이것은 원래 질문의 맥락에서 좋은 대답이 아닙니다 (예 : 인터뷰 중 답변). 그러나 인터뷰 시간이 24 시간이면 그리 나쁘지 않습니다. ;)

아이디어는 내가 유효한 답인 n을 가지고 있다면, 순서의 다음은 2의 거듭 제곱을 5의 거듭 제곱으로 나눈 값이 될 것입니다. 그렇지 않으면 n의 5의 거듭 제곱은 2의 힘. 그것이 균등하게 나눠 졌다면. (... 또는 제수는 1 일 수 있습니다.)이 경우 2 또는 5를 곱하면됩니다.

예를 들어, 625에서 640으로 가려면 5 ** 4/2 ** 7을 곱하십시오. 또는 더 일반적으로, 2 ** m * 5 ** n일부 m에 대해 n의 값을 곱하면 n은 양수이고 하나는 음수 또는 0이며 승수는 숫자를 균등하게 나눕니다.

이제 까다로운 부분은 승수를 찾는 것입니다. 그러나 우리는 a) 제수가 숫자를 균등하게 나눠야한다. b) 승수는 1보다 커야한다 (숫자는 계속 증가한다), 그리고 c) 최저 승수를 1보다 크게 선택한 경우 (즉, 1 <f <다른 모든 f ) 다음 단계가 보장됩니다. 그 이후의 단계는 가장 낮은 단계입니다.

불쾌한 부분은 m, n의 값을 찾는 것입니다. 포기할 수있는 2 또는 5가 너무 많기 때문에 log (n) 가능성 만 있지만 반올림을 처리하는 부주의 한 방법으로 -1에서 +1의 요소를 추가해야했습니다. 따라서 우리는 각 단계마다 O (log (n))를 반복해야합니다. 전체적으로 O (n log (n))입니다.

좋은 소식은 값을 가져와 다음 값을 찾기 때문에 시퀀스의 어느 곳에서나 시작할 수 있다는 것입니다. 따라서 10 억 이후에 다음을 원한다면 2/5 또는 5/2를 반복하고 1보다 큰 가장 작은 승수를 선택하면 찾을 수 있습니다.

(파이썬)

MAX = 30
F = - math.log(2) / math.log(5)

def val(i, j):
    return 2 ** i * 5 ** j

def best(i, j):
    f = 100
    m = 0
    n = 0
    max_i = (int)(math.log(val(i, j)) / math.log(2) + 1) if i + j else 1
    #print((val(i, j), max_i, x))
    for mm in range(-i, max_i + 1):
        for rr in {-1, 0, 1}:
            nn = (int)(mm * F + rr)
            if nn < -j: continue
            ff = val(mm, nn)
            #print('  ' + str((ff, mm, nn, rr)))
            if ff > 1 and ff < f:
                f = ff
                m = mm
                n = nn
    return m, n

def detSeq():

    i = 0
    j = 0
    got = [val(i, j)]

    while len(got) < MAX:
        m, n = best(i, j)

        i += m
        j += n
        got.append(val(i, j))

        #print('* ' + str((val(i, j), m, n)))
        #print('- ' + str((v, i, j)))

    return got

정렬 된 목록 솔루션으로 생성 된 첫 번째 10,000에 대해 생성하는 처음 10,000 개의 숫자를 확인했으며 적어도 그 정도까지 작동합니다.

BTW 다음 조 1 조 뒤에 1,024,000,000,000 인 것으로 보입니다.

...

흠. best()점진적으로 확장되는 조회 테이블로 처리하여 O (n) 성능-값당 O (1) (!) 및 O (log n) 메모리 사용량을 얻을 수 있습니다. 지금은 매번 반복하여 메모리를 절약하지만 많은 중복 계산을 수행하고 있습니다. 중간 값과 최소값 목록을 유지하면 중복 작업을 피하고 속도를 크게 높일 수 있습니다. 그러나 중간 값 목록은 n에 따라 증가하므로 O (log n) 메모리입니다.


좋은 대답입니다. 코딩하지 않았다는 비슷한 아이디어가 있습니다. 이 아이디어에서는 2와 5의 추적기를 유지합니다. 최대 값 nm지금까지 시퀀스의 숫자를 통해 사용 된 추적기를 추적합니다 . 각 반복에서 n또는 m올라갈 수도 있고 올라가지 않을 수도 있습니다. 새로운 숫자를 2^(max_n+1)*5^(max_m+1)만든 다음 현재 숫자보다 큰 최소값을 얻을 때까지 각 호출에서 지수를 1 씩 줄이면서 철저한 재귀 방식으로이 숫자를 줄입니다. 우리는 업데이트 max_n, max_m필요에 따라. 이것은 일정한 mem입니다. O(log^2(n))DP 캐시가 축소 호출에 사용되는 경우 mem 일 수 있음
InformedA

흥미 롭군 여기서 최적화는 올바른 m을 알고 n이 1에 가장 가까운 승수를 생성하기 때문에 m & n의 모든 쌍을 고려할 필요가 없다는 것입니다. 따라서 m = -i를 max_i로 평가하면됩니다. 반올림을 위해 쓰레기를 던져서 n을 계산할 수 있습니다 (나는 느슨하고 -1에서 1로 반복했지만 더 많은 생각을합니다.)).
Rob

그러나 나는 당신과 같은 생각을하고 있습니다 ... 시퀀스가 ​​결정적 일 것입니다 ... 한 방향으로 큰 파스칼의 삼각형 i + 1과 다른 방향으로 j + 1과 같습니다. 따라서 순서는 수학적으로 결정적이어야합니다. 삼각형의 노드에 대해서는 항상 수학적으로 다음 노드가 결정됩니다.
Rob

1
다음 수식에 대한 수식이있을 수 있으므로 검색하지 않아도 될 수 있습니다. 확실하지 않습니다.
정보 : A

내가 생각할 때, 다음의 대수 형태가 존재하지 않을 수도 있습니다 (모든 결정 론적 문제가 솔루션에 대한 대수 형태를 갖는 것은 아닙니다). 이 공식을 해결하려고합니다. 어떤 사람이 공식을 알고 있다면 아마도 그것에 대해 조금 읽었을 것입니다.
정보 : A

2

Brian은 절대적으로 옳았습니다. 다른 대답은 너무 복잡했습니다. 더 간단하고 빠른 방법이 있습니다.

유클리드 평면의 사분면 I을 정수로 제한한다고 상상해보십시오. 한 축을 i 축, 다른 축을 j 축이라고합니다.

분명히, 원점에 가까운 점은 원점에서 멀리 떨어진 점보다 먼저 선택됩니다. 또한 활성 영역은 j 축에서 멀어지기 전에 i 축에서 멀어집니다.

포인트가 사용되면 다시는 사용되지 않습니다. 바로 아래 또는 왼쪽의 포인트가 이미 사용 된 경우에만 포인트를 사용할 수 있습니다.

이것들을 종합하면, 원점을 중심으로 시작하는 "프런티어"또는 "리딩 엣지"를 상상할 수 있으며, j 축보다 i 축을 따라 더 넓게 퍼지고 오른쪽으로 퍼집니다.

실제로, 우리는 더 많은 것을 알아낼 수 있습니다 : 주어진 i- 값에 대해 프론티어 / 에지에 최대 한 지점이있을 것입니다. (j의 증분과 같도록 i를 2 배 이상 증가시켜야합니다.) 따라서, 우리는 각 i 좌표에 대해 하나의 요소를 포함하는 목록으로 프론티어를 표현할 수 있으며 j 좌표와 함수 값에 따라 다릅니다.

각 패스마다 리딩 엣지에서 최소 요소를 선택한 다음 j 방향으로 한 번 이동합니다. 마지막 요소를 올리면 i 값이 증가하고 j 값이 0 인 새 마지막 요소를 더 추가합니다.

using System;
using System.Collections.Generic;
using System.Text;

namespace TwosFives
{
    class LatticePoint : IComparable<LatticePoint>
    {
      public int i;
      public int j;
      public double value;
      public LatticePoint(int ii, int jj, double vvalue)
      {
          i = ii;
          j = jj;
          value = vvalue;
      }
      public int CompareTo(LatticePoint rhs)
      {
          return value.CompareTo(rhs.value);
      }
    }


    class Program
    {
        static void Main(string[] args)
        {
            LatticePoint startPoint = new LatticePoint(0, 0, 1);

            var leadingEdge = new List<LatticePoint> { startPoint } ;

            while (true)
            {
                LatticePoint min = leadingEdge.Min();
                Console.WriteLine(min.value);
                if (min.j + 1 == leadingEdge.Count)
                {
                    leadingEdge.Add(new LatticePoint(0, min.j + 1, min.value * 2));
                }
                min.i++;
                min.value *= 5;
            }
        }
    }
}

공간 : 지금까지 인쇄 된 요소 수의 O (n).

속도 : O (1) 인서트이지만 매번 수행되는 것은 아닙니다. (때로는 List<>성장해야 할 때 더 길지만 여전히 O (1)가 상각되었습니다.) 큰 시간 싱크는 지금까지 인쇄 된 요소 수에서 최소값 O (n)을 검색하는 것입니다.


1
어떤 알고리즘을 사용합니까? 왜 작동합니까? 질문의 핵심 부분 Does anyone have advice on how to solve such a problem?은 근본적인 문제를 이해하려는 시도입니다. 코드 덤프는 그 질문에 잘 대답하지 못합니다.

좋은 지적, 나는 내 생각을 설명했다.
스노우 바디

+1 이것은 두 번째 스 니펫과 거의 동일하지만 변경 불가능한 모서리를 사용하면 모서리 수가 어떻게 증가하는지 명확하게 알 수 있습니다.
Brian

이것은 Brian의 수정 된 코드 조각보다 확실히 느리지 만, 계속해서 요소를 삭제하고 추가하지 않기 때문에 메모리 사용 동작이 훨씬 나아질 것입니다. (CLR 또는 SortedSet <>에 내가 모르는 요소를 재사용하는 방법이
없다면

1

세트 기반 솔루션은 아마도 면접관이 찾고 있던 것일 수도 있지만, 시퀀싱 요소에 대한 O(n)메모리와 O(n lg n)총 시간을 갖는 것은 불행한 결과입니다 n.

약간의 수학은 O(1)공간 및 O(n sqrt(n))시간 솔루션을 찾는 데 도움이됩니다 . 그 주목 2^i * 5^j = 2^(i + j lg 5). 제 찾기 n요소 것은 {i,j > 0 | 2^(i + j lg 5)}제 찾는 감소 n요소 {i,j > 0 | i + j lg 5}기능 때문에 (x -> 2^x)엄격하게 단조 증가되어 있으므로 어떤 유일한 길 경우이다 .a,b2^a < 2^ba < b

이제, 우리는 단지의 순서 찾는 알고리즘을 필요로 i + j lg 5, i,j자연수를. 다시 말해, 현재 값인을 고려할 i, j때 다음 이동을 최소화하는 것 (즉, 시퀀스에서 다음 번호를 제공하는 것)은 다른 값 j += 1의 감소와 함께 값 중 하나 (예 :)가 약간 증가하는 것입니다 ( i -= 2). 우리를 제한하는 유일한 것은입니다 i,j > 0.

고려해야 할 두 가지 경우가 있습니다- i증가 또는 j증가. 시퀀스가 증가하기 때문에 그중 하나가 증가해야하며, 그렇지 않으면 증가하는 용어 만 건너 뛰기 때문에 둘 다 증가하지 않습니다 i,j. 따라서 하나는 증가하고 다른 하나는 동일하게 유지되거나 감소합니다. C ++ 11로 표현 된 전체 알고리즘과 세트 솔루션과의 비교는 여기에서 제공됩니다 .

출력 배열 외에 메소드에 할당 된 일정한 양의 객체 만 있기 때문에 일정한 메모리를 얻을 수 있습니다 (링크 참조). 어떤 주어진 대해 이후에있어서 대수 시간마다 반복을 달성 (i,j)그것이 최상의 쌍 횡단 (a, b)(i + a, j + b)의 값에서 가장 작은 증가이다 i + j lg 5. 이 순회는 O(i + j)다음과 같습니다.

Attempt to increase i:
++i
current difference in value CD = 1
while (j > 0)
  --j
  mark difference in value for
     current (i,j) as CD -= lg 5
  while (CD < 0) // Have to increase the sequence
    ++i          // This while will end in three loops at most.
    CD += 1
find minimum among each marked difference ((i,j) -> CD)

Attempt to increase j:
++j
current difference in value CD = lg 5
while (j > 0)
  --i
  mark difference in value for
     current (i,j) as CD -= 1
  while (CD < 0) // have to increase the sequence
    ++j          // This while will end in one loop at most.
    CD += lg 5
find minimum among each marked difference ((i,j) -> CD)

모든 반복 시도를 업데이트 i한 후 j, 두의 작은 업데이트 간다.

이후 ij가장에있다 O(sqrt(n)), 우리는 총이 O(n sqrt(n))시간을. ij의 제곱의 비율로 성장 n어떠한 최대 valiues에 대한 이후 imaxjmax존재 O(i j)우리의 순서 인 경우에 우리의 순서를 만드는에서 고유 한 쌍의 n용어 및 ij지수가 선형으로 구성되어 있기 때문에 서로 (일부 일정 비율 내에서 성장 두 FO 조합), 우리는 알고 i하고 j있습니다 O(sqrt(n)).

부동 소수점 오류에 대해 걱정할 필요가 없습니다. 항이 기하 급수적으로 증가하기 때문에 플롭 오류가 몇 배나 걸리기 전에 오버플로를 처리해야합니다. 시간이 있으면 더 많은 토론을 할 것입니다.


큰 대답은, 소수의 소수에 대한 순서를 증가시키는 패턴이 있다고 생각합니다.
InformedA

@randomA 감사합니다. 몇 가지 추가 생각을 한 후 현재 알고리즘이 생각보다 빠르지 않다는 결론에 도달했습니다. "i / j 증가 시도"를 평가하는 더 빠른 방법이 있다면, 이것이 로그 시간을 얻는 열쇠라고 생각합니다.
VF1

저는 그 생각을했습니다 : 우리는 숫자를 늘리려면 소수 중 하나의 숫자를 늘려야한다는 것을 알고 있습니다. 예를 들어, 증가하는 한 가지 방법은 8로 mul을 5로 나누는 것입니다. 따라서 숫자를 늘리거나 줄이는 모든 방법을 얻습니다. 여기에는 mul 16 div 5가 아닌 mul 8 div 5와 같은 기본 방법 만 포함됩니다. 감소시키는 또 다른 기본 방법도 있습니다. 이 두 세트를 증가 또는 감소 계수별로 정렬하십시오. 숫자가 주어지면 다음 세트는 증가 세트에서 가장 작은 요소로 적용 가능한 증가 방법을 찾아서 찾을 수 있습니다.
InformedA

적용 가능한 의미는 mul 및 div를 수행하기에 충분한 소수가 있음을 의미합니다. 그런 다음 새로운 숫자로 축소하는 방법을 찾으므로 가장 감소하는 숫자부터 시작합니다. 줄이려면 새로운 방법을 계속 사용하고 새 숫자가 원래 숫자보다 작 으면 중지합니다. 소수 세트가 일정하기 때문에 이는 두 세트에 대해 일정한 크기를 의미합니다. 이것도 약간의 증거가 필요하지만, 각 시간마다 일정한 시간, 일정한 메모리처럼 보입니다. 따라서 n 개의 숫자를 인쇄하기 위해 일정한 메모리와 선형 시간이 필요합니다.
알림 A

@randomA 어디서 나왔니? 완전한 답변을 하시겠습니까? 귀하의 의견을 이해하지 못합니다.
VF1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.