leetcode essence of algorithm interview in Dachang 5. Binary search

Keywords: leetcode

leetcode essence of algorithm interview in Dachang 5. Binary search

Video tutorial (efficient learning): Click to learn

catalog:

1. Introduction

2. Time and space complexity

3. Dynamic planning

4. Greed

5. Binary search

6. Depth first & breadth first

7. Double pointer

8. Sliding window

9. Bit operation

10. Recursion & divide and conquer

11 Pruning & backtracking

12. Reactor

13. Monotone stack

14. Sorting algorithm

15. Linked list

16.set&map

17. Stack

18. Queue

19. Array

20. String

21. Trees

22. Dictionary tree

23. Consolidation

24. Other types of questions

Binary search

Time complexity O(logn)

Steps:

  • Start with the element in the middle of the array. If the element in the middle is exactly the target value, the search ends
  • If the target value is greater than or less than the middle element, the search continues on the half of the element greater than or less than the middle element

Code Template

//Binary search pseudo code template
while (left <= right) {
  mid = (left + right) / 2;
  if (array[mid] === target) return result;
  else if (array[mid] < target) left = mid + 1;
  else right = mid - 1;
}

704. Binary search (easy)

Method 1: recursion
  • Idea: first find the middle position and judge whether it is the target value to be searched. If yes, return. If not, judge the size of the target value and the middle element, and then continue to search recursively in the left and right subtrees
  • Complexity: time complexity O(logn), space complexity O(logn), recursive stack size

js:

var search = function (nums, target) {
    return search_interval(nums, target, 0, nums.length - 1)
};

function search_interval(nums, target, left, right) {
    if (left > right) {
        return -1
    }
    let mid = left + Math.floor((right - left) / 2);
    if (nums[mid] === target) {//Determine the target value and the size of the intermediate element
        return mid
    } else if (nums[mid] < target) {//Recursive search for target element
        return search_interval(nums, target, mid + 1, right)
    } else {
        return search_interval(nums, target, left, mid - 1)
    }
}

java:

class Solution {
    public int search(int[] nums, int target) {
        return search_interval(nums, 0, nums.length - 1, target);
    }
    private int search_interval(int[] nums, int l, int r, int target) {
        if (l > r) {
            return -1;
        }
        int mid = l + (r - l) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] > target) {
            return search_interval(nums, l, mid - 1, target);
        } else {
            return search_interval(nums, mid + 1, r, target);
        }
    }
}
Method 2: non recursive
  • Idea: define the left and right pointers, compare the size of the target element and the middle element, and then continuously narrow the range of the left and right pointers to continue to find the target element
  • Complexity: time complexity O(logn), space complexity O(1)

js:

