코드 랜덤 기록 알고리즘 훈련 넷째 날|24. 연결 리스트의 노드를 쌍으로 교환, 19. 연결 리스트의 끝에서 두 번째 N 노드 삭제, 인터뷰 질문 02.07. 연결 리스트 교차, 142. 링 연결 리스트 II

오늘 배운 기사 및 비디오 링크

24 기사 링크: 링크
24 비디오 설명 링크: 링크
19 기사 링크: 링크
19 비디오 설명 링크: 링크 인터뷰 질문 02.07 기사 링크: 링크 인터뷰 질문 02.07 비디오 설명 없음 142 기사 링크: 링크 142 비디오 설명 링크: 링크



24. 연결 리스트의 노드를 둘씩 교환

제목보고 첫생각

제목:
연결 리스트를 주고, 그 안의 인접 노드를 둘씩 교환하고, 교환 후 연결 리스트의 헤드 노드를 반환합니다. 노드 내부의 값을 수정하지 않고(즉, 노드 스왑만) 이 연습을 완료해야 합니다.

다음과 같은 아이디어가 있습니다:
쌍별 교환을 완료하기 위해 두 개의 인접한 노드의 교환을 루프에 작성합니다. 루프의 핵심은 노드 포인터의 순서 임시 노드 의 레코드 를 수정하는 것입니다 .

코드 변덕을 읽은 후의 생각

코드 랜덤 레코드는 그림을 그려서 포인터의 작동 단계를 보여주고 포인터의 작동 순서는 한 눈에 명확합니다.

처음에 cur은 가상 헤드 노드를 가리키고 다음 세 단계를 수행합니다.
여기에 이미지 설명 삽입
작업 후 연결 목록은 다음과 같습니다.
여기에 이미지 설명 삽입
위의 세 단계를 통해 인접한 두 요소의 교환을 완료합니다.

다음으로 전체 연결 목록에서 인접 노드의 쌍 교환을 완료하기 위해 이 작업을 반복하기만 하면 됩니다.

구현 중 발생하는 어려움

노드 2가 노드 3과 연결이 끊어져 2단계에서 노드 1의 다음 노드가 노드 3의 위치를 ​​찾을 수 없기 때문에 임시 노드가 처음에 기록되지 않았습니다 .

따라서 노드 포인터에서 작업하기 전에 임시 노드를 기록 해야 합니다 .

ListNode* cur = dummyHead;

가상 헤드 노드를 가리키도록 포인터 커서 설정

코드

class Solution {
    
    
public:
    ListNode* swapPairs(ListNode* head) {
    
    
        ListNode* dummyHead = new ListNode(0);//设置一个虚拟头节点
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr){
    
    
            ListNode* tmp = cur->next;
            ListNode* tmp1 = cur->next->next->next;
			//以下三步与图中操作相对应
            cur->next = cur->next->next;
            cur->next->next = tmp;
            cur->next->next->next = tmp1;

            cur = cur->next->next;
        }
        return dummyHead->next;
    }
};

19. 연결된 목록의 마지막 N 노드 삭제

제목보고 첫생각

제목:
연결된 목록을 제공하고 연결된 목록의 n두 번째 하고 연결된 목록의 헤드 노드를 반환합니다.

나는 다음과 같은 아이디어를 가지고 있습니다:
연결 리스트에서 지정된 상호 노드를 삭제하는 것은 빠르고 느린 포인터를 통해 수행할 수 있습니다. 빠른 포인터가 n 단계 먼저 이동하도록 한 다음 느린 속도가 빠른 속도에 도달할 때 동시에 빠르게 이동하기 시작합니다. 연결 리스트의 끝에서 slow 가 가리키는 포인터를 삭제하면 됩니다.

코드 변덕을 읽은 후의 생각

코드 랜덤 녹음은 다음 단계로 나뉩니다.

  • 빠른 포인터와 느린 포인터를 정의합니다. 초기 값은 그림과 같이 가상 헤드 노드입니다.
    여기에 이미지 설명 삽입
  • Fast는 먼저 n+1 단계를 수행합니다. 왜 n+1 단계입니까? 그림과 같이 동시에 이동할 때만 slow가 삭제된 노드의 이전 노드를 가리킬 수 있기 때문입니다.

