Array series of interview high frequency algorithm questions

Posted by sotusotusotu on Mon, 08 Nov 2021 10:51:40 +0100

Hello, I'm a senior programmer~

Today, I'll bring you a detailed analysis of the array of high-frequency algorithm questions in the interview. The full text contains 19 real questions about the algorithm in the written interview of large factories. I won the knowledge point of the array in one fell swoop, so that the algorithm will no longer become a stumbling block to enter large factories.

If you like, remember to pay attention~

This article is a little long. I have made this article into a PDF version with a directory. For the PDF version of this article, please pay attention to [programmer senior] and send me a private letter.

The github address has been updated. Welcome to star.
https://github.com/meetalgo/m...

Full text overview

Basic knowledge of arrays

Definition and characteristics of array

Array is a linear table data structure, which is a collection of data of the same type stored in continuous memory space.

Array has the following characteristics.

  1. The subscript of the array starts at 0.
  2. Continuous memory space and the same data type.

Because the memory space of the array is continuous, we can "randomly access" the elements in the array. But there are advantages and disadvantages. If you want to insert or delete an element in the array, it is inevitable to move other elements in order to ensure the continuity of the memory space of the array.

For example, to delete an element with a subscript of 2, you need to move the elements after subscript 2 forward. As shown in the figure:

There are clever ways to solve problems

dichotomy

If the given array is ordered, we need to consider whether we can use dichotomy to solve it (the time complexity of dichotomy is O(logn)). Dichotomy in the interview is the knowledge point often tested in the interview. It is suggested that we must exercise our ability to tear dichotomy.

Double finger needling

We can complete the work of two for loops under one for loop through a fast pointer and a slow pointer. For example, when we need to enumerate two elements in an array, if we find that the second element decreases with the increase of the first element, we can use the double pointer method to reduce the time complexity of enumeration from O(N^2) to O(N).

sliding window

As the name suggests, the so-called sliding window is a window that can slide on a sequence. The window size has fixed length and variable length. For example, given the array [2,2,3,4,8,99,3] and the window size is 3, finding the sum of elements of each window is the problem of fixed size windows. If finding the longest continuous subarray of the array [2,2,3,4,8,99,3] is the problem of variable windows. Using sliding window, we can reduce the time complexity of the algorithm.

When using sliding window to solve the problem, we mainly need to know what conditions to move the starting position of the window and when to dynamically expand the window, so as to solve the problem.

Hash table method

If we need to find whether an element exists in the array, we can use the hash table method to reduce the time complexity of finding elements from O(n) to O(1).

Sum of two numbers

Problem description

LeetCode 1. Sum of two numbers

Given an integer array nums and an integer target value target, please find the two integers with and as the target value target in the array and return their array subscripts. You can assume that each input will correspond to only one answer. However, the same element in the array cannot be repeated in the answer. You can return answers in any order.

Example 1:

Input: num = [2,7,11,15], target = 9

Output: [0,1]

Explanation: because num [0] + num [1] = = 9, return [0, 1].

Analyze problems

To solve this problem, the simplest and intuitive idea is to find out whether target-x exists in the array for each element X in the array.

def twoSum(nums, target):
    n = len(nums)
    for i in range(n):
        #For each element i in the array
        #The elements before it have been matched with it, and there is no need to match it repeatedly
        for j in range(i + 1, n):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []

We can clearly know that the time complexity of this algorithm is O(n^2). How can we reduce the time complexity? It can be noted that the reason for the high complexity of the algorithm is that when looking for the element target-x, we need to traverse the array, so we need to optimize this block. We can use hash table to reduce the time complexity of finding element target-x from O(n) to O(1).

When we traverse the array, for each element x, we first query whether target-x exists in the hash table. If it exists, the matching result will be returned directly. If it does not exist, we insert x into the hash table.

Tips: we use a dictionary instead of a hash table. When the inserted elements are repeated, we can overwrite them. This can ensure that the time complexity of the search is O(1). Why can we cover it here? Because the topic requires to find two numbers and equal to target. When two elements are repeated, we think they are equivalent, so we only need to keep one.

def twoSum(nums, target):
    hash_table = dict()
    for i, num in enumerate(nums):
        if hash_table.__contains__(target-num):
            return [hash_table[target - num], i]
        hash_table[nums[i]] = i
    return []

nums = [2,7,11,15]
target = 9
print(twoSum(nums,target))

We can see that the time complexity of the algorithm is O(n) and the space complexity is O(n).

optimization

When we need to enumerate two elements in the array, if we find that the second element decreases with the increase of the first element, we can use the double pointer method to reduce the time complexity of enumeration from O(N^2) to O(N). So we can use the double pointer method to solve it. First, we sort the array, and then use the left and right pointers to point to the left and right of the array respectively. At this time, sum = num [left] + num [right]. We move the pointer according to the size relationship between sum and target.

  1. If sum > target, the right pointer moves to the left and decreases the sum value, that is, right=right-1.
  2. If sum < target, move the left pointer to the right and increase the value of sum, that is, left=left+1.
  3. If sum=target, return directly.

Let's take a look at the implementation of the code.

def twoSum(nums, target):
    nums=sorted(nums)
    left=0
    right=len(nums)-1
    while left < right:
        sum=nums[left]+nums[right]
        if sum>target:
            right=right-1
        elif sum<target:
            left=left+1
        else:
            return [left,right]

sorted is used for sorting. The time complexity is O(nlogn) and the space complexity is O(n). Therefore, the time complexity of the algorithm is O(nlogn) and the space complexity is O(n).

Longest non repeating subarray

Problem description

LeetCode3. Longest substring without duplicate characters

Given an array arr, returns the length of the longest non repeating element subarray of arr. non repeating means that all numbers are different. The subarray is continuous. For example, the subarray of [1,3,5,7,9] has [1,3], [3,5,7], etc., but [1,3,7] is not a subarray.

Example

Input: [2,2,3,4,8,99,3]

Return value: 5

Description: [2,3,4,8,99] is the longest subarray

Analyze problems

Before we begin, let's first introduce what sliding window is. As the name suggests, the so-called sliding window is a window that can slide on a sequence. As shown in the figure, assuming that our sequence is abcabcbb, we define a window with a fixed size of 3 to slide around the sequence.

In practical use, the sliding windows we use are variable length.

We can use double pointers to maintain the beginning and end of the window, and change the window size and slide the window by moving the left and right pointers.

Let's take a look at the topic. The topic is to find the longest non repeating element subarray. If we can find all the non repeating element subarrays, it's good to take out the longest one. Let's look at how to solve it. We just need to maintain a window that slides in the array.

  1. At first, 2 is not in the window, so expand the window.

  1. The next element 2 appears in the window, so we need to move all the elements that appear and the elements to the left out of the window, that is, 2.

  1. The following elements 3, 4, 8 and 99 have never appeared in the window, so we add them to the window.

  1. The next element 3 has appeared in the window, so we want to remove the existing element and the element to the left, that is, 2,3.

Let's take a look at how the code is implemented.

    if not s:
        return 0
    left = 0
    # Record whether each character has appeared
    window = set()
    n = len(s)
    max_len = 0
    for i in range(n):
        #If so, remove the duplicate element and the element to its left
        while s[i] in window:
            window.remove(s[left])
            left += 1
        #No, join the window
        window.add(s[i])

        max_len = max(len(window),max_len)
        
    return max_len

The time complexity of the algorithm is O(n).

Merge two ordered arrays

Problem description

LeetCode 88. Merge two ordered arrays

