Interview must-see algorithm questions|Summary of linked list questions

Preface

Questions related to linked lists appear frequently in interviews. These questions are often the basis for solving other complex problems;
in this article, I will sort out the problems & solutions of linked list problems. If you can help, please like and pay attention. This is really important to me.


table of Contents


1 Overview

1.1 Definition of linked list

Linked list is a common basic data structure and a linear list. Different from the sequence table, each node in the linked list is not stored sequentially, but points to the next node through the node's pointer field.

1.2 Advantages and disadvantages of linked lists

1.3 Types of linked lists

Single linked list, double linked list, circular linked list, static linked list


2. Delete the linked list node

When deleting a linked list node, considering that the first node of the linked list (no predecessor node) may be deleted, for coding convenience, consider adding a  sentinel node . Among them, in the problem of deleting the Nth node from the bottom of the linked list, it is a more important programming skill to use the speed pointer to find the Nth node from the bottom in a scan.

237. Delete Node in a Linked List  [Problem Solution]
203. Remove Linked List Elements  [Problem Solution]
不移除野指针
class Solution {
    fun removeElements(head: ListNode?, `val`: Int): ListNode? {
        // 哨兵节点
        val sentinel = ListNode(-1)
        sentinel.next = head

        var pre = sentinel
        var cur: ListNode? = sentinel
        while (null != cur) {
            if (`val` == cur.`val`) {
                // 移除
                pre.next = cur.next
            } else {
                pre = cur
            }
            cur = cur.next
        }
        return sentinel.next
    }
}

移除野指针
class Solution {
    fun removeElements(head: ListNode?, `val`: Int): ListNode? {
        // 哨兵节点
        val sentinel = ListNode(-1)
        sentinel.next = head

        var pre = sentinel
        var cur: ListNode? = sentinel
        while (null != cur) {
            val removeNode = if (`val` == cur.`val`) {
                // 移除
                pre.next = cur.next
                cur
            } else {
                pre = cur
                null
            }
            cur = cur.next
            if (null != removeNode) {
                removeNode.next = null
            }
        }
        return sentinel.next
    }
}
19. Remove Nth Node From End of List Remove Nth Node From End of List  [Problem Solution]

Given a linked list, delete the nth node from the bottom of the linked list, and return the head node of the linked list.

class Solution {
    fun removeNthFromEnd(head: ListNode, n: Int): ListNode? {
        // 哨兵节点
        val sentinel = ListNode(-1)
        sentinel.next = head

        var fast: ListNode? = sentinel
        var slow: ListNode? = sentinel

        for (index in 0 until n) {
            fast = fast!!.next
        }

        // 找到倒数第 k 个节点的前驱
        while (null != fast!!.next) {
            fast = fast.next
            slow = slow!!.next
        }
        slow!!.next = slow.next!!.next
        return sentinel.next
    }
}

Complexity analysis:

Similarly, the middle node of 876. Middle of the Linked List  [Problem Solution]  also finds the middle node through the speed pointer:

class Solution {
    fun middleNode(head: ListNode?): ListNode? {
        if (null == head || null == head.next) {
            return head
        }
        var fast = head
        var slow = head

        while (null != fast && null != fast.next) {
            fast = fast.next!!.next
            slow = slow!!.next
        }

        return slow
    }
}
86. Partition List  [Problem Solution]

Delete all nodes in the linked list that are equal to the given value val.

Idea: Separating the linked list is nothing more than removing the nodes greater than or equal to val from the original linked list to the second linked list, and finally splicing the two linked lists.

class Solution {
    fun partition(head: ListNode?, x: Int): ListNode? {
        if (null == head) {
            return null
        }

        // 哨兵节点
        val sentinel = ListNode(-1)
        sentinel.next = head
        var pre = sentinel
        // 第二链表
        var bigHead : ListNode? = null
        var bigRear = bigHead

        var cur = head
        while (null != cur) {
            if (cur.`val` >= x) {
                // 大于等于:移除
                pre.next = cur.next
                if(null == bigHead){
                    bigHead = cur
                    bigRear = cur
                }else{
                    bigRear!!.next = cur
                    bigRear = cur
                }
            } else {
                pre = cur
            }
            if (null == cur.next) {
                // 拼接
                pre.next = bigHead
                bigRear?.next = null
                break
            }
            cur = cur.next
        }
        return sentinel.next
    }
}

Complexity analysis:

328. Odd Even Linked List  【Problem Solution】

Idea: The odd-even linked list is nothing more than putting the odd node in one linked list first, placing the even node in another linked list, and finally connecting the even node to the end of the odd linked list

