LeetCode algorithm -- binary search + double pointer

Keywords: Algorithm leetcode

Although lz is still a master of laboratory brick moving, with the ambition of becoming an excellent programmer ape and the idea of "stupid birds fly first", I decided to start my journey of LeetCode (sprinkle flowers ★, °: ☆ ( ̄▽  ̄) / $:. ° ★.)

I measured my level rationally. After some selection, I decided to start with the introduction column of LeetCode algorithm. Although the difficulty is not high, it is a good way to get familiar with the basic operation of LeetCode and master some fixed routines of question types~

In that case, let's start our journey by following the question type setting of the algorithm introduction column.

1, Binary search

1. Binary search

Given an n-element ordered (ascending) integer array nums and a target value target, write a function to search the target in nums. If the target value exists, return the subscript, otherwise return - 1.

Example 1:
Input: num = [- 1,0,3,5,9,12], target = 9
Output: 4
Explanation: 9 appears in nums and the subscript is 4
Example 2:
Input: num = [- 1,0,3,5,9,12], target = 2
Output: - 1
Explanation: 2 does not exist in nums, so - 1 is returned

The method of solving this problem is very simple. It can solve the problem in one traversal. I believe it is also the method you can think of in the first reaction.

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        for i in range(len(nums)):
            if nums[i] == target:
                return i
        return -1

However, the time complexity of such violence is O(N). If you want to get the algorithm of O(logN), you need to use one of today's topics - half search. The so-called binary search is to continuously use the binary method to determine the search direction in the sequentially determined data, so as to quickly find the target.

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        l, r = 0, len(nums) - 1  # Get the left and right coordinates of the list
        while l <= r:
            mid = l + (r - l) // 2 # get the middle coordinates of the list
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:  # When the middle value of the list is less than the target, it means that the possible result falls in the right half of the mid. at this time, we need to put the search range in the right half
                l = mid + 1
            else:   # Similarly, when the middle value of the list is greater than target, it means that the possible result falls in the left half of the mid. at this time, we need to put the search range in the left half
                r = mid - 1
        return -1  

2. The first wrong version

You are a product manager and are currently leading a team to develop new products. Unfortunately, the latest version of your product has not passed the quality test. Since each version is developed based on the previous version, all versions after the wrong version are wrong.

Suppose you have n versions [1, 2,..., n], you want to find the first wrong version that causes errors in all subsequent versions.

You can call the bool isBadVersion(version) interface to determine whether the version number is wrong in the unit test. Implement a function to find the first wrong version. You should minimize the number of calls to the API.

Example 1:
Input: n = 5, bad = 4
Output: 4
Explanation: call isbadversion (3) - > false
Call isbadversion (5) - > true
Call isbadversion (4) - > true
So, 4 is the first wrong version.

Example 2:
Input: n = 1, bad = 1
Output: 1
Tips:

  • 1 <= bad <= n <= 2 31 2^{31} 231 - 1

Because this question gives a hint that there is a large bad value in the test case, the probability of violence method will exceed the time limit. We directly use the binary search method.

# The isBadVersion API is already defined for you.
# @param version, an integer
# @return an integer
# def isBadVersion(version):

class Solution:
    def firstBadVersion(self, n):
        """
        :type n: int
        :rtype: int
        """
        l, r = 1, n
        while l < r:  # Because the mid value may be the answer, and this question is not exactly target ed like the first one, this template is used
            mid = l + (r - l) // 2
            if isBadVersion(mid) == True:
                r = mid
            else:
                l = mid + 1
        return l

3. Search insertion location

Given a sort array and a target value, find the target value in the array and return its index. If the target value does not exist in the array, returns the position where it will be inserted in order.

You must use an algorithm with a time complexity of O(log n).

When you see O(logN), you don't have to think that it must be binary search. We should also develop this conditioned reflex.

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        if len(nums) == 0:
            return 0

        l, r = 0, len(nums) - 1
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] > target:
                r = mid - 1
            else:
                l = mid + 1
        return l

2, Double pointer

1. Square of ordered array

Give you an integer array nums sorted in non decreasing order, and return a new array composed of the square of each number. It is also required to sort in non decreasing order.