You are given two integer arrays nums1 and nums2 in non decreasing order, and two integers m and n representing the number of elements in nums1 and nums2 respectively. Please merge nums2 into nums1 so that the merged array is also arranged in non decreasing order.

Note: finally, the merged array should not be returned by the function, but stored in the array nums1. In order to deal with this situation, the initial length of nums1 is m + n, where the first m elements represent the elements that should be merged, and the last n elements are 0, which should be ignored. The length of nums2 is n.

Example:

Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3

Output: [1,2,2,3,5,6]

Explanation: it is necessary to combine [1,2,3] and [2,5,6]. The combined results are [1,2,2,3,5,6].

Analyze problems

The simplest way is to put nums2 directly into the last n positions of nums1, and then sort nums1 directly. We won't repeat it here.

def merge(nums1, m, nums2, n):
    nums1[m:] = nums2
    nums1.sort()

nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
merge(nums1,m,nums2,n)
print(nums1)

So since the given two arrays are ordered, why don't we use this condition to optimize the code. Therefore, we can use two pointers p1 and p2 to point to the starting positions of the two arrays respectively, then compare the size, put the smaller one into the result, and then move the pointer back until all the elements are in order.

Let's take a look at the implementation of the code.

def merge(nums1, m, nums2, n):
    #Temporarily store ordered elements
    sorted = []
    p1, p2 = 0, 0
    #When the array is not traversed
    while p1 < m and p2 < n:
        #p1 element traversal completed
        if nums1[p1] <= nums2[p2]:
            sorted.append(nums1[p1])
            p1 += 1
        else:
            sorted.append(nums2[p2])
            p2 += 1
    if p1 == m:
        for x in nums2[p2:]:
            sorted.append(x)
    else:
        for x in nums1[p1:m]:
            sorted.append(x)
    nums1[:] = sorted

nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
merge(nums1,m,nums2,n)
print(nums1)

We can know that the time complexity is O(m+n) and the space complexity is O(m+n).

optimization

When using the double pointer method, we traverse the array from front to back. If we directly use nums1 to store the merge results, the elements in nums1 may be overwritten before being taken out. So we introduce a temporary variable sorted to store. What can be done to prevent the elements in nums1 from being overwritten? Since it's not possible from the front to the back, can we move forward from the back? Because the second half of nums1 is empty and can be directly covered without affecting the result, the reverse double pointer method is introduced here.

Let's look at the code implementation.

def merge(nums1, m, nums2, n):
    #Points to the end element of the array
    p1 = m -1
    p2 = n - 1
    tail = m + n - 1
    while p1 >= 0 or p2 >= 0:
        #Indicates that nums1 traversal is complete
        if p1 == -1:
            nums1[tail] = nums2[p2]
            p2 -= 1
        #Indicates that nums2 traversal is complete
        elif p2 == -1:
            nums1[tail] = nums1[p1]
            p1 -= 1
        #Merge large elements
        elif nums1[p1] >= nums2[p2]:
            nums1[tail] = nums1[p1]
            p1 -= 1
        else:
            nums1[tail] = nums2[p2]
            p2 -= 1
        tail -= 1

The time complexity of the algorithm is O(m+n) and the space complexity is O(1).

Spiral matrix

Problem description

LeetCode 54. Spiral matrix

Give you a matrix with m rows and n columns. Please return all the elements in the matrix in a clockwise spiral order.

Example:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]

Output: [1,2,3,6,9,8,7,4,5]

Analyze problems

The title is required to output in clockwise spiral order, that is, first from left to right, then from top to bottom, then from right to left, and finally from bottom to top, and so on until all elements are traversed. In the process of traversal, the key point is to record which elements have been accessed. If we encounter accessed elements, we need to adjust the direction clockwise.

To judge whether the elements have been accessed, the most intuitive idea is to declare a matrix with the same size as the matrix to identify whether the elements in the matrix have been accessed.

Let's take a look at how the code is implemented.

def spiralOrder(matrix):
    #If the matrix is empty, return directly
    if not matrix or not matrix[0]:
        return []
    rows=len(matrix)
    columns=len(matrix[0])
    visited = [[False for _ in range(columns)] for _ in range(rows)]
    #Number of total elements
    count=rows*columns
    result=[0]*count
    #Represents the direction, that is, from left to right, from top to bottom, from right to left, and from bottom to top
    directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]
    row, column = 0, 0
    #Traverse from the top left corner
    directionIndex = 0
    for i in range(count):
        result[i] = matrix[row][column]
        #Mark the accessed elements
        visited[row][column] = True
        nextRow, nextColumn = row + directions[directionIndex][0], column + directions[directionIndex][1]
        #Do not cross the boundary and have been visited. Adjust the direction clockwise
        if not (0 <= nextRow < rows and 0 <= nextColumn < columns and not visited[nextRow][nextColumn]):
            directionIndex = (directionIndex + 1) % 4

        row += directions[directionIndex][0]
        column += directions[directionIndex][1]
    return result

Since a matrix of the same size as the original matrix is created to indicate whether the elements have been accessed, the spatial complexity of the algorithm is O(mn). The elements in the matrix will be traversed once, so the time complexity of the algorithm is also O(mn).

optimization

Is there any way to optimize the spatial complexity of the algorithm? That is, we don't have to open up a new space to save whether the elements in the matrix have been accessed.

In fact, we can constantly change the boundary conditions in the process of traversal. When the elements in the first row of the matrix are accessed, the upper boundary needs to be + 1; If the last column of elements has been accessed, the right boundary needs - 1 operation; If the last row of elements has been accessed, the lower boundary needs - 1 operation; If the first column has been accessed, the + 1 operation is required; And so on until the traversal is complete.

def spiralOrder(matrix):
    # If the matrix is empty, return directly
    if not matrix or not matrix[0]:
        return []
    rows = len(matrix)
    columns = len(matrix[0])
    result=[]
    #At the beginning, the left, right, upper and lower boundaries
    left=0
    right=columns-1
    up=0
    down=rows-1
    while True:
        #From left to right
        for i in range(left,right+1):
            result.append(matrix[up][i])
        #Upper boundary adjustment
        up=up+1
        #Cross the line, exit
        if up>down:
            break
        #From top to bottom
        for i in range(up,down+1):
            result.append(matrix[i][right])
        #Right boundary adjustment
        right=right-1
        # Cross the line, exit
        if right<left:
            break
        #Right to left
        for i in range(right,left-1,-1):
            result.append(matrix[down][i])
        #Lower boundary adjustment
        down=down-1
        if down<up:
            break
        #From bottom to top
        for i in range(down,up-1,-1):
            result.append(matrix[i][left])
        left=left+1
        if left>right:
            break
    return result

The time complexity of the algorithm is O(m*n) and the space complexity is O(1).

Three numbers with zero in the array

Problem description

LeetCode refers to the three numbers with 0 in the Offer II 007. Array

Given an array num containing n integers, judge whether there are three elements a, b and c in num, so that a + b + c = 0? Please find all triples with a sum of 0 and no duplicates.

Example:

Input: num = [- 1,0,1,2, - 1, - 4]

Output: [- 1, - 1,2], [- 1,0,1]]

Analyze problems

