В этом блоге я буду задавать вопросы о Linked List, которые лично помогли мне освоиться с ним. Наличие предварительных знаний о STL, связанных списках и рекурсии поможет вам лучше понять этот блог, ориентированный на решение проблем.

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

Я выбрал вопросы с платформы LeetCode, которая является одной из лучших платформ для решения проблем и решения алгоритмических проблем. Я не рассмотрел основы, так как в Интернете уже есть много ресурсов для этого. Этот блог больше ориентирован на решение проблем и предоставление важных вопросов по связанному списку. Я также предоставил код для некоторых из них на языке программирования C++.

СВЯЗАННЫЙ СПИСОК

Я быстро пройдусь по некоторым моментам, которые необходимо иметь в виду, думая о связанном списке в целом. Во-первых, нужно знать, что связанный список — это линейная структура данных. Здесь элементы не хранятся в смежных ячейках памяти, в отличие от массива. В связанном списке элементы связаны друг с другом с помощью указателей, что дает нам возможность добавлять, удалять или вставлять элементы в связанный список. Нет необходимости инициализировать размер связанного списка, где, как и в массиве, это необходимо. Трудно получить доступ к элементу напрямую в связанном списке, поскольку для достижения этого элемента необходимо пройти по связанному списку, в отличие от массива, где мы можем получить доступ к любому элементу с помощью индекса.

УКАЗАТЕЛЬ

Указатель — это переменная, которая хранит адрес памяти в качестве своего значения.

Теперь возьмем несколько примеров, чтобы понять, что такое указатель:

int *ptr = // присвоение адреса переменной var указателю ptr.

char*ptr = // присвоение адреса переменной var указателю ptr.

интервал*указатель; // ‘ptr’ — это указатель, указывающий на какое-то мусорное значение.

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

В основном можно решить большинство вопросов, связанных со связанным списком, используя подход «Быстрые и медленные указатели» и «Обратное обращение связанного списка на месте».

ПРОБЛЕМЫ

  1. Быстрые и медленные указатели.

а. Середина связанного списка



 ListNode* middleNode(ListNode* head) {
        
        ListNode *slow = head, *fast = head;
        
        while(fast && fast -> next)
            slow = slow -> next
            fast = fast -> next -> next;
        
        return slow;
    }

б. Связанный список палиндромов



bool isPalindrome(ListNode* head) {
       
        ListNode *slow = head, *fast = head, *prev, *temp;
       
        while(fast && fast -> next)
            
            slow = slow -> next, fast = fast -> next -> next;
            prev = slow, slow = slow -> next, prev-> next = NULL;

        while(slow)
            temp = slow -> next, slow -> next = prev, prev = slow, slow = temp;
            fast = head, slow = prev;
        while(slow) 
            if(fast -> val != slow -> val) return false;
            else fast = fast -> next, slow = slow -> next;
        return true;
    }

в. Цикл связанного списка 1



bool hasCycle(ListNode *head) {
 
        if(head == NULL) return false;
        ListNode *fast = head;
        ListNode *slow = head;
        
        while(fast != NULL and fast -> next != NULL) {
            
            fast = fast -> next -> next;
            slow = slow -> next;
            
            if(fast == slow) return true;
        }
        return false;
    }

д. Цикл связанного списка 2 (расширение цикла 1 связанного списка)



ListNode *detectCycle(ListNode *head) {

    if(head == NULL || head -> next == NULL) return NULL;
    
    ListNode *slow  = head;
    ListNode *fast  = head;
    ListNode *cycleStart = head;
    
    while(fast -> next and fast -> next -> next) {

        slow = slow -> next;
        fast = fast -> next -> next;

         if(slow == fast) {
            while(slow != cycleStart) {           
                slow  = slow -> next;
                cycleStart = cycleStart -> next;
            }
            return cycleStart;
        }
    }
    return NULL;                              
  }

е. Удалить элементы связанного списка



ListNode* removeElements(ListNode* head, int val) {

        if(head == nullptr) return head;

        while(head != nullptr && head -> val == val) {

            head = head -> next;
        }
        ListNode* curr = head;

        while(curr != nullptr  and curr -> next != nullptr) {

            if(curr -> next -> val == val) {
                curr -> next = curr -> next -> next;
            }
            else
                curr = curr -> next;
        }
        return head;
    }

ф. Объединить два отсортированных списка



 ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {

    if(!list1 || list2 && list1 -> val > list2 -> val) 
        swap(list1, list2);

    if(list1) 
        list1 -> next = mergeTwoLists(list1 -> next, list2);
    return list1;
    }

2. Инверсия связанного списка на месте.

а. Обратно связанный список 1



ListNode* reverseList(ListNode* head) {
     
        ListNode* nextNode;
        ListNode* prevNode = NULL;
        
        while(head) {
            nextNode = head -> next;
            head -> next = prevNode;
            prevNode = head;
            head = nextNode;
        }
        return prevNode;
    }

б. Обратно связанный список 2



Вы должны попробовать это самостоятельно. Это расширение вышеуказанной проблемы.

в. Перестановка узлов в связанном списке



ListNode* swapNodes(ListNode* head, int k) {

        ListNode* temp = head;
        int cnt = 0;
        while(temp) {
            cnt++;
            temp = temp -> next;
        }
        ListNode *start = head, *end = head;

        int s = k - 1, e = cnt - k;

        for(int i = 0; i < s; i++)
            start = start -> next;
        for(int i = 0; i < e; i++)
            end = end -> next;
        
        swap(start -> val, end -> val);
        return head;
    }