Example 1:
Input: num = [- 4, - 1,0,3,10]
Output: [0,1,9,16100]
Explanation: after squaring, the array becomes [16,1,0,9100]
After sorting, the array becomes [0,1,9,16100]
Example 2:
Input: num = [- 7, - 3,2,3,11]
Output: [4,9,9,49121]

Since we use python to do problems, we can certainly think of the sorted function, which is naturally output directly.

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
    	return sorted(num **2 for num in nums)

Of course, let's talk about my logic: the non decreasing array num has a negative number, so there must be a process of decreasing first and then increasing after the square, so you only need to constantly compare the num [l] square with the num [R] square in the decreasing process, and fill the right end of the answer list; During the increment process, you can directly fill the remaining numbers in the left end of the list.

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        ans = [0] * n
        l, r, p = 0, n - 1, n - 1

        while l < r:
            if nums[l] ** 2 >= nums[l + 1] ** 2 and nums[r] ** 2 >= nums[l] ** 2:
                ans[p] = nums[r] ** 2
                r -= 1
            elif nums[l] ** 2 >= nums[l + 1] ** 2 and nums[r] ** 2 < nums[l] ** 2:
                ans[p] = nums[l] ** 2
                l += 1
            elif nums[l] ** 2 < nums[l + 1] ** 2:
                ans[p] = nums[r] ** 2
                r -= 1
            p -= 1
        ans[0] = nums[l] ** 2
        return ans

The above method logic is OK, but it is too convoluted. In fact, it can be sorted directly. It is simplified as follows:

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        ans = [0] * n
        l, r, p = 0, n - 1, n - 1

        while l <= r:
            if nums[l] ** 2 < nums[r] ** 2:
                ans[p] = nums[r] ** 2
                r -= 1
            else:
                ans[p] = nums[l] ** 2
                l += 1
            p -= 1
        return ans

2. Rotate array

Given an array, move the elements in the array K positions to the right, where k is a non negative number.

Advanced:

Think of as many solutions as possible. There are at least three different ways to solve this problem.
Can you use the in-situ algorithm with spatial complexity O(1) to solve this problem?

Example 1:
Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
Rotate one step to the right: [7,1,2,3,4,5,6]
Rotate 2 steps to the right: [6,7,1,2,3,4,5]
Rotate 3 steps to the right: [5,6,7,1,2,3,4]
Example 2:
Input: num = [- 1, - 100,3,99], k = 2
Output: [3,99, - 1, - 100]
Explanation:
Rotate 1 step to the right: [99, - 1, - 100,3]
Rotate 2 steps to the right: [3,99, - 1, - 100]

The first thing I thought of in this question was slicing. If I cut the first few numbers out of nums and concat enate them with the previous slices of the first few numbers, wouldn't they be the rotated array?

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        k %= n   # k may be greater than n, resulting in overflow, so do the remainder operation
        list1 = nums[-k: n]   # Move to the list slice composed of several numbers in front
        list2 = nums[0: -k]   # A list slice of several numbers that have not been moved
        ans = list1 + list2
        for i in range(n):
            nums[i] = ans[i]

You can be more concise.

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        k %= len(nums) 
        nums[:] = nums[-k:] + nums[:-k]

Or simply fill the numbers in nums directly into the new array.

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        ans = [0] * n
        for i in range(n):
            ans[(i + k) % n]  = nums[i]
        for i in range(n):
            nums[i] = ans[i]

However, the above methods do not meet the condition that the spatial complexity is O(1), because even without additional arrays, the slice will produce temporary variables, so we do it again with pointers.

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        k = k % n 
        
        def reverse(nums: List[int]):
            l, r = 0, len(nums) - 1
            while l < r:
                nums[l], nums[r] = nums[r], nums[l]
                l += 1
                r -= 1
            return nums
        
        # Triple flip
        nums = reverse(nums)
        nums[:k] = reverse(nums[:k])
        nums[k:] = reverse(nums[k:])

3. Move zero

Given an array num, write a function to move all zeros to the end of the array while maintaining the relative order of non-zero elements.

Example:
Input: [0,1,0,3,12]
Output: [1,3,12,0,0]
Note: you must operate on the original array and cannot copy additional arrays. Minimize the number of operations.

No more, just traverse the exchange on the original array.

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        l, r = 0, 0
        while r < n:
            if nums[r] != 0:
                nums[l], nums[r] = nums[r], nums[l]
                l += 1
            r += 1

