한 번에 링크 된 목록의 중간 요소를 찾는 방법은 무엇입니까?


12

데이터 구조와 알고리즘에서 가장 인기있는 질문 중 하나는 주로 전화 인터뷰에 관한 것입니다.


5
이 글에 제시된 답변이 면접관이 예상 한 것이라면,이 질문은 기술적 능력을 테스트하지는 않지만, 후보자가 변호사처럼 얼마나 잘 피할 수 있는지를 테스트합니다.
G. 바흐

2
이것은 모호하고 모호하며 주관적인 " 통과 " 라는 용어에 비판적으로 달려 있기 때문에 끔찍한 인터뷰 질문 입니다. 이 질문에 대한 거의 모든 정답은 정의를 남용하여 효과적으로 무시할 수 있습니다.
RBarryYoung

2
글쎄, 질문은 여기서 많은 토론을 불러 일으킨다. 그것은 한면에서 좋은 면접 질문이라는 것을 의미합니다. 생각을 시작합니다.
Hendrik Jan

3
"모든 요소를 ​​벡터로 읽은 다음 size () / 2 위치의 요소에 액세스하십시오"
Cort Ammon

1
@HendrikJan 사실, 나는 그것이 완전히 끔찍한 인터뷰 질문이라고 생각합니다. 첫째, 생산적인 토론보다는 "한 번에"정확히 의미하는 바에 대한 논쟁으로 이어질 수 있습니다. 둘째, 응시자는 "올바른"답변을 파악한 다음 "한 번에"기준을 위반했다고 생각하기 때문에 거부 할 수 있습니다. 셋째, 잘 알려진 질문이므로 "인기 면접 질문을 알고 있습니까?"에 대한 더 나은 테스트입니다. "당신은이 직업에 적합합니까?" 그중 하나라도 이것을 질문으로 싱크하기에 충분해야합니다. 세 가지가 동시에 재앙입니다.
David Richerby

답변:


24

부정 행위와 동시에 두 번의 패스를 수행함으로써. 그러나 나는 신병 모집 자들이 이것을 좋아할지 모르겠다.

좋은 트릭으로 하나의 링크 된 목록에서 수행 할 수 있습니다. 두 개의 포인터가 목록 위로 이동합니다. 하나는 배속입니다. 빠른 사람이 끝에 도달하면 다른 사람이 절반입니다.


1
예, 이것이 단일 패스인지 확실하지 않습니다. 그 시점에서 질문은 불분명합니다.
David Richerby

2
그건 그렇고, 이것은 두 개의 양초가있는 퍼즐과 관련이 있습니다. 각각 1 시간마다 45 분을 측정하라는 메시지가 표시됩니다.
Hendrik 1

2
목록을 반복하고 요소를 계산 한 다음 절반 지점까지 두 번 반복하는 것과 다른 점이 있습니까? 다른 점은 여분의 절반을 반복 할 때입니다. @RBarryYoung이 다른 비슷한 답변에 대해 언급했듯이 실제로 단일 패스가 아니며 패스와 반입니다.

2
목록이 길면 두 포인터를 "병렬로"이동하면 처음부터 반복하는 것보다 적은 캐시 누락이 발생합니다.
zwol

2
이것은 사이클 감지를위한 거북이 및 토끼 알고리즘과 동일한 원리를 사용합니다 .
Joshua Taylor

7

이중 연결 목록이 아닌 경우 목록을 계산하고 사용할 수 있지만 최악의 경우에는 메모리를 두 배로 늘려야하며 목록이 너무 커서 메모리에 저장하기에는 작동하지 않습니다.

간단하고 거의 바보 같은 해결책은 두 노드마다 중간 노드를 늘리는 것입니다.

function middle(start) {
    var middle = start
    var nextnode = start
    var do_increment = false;
    while (nextnode.next != null) {
        if (do_increment) {
             middle = middle.next;
        }
        do_increment = !do_increment;
        nextnode = nextnode.next;
    }
    return middle;
}