class Solution {
    fun oddEvenList(head: ListNode?): ListNode? {
        if (null == head) {
            return null
        }

        var odd: ListNode = head
        var even = head.next
        val evenHead = even

        while (null != even && null != even.next) {
            // 偶节点
            odd.next = even.next
            odd = odd.next!!
            // 奇节点
            even.next = odd.next
            even = even.next
        }
        odd.next = evenHead
        // 头节点不动
        return head
    }
}
83. Remove Duplicates from Sorted List delete duplicate elements in the sorted list
82. Remove Duplicates from Sorted List II Remove Duplicates from Sorted List II

3. Reverse linked list

The frequency of reversal linked list questions in interviews is  very, very high . I believe students who have had several interviews will agree with this point of view. Here, I have found 4 problems with inverted linked lists, extending from simple to difficult, come and try.

206. Reverse Linked List  【Problem Solution】

Reverse a singly linked list.

Solution 1: Recursion

class Solution {
    fun reverseList(head: ListNode?): ListNode? {
        if(null == head || null == head.next){
            return head
        }
        val prefix = reverseList(head.next)
        head.next.next = head
        head.next = null
        return prefix
    }
}
复制代码

Complexity analysis:

Solution 2: Iteration

class Solution {
    fun reverseList(head: ListNode?): ListNode? {
        var cur: ListNode? = head
        var headP: ListNode? = null

        while (null != cur) {
            val tmp = cur.next
            cur.next = headP
            headP = cur
            cur = tmp
        }
        return headP
    }
}

Complexity analysis:

92. Reverse Linked List II  [Question Solution]

Given a linked list, rotate the linked list and move each node of the linked list k positions to the right, where k is a non-negative number.

class Solution {
    fun reverseBetween(head: ListNode?, m: Int, n: Int): ListNode? {
        if (null == head || null == head.next) {
            return head
        }

        // 哨兵节点
        val sentinel = ListNode(-1)
        sentinel.next = head
        var rear = sentinel

        // 1\. 找到反转开始位置前驱节点
        var cur = sentinel
        for (index in 0 until m - 1) {
            cur = cur.next!!
            rear = cur
        }

        // 2\. 反转指定区域
        rear.next = reverseList(rear.next!!, n - m + 1)
        return sentinel.next
    }

    /**
     * 反转指定区域
     * @param size 长度
     */
    fun reverseList(head: ListNode, size: Int): ListNode? {
        var cur: ListNode? = head
        var headP: ListNode? = null
        // 反转的起始点需要连接到第 n 个节点
        val headTemp = head

        var count = 0
        while (null != cur && count < size) {
            val tmp = cur.next
            cur.next = headP
            headP = cur
            cur = tmp

            count++
        }

        // 连接到第 n 个节点
        headTemp.next = cur
        return headP
    }
}

Complexity analysis:

234. Palindrome Linked List  【Problem Solution】

Please determine whether a linked list is a palindrome linked list.

Idea: Use the fast and slow pointers to find the intermediate node, reverse the second half of the linked list (based on the reversed linked list II), compare whether the two linked lists are the same, and finally reverse to return to the original linked list.

class Solution {
    fun isPalindrome(head: ListNode?): Boolean {
        if (null == head || null == head.next) {
            return true
        }

        // 1\. 找到右边中节点(右中节点)
        var fast = head
        var slow = head

        while (null != fast && null != fast.next) {
            slow = slow!!.next
            fast = fast.next!!.next
        }

        // 2\. 反转后半段
        val reverseP = reverseList(slow!!)

        // 3\. 比较前后两段是否相同
        var p = head
        var q: ListNode? = reverseP
        var isPalindrome = true

        while (null != p && null != q) {
            if (p.`val` == q.`val`) {
                p = p.next
                q = q.next
            } else {
                isPalindrome = false
                break
            }
        }

        // 4\. 恢复链表
        reverseList(reverseP)
        return isPalindrome
    }

    /**
     * 反转链表
     */
    private fun reverseList(head: ListNode): ListNode {
        // 略,见上一节...
    }
}

Complexity analysis:

25. K Reverse Nodes in k-Group

Give you a linked list, every k nodes are flipped, please return to the flipped linked list.


4. Combine ordered linked lists

Merging sorted linked list problems frequencies in the interview  higher , which merge the two ordered list  is relatively simple, and its advanced version  merger of K ascending list  of factors to be considered in a more comprehensive, the difficulty has also been enhanced, fast Come and try it.

21. Merge Two Sorted Lists  [Problem Solution]

Combine two ascending linked lists into a new ascending linked list and return. The new linked list is composed by splicing all the nodes of the given two linked lists.