This problem is an upgraded version of the sum of two, so we can also use the double pointer method to solve it. How to use the double finger needle method for the three numbers is actually very simple. Let's fix one number first, and then use the double pointer to find the other two numbers. According to the Convention, we first sort the array, then fix the first number, assuming that it is num [i], and then use the double finger needle method to find that the sum of the two numbers in the array is equal to - num [i]. Since the problem requires that the triples are not repeated, it is necessary to judge to remove the repeated solution. There are two main cases of repeated solutions.

  1. When num [first] = num [first-1], since we have solved the triple whose first element is num [first-1], there is no need to solve it repeatedly.
  2. When num [first] + num [left] + num [right] = 0, if num [left] = num [left + 1] or num [right] = num [right + 1], it will lead to repeated solutions, so it needs to be removed.

Let's look at the implementation of the code.

def threeSum(nums):
    n=len(nums)
    result=[]
    if n<3:
        return result
    nums=sorted(nums)
    print(nums)
    #Traversal array
    for i in range(n):
        #Fixed first number
        first=nums[i]
        #The first number is greater than 0, because the second and third numbers are greater than the first number
        #So it's impossible to add up to 0
        if first>0:
            break
        #It has been searched, so you don't need to continue searching. Skip directly
        if i>0 and first==nums[i-1]:
            continue
        #The third number starts at the rightmost end of the array
        target=-first
        right=n-1
        left=i+1
        while left<right:
            if nums[left]+nums[right]==target:
                result.append([nums[i],nums[left],nums[right]])
                #If left and left+1 are the same for the element, because left has been added to the result
                #To avoid repetition, we skip the same elements
                while left<right and nums[left]==nums[left+1]:
                        left=left+1
                #Similarly, skip the same elements as right
                while left<right and nums[right]==nums[right-1]:
                        right=right-1
                left=left+1
                right=right-1

            elif nums[left]+nums[right]>target:
                right=right-1
            else:
                left=left+1
    return result

nums=[-1,0,1,2,-1,-4]
print(threeSum(nums))

A number that appears more than half the times in the array

Problem description

LeetCode refers to the number that appears more than half of the times in the Offer 39. Array

Give an array with a length of n. a number in the array appears more than half the length of the array. Please find out this number. For example, enter an array of length 9 [1,2,3,2,2,2,5,4,2]. Since the number 2 appears five times in the array, more than half the length of the array, 2 is output.

Example:

Input: [1,2,3,2,2,2,5,4,2]

Output: 2

Analyze problems

Hash table method

The easiest way we can think of is to use the hash table method to count the number of occurrences of each number, which can be easily calculated.

def majorityElement(nums):
    #Use a dictionary to save the number of occurrences of each number
    data_count = {}
    for x in nums:
        if x in data_count:
            data_count[x] = data_count[x] + 1
        else:
            data_count[x] = 1
    max_value=0
    max_key=0
    #Traverse the dictionary and fetch the most frequently
    #Because the title says that there must be a mode in a given array
    #So the maximum number of times is the mode
    for key in data_count:
        value=data_count[key]
        if value>max_value:
            max_value=value
            max_key=key
    return max_key

data=[1,2,3,2,2,2,5,4,2]
print(majorityElement(data))

The time complexity of the algorithm is O(n), and the space complexity is O(n).

Sorting algorithm

We sort the array, and the midpoint of the sorted array must be the mode.

def majorityElement(nums):
        #Sort array
        nums.sort()
        #Returns the midpoint of a sorted array
        return nums[len(nums) // 2]

data=[1,2,3,2,2,2,5,4,2]
print(majorityElement(data))

Boyer Moore voting algorithm

The most classic solution to this problem is Boyer Moore voting algorithm. The core idea of Boyer Moore voting algorithm is the positive and negative cancellation of votes, that is, when we encounter mode, we add votes + 1, when we encounter non mode, we add votes - 1, and then all the votes must be greater than 0.

We assume that the mode of array nums is x and the length of the array is n. We can easily know that if the sum of votes of the first a digits of the array is 0, the sum of votes of the remaining n-a digits must be greater than 0, that is, the mode of the last n-a digits is still X.

We remember that the first element of the array is n1 and the mode of the array is X. traverse and count the votes. When the sum of votes is 0, the mode of the remaining elements of the array must also be x because:

  1. When n1 equals x, half of all the numbers offset are mode X.
  2. When n1 is not equal to x, among all the numbers offset, the individual of mode is at least 0 and at most half.

So, in removing m characters, we only remove half of the mode at most, so in the remaining n-m elements, x is still the mode. Using this feature, we can narrow the remaining array interval every time the sum of votes is 0. When the traversal is complete, the number assumed in the last round is the mode.

class Solution:
    def majorityElement(self, nums):
        #Number of votes and
        counts=0
        for num in nums:
            #If the sum of votes is 0, we assume that the num element is mode
            if counts == 0:
                x = num
            #If it is mode, the number of votes is + 1, otherwise the number of votes is - 1
            if num==x:
                counts=counts+1
            else:
                counts=counts-1
        return x

The time complexity of the algorithm is O(n) and the space complexity is O(1).

Merge interval

Problem description

LeetCode 56. Consolidation interval

The set of several intervals is represented by an array of intervals, in which a single interval is intervals[i] = [starti, endi]. Please merge all overlapping intervals and return a non overlapping interval array, which needs to cover all the intervals in the input.

Input: intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]
Output: [[1, 6], [8, 10], [15, 18]]
Explanation: interval [1,3] and [2,6] overlap and merge them into [1,6]

Analyze problems

For any two intervals A and B, the relationship between them can have the following six cases.

We compare and exchange these two intervals so that the condition that the starting position of the first interval ≤ the starting position of the second interval holds. In this way, we can convert these six cases into the following three.

According to this idea, we sort all intervals according to the left end point, so we can ensure any two consecutive intervals. The starting position of the first interval ≤ the starting position of the second interval, so their relationship is only in the above three cases.

algorithm

For the above three cases, we can use the following algorithm to solve them.

First, we use the array merged to store the final answer. Then we add the first interval to the merged array and consider each subsequent interval in order:

  • If the left end point of the current interval is after the right end point of the last interval in the array merged, that is, the second case in the above figure, they will not coincide. We can directly add this interval to the end of the array merged;
  • Otherwise, they have overlapping parts, that is, in the first and third cases in the above figure, we need to update the right endpoint of the last interval in the array merged with the right endpoint of the current interval and set it to the larger value of the two.

In this way, we can solve the above three cases. Let's take a look at the implementation of the code.

class Solution:
    def merge(self, intervals):
        #Sort the interval array in ascending order according to the left endpoint
        intervals.sort(key=lambda x: x[0])
        #Store consolidated results
        merged = []
        for interval in intervals:
            #If the list is empty
            #Or, if the left end point of the current interval is greater than the right end point of the last element of merged, add it directly
            if not merged or merged[-1][1] < interval[0]:
                merged.append(interval)
            else:
                #Otherwise, we can merge with the previous interval
                #Modify the right endpoint of the last element of merged to the maximum of both
                merged[-1][1] = max(merged[-1][1], interval[1])
        return merged

The time complexity of the algorithm is O(nlogn), where n is the number of intervals. Excluding the sorting overhead, we only need one linear scan, so the main time overhead is the sorting O(n logn). The spatial complexity is O(logn).

Find the upper median in two sorted arrays of equal length

Problem description

LeetCode 4. Find the median of two positively ordered arrays

Given two incremental arrays arr1 and arr2, given that the length of both arrays is n, find the upper median of all numbers in the two arrays. Upper median: assuming that the length of the increasing sequence is n, it is the nth / 2nd number.

Requirements: time complexity O (n), space complexity O(1).

Advanced: the time complexity is O(logN) and the space complexity is O(1).

Example:

Input: [1, 2, 3, 4], [3, 4, 5, 6]

Return value: 3