여기에 이미지 설명 삽입

  • 그림과 같이 fast가 끝을 가리킬 때까지 fast 및 slow 이동을 동시에 수행합니다.

여기에 이미지 설명 삽입

  • 그림과 같이 slow가 가리키는 다음 노드를 삭제합니다.

여기에 이미지 설명 삽입

구현 중 발생하는 어려움

구현할 때 우리는 빠르게 n+1 단계를 이동하도록 한 다음 빠르고 느린 포인터를 동시에 이동하여 slow가 삭제할 노드의 이전 노드를 가리킬 수 있도록 주의해야 합니다.

그런 다음 슬로우의 다음 노드가 삭제할 노드의 다음 지점을 가리키도록 하여 노드 삭제를 완료합니다.

코드

class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        n++;
        while(n-- && fast != NULL){
    
    
            fast = fast->next;
        }
        while (fast != NULL){
    
    
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummyHead->next;
    }
};

인터뷰 질문 02.07 연결 리스트 교차점

제목보고 첫생각

제목 설명:
개의 단일 연결 목록의 헤드 노드가 주어졌고 두 개의 단일 연결 목록이 교차하는 시작 노드를 찾아서 반환하십시오. 두 개의 연결된 목록 사이에 교차가 없으면 를 반환합니다 .headAheadBnull

다음 아이디어가 있습니다.
포인터가 교차하려는 경우 교차하는 노드에서 시작하는 노드의 요소는 모두 동일하고 길이 차이가 n이라고 가정하고 두 연결 목록 간의 길이 차이를 찾은 다음 더 긴 연결 리스트의 포인터가 n+ 1 위치로 이동한 다음, 두 연결 리스트의 포인터가 교차점을 찾기 위해 만날 때까지 두 연결 리스트의 포인터를 동시에 이동합니다.

코드 변덕을 읽은 후의 생각

생각은 나랑 똑같다

구현 중 발생하는 어려움

어려움 없음

코드