class Solution {
    fun mergeTwoLists(l1: ListNode?, l2: ListNode?): ListNode? {
        if (null == l1) return l2
        if (null == l2) return l1

        // 哨兵节点
        val sentinel = ListNode(-1)
        var rear = sentinel

        var p = l1
        var q = l2

        while (null != p && null != q) {
            if (p.`val` < q.`val`) {
                rear.next = p
                rear = p
                p = p.next
            } else {
                rear.next = q
                rear = q
                q = q.next
            }
        }
        rear.next = if (null != p) p else q

        return sentinel.next
    }
}

Complexity analysis:

23. Merge k Sorted Lists  [Problem Solution]

Give you an array of linked lists, each of which has been arranged in ascending order. Please merge all linked lists into an ascending linked list and return the merged linked list.

Solution 1: Violence

Idea 1: Similar to merging two ordered linked lists, the smallest node is taken from k linked lists in each round and inserted into the resulting linked list. Among them, the time complexity of extracting the smallest node from k numbers is O(k)O(k)O(k).

Idea 2: This idea is similar to the previous idea, the time complexity and space complexity page are the same, that is, k linked lists and result linked lists are merged in sequence.

Complexity analysis:

Solution 2: Sorting method

Idea: After storing all nodes in an array, perform a quick sort, and then output the array to a singly linked list.

class Solution {
    fun mergeKLists(lists: Array<ListNode?>): ListNode? {
        if (lists.isNullOrEmpty()) {
            return null
        }
        // 1\. 用一个数组保存所有节点
        val array = ArrayList<ListNode>()
        for (list in lists) {
            var cur = list
            while (null != cur) {
                array.add(cur)
                cur = cur.next
            }
        }
        // 2\. 快速排序
        array.sortWith(Comparator { node1, node2 -> node1.`val` - node2.`val` })
        // 3\. 输出为链表
        val newHead = ListNode(-1)
        var rear = newHead
        for (node in array) {
            rear.next = node
            rear = node
        }
        return newHead.next
    }
}

Complexity analysis:

Solution 3: merge method

Idea: Divide the k groups of linked lists into two parts, then process the two groups of linked lists recursively, and finally merge them.

class Solution {

    // 合并 k 个有序链表
    fun mergeKLists(lists: Array<ListNode?>): ListNode? {
        if (lists.isNullOrEmpty()) {
            return null
        }
        return mergeKLists(lists, 0, lists.size - 1)
    }

    fun mergeKLists(lists: Array<ListNode?>, left: Int, right: Int): ListNode? {
        if (left == right) {
            return lists[left]
        }
        // 归并
        val mid = (left + right) ushr 1
        return mergeTwoLists(
            mergeKLists(lists, left, mid),
            mergeKLists(lists, mid + 1, right)
        )
    }

    // 合并两个有序链表
    fun mergeTwoLists(l1: ListNode?, l2: ListNode?): ListNode? {
        // 略,见上一节...
    }
}

Complexity analysis:

Solution 4: Small top pile method

class Solution {

    // 合并 k 个有序链表
    fun mergeKLists(lists: Array<ListNode?>): ListNode? {
        if (lists.isNullOrEmpty()) {
            return null
        }

        // 最小堆
        val queue = PriorityQueue<ListNode>(lists.size) { node1, node2 -> node1.`val` - node2.`val` }

        // 1\. 建堆
        for (list in lists) {
            if (null != list) {
                queue.offer(list)
            }
        }

        val sentinel = ListNode(-1)
        var rear = sentinel

        // 2\. 出队
        while (queue.isNotEmpty()) {
            val node = queue.poll()!!
            // 输出到结果链表
            rear.next = node
            rear = node
            // 存在后继节点,加入堆中
            if (null != node.next) {
                queue.offer(node.next)
            }
        }
        return sentinel.next
    }
}

Complexity analysis:


5. Sort Linked List

147. Insertion Sort List to insert and sort the linked list  | 【Problem Solution】
148. Sort List  [Problem Solution]

6. Circular Linked List

Linked list intersection & ring formation problems can be classified into a category of questions, which appear frequently in interviews; in a previous article, we discussed separately: "Algorithmic Intersection Questions | Linked List Intersection & Ring Formation Problems"

At last

The domestic Internet interview process is gradually moving closer to foreign countries. For large companies like Bytedance and BAT, shredding algorithmic questions has become a must. This is also a low-cost method for companies to interview and screen people. If you write an algorithm and pass it, either you are smart or you are diligent.

I hope everyone will pay attention and prepare well.

In addition, I put the most important and popular learning directions for Android that I have compiled during this time on my GitHub , which also contains self-learning programming routes in different directions, interview question collections/faces, and a series of technical articles.

The resources are continuously updated, and everyone is welcome to learn and discuss together.

Original address: https://juejin.cn/post/6882370280946302983

Guess you like

Origin blog.csdn.net/weixin_49559515/article/details/112629147