Note: there are 8 numbers in total. The upper median is the fourth smallest number, so 3 is returned.

Analyze problems

The most intuitive idea of this problem is to combine two ordered arrays into a large ordered array by merging and sorting. The upper median of a large ordered array is the nth / 2nd number. We can know that the time complexity of the algorithm is O(N) and the spatial complexity is O(N). Obviously, it does not meet the spatial complexity of O(1) required by the problem. In fact, we don't need to merge two ordered arrays. We just need to find the position of the upper median. Given two arrays of length N, we can know that the position of the middle digit is n, so we maintain two pointers. Initially, we point to the position of the subscript 0 of the two arrays, and move the pointer pointing to the smaller value one bit later each time (if one pointer has reached the end of the group, we only need to move the pointer of the other array), Until the median position is reached.

Let's take a look at how the code is implemented.

class Solution:
    def findMedianinTwoSortedAray(self , arr1 , arr2 ):
        # write code here
        #Length of array
        N=len(arr1)
        #Define two pointers to the start of two arrays
        p1=p2=0
        ans=0
        while p1+p2<N:
            #Moves the pointer position of the smaller element
            if arr1[p1]<=arr2[p2]:
                ans=arr1[p1]
                p1=p1+1
            else:
                ans=arr2[p2]
                p2=p2+1

        return ans

The time complexity of the algorithm is O(n) and the space complexity is O(1).

Advanced

Let's take a look at how to reduce the time complexity to O(logN). We can use the idea of binary search to solve it.

For arrays of length N, arr1 and arr2, its upper median is the nth element of the two ordered arrays. Therefore, we can turn this problem into finding the k-th smallest element of two ordered arrays, where k=N.

To find the nth element, we can compare arr1[N/2-1] and arr2[N/2-1], where "/" represents integer division. Since arr1[N/2-1] and arr2[N/2-1] are preceded by arr1[0...N/2-2] and arr2[0...N/2-2], that is, N/2-1 elements. For the smaller values of arr1[N/2-1] and arr2[N/2-1], at most N/2-1+N/2-1=N-2 elements are smaller than it, so it is not the nth smallest element.

Therefore, we can summarize the following three cases.

  • If arr1[N/2-1] < arr2 [N/2-1], the number smaller than arr1[N/2-1] is only the first N/2-1 number of arr1 and the first N/2-1 number of arr2 at most, that is, the number smaller than arr1[N/2-1] is only N-2 at most. Therefore, arr1[N/2-1] cannot be the nth number, and neither arr1[0] nor arr1[N/2-1] can be the nth number, so it can be deleted.
  • If Arr1 [n / 2-1] > arr2[N/2-1], arr2[0] to arr2[N/2-1] can be excluded.
  • If arr1[N/2-1]==arr2[N/2-1], it can be classified as the first case for processing.

It can be seen that after a round of comparison, we can check N/2, which cannot be the smallest number N, and the search range is reduced by half. At the same time, we will continue to perform binary search on the excluded new array, and reduce the value of N according to the number of excluded numbers, because the number of excluded numbers is not greater than the smallest number of N.

Let's take a concrete example.

class Solution:
    def findMedianSortedArrays(self, arr1, arr2):
        N = len(arr1)
        #If N=1, you can directly return the minimum value of the first element of the two arrays
        if N==1:
            return min(arr1[0],arr2[0])

        index1, index2 = 0, 0
        #The median position is N, but exceeds the subscript of the partitioned array
        k=N
        while k>1:
        
            new_index1 = index1 +  k // 2 - 1
            new_index2 = index2 +  k // 2 - 1
            data1, data2 = arr1[new_index1], arr2[new_index2]
            #Select a smaller value and delete the first k//2 elements at the same time
            if data1 <= data2:
                k=k-k//2
                #Delete the first k//2 elements
                index1 = new_index1+1
            else:

                k=k-k//2
                #Delete the first k//2 elements
                index2 = new_index2 + 1

        return min(arr1[index1],arr2[index2])

Add meal

If the given two ordered arrays have different sizes, that is, give two positive ordered (from small to large) arrays nums1 and nums2 with sizes m and n respectively. Please find and return the median of these two positive arrays.

According to the definition of median, when m+n is odd, the median is the (m+n + 1) / 2nd element of two ordered arrays. When m+n is an even number, the median is the average of the (m+n)/2 and (m+n)/2+1 elements of the two ordered arrays. Therefore, this problem can be transformed into finding the smallest number k of two ordered arrays, where k is (m+n)/2 or (m+n)/2+1.

Therefore, the solution of this problem is similar to that of the previous problem, but there are some situations that need special treatment.

  • If nums1[k/2-1] or nums[k/2-1] is out of bounds, we need to select the last element of the corresponding array, that is, min(k/2-1,m-1) or min(k/2-1,n-1).
  • If an array is empty, we can directly return the k-th smallest element in another array.
  • If k=1, we only need to return the minimum value of the first element of the two arrays.

Let's take a look at the implementation of the code.

Let's take a look at the implementation of the code.