class Solution {
    
    
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    
    
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != NULL) {
    
     // 求链表A的长度
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) {
    
     // 求链表B的长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
    
    
            swap (lenA, lenB);
            swap (curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
    
    
            curA = curA->next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != NULL) {
    
    
            if (curA == curB) {
    
    
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

142. 순환 연결 목록 II

제목보고 첫생각

제목 설명:

연결된 목록의 헤드 노드가 주어지면 head연결된 목록이 링에 진입하기 시작하는 첫 번째 노드를 반환합니다. 연결된 목록이 비주기적이면 null을 반환합니다.

다음과 같은 아이디어가 있습니다.

  • 링이 있는지 판단: 빠르고 느린 포인터 방법을 사용하여 빠르고 느린 포인터를 정의하고 헤드 노드에서 시작하여 빠른 포인터는 한 번에 두 노드를 이동하고 느린 포인터는 한 번에 한 노드를 이동합니다. 도중에 두 개의 포인터가 만나면 연결된 목록에 링이 있습니다.
  • 링의 입구를 찾는 방법: 링의 입구 노드포인터가 만나는 노드의 두 가지 핵심 포인트가 있습니다 .

헤드 노드에서 링 진입 노드까지의 노드 수를 x, 링 진입 노드에서 두 포인터가 만나는 노드까지의 노드 수를 y, 만남 노드에서 링 진입 노드까지의 수를 가정합니다. 지입니다.

두 포인터가 만났을 때 느린 포인터가 통과한 노드의 수는 x+y이고 빠른 포인터가 통과한 노드의 수는 x+y+n(y+z)입니다. 여기서 n은 노드의 수입니다
. 빠른 포인터가 링에서 처음으로 느린 포인터를 만났다는 것을 링에서 n번 원을 그리며 걷습니다.
(y + z)는 원의 노드 수입니다.

그리고 fast포인터는 2스텝, slow포인터는 1스텝(물리적으로 fast의 속도는 slow의 2배임을 알 수 있음)이므로 fast가 이동한 거리는 slow의 2배가 된다. (x + y) * 2 = x + y + n (y + z
)

수식을 제거하고 전치하면 다음이 생성됩니다.
x = n (y + z) - y

수식에서 뺄셈을 피하기 위해 n(y+z)에서 하나의 (y+z)를 제안하고, 수식을 마친 후 수식은 다음과 같습니다. x = (n - 1) (y + z) +
z

이 공식의 의미는 포인터가 헤드 노드에서 시작되고 포인터도 회의 노드에서 시작된다는 것입니다. 이 두 포인터는 한 번에 하나의 노드에만 이동하므로 두 포인터가 만날 때 노드입니다. 링 입구.

코드 변덕을 읽은 후의 생각

코드 카프리스의 애니메이션은 포인터가 만나는 상황과 원형 입구를 찾는 방법을 보다 직관적으로 보여줍니다.

고속 포인터와 저속 포인터의 만남:
여기에 이미지 설명 삽입
원형 연결 리스트의 키 노드 간 거리의 개략도:
여기에 이미지 설명 삽입
진입 노드를 찾는 개략도:
여기에 이미지 설명 삽입

구현 중 발생하는 어려움

이 질문을 본 것은 이번이 두 번째이기 때문에 어떤 인상이 있습니다. 그런데 이 질문을 처음 봤을 때, 링에서 처음 만났을 때 왜 x + 몇 개의 원의 길이 + y가 아니라 느린 걸음의 수 x+y가 되는 걸까요?

이유는 다음과 같습니다.

첫째, 슬로우가 링에 진입할 때 패스트는 이미 링에 진입한 것입니다.

슬로우가 링 입구에 들어가고 패스트도 링 입구에 있으면 아래 그림과 같이 링을 직선으로 확장합니다. 슬로우와 패스트가 링 입구에서 걷기 시작하는 것을 볼 수 있습니다
여기에 이미지 설명 삽입
. 동시에 반지, 그럼 그들은 반드시 반지의 입구 3에서 만날 것입니다. 이때 느리게 한 바퀴, 빠르게 두 바퀴를 걸었습니다.

slow가 링에 진입하면 그림과 같이 fast가 링의 모든 위치에 있다고 가정합니다.
여기에 이미지 설명 삽입
그런 다음 fast 포인터가 링 항목 3에 도달하면 k + n 노드가 이미 이동했으며 그에 따라 slow가 이동해야 합니다(k + n) / 2 노드.

k < n이므로 (k + n) / 2도 n보다 작아야 합니다.

이것은 천천히 걷는 첫 번째 고리에서 빠르게 만날 것임을 의미합니다.

그리고 빠름은 한 번에 두 개의 노드를 이동하고 느린 것은 한 번에 한 노드를 이동하므로 상대 속도는 빠른 추적이 한 번에 한 노드의 속도로 느려지기 때문에 느리게 건너뛰지 않습니다 .

코드

class Solution {
    
    
public:
    ListNode *detectCycle(ListNode *head) {
    
    
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next !=NULL){
    
    
            slow = slow->next;
            fast = fast->next->next;
            if(slow==fast){
    
    
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while(index1!=index2){
    
    
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2;
            }
        }
        return NULL;
    }
};

오늘 수확

1. pairwise 교환 연결 목록에서 임시 노드 레코드의 문제를 기억하십시오.

2. Linked List의 끝에서 두 번째 노드를 삭제함으로써 이중 포인터 방식에 대한 이해가 다시 깊어집니다.

3. Linked List 교차점의 특성을 검토한다.

4. 순환 연결 리스트 이 질문은 더 어렵습니다. 이 질문을 통해 순환 연결 리스트 처리에 대한 이해를 심화하십시오.

오늘 공부시간은 3시간

이 기사의 사진은 모두 Carl의 코드 카프리스에서 가져온 것이며 대단히 감사합니다.

Supongo que te gusta

Origin blog.csdn.net/m0_46555669/article/details/127030258
Recomendado
Clasificación