4. Sum of two numbers

Given an integer array numbers that has been arranged in non decreasing order, please find out that the sum of the two numbers in the array is equal to the target number target.

The function should return the subscript values of these two numbers as an array of integers of length 2. The subscript of numbers starts counting from 1, so the answer array should meet 1 < = answer [0] < answer [1] < = numbers.length.

You can assume that each input only corresponds to a unique answer, and you can't reuse the same elements.

Example 1:
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: the sum of 2 and 7 is equal to the target number 9. So index1 = 1, index2 = 2.
Example 2:
Input: numbers = [2,3,4], target = 6
Output: [1,3]
Example 3:
Input: numbers = [-1,0], target = -1
Output: [1,2]

In this problem, we must pay attention to the condition that the subscript numbers starts counting from 1, so the return is [l + 1, r + 1].

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        n = len(numbers)
        l, r = 0, n - 1

        while l < r:
            if numbers[l] + numbers[r] == target:
                return [l + 1, r + 1]
            elif numbers[l] + numbers[r] < target:
                l += 1
            else:
                r -= 1
        return [l + 1, r + 1]

5. Reverse string

Write a function that inverts the input string. The input string is given as a character array s.

Do not allocate additional space to another array. You must modify the input array in place and use the additional space of O(1) to solve this problem.

It's too simple to explain.

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        l, r = 0, len(s) - 1
        while l < r:
            s[l], s[r] = s[r], s[l]
            l += 1
            r -= 1

6. Reverse the words in the string

Given a string, you need to reverse the character order of each word in the string while still retaining the initial order of spaces and words.

Example:
Enter: "Let's take LeetCode contest"
Output: "s'teL ekat edoCteeL tsetnoc"

Just deal with two more steps than the previous question. First, you need to separate the words according to the space, reverse and combine them.

class Solution:
    def reverseWords(self, s: str) -> str:
        new_s = ""
        for word in s.split():
            word = list(word)
            l, r = 0, len(word) - 1
            while l < r:
                word[l], word[r] = word[r], word[l]   # str type cannot do such an operation
                l += 1
                r -= 1
            new_s += "".join(word)   # Recombine words converted to single characters into strings
            new_s += " "
        return new_s[:len(new_s) - 1]   # There is an extra space at the end of the sentence, so slice the front

7. Intermediate node of linked list

Given a non empty single linked list with head node, return the intermediate node of the linked list.

If there are two intermediate nodes, the second intermediate node is returned.

Example 1:
Input: [1,2,3,4,5]
Output: node 3 in this list (serialized form: [3,4,5])
The returned node value is 3. (the serialization expression of this node in the evaluation system is [3,4,5]).
Note that we returned an object ans of ListNode type, as follows:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, and ans.next.next.next = NULL
Example 2:
Input: [1,2,3,4,5,6]
Output: node 4 in this list (serialized form: [4,5,6])
Since the list has two intermediate nodes with values of 3 and 4, we return the second node.

If we use the idea of double pointers and assume that two pointers start at the same time, when one pointer traverses the linked list, the other pointer reaches half the position of the linked list, then the traversal speed of the former pointer should be twice that of the latter, which is the fast and slow pointer method.

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def middleNode(self, head: ListNode) -> ListNode:
        s = f = head
        while f and f.next:
            f = f.next.next
            s = s.next
        return s

8. Delete the penultimate node of the linked list

Give you a linked list, delete the penultimate node of the linked list, and return the head node of the linked list.

Advanced: can you try using one scan?

This topic still follows the fast and slow pointer idea of the previous topic. Just let the fast pointer stop in front of the node to be deleted when the fast pointer completes the linked list, and then let its next position directly point to the next position of the deleted node.

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummy = ListNode(0, head)   # Defining dummy nodes is a normal operation. dummy.next points to head node
        s = dummy
        f = head
        for i in range(n):
            f = f.next
        while f:
            f = f.next
            s = s.next
        s.next = s.next.next
        return dummy.next

(more related topics will be updated in the future. Please pay attention. Novices can see it here.)

Source: LeetCode
Link: https://leetcode-cn.com

Posted by rroc on Mon, 01 Nov 2021 05:24:01 -0700