junhong7 / Algorithm

0 stars 0 forks source link

Binary Search #3

Open junhong7 opened 4 years ago

junhong7 commented 4 years ago

主要题型

给定数组,寻找某个数第一次出现/第n次出现/最后一次出现的下标

时间复杂度

Recursion or While loop?

面试中是否使用 Recursion 的几个判断条件

  1. 面试官是否要求了不使用 Recursion (如果你不确定,就向面试官询问)
  2. 不用 Recursion 是否会造成实现变得很复杂
  3. Recursion 的深度是否会很深
  4. 题目的考点是 Recursion vs Non-Recursion 还是就是考你是否会Recursion?

递归的危险:StackOverflow,内存消耗极大 记住:不要自己下判断,要跟面试官讨论!

Binary Search 的常见痛点

  1. 循环结束条件到底是哪个?
    1. start <= end
    2. start < end
    3. start + 1 < end
  2. 指针变化到底是哪个?
    1. start = mid
    2. start = mid + 1
    3. start = mid - 1

mid = start + (end - start)/2 相较于 mid = (start + end)/2的优势在于,如果start和end过大,有可能造成越界while(start < end) 的结束条件是start == end, 而start + 1 < end则为两个指针相邻时结束,可以有效避免死循环。

while(start < end):
    mid = int(start + (end - start)/2)
    if a[mid] == target:
        start = mid # 因为要找最后一个出现,若start=mid+1,当mid为最后一次时,就会漏检
    elif a[mid] < target:
        start = mid # or start = mid + 1
    else:
        end = mid # or end = mid - 1

因为mid偏左,如果a[mid] == target, start = mid时,有可能一轮检测下来,start不会发生移动 例如寻找[1,1]中最后一次1出现的位置时,mid = start = 0start停留在原地 解决思路就是修改循环结束的判断条件,将start < end改为start + 1< end

关键点总结

  1. start + 1 < end避免死循环
  2. mid = start + (end - start)/2避免内存越界
  3. <,==,>三种情况分类讨论,即使有可能存在<=,>=为一类处理模式,但是分开讨论可以避免细节错误
  4. 循环结束时,判断返回值为start还是end

BS直观题目:寻找满足条件的的第一个/最后一个位置

1. 寻找ooooo_ox_xxxxxx

BS非直观题目(half half):无法轻易找到ooxx条件进行二分判断

二分的本质思考:利用一次判断,缩小一半搜索的内容

junhong7 commented 4 years ago

LC.981

有助于理解,mid的取值与逼近方向的联系

具体判断寻找oooxxx中最后一次出现的o还是第一次出现的x

junhong7 commented 4 years ago

LC.240 Search a 2D Matrix II

Solution1: BS in each row and col

def searchMatrix(self, matrix, target): """ :type matrix: List[List[int]] :type target: int :rtype: bool """ if not matrix: return False

start from the top left[0][0], iterate through [x][x] until reach the bottom O(min(n,m))

# each time, from [start][start], we use BS to search inside the row and col O(log(len-start))
# the whole time complexity is O(logn)+O(log(n-1)) + ... + O(log1)
# T = O(log(n*(n-1)*(n-2)**(1))) = O(log(n!)) <= O(nlogn) = O(logn^n)
for i in range(min(len(matrix), len(matrix[0]))):
    horizonResult = self.search(matrix, target, i, True)
    verticalResult = self.search(matrix, target, i, False)
    if horizonResult or verticalResult:
        return True
return False
#### Solution2: Divide and Conquer
- DC涉及到递归**Recursion**,所以当矩阵规模很大时,**容易内存溢出**,所以尽量避免使用Recursion
- 因为矩阵有序,所以如果确定了其中一个值,那么左上角的矩阵元素都比他小,右下角的有比他大
- 利用这个特性,确定中间一列为`mid = (left + right)//2`,之后遍历`row from top to bottom`
- 当`matrix[row−1][mid] < target < matrix[row][mid]`的时候,即可排除左上角和右下角的两个矩阵
- 在左下和右上两个子矩阵内继续搜索,直到`subMatrix[row][mid] == target`,否则矩阵内不存在`target`
- **Time complexity : O(nlgn)**
```Python
def searchMatrix(self, matrix, target):
    # an empty matrix obviously does not contain `target`
    if not matrix:
        return False

    def search_rec(left, up, right, down):
        # this submatrix has no height or no width.
        if left > right or up > down:
            return False
        # `target` is already larger than the largest element or smaller
        # than the smallest element in this submatrix.
        elif target < matrix[up][left] or target > matrix[down][right]:
            return False

        mid = left + (right-left)//2

        # Locate `row` such that matrix[row-1][mid] < target < matrix[row][mid]
        row = up
        while row <= down and matrix[row][mid] <= target:
            if matrix[row][mid] == target:
                return True
            row += 1

        return search_rec(left, row, mid-1, down) or search_rec(mid+1, up, right, row-1)

    return search_rec(0, 0, len(matrix[0])-1, len(matrix)-1)

Solution3: Search Space Reduction(best soluion)