두 번째 옵션은 정답입니다 (물론 IMHO).
Matthew Crumley

1
실제로 링크 된 목록을 1 1/2로 통과시킵니다.
RBarryYoung

6

Hendrik의 답변 에 대해 자세히 설명

이중 연결 목록 인 경우 양쪽 끝에서 반복

function middle(start, end) {
  do_advance_start = false;
  while(start !== end && start && end) {
     if (do_advance_start) {
        start = start.next
     }
     else {
        end = end.prev
     }
     do_advance_start = !do_advance_start
  }
  return (start === end) ? start : null;
}

주어진 [1, 2, 3] => 2

1, 3
1, 2
2, 2

주어진 [1, 2] => 1

1, 2
1, 1

주어진 [1] => 1

주어진 [] => null


이것이 어떻게 효율적입니까? 또한 n / 2가 아닌 n 번 반복합니다.
Karan Khanna

3

연결된 목록의 노드를 가리킬 수있는 포인터와 목록의 노드 수를 유지하는 정수 변수를 사용하여 구조를 작성하십시오.

struct LL{
    struct node *ptr;
    int         count;
}start;

start.ptrstart.count=1
start.count

start.count


-2

배열의 각 요소가 처음부터 시작하여 순회 순서로 목록의 각 노드에 대한 포인터 인 동적 배열을 작성하십시오. 방문한 노드 수를 추적하는 1로 초기화 된 정수를 작성하십시오 (새 노드로 갈 때마다 증가 함). 끝에 도달하면 목록의 크기를 알 수 있으며 각 노드에 대한 포인터 배열이 정렬되어 있습니다. 마지막으로, 목록의 크기를 2로 나누고 (0 기반 색인의 경우 1을 빼기) 배열의 해당 색인에있는 포인터를 가져옵니다. 목록의 크기가 홀수 인 경우 반환 할 요소를 선택할 수 있습니다 (여전히 첫 번째 요소를 반환합니다).

여기에 요점을 얻는 Java 코드가 있습니다 (동적 배열의 아이디어는 약간 기발한 것임에도 불구하고). 나는 C / C ++를 제공 할 것이지만 그 지역에서는 매우 녹슬었다.

public Node getMiddleNode(List<Node> nodes){

    int size = 1;
    //add code to dynamically increase size if at capacity after adding
    Node[] pointers = new Node[10];

    for (int i = 0; i < nodes.size(); i++){
        //remember to dynamically allocate more space if needed
        pointers[i] = nodes.get(i);
        size++;
    }

    return pointers[(size - 1)/2];

}

-3

재귀는 하나 이상의 패스로 간주됩니까?

정수 카운트를 참조로 전달하여 목록을 끝까지 탐색하십시오. 나중에 참조 할 수 있도록 각 레벨에서 해당 값의 로컬 사본을 작성하고 다음 호출로가는 참조 횟수를 늘리십시오.

마지막 노드에서 카운트를 2로 나누고 결과를 잘라 내고 (두 요소 만있을 때 첫 번째 노드가 "중간"이되도록하려면) 반올림 (두 번째 노드를 원하는 경우) 중간"). 0 또는 1 기준 인덱스를 적절하게 사용하십시오.

풀기, 참조 카운트를 로컬 카피 (노드 번호)와 일치시킵니다. 같으면 해당 노드를 반환하십시오. 그렇지 않으면 재귀 호출에서 반환 된 노드를 반환합니다.
.