var search = function (nums, target) {
    let left = 0,
        right = nums.length - 1;
    while (left <= right) {
        let mid = Math.floor((left + right) / 2);
        if (nums[mid] === target) {
            return mid;
        } else if (target < nums[mid]) {//Compare the size of the target and intermediate elements, and then keep narrowing the left and rihgt pointers
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return -1;
};

java:

class Solution {
    public int search(int[] nums, int target) {
        int low = 0, high = nums.length - 1;
        while (low <= high) {
            int mid = (high - low) / 2 + low;
            int num = nums[mid];
            if (num == target) {
                return mid;
            } else if (num > target) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }
}

35. Search insertion position (easy)

Time complexity O(logn), space complexity O(1)

js:

var searchInsert = function(nums, target) {
    const n = nums.length;
    let left = 0, right = n - 1, ans = n;
    while (left <= right) {
        let mid = ((right - left) >> 1) + left;
        if (target <= nums[mid]) {
            ans = mid;
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return ans;
};

java:

class Solution {
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;
        int left = 0, right = n - 1, ans = n;
        while (left <= right) {
            int mid = ((right - left) >> 1) + left;
            if (target <= nums[mid]) {
                ans = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
}

69. Sqrt(x)(easy)

Method 1: dichotomy
  • Idea: from 0-x continuous dichotomy to
  • Complexity analysis: time complexity O(logx), that is, the number of times required for binary search. Space complexity O(1)

js:

var mySqrt = function (x) {
    let left = 0
    let right = x
    while (left <= right) {
        let mid = left + ((right - left) >> 1)//The middle position index x > > 1 is divided by 2 and rounded to narrow the traversal range
        if (mid * mid <= x) {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return right
};

Java:

class Solution {
    public int mySqrt(int x) {
        int left = 0, right = x, ans = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if ((long) mid * mid <= x) {
                ans = mid;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }
}

Method 2: Newton iteration
  • Idea: r = (R + X / R) / 2
  • Complexity analysis: time complexity O(logx). Space complexity O(1)

js:

var mySqrt = function(x) {
    let r = x

    while (r ** 2 > x) r = ((r + x / r) / 2) | 0//Rounding

    return r
};

Java:

class Solution {
    public int mySqrt(int x) {
        if (x <= 1) {
            return x;
        }
        double l = 0;
        double r = 1;
        while (l != r) {
            l = r;
            r = (r + x / r) / 2;
        }
        return (int)r;
    }
}

300. Longest increasing subsequence (medium)

The animation is too large. Click to view it

Method 1. Dynamic programming
  • Idea: dp[i] indicates the length of the longest ascending subsequence that selects num [i] and ends with num [i]. Two layer cycle, I: 1 ~ num.length,

    j: 0 ~ I, if num [i] > num [J], it forms a rising pair, dp[i] selects the larger one from dp[i], dp[j]+1, and finally returns the total maximum number of dp array

  • Complexity analysis: time complexity O(n^2), n is the length of nums, the outer layer needs to be cycled n times, and dp[i] needs to be from dp[0~i-1], so the complexity is O(n^2). The space complexity is O(n), that is, the space of dp array

js:

const lengthOfLIS = (nums) => {
    let dp = Array(nums.length).fill(1);
    let result = 1;

    for(let i = 1; i < nums.length; i++) {
        for(let j = 0; j < i; j++) {
            if(nums[i] > nums[j]) {//When num [i] > num [J], a rising pair is formed
                dp[i] = Math.max(dp[i], dp[j]+1);//Update dp[i]
            }
        }
        result = Math.max(result, dp[i]);//Update results
    }

    return result;
};

Java:

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;
        int result = 1;
        for (int i = 1; i < nums.length; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}

Method 2. Binary search + greed
  • Idea: prepare the tail array to store the longest ascending subsequence. The core idea is to put the smaller numbers forward, so that more numbers can be added to the tails array. Add the numbers in nums to the tail continuously. When the element in nums is larger than the last one in the tail, you can rest assured to push into the tail. Otherwise, binary search will be carried out to find the appropriate position for the smaller binary, so that more numbers will form a rising sub sequence with this number
  • Complexity: time complexity O(nlogn). N is the length of nums. logn is required for each binary search, so the overall complexity is O(nlogn). The space complexity is O (n), the overhead of tail array

js:

var lengthOfLIS = function (nums) {
    let n = nums.length;
    if (n <= 1) {
        return n;
    }
    let tail = [nums[0]];//Store the longest ascending subsequence array
    for (let i = 0; i < n; i++) {
        if (nums[i] > tail[tail.length - 1]) {//When the element in num is larger than the last one in tail, you can safely push into tail
            tail.push(nums[i]);
        } else {//Otherwise, perform binary search
            let left = 0;
            let right = tail.length - 1;
            while (left < right) {
                let mid = (left + right) >> 1;
                if (tail[mid] < nums[i]) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            tail[left] = nums[i];//Put num [i] in the right place, and the preceding elements are smaller than num [i]
        }
    }
    return tail.length;
};

Java:

class Solution {
    private int peek(){
        return tail.get(tail.size() - 1);
    }
    private ArrayList<Integer> tail;
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        int last = nums[0];
        int max = 1;
        
        tail = new ArrayList<Integer>(len);
        tail.add(nums[0]);

        for(int i = 1 ; i < len ; ++i){
            if( nums[i] > peek() )
                tail.add(nums[i]);
            else{
                int l = 0, r = tail.size() - 1, mid = 0;
                while(l <= r){
                    mid = l + ((r - l) >> 1);
                    if( nums[i] > tail.get(mid) )
                         l = mid + 1;

                    else if( nums[i] < tail.get(mid) )
                         r = mid - 1;
                    else {l = mid; break;}
                 }
                 tail.set(l, nums[i]);

            }
        }
        
        return tail.size();
    }
}

4. Find the median of two positively ordered arrays (hard)

Method 1. Binary search

  • Idea: after array merging, the sorting complexity is O((m+n) log(m+n)), which does not meet the meaning of the topic. The topic requires O(log (m+n)). As soon as we see the complexity of logn, we associate it with dichotomy. For an array with a smaller bisection length, find the bisection position of the array. Find the bisection position of another array according to the bisection position and the total length of the two arrays. Compare whether the four numbers of the two positions meet the cross less than or equal to. If they do not meet the requirement of continuing bisection, find out if they are satisfied
  • Complexity: time complexity O (log (min (m, n)), m and N are the lengths of nums1 and nums2 respectively. The length of each binary loop will be reduced by half, as long as the binary array is relatively short. Space complexity O(1)

Js:

var findMedianSortedArrays = (nums1, nums2) => {
    let len1 = nums1.length, len2 = nums2.length
    if (len1 > len2) return findMedianSortedArrays(nums2, nums1)//For the smaller binary of nums1 and nums2
    let len = len1 + len2//Total length
    let start = 0, end = len1 //Start and end positions for dichotomy
    let partLen1, partLen2

    while (start <= end) {
        partLen1 = (start + end) >> 1//nums1 binary position
        partLen2 = ((len + 1) >> 1) - partLen1//nums2 binary position

        //L1: left position after nums1 two points, L2, right position after nums1 two points
        //R1: position on the left after nums2 score, R2: position on the right after nums2 score

        //If there are no characters on the left, it is defined as - Infinity, so that all numbers are greater than it. Otherwise, it is one on the left of the binary position of nums1
        let L1 = partLen1 === 0 ? -Infinity : nums1[partLen1 - 1]
        //If there are no characters on the left, it is defined as - Infinity, so that all numbers are greater than it. Otherwise, it is one on the left of nums2 binary position
        let L2 = partLen2 === 0 ? -Infinity : nums2[partLen2 - 1]
        //If there is no character on the right, it is defined as Infinity, so that all numbers are less than it, otherwise it is the position of nums1 dichotomy
        let R1 = partLen1 === len1 ? Infinity : nums1[partLen1]
        //If there is no character on the right, it is defined as Infinity, so that all numbers are less than it, otherwise it is the position of nums1 dichotomy
        let R2 = partLen2 === len2 ? Infinity : nums2[partLen2]

        if (L1 > R2) {//Non conforming crossing is less than or equal to continue two points
            end = partLen1 - 1
        } else if (L2 > R1) {//Non conforming crossing is less than or equal to continue two points
            start = partLen1 + 1
        } else { // L1 < = R2 & & L2 < = R1 meets the cross less than or equal to
            return len % 2 === 0 ?
                (Math.max(L1, L2) + Math.min(R1, R2)) / 2 : //If the length is an even number, return as half of the sum of the larger one on the left and the smaller one on the right
                Math.max(L1, L2)	//If the length is an odd number, return it as the larger one on the left
        }
    }
}

Java:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            return findMedianSortedArrays(nums2, nums1);
        }

        int m = nums1.length;
        int n = nums2.length;
        int left = 0, right = m;
        int median1 = 0, median2 = 0;

        while (left <= right) {
            int i = (left + right) / 2;
            int j = (m + n + 1) / 2 - i;
      
            int nums_im1 = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]);
            int nums_i = (i == m ? Integer.MAX_VALUE : nums1[i]);
            int nums_jm1 = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]);
            int nums_j = (j == n ? Integer.MAX_VALUE : nums2[j]);

            if (nums_im1 <= nums_j) {
                median1 = Math.max(nums_im1, nums_jm1);
                median2 = Math.min(nums_i, nums_j);
                left = i + 1;
            } else {
                right = i - 1;
            }
        }

        return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
    }
}

162. Look for peaks(medium)

  • Train of thought: both num [- 1] and num [n] are - ∞. Therefore, as long as there are two adjacent elements in the array that are increasing, the peak can be found along it
  • Complexity: time complexity O(logn), space complexity O(1)

js:

const findPeakElement = nums => {
    let [left, right] = [0, nums.length - 1];
    while (left < right) {
        const mid = left + (right - left) >> 1;//Keep looking for rising element pairs
        if (nums[mid] > nums[mid + 1]) {
            right = mid;//decline
        } else {
            left = mid + 1;//rise
        }
    }
    return left;
};

java:

class Solution {
    public int findPeakElement(int[] nums) {
        int left = 0, right = nums.length - 1;
        for (; left < right; ) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[mid + 1]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

74. Search two-dimensional matrix (medium)

  • Idea: the matrix satisfies the property of increasing from left to right and from top to bottom, so the two-dimensional array can be regarded as a one-dimensional increasing array, and then binary search can be carried out. Only one coordinate needs to be converted into two-dimensional coordinates.
  • Complexity: time complexity O(log(mn)), m, n are the rows and columns of the matrix. In space complexity O(1)

js:

var searchMatrix = function(matrix, target) {
    const m = matrix.length, n = matrix[0].length;
    let low = 0, high = m * n - 1;
    while (low <= high) {
        const mid = Math.floor((high - low) / 2) + low;
        const x = matrix[Math.floor(mid / n)][mid % n];//Convert one-dimensional coordinates to two-dimensional coordinates
        if (x < target) {
            low = mid + 1;
        } else if (x > target) {
            high = mid - 1;
        } else {
            return true;
        }
    }
    return false;
};

java:

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length, n = matrix[0].length;
        int low = 0, high = m * n - 1;
        while (low <= high) {
            int mid = (high - low) / 2 + low;
            int x = matrix[mid / n][mid % n];
            if (x < target) {
                low = mid + 1;
            } else if (x > target) {
                high = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
}


34. Find the first and last position of an element in a sorted array (medium)

Method 1: first dichotomy, looking for the left and right boundaries

  • Idea: binary search, and then try to find the same element to the left and right
  • Complexity: time complexity O(n), space complexity O(1)

js:

//nums = [5,7,7,8,8,10], target = 8
var searchRange = function(nums, target) {
    let left = 0, right = nums.length - 1, mid;
    while (left <= right) {//Binary search target
        mid = (left + right) >> 1;
        if (nums[mid] === target) break;
        if (nums[mid] > target) right = mid - 1;
        else left = mid + 1;
    }
    if(left > right) return [-1, -1];
    let i = mid, j = mid;
    while(nums[i] === nums[i - 1]) i--;//Try to find the same element to the left
    while(nums[j] === nums[j + 1]) j++;//Try to find the same element to the right
    return [i, j];
};

java:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;
        if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
            return new int[]{leftIdx, rightIdx};
        } 
        return new int[]{-1, -1};
    }

    public int binarySearch(int[] nums, int target, boolean lower) {
        int left = 0, right = nums.length - 1, ans = nums.length;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
}

Method 2: modified dichotomy

  • Idea: transform the dichotomy and find the starting and ending positions of the target value
  • Complexity: time complexity O(logn), space complexity O(1)

js:

//nums = [5,7,7,8,8,10], target = 8
const binarySearch = (nums, target, lower) => {
    let left = 0, right = nums.length - 1, ans = nums.length;
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        if (nums[mid] > target || (lower && nums[mid] >= target)) {
            right = mid - 1;
            ans = mid;
        } else {
            left = mid + 1;
        }
    }
    return ans;
}

var searchRange = function(nums, target) {
    let ans = [-1, -1];
    const leftIdx = binarySearch(nums, target, true);
    const rightIdx = binarySearch(nums, target, false) - 1;
    if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] === target && nums[rightIdx] === target) {
        ans = [leftIdx, rightIdx];
    } 
    return ans;
};

java:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;
        if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
            return new int[]{leftIdx, rightIdx};
        } 
        return new int[]{-1, -1};
    }

    public int binarySearch(int[] nums, int target, boolean lower) {
        int left = 0, right = nums.length - 1, ans = nums.length;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
}