class Solution:
    def findMedianSortedArrays(self, nums1, nums2):
        #Gets the k-th smallest element
        def getKthElement(k):
            #Indicates the subscript position of two ordered arrays
            index1, index2 = 0, 0
            while True:
                #If the traversal of an array is completed, the k-th element of another array is directly returned
                if index1 == m:
                    return nums2[index2 + k - 1]
                if index2 == n:
                    return nums1[index1 + k - 1]
                #If k is 1, returns the minimum value of the first element of two ordered arrays
                if k == 1:
                    return min(nums1[index1], nums2[index2])

                #To prevent the array from crossing the bounds, the minimum values of index1 + k // 2 - 1 and m - 1 are taken
                new_index1 = min(index1 + k // 2 - 1, m - 1)
                #Similarly, take the minimum value of index2 + k // 2 - 1 and n - 1
                new_index2 = min(index2 + k // 2 - 1, n - 1)
                data1, data2 = nums1[new_index1], nums2[new_index2]
                #If data1 < data2
                if data1 <= data2:
                    #Reduce the value of k by new_index1 - index1 + 1 multiple elements
                    k -= new_index1 - index1 + 1
                    #Removing Elements 
                    index1 = new_index1 + 1
                else:
                    #Reduce the value of k by new_index2 - index2 + 1 multiple elements
                    k -= new_index2 - index2 + 1
                    #Removing Elements 
                    index2 = new_index2 + 1

        m, n = len(nums1), len(nums2)
        #Length of two arrays
        lens = m + n
        #If it is odd, the median is (lens+1)/2
        #If it is even, the median is the average of lens/2 and lens/2+1
        if lens % 2 == 1:
            return getKthElement((lens + 1) // 2)
        else:
            return (getKthElement(lens // 2) + getKthElement(lens // 2 + 1)) / 2.0

The time complexity of the algorithm is O(log(m+n)), and the space complexity is O(1).

Missing first positive integer

Problem description

LeetCode 41. Missing first positive number

Give you an unordered integer array nums without repeated elements. Please find the smallest positive integer that does not appear.

Please implement a solution with time complexity of O(n) and only use constant level additional space.

Example:

Input: num = [1, 2, 0]

Output: 3

Analyze problems

For an array with no repeating elements and length N, the minimum integer that does not appear can only be in [1,N+1]. This is because if [1,N] appears in the array, it means that the N numbers have filled the array, then the answer is N+1, otherwise it is the minimum integer that does not appear in [1,N]. Therefore, we can apply for an auxiliary array temp with a size of N. by traversing the original array, we put the numbers within the range of [1,N] into the corresponding position in the auxiliary array, so that temp[i-1] = i holds. After traversal, the first positive integer in temp that does not meet the condition of temp[i-1] = i is the smallest positive integer. If all conditions are met, the smallest positive integer is N+1.

Let's look at the code implementation.

class Solution:
    def firstMissingPositive(self, nums):
        n = len(nums)
        #Apply for a temporary array to store the elements in the array
        temp = [0]*n
        for i in range(n):
            #If the integer is not within the range of [1,N], it will not be processed
            if nums[i] <= 0 or nums[i] > n:
                continue
            else:
                #Otherwise, put the integer in the corresponding position of temp
                temp[nums[i]-1]=nums[i]

        #Traverse temp and find the first one that does not meet temp [i]= Integer of I + 1
        #Is the smallest integer that does not exist in the array
        for i in range(n):
            if temp[i]!=i+1:
                return i+1
        #If both exist, return N+1
        return n+1

We can know that the time complexity and spatial complexity of the algorithm are O(n). Obviously, the spatial complexity does not meet the requirements of the problem. How can we reduce the spatial complexity of the algorithm? Through observation, we can find that the auxiliary array is the same size as the original array, so can we reuse the original array nums? The answer is obviously yes. In the process of traversing the array, we assume that the traversed element value is X. if x belongs to [1,N], we exchange the elements of element X and num [X-1], so that x appears in the correct position, otherwise we do not process it. When the traversal is completed, the first positive integer in nums that does not meet the condition of nums[i-1] = i is the smallest positive integer. If all conditions are met, the smallest positive integer is N+1.

Let's take a look at the implementation of the code.

class Solution:
    def firstMissingPositive(self, nums):
        #Length of array
        n = len(nums)
        #Traverse the array and put the elements in the correct position
        for i in range(n):
            #If nums[i] is in the range of [1,n] and nums[i] is not in the correct position, we exchange it
            #Otherwise, it will not be handled
            while 1 <= nums[i] <= n \
                    and nums[nums[i] - 1] != nums[i]:
                nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
                
        #Find the first array that does not satisfy num [i]= I + 1 conditional
        #Is the smallest positive integer in the array
        for i in range(n):
            if nums[i] != i + 1:
                return i + 1
        #If both are satisfied, the smallest integer is n+1    
        return n + 1

The time complexity of the algorithm is O(n) and the space complexity is O(1).

Rotate array clockwise

Problem description

LeetCode interview question 01.07. Rotation matrix

There is an NxN integer matrix. Please write an algorithm to rotate the matrix 90 degrees clockwise.

Requirements: time complexity O(N^2), space complexity O(N^2).

Advanced: the time complexity is O(N^2) and the space complexity is O(1)

Example:

[                                                       [     
  [ 5, 1, 9,11],                After 90 degrees of rotation                  [15,13, 2, 5],
  [ 2, 4, 8,10],              ============>                [14, 3, 4, 1],
  [13, 3, 6, 7],                                           [12, 6, 8, 9], 
  [15,14,12,16]                                            [16, 7,10,11]
]                                                        ]

Analyze problems

For the first row element in the matrix, it appears in the penultimate column after 90 degrees of rotation, as shown in the following figure.

Moreover, the i-th element in the first row is exactly the i-th element in the penultimate column after rotation. The same is true for the elements of the second row, which becomes the element of the penultimate column after rotation, and the i-th element of the second row happens to be the i-th element of the penultimate column after rotation. Therefore, we can draw a rule that for the j-th element in the i-th row of the matrix, after rotation, it appears at the j-th position in the penultimate column, that is, for the matrix[i] [j] element in the matrix, after rotation, its new position is matrix [j] [n-i-1].

Therefore, we apply for a new matrix of size n * n to store the rotated results temporarily. By traversing all elements in the matrix, we store the elements in the corresponding positions in the new matrix according to the above rules. After traversal, copy the new matrix to the original matrix. Let's look at the code implementation.

class Solution(object):
    def rotate(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: None Do not return anything, modify matrix in-place instead.
        """
        #Size of matrix
        n = len(matrix)
        #Apply for an auxiliary matrix
        temp = [[0] * n for _ in range(n)]
        #Traverse all elements in the matrix and put them in the corresponding positions of the auxiliary matrix
        for i in range(n):
            for j in range(n):
                temp[j][n - i - 1] = matrix[i][j]
                
        #Copy auxiliary matrix to matrix
        matrix[:] = temp

The time complexity of the algorithm is O(N^2) and the space complexity is O(N^2).

Advanced

How can we realize the in-situ rotation of the matrix without using the auxiliary space? Let's see why auxiliary space is introduced in method 1. For the elements in matrix, we use the formula temp[j] [n - i - 1] = matrix[i] [j] to rotate. If we don't apply for auxiliary matrix, we directly put the element matrix[i] [j] in the position of matrix[j] [n - i - 1], and the matrix[j] [n - i - 1] elements in the original matrix will be covered, This is obviously not the result we want.

After knowing how to rotate the matrix in place, here is another point to be clear: which locations should we select for the above in place exchange operation? According to the above analysis, four positions can be exchanged at one time, so:

  1. When n is an even number, we need to select n^2 / 4 = (n/2) * (n/2) elements for in-situ exchange. The figure can be divided into four blocks to ensure that all elements are not repeated or omitted;
  2. When n is an odd number, since the position of the center remains unchanged after rotation, we need to select (n ^ 2-1) / 4 = (n-1) / 2 (n + 1) / 2 elements for in-situ exchange. Taking the matrix of 55 as an example, we can divide it in the following ways to ensure that all elements are rotated without repetition and leakage.

Let's take a look at the implementation of the code.

class Solution(object):
    def rotate(self, matrix):
        #Size of matrix
        n = len(matrix)
        for i in range(n // 2):
            for j in range((n + 1) // 2):
                #Make a round of in-situ rotation, rotating 4 elements
                temp = matrix[i][j]
                matrix[i][j] = matrix[n - j - 1][i]
                matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1]
                matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1]
                matrix[j][n - i - 1] = temp

The time complexity of the algorithm is O(n^2) and the space complexity is O(1).

Longest continuous subsequence in array

Problem description

Leetcode 128. Longest continuous sequence

Given an unordered array arr, return the length of the longest continuous subsequence (the value is required to be continuous, and the position can be discontinuous. For example, 3,4,5,6 are continuous natural numbers). Please design and implement an algorithm with time complexity of O(n) to solve this problem.

Example:

Input: [100,4200,1,3,2]

Output: 4

Note: the longest continuous sequence of numbers is [1, 2, 3, 4]. Its length is 4.

Analyze problems

Because the given array is unordered, our most intuitive idea is to traverse each element in the array, and then consider taking it as the starting point to constantly look for the existence of x+1, x+2,..., x+n in the array. If the longest matches x+n, it means that the longest continuous sequence starting from X is x, x+1, x+2,..., x+n, and its length is n+1 The time complexity of O(n) is needed to find whether a number exists or not, while the time complexity of O(1) is only needed to judge whether a number exists in the hash table, so we can reduce the time complexity of the algorithm by introducing a hash table.

In the worst case, the time complexity of the algorithm is O(n^2) (that is, the outer layer needs to enumerate n numbers and the inner layer needs to match n times), which can not meet the requirements of the problem. How can we optimize it?

Let's analyze the execution process of the algorithm. If we already know that there is a continuous sequence of X, x+1, x+2,..., x+n in the array, we don't need to continue to look for the continuous sequence in the array starting from x+1, x+2,..., x+n, because the result won't be better than the continuous sequence starting from X. therefore, in the outer loop If you encounter this situation, just skip it directly.

Specifically, when traversing the element x, we judge whether its precursor number x-1 exists. If so, we do not need to execute the following logic, because the result of matching from x-1 is better than that from X, so we skip X.

Let's take a look at the implementation of the code.

class Solution:
    def longestConsecutive(self, nums):
        #Record the length of the longest subsequence
        length = 0

        #Put the elements in the array into the set
        num_set = set(nums)

        for num in num_set:
            #Judge whether num-1 exists in the hash table. If it exists, skip it directly
            if num - 1 not in num_set:
                currentdata = num
                currentlength = 1
                #Keep looking
                while currentdata + 1 in num_set:
                    currentdata += 1
                    currentlength += 1
                #Take the maximum value
                length = max(currentlength, length)

        return length

The time complexity of the algorithm is O(n), and the space complexity is O(n).

Find peak

Problem description

LeetCode 162

The peak element refers to the element whose value is strictly greater than the left and right adjacent values. Give you an integer array nums, find the peak element and return its index. The array may contain multiple peaks. In this case, just return the location of any peak. You can assume nums[-1] = nums[n] = - ∞. You must implement an algorithm with a time complexity of O(log n) to solve this problem.

Example:

Input: num = [1,2,3,1]

Output: 3 is the peak element, and your function should return its index 2.

Analyze problems

Peak refers to the element whose value is strictly greater than the left and right adjacent values. Obviously, the maximum value in the array must be a peak, because it must be greater than the left and right elements. Therefore, we can traverse the array once, and then find the position of its maximum value. The time complexity of the algorithm is O(n), which does not meet the requirements of the problem. How can we optimize it.

We can think about it this way. If we start from one position and keep walking up, we will eventually reach a peak position.

Therefore, we first randomly select an initial position I within the range of [0, n], and then decide which direction to go according to the relationship among num [I-1], Num [i], Num [i + 1].

  • How to nums [I-1] < nums [i] > nums [i + 1], then position I is the position of the peak, and we directly return I.
  • If num [I-1] < num [i] < num [i + 1], position I is in the uphill position. To find the peak, you need to go to the right, i=i+1.
  • If nums [I-1] > nums [i] > nums [i + 1], then position I is in the downhill position. To find the peak, you need to go to the left, season i=i-1.
  • If nums [I-1] > nums [i] < nums [i + 1], I is at the bottom of the slope. To find the peak, we can go in both directions. We assume that this situation needs to go to the right.

To sum up, when i is not the peak.

  • If nums [i] > nums [i + 1], I needs to go to the left, that is, execute i=i-1.
  • If nums [i] < nums [i + 1], I needs to go to the right, that is, execute i=i+1.
import random
class Solution:
    def findPeakElement(self, nums):
        #Length of array
        n = len(nums)
        #Initialize a location randomly
        idx = random.randint(0, n - 1)

        
        #It is convenient to handle the boundary of nums[-1] and nums[n]
        def get_value(i):
            if i == -1 or i == n:
                return float('-inf')
            return nums[i]
        #When I is not the peak value, if num [i] < num [i + 1], you need to go to the right, that is, i=i+1
        #Otherwise, you need to go left, that is, i=i-1
        while not (get_value(idx - 1) < get_value(idx) > get_value(idx + 1)):
            if get_value(idx) < get_value(idx + 1):
                idx = idx + 1
            else:
                idx = idx - 1
                
        return idx

In the worst case, assuming that num is monotonically increasing and we start from 0, we need to go all the way to the right to the last position of the array. The time complexity of the algorithm is O(n), and the time complexity required by the problem is O(logn), which is obviously inconsistent. How can we solve it? For example, O(logn) In this form of time complexity, we first think of dichotomy, but the elements in the array are not sorted. How can we use dichotomy to solve this problem? Let's take a look.

Through analysis, we can find that when num [i] < num [i+1], we need to make I go right, that is, execute i=i+1, then all positions on the left of I and I will never go in subsequent iterations. Because assuming that we are at the position of i+1 at this time, we need num [i] > num [i+1] to go left to position i So we can design the following algorithm. First, create two variables L and r to represent the walkable left and right boundaries. At the beginning, l=0 and r=n-1.

  1. Take the midpoint of interval [l, r], that is, mid=(l+r)/2.
  2. If the subscript mid is the peak, return directly.
  3. If num [mid] < num [mid+1], it means that the peak is on the right side of mid, so abandon the interval [l, mid] and look for it in the remaining interval [mid+1, r].
  4. If num [mid] > num [mid + 1], it means that the peak is on the left of mid, so abandon the interval [mid, r] and look for it in the remaining interval [l, mid-1].

In this way, the algorithm eliminates half of the elements each time, so the time complexity is O(logn).

Let's take a look at the implementation of the code.

class Solution:
    def findPeakElement(self, nums):
        n = len(nums)
        # It is convenient to handle the boundary of nums[-1] and nums[n]
        def get_value(i):
            if i == -1 or i == n:
                return float('-inf')
            return nums[i]

        #l. R represents the left and right boundaries of the interval
        l=0
        r=n-1
        ans=-1
        while l <= r:
            #Take the midpoint
            mid = (l + r) // 2
            #If it is a peak, return directly
            if get_value(mid - 1) < get_value(mid) > get_value(mid + 1):
                ans = mid
                break
            #If num [mid] < num [mid + 1], the peak value is [mid+1,r]
            #Otherwise, in the interval [l,mid-1]
            if get_value(mid) < get_value(mid + 1):
                l = mid + 1
            else:
                r = mid - 1

        return ans

Find in 2D array

Problem description

LeetCode sword refers to Offer 04. Search in two-dimensional array

In an n * m two-dimensional array, each row is sorted in ascending order from left to right, and each column is sorted in ascending order from top to bottom. Please complete an efficient function and enter such a two-dimensional array and an integer to judge whether the array contains the integer.

Example:

The existing matrix is as follows:

[
  [1, 2, 8, 9],
  [2, 4, 9, 12],
  [4, 7, 10, 13],
  [6, 8, 11, 15]
]

Given target = 9, return true.

Given target = 20, false is returned.

Analyze problems

To find an element in a two-dimensional array, we can traverse each element in the two-dimensional array, and then judge whether it is the same as the target value. The time complexity of the algorithm is O(m*n), which is obviously not the optimal solution. Let's take a look at how to optimize.

Because every row and column in a given array is arranged incrementally. When we start from the lower left corner, we have only two choices: up and right. The value on the upper side is strictly small and the value on the right side is strictly large. Therefore, we can use this property.

We start from the lower left corner of the two-dimensional array and start traversing the array.

  • When the element matrix [i] [j] < target, it means that the target is to the right of matrix [i] [j], so go to the right, that is, execute j=j+1.
  • When the element matrix [i] [j] > target, it means that the target is above the matrix [i] [j], so go up, that is, execute i= i-1.
  • When the element matrix [i] [j] == target, it means that the target value has been found, and you can directly return true.

Finally, when the boundary of the two-dimensional array is exceeded, it means that the element does not exist in the array, and false is returned directly.

Let's take a look at the implementation of the code.

class Solution(object):
    def findNumberIn2DArray(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        m=len(matrix)
        #When the matrix is null, False is returned directly
        if m==0:
            return False
        n=len(matrix[0])
        #Traverse from the lower left corner
        i = m - 1
        j = 0
        while i >= 0 and j <= n - 1:
            # Equal returns True
            if matrix[i][j] == target:
                return True
            # Greater than walking up
            elif matrix[i][j] > target:
                i = i - 1
            # Go right
            elif matrix[i][j] < target:
                j = j + 1
        return False

The time complexity of the algorithm is O(m+n) and the space complexity is O(1).

Reverse order pairs in an array

Problem description

LeetCode refers to the reverse order pair in the Offer 51. Array

Two numbers in the array. If the previous number is greater than the following number, the two numbers form an inverse pair. Enter an array and find the total number of inverse pairs P in the array.

Example:

Input: [7,5,6,4]

Output: 5

Analyze problems

The easiest way to think of this problem is the violent solution, that is, use the two-layer for loop to judge whether it constitutes a reverse order relationship one by one.

class Solution:
    def reversePairs(self, nums) :
        n = len(nums)
        #If the number of elements in the array
        # When it is 0 or 1, it indicates that there is no reverse order pair and returns 0 directly
        if n < 2:
            return 0
        #Number of pairs in reverse order
        res = 0
        #Two layers of loops are used to judge whether it is a reverse sequence pair one by one
        for i in range(0,  n - 1):
            for j in range(i + 1, n):
                if nums[i] > nums[j]:
                    res += 1
        return res

The time complexity of the algorithm is O(n^2) and the space complexity is O(1).

The algorithm is obviously not the optimal solution. The first time we heard about the concept of reverse order pair should be when sorting arrays. Therefore, we can use merge sort algorithm to solve the number of reverse pairs.

First, let's review what merge sorting is. Merging and sorting is a typical application of divide and conquer, which includes the following three steps:

  1. Decomposition: assuming that the interval to be sorted is [l,r], we make mid=(l+r)//2 and divide the interval into [l,mid] and [mid+1,r].
  2. Solution: use merge sort recursively to solve the two subsequences to make them orderly.
  3. Merge: merge two ordered subsequences [l,mid] and [mid+1,r].

Let's look at how to use merge sort to solve inverse pairs? The key is the merging step of merging and sorting, that is, using the partial order of the array, the number of reverse numbers before or after a number can be calculated at one time; Let's look at a specific example. Suppose there are two ordered sequences waiting to be merged, namely L=[8, 17, 30, 45] and R=[10, 24, 27, 39], as shown in the figure below.

Let's see how to combine L and R into an ordered array. The overall idea is to copy the original array to the auxiliary array, and then use the double finger needle method to merge the smaller elements back each time.

Let's take a look at the implementation of the code.

class Solution:
    def reversePairs(self, nums) :
         n = len(nums)
         #When the number in the array is less than 2, there is no reverse order pair, so 0 is returned
         if n < 2:
            return 0

         #Auxiliary array for merging
         temp = [0 for _ in range(n)]
         return self.reverse_pairs(nums, 0, n - 1, temp)

    #Merge sort num
    def reverse_pairs(self, nums, left, right, temp):
        if left == right:
            return 0
        mid = (left + right)//2
        #nums is divided into left and right parts and solved recursively
        left_pairs = self.reverse_pairs(nums, left, mid, temp)
        right_pairs = self.reverse_pairs(nums, mid + 1, right, temp)

        #The subsequences [left, mid] and [mid + 1, right] have been sorted and the reverse pairs have been calculated
        reverse_pairs = left_pairs + right_pairs
        #Num [mid] < = num [mid + 1], now [left, right] is already in order
        #Therefore, there is no reverse order pair across two intervals, and reverse is returned directly_ Pairs
        if nums[mid] <= nums[mid + 1]:
            return reverse_pairs

        #Calculate the reverse order pairs across two intervals
        cross_pairs = self.merge_and_count(nums, left, mid, right, temp)

        return reverse_pairs + cross_pairs

    def merge_and_count(self, nums, left, mid, right, temp):
        """
        [left, mid] Orderly,[mid + 1, right] Ordered, combining two ordered subsequences into one ordered subsequence
        """
        #copy the elements of nums into the auxiliary array
        for i in range(left, right + 1):
            temp[i] = nums[i]

        i = left
        j = mid + 1
        res = 0
        for k in range(left, right + 1):
            #i> Mid, indicating that the left part has been traversed. Insert right directly into num
            if i > mid:
                nums[k] = temp[j]
                j += 1
            #j> Right indicates that the right part has been traversed, and the left is directly inserted into nums
            elif j > right:
                nums[k] = temp[i]
                i += 1
            # At this time, the left array element is small and inserted into nums. The reverse order number is not calculated
            elif temp[i] <= temp[j]:
                nums[k] = temp[i]
                i += 1
            # At this time, the right array element is small, insert it into nums, and count the reverse order pairs,
            # The number of inverse pairs of an interval can be counted at a time
            else:
                nums[k] = temp[j]
                j += 1
                res += (mid - i + 1)
        return res

The time complexity of the algorithm is O(nlogn) and the space complexity is O(1).

Rotate array

Problem description

LeetCode189. Rotate array

There are n integers in an array A. on the premise that other arrays are not allowed, move each integer cycle to the right by M (M > = 0), that is, transform the data in a from (A0 A1... An-1) to (an-m... An-1, A0 A1... An-m-1) (the last m number cycles to the front m positions).

Example:

Input: [1,2,3,4,5,6,7]

Output: [5,6,7,1,2,3,4]

Analyze problems

The most intuitive idea of this problem is to use an additional array to put each element in the correct position. We use n to represent the length of the array, then traverse the original array, put the element with the index of i in the original array to the position with the index of (i + k)% n in the new array, and finally copy the new array to the original array.

class Solution:
    def rotate(self,nums,k):
        n=len(nums)
        tmp=[0]*n

        for i in range(0,n):
            #Put the array nums[i] in the corresponding position of the new array
            tmp[(i+k)%n]=nums[i]
        #Copy the new array to the original array
        nums[:]=tmp[:]

The time complexity of the algorithm is O(n), and the space complexity is O(n) However, the problem requires that no other array should be used. Obviously, the algorithm is not in line with the meaning of the problem. Let's observe the changes before and after the array is moved. When we move the elements of the array to the right K times, the tail K mod n elements will move to the head of the array, and the other elements will move back K mod n positions. Therefore, we can use the method of array flipping to solve the problem. The specific idea is as follows: first, we flip all the elements, so that the tail K mod n elements are moved to the head of the array. Then we flip the elements in the [0, (k mod n)-1] interval and the elements in the [k mod n, n-1] interval to get the final answer.

Let's take a look at the implementation of the code.

class Solution:
    #Flip the elements in the array
    def reverse(self,nums,start,end):
        while start < end:
            tmp=nums[start]
            nums[start]=nums[end]
            nums[end]=tmp
            start=start+1
            end=end-1
    def rotate(self,nums,k):
        n=len(nums)
        k=k%n
        #Invert the array
        self.reverse(nums,0,n-1)
        #Flip the interval nums[0,k-1] again
        self.reverse(nums,0,k-1)
        #Flip the interval nums[k,n-1] again
        self.reverse(nums,k,n-1)

The time complexity of the algorithm is O(n) and the space complexity is O(1).

Adjust the array order so that odd numbers precede even numbers

Problem description

Enter an integer array and implement a function to adjust the order of numbers in the array so that all odd numbers are in the first half of the array and all even numbers are in the second half of the array.

Example:

Input: [1,2,3,4]

Output: [1,3,2,4]

Analyze problems

We can use the double pointer method to solve this problem. The specific ideas are as follows.

  1. First, apply for two pointers I and j, pointing to the left and right ends of the array nums, i.e. i=0, j=n-1.
  2. When the position indicated by i is an odd number, i=i+1 is executed until an even number is encountered.
  3. When the position indicated by J is an even number, j=j-1 is executed until an odd number is encountered.
  4. Then exchange the values of nums[i] and nums[j]
  5. Repeat until i==j.

Let's take a look at the implementation of the code.

class Solution(object):
    def exchange(self, nums):
        #Apply for two variables i and j, starting at both ends of the array
        i=0
        j=len(nums)-1
        while i < j:
            #Start with i and look from left to right until you find the first even number
            while i < j and nums[i] % 2 == 1:
                i = i + 1
            #Start from j, look from right to left until you find the first odd number
            while i < j and nums[j] % 2 == 0:
                j = j - 1
            nums[i], nums[j] = nums[j], nums[i]
        return nums

In fact, we can also use the fast and slow pointer method to solve this problem. First, we define two pointers fast and slow. Fast is used to search for the location of the odd number forward, and slow is used to point to the location where the next odd number should be stored. In the process of fast moving forward, when it searches for the odd number, it will be compared with num [slow] Swap, then move slow forward one position and repeat the above operation until fast points to the end of the array.

class Solution:
    def exchange(self, nums):
        slow = 0
        fast = 0
        #Loop through until fast points to the end of nums
        while fast < len(nums):
            #If fast points to an odd number,
            #Swap num [slow] and num [fast]
            if nums[fast] % 2 == 1:
                nums[slow], nums[fast] = nums[fast], nums[slow]
                slow=slow+1
            fast=fast+1
        return nums

The time complexity of the algorithm is O(n) and the space complexity is O(1).

Matrix multiplication

Problem description

Given two matrices A and B of N, find a and B.

Example:

Input: [[1,2], [3,2]], [[3,4], [2,1]]

Output: [[7,6], [13,14]]

Analyze problems

We can use matrix multiplication rules to solve it. When matrix A of N and matrix B are multiplied, the elements in row i and column J of matrix C can be expressed as Ci, j = AI, 1 B1, j + AI, 2 B2, j +... + AI, N, BN, J, that is, it is equal to the sum of the product of the corresponding elements in row i of a and column J of B.

class Solution:
    def solve(self , a, b):
        # write code here
        #Matrix a and matrix b are matrices of n*n
        n=len(a)
        res=[[0] * n for _ in range(n)]

        for i in range(0,n):
            for j in range(0,n):
                for k in range(0,n):
                    #The elements in row i and column j of C are
                    #The sum of the product of the corresponding elements in row i of A and column j of B
                    res[i][j] += a[i][k]*b[k][j]
        return res

The time complexity of the algorithm is O(N^3) and the space complexity is O(N^2).

We all know that for two-dimensional arrays, they are actually stored sequentially in the memory of the computer, as shown below:

When the operating system loads data into the cache, it loads a batch of data near the hit data into the cache, because the operating system thinks that if a memory location is referenced, the program is likely to reference a memory location nearby in the near future. Therefore, we optimize by adjusting the reading order of the array, so that matrices A and B are read in order, and then sent to the CPU for calculation. Finally, the running time can be faster. Let's take a look at the specific methods:

class Solution:
    def solve(self , a, b):
        # write code here
        #Matrix a and matrix b are matrices of n*n
        n=len(a)
        res=[[0] * n for _ in range(n)]

        for i in range(0,n):
            for j in range(0,n):
                #Sequentially access the elements of matrix A
                temp=a[i][j]

                for k in range(0,n):
                    #The elements of matrix b are also accessed sequentially
                    res[i][k] += temp * b[j][k]

        return res

The time complexity of the algorithm is O(N^3), but the algorithm uses cache optimization to read the elements in array A and array B sequentially, so it generally runs faster than the first method. The spatial complexity of the algorithm is O(N^2).

The number of occurrences of a number in an ascending array

Problem description

Given a non descending array of length n and a non negative integer k, it is required to count the number of occurrences of K in the array.

Example:

Input: [1,2,3,3,3,4,5], 3

Output: 4

Analyze problems

No matter whether the array is ordered or not, if we want to find out whether there is an element in the array, we only need to traverse the general array.

class Solution:
    def GetNumberOfK(self,data, k):
        n=0
        for x in data:
            if x==k:
               n=n+1
        return n

The time complexity of the algorithm is O(n) and the space complexity is O(1).

Because the array given by the problem is ordered, we can use binary search to solve it. For an ordered array, if there are multiple target values to find, they must be connected together, so we can find the upper and lower bounds of the range of target values through quadratic binary search. First, let's look at the definitions of upper and lower bounds.

  • The lower bound is defined as: if there is a target value, it points to the first target value; If it does not exist, it points to the first value greater than the target value.
  • The upper bound is defined as: whether the target value exists or not, it points to the first value greater than the target value.

Let's look at the code implementation.

class Solution:
    def GetNumberOfK(self,data, k):
        l=0
        r=len(data)-1

        #Finding the lower bound by dichotomy
        while l<r:
            mid = (r+l) // 2
            if data[mid] < k:
                l = mid + 1
            else:
                r = mid

        left=l
        #Find upper bound
        l = 0
        r = len(data)-1
        while l<r:
            mid=(r+l)//2
            if data[mid] <= k:
                l=mid+1
            else:
                r=mid

        right=l
        return right - left

The time complexity of the algorithm is O(logN) and the space complexity is O(1).

Maximum product of three numbers

Problem description

Leetcode 628. Maximum product of three numbers

Given an unordered array A with A length of n, which contains positive numbers, negative numbers and 0, please find three numbers to maximize the product and return this product.

Example:

Input: num = [1,2,3,4]

Output: 24

Analyze problems

The maximum product of three numbers in the array has the following two cases.

  1. If the elements in the array are all non negative or non positive numbers, the multiplication of the three largest numbers in the array is the maximum product.
  2. If the elements in the array have both positive and negative numbers, the maximum product may be the product of three maximum positive numbers, or the product of two minimum negative numbers (maximum absolute value) and the maximum positive number.

Therefore, we only need to find the largest three numbers and the smallest two numbers in the array to get the result. Let's look at how to solve it. The easiest way to think of is to sort the array in descending order. The first three digits and the last two digits of the ordered array are the maximum three numbers and the minimum two numbers to find.

class Solution:
    def maximumProduct(self,nums):
        nums=sorted(nums)
        n=len(nums)
        return max(nums[0] * nums[1] * nums[n-1], nums[n - 3] * nums[n - 2] * nums[n-1])

The time complexity of the algorithm is O(nlogn), where n is the length of the array. Sorting takes O(nlogn) time.

The spatial complexity is O(logn), which is mainly the overhead of sorting.

In fact, we can also scan the array to find the five numbers, as shown below.

import sys
class Solution:
    def maximumProduct(self,nums):
        #Minimum and second smallest
        min1=min2=sys.maxsize
        #Largest, second largest, third largest
        max1=max2=max3=-sys.maxsize-1
        for x in nums:
            if x < min1:
                min2=min1
                min1=x
            elif x<min2:
                min2=x

            if x>max1:
                max3=max2
                max2=max1
                max1=x
            elif x>max2:
                max3=max2
                max2=x
            elif x>max3:
                max3=x

        return max(max1*max2*max3,max1*min1*min2)

The time complexity of the algorithm is O(n) and the space complexity is O(1).

last

Original is not easy! If you think the article is good, you might as well like it (reading), leave a message and forward it!

The more you know, the more open your mind is. I'll see you next time.

Topics: Algorithm leetcode Interview array