д. Список повторного заказа



 void reorderList(ListNode* head) {
        if((!head) || (!head -> next) || (!head -> next -> next)) return;//Edge Cases
        
        stack<ListNode*> st;
        ListNode* temp = head;
        int size = 0;
        while(temp != NULL) {
            st.push(temp);
            size++;
            temp = temp -> next;
        }
        ListNode* ptr = head;
        for(int j = 0; j < size/2; j++)//Between every two nodes insert the one in the top of the stack
        {
            ListNode* element = st.top();
            st.pop();
            element -> next = ptr -> next;
            ptr -> next = element;
            ptr = ptr -> next -> next;
        }
        ptr -> next = NULL;
    }

е. Поменять местами узлы в парах



ListNode* swapPairs(ListNode* head) {
       
        if(head == nullptr || head -> next == nullptr) return head; 

        ListNode* dummy = new ListNode();
        ListNode* prev = dummy;
        ListNode* curr = head;
        
        while(curr and curr -> next) {

            prev -> next = curr -> next;
            curr -> next = prev -> next -> next;
            prev -> next -> next = curr;

            prev = curr;
            curr = curr -> next;
         }
       return dummy -> next;
     }

ф. Обратные узлы в группе четной длины



ListNode* reverseEvenLengthGroups(ListNode* head) {

        if(!head ) return head;

        ListNode* curr = head, *p;
        int sz = 1;
        
        while(curr) {
            
            p = curr;
            vector<int> vec;
            for(int i = 0 ;i < sz && curr != NULL ; i++) {
                vec.push_back(curr -> val);
                curr = curr -> next;    
            }
            if(vec.size() % 2 == 0) {
                reverse(vec.begin(), vec.end());
                for(int i = 0; i < vec.size(); i++){
                    
                    p -> val = vec[i];
                    p = p -> next;    
                }
            }
            sz++;
        }
        return head;
    }

3. Несколько важных вопросов для практики.

Сначала попробуйте их сами, чтобы лучше понять.

а. Свести двоичное дерево к связанному списку



void flatten(TreeNode* root) {

        if(root == NULL) return;

        flatten(root -> left);
        flatten(root -> right);

        if(root -> left) {

            TreeNode* right = root -> right;
            root -> right = root -> left;
            root -> left = NULL;

            while(root -> right) 
                 root = root -> right;
                 root -> right = right;
        }
    }

б. Повернуть список



 ListNode* rotateRight(ListNode* head, int k) {

        if(head == nullptr || head -> next == nullptr || k == 0)
            return head;
        int len = 1;
        ListNode* tail = head;
        while(tail -> next != nullptr) {
            len++;
            tail = tail -> next;
        }
        tail -> next = head;
        k = len - k % len;

        while(k) {
            tail = tail -> next; 
            k--;
        }
        head = tail -> next;
        tail -> next = nullptr;

        return head;
    }

в. Удалить N-й узел из конца списка



ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head == nullptr) return head;
            ListNode* temp = new ListNode();
            temp -> next = head;
            ListNode* fast = temp;
            ListNode* slow = temp;

            for(int i = 0; i < n; i++) {
                fast = fast -> next;
            }
            while(fast -> next != nullptr) {
                fast = fast -> next;
                slow = slow -> next;
            }
            slow -> next = slow -> next -> next;
            return temp -> next;
    }

д. Нечетный четный связанный список



ListNode* oddEvenList(ListNode* head) {

        if(!head || !head -> next || !head -> next -> next) 
            return head;

        ListNode* odd_p = head;
        ListNode* even_p = head -> next;
        ListNode* headofeven = even_p;
        
        while(odd_p -> next && even_p -> next) {

            odd_p -> next = odd_p -> next -> next;
            even_p -> next = even_p -> next -> next;

            odd_p = odd_p -> next;
            even_p = even_p -> next;
        }
        odd_p -> next = headofeven;
        return head;
    }

е. Заполнение следующих правых указателей в каждом узле



 Node* connect(Node* root) {

    if(root == NULL) return NULL;
    //connects the left subtree of same level with right subtree of that same level 
    if(root -> left != NULL) 
         root -> left -> next = root -> right;
    //connect the rightmost node of a level to the leftmost node of the next level.
    if(root -> right != NULL && root -> next != NULL) 
         root -> right -> next = root -> next -> left;
    //recursive calls for left and right subtrees.
    connect(root -> left);
    connect(root -> right);
    return root;
   }

4. Несколько сложных, но очень важных вопросов. Неоднократно спрашивали на собеседованиях.

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

а. Объединить k отсортированные списки



б. Обратные узлы в k-группах



в. LFU-кэш



ЗАКЛЮЧЕНИЕ

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

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

Я попытался объединить некоторые важные вопросы о Linked List в этом блоге. Я тщательно изучил Linked List на нескольких платформах, изучая его, и нашел эти вопросы наиболее полезными. Знание этих вопросов определенно даст вам преимущество перед другими.

Любые предложения или конструктивная критика очень приветствуются. В этом блоге я также пересматриваю свои концепции структур данных и алгоритмов и пытаюсь внести свой вклад в сообщество, что в первую очередь помогло мне его изучить.