153. Find the minimum value in the rotation sort array (medium)

  • Idea: the low and high pointers keep moving to the middle and dichotomy. The value of the current node is smaller than that of the high node, so that high is equal to pivot. When the current node ratio is greater than or equal to the high node, let low be equal to pivot+1, and the node that finally meets is the minimum value
  • Complexity: time complexity O(logn). Space complexity O(1)

js:

var findMin = function(nums) {
    let low = 0;
    let high = nums.length - 1;
    while (low < high) {
        const pivot = low + Math.floor((high - low) / 2);//Intermediate node
        if (nums[pivot] < nums[high]) {//The value of the current node is smaller than that of the high node. Let high be equal to pivot
            high = pivot;
        } else {
            low = pivot + 1;//When the current node ratio is greater than or equal to the high node, let low be equal to pivot+1
        }
    }
    return nums[low];//The last node to meet is the minimum
};

java:

class Solution {
    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            } else {
                low = pivot + 1;
            }
        }
        return nums[low];
    }
}

374. Guess the size of the number (easy)

  • Complexity: time complexity O(logn). Space complexity O(1)

js:

var guessNumber = function(n) {
    let left = 1, right = n;
    while (left < right) { 
        const mid = Math.floor(left + (right - left) / 2); 
        if (guess(mid) <= 0) {
            right = mid; //The update search interval is [left, mid]
        } else {
            left = mid + 1; //The update search interval is [mid+1, right]
        }
    }
    //left == right is the answer
    return left;
};

java:

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int left = 1, right = n;
        while (left < right) {
            int mid = left + (right - left) / 2; 
            if (guess(mid) <= 0) {
                right = mid;
            } else {
                left = mid + 1; 
            }
        }
        return left;
    }
}

Posted by Adamthenewbie on Tue, 23 Nov 2021 22:03:48 -0800