이를 수행하는 다른 방법이 있습니다. 그들 중 일부는 덜 성 가실 수 있습니다 (누군가 그것을 배열로 읽고 배열 길이를 사용하여 중간 정도를 결정한다고 말하는 것을 알았습니다). 그러나 솔직히 말해서 좋은 답변은 없습니다. 어리석은 인터뷰 질문이기 때문입니다. 여전히 연결 목록 ( 지지 의견 )을 사용하는 1 위; 둘째, 중간 노드를 찾는 것은 실제 시나리오에서 가치가없는 임의의 학문적 운동입니다. 셋째, 실제로 중간 노드를 알아야 할 경우 연결된 목록에 노드 수가 노출됩니다. 중간 노드를 원할 때마다 전체 목록을 순회하는 시간을 낭비하는 것보다 해당 속성을 유지 관리하는 것이 더 쉽습니다. 마지막으로 네 명, 모든 면접관은 다른 답변을 좋아하거나 거부 할 것입니다. 한 면접관이 매끄럽다 고 생각하는 사람은 말도 안됩니다.

나는 거의 항상 더 많은 질문으로 인터뷰 질문에 대답합니다. 이와 같은 질문을 받으면 (필자는 없음) (1)이 링크 된 목록에 무엇을 저장하고 있으며 실제로 필요한 경우 중간 노드에 효율적으로 액세스 할 수있는 더 적절한 구조가 있습니까? ; (2) 나의 제약은 무엇인가? 메모리가 문제가 아닌 경우 (예 : 배열 응답) 더 빨라질 수 있지만, 인터뷰 담당자가 메모리를 크게 늘리는 것이 낭비라고 생각하면 쇠약해질 것입니다. (3) 어떤 언어로 개발할 예정입니까? 내가 아는 거의 모든 현대 언어에는 목록을 탐색 할 필요가없는 링크 된 목록을 처리하기위한 내장 클래스가 있습니다. 왜 언어 개발자가 효율성을 위해 조정 한 것을 다시 발명해야합니까?


6
한 사람이 모든 것을 다운 투표했다고 결론을 내렸다는 결론을 누가 내 렸는지 알 수는 없지만 실제로는 사실에 근거가 없습니다. 그러나 코드 더미가 아닌 설명을 찾고 있기 때문에 귀하의 답변을 내리고 있습니다.
David Richerby

아마도 가정 일지 모르지만 잠시 후 30 초 미만이 지나면 모든 게시물에 정확히 -1이 있으면 불합리한 것이 아닙니다. 비록 5 명에서 6 명의 사람이 있었지만 그들 중 누구도 왜 그런지 언급하지 않았습니다. 하지만 적어도 이유를 말씀해 주셔서 감사합니다. 코드보다 설명이 더 좋은 이유를 알 수 없습니다. 예, 전화 인터뷰를위한 것이지만 OP에 역류에 대한 미리 준비된 답변을 제공하지는 않습니다. IE 코드가 없기 때문에 게시물을 내리는 것이 바람직하다고 생각하지만 적어도 왜 그랬는지 말해 주셔서 감사합니다.
James K

페어 포인트-투표 시점을 알 수있는 것은 나에게 발생하지 않았습니다 (투표가 발생하는 동안 페이지가로드되는 것은 우리와 같은 일반 사용자가 그것을 찾을 수있는 유일한 방법이라고 생각합니다). 실제 코드를 피하려고하는 이유에 대한 몇 개의 메타 게시물이 있습니다 : (1) (2) .
David Richerby

메타 링크에 감사드립니다. 나는 FAQ를 읽었지만 거기에서 아무것도 보지 못했습니다-아마도 내가 예상했던 것과는 다른 것을 알지 못했을 것입니다. 그러나 찌그러진 후에 다시 확인했을 때 아무것도 찾을 수 없었습니다. 나는 아직도 그것을 간과하고 있을지 모르지만 나는 보았다. 메타 게시물의 추론은 의미가 있습니다. 답변 주셔서 감사합니다.
James K

-5

2 개의 포인터를 사용합니다. 반복 할 때마다 하나씩 증가시키고 매번 반복 할 때마다 하나씩 증가시킵니다. 첫 번째 포인터가 연결된 목록의 끝을 가리킬 때 두 번째 포인터는 연결된 목록의 중간 모드를 가리 킵니다.


이것은 Hendrick의 답변을 복제 합니다. 할 말이 없다면 대답하지 마십시오.
David Richerby
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.