代码随想录算法训练营Day1 | 704. 二分查找,27. 移除元素

数组理论基础

文章链接

数组(array)是存放在连续内存空间上的相同数据的集合。

关于数组需要注意的点:

  • 数组的下标都是从0开始 (start from index 0 not 1)
  • 数组内存空间的地址是连续的 => 添加/删除元素时难免需要移动其他元素的地址
  • C++: vector != array, vector的底层实现是array, 但严格来说vector是容器不是数组
  • 数组元素不能删除只能覆盖
  • 二维数组的内存管理根据编译语言各不相同,C++是连续的 (int数组每节有4哥字节)

704. 二分查找 Binary Search

题目链接  |  文章链接  |  视频链接  |

题目要求从一个被从小到大排列好且唯一的数组里找到目标(target) with O(log N) runtime complexity.

找到目标返回目标index,目标不存在则返回-1

二分法需要三个点:左边界,中间点,右边界,通过这三个点与目标进行对比从而把数组分段,锁定搜索区域,起到提高效率的作用。Brute force搜索此题会需要 O(N)

Python解法 - While Loop

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left = 0
        right = len(nums) - 1

        while (left <= right):
            mid = int(left + (right - left) / 2)
            if nums[mid] == target:
                return mid
            elif nums[mid] > target:
                right = mid - 1
            else:
                left = mid + 1

        return -1

Python解法 - Recursion

class Solution(object):
    def BFS(self, nums, left, right, target):
        
        if (left > right):
            return -1

        mid = int(left + (right - left) / 2)
        if nums[mid] == target:
            return mid
            
        if nums[mid] > target:
            return self.BFS(nums, left, mid - 1, target)
        else:
            return self.BFS(nums, mid+1, right, target)

        return -1

    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """

        return self.BFS(nums, 0, len(nums)-1, target)

C++解法 - While Loop

class Solution {
public:
    int search(vector& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target){
                return mid;
            } else if (nums[mid] > target){
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        return -1;
    }
};

C++解法 - Recursion

class Solution {
public:
    int BFS(vector& nums, int left, int right, int target){
        if (left > right){
            return -1;
        }
        int mid = left + (right - left) / 2;
        if (nums[mid] == target){
            return mid;
        } else if (nums[mid] > target){
            return BFS(nums, left, mid - 1, target);
        } else {
            return BFS(nums, mid + 1, right, target);
        }
    }

    int search(vector& nums, int target) {
        return BFS(nums, 0, nums.size()-1, target);
    }
};

C解法 - While Loop

int search(int* nums, int numsSize, int target){
    int left = 0;
    int right = numsSize - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target){
            return mid;
        } else if (nums[mid] > target){
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }

    return -1;
}

C解法 - Recursion

int BFS(int* nums, int left, int right, int target){
    if (left > right){
        return -1;
    }
    int mid = left + (right - left) / 2;
    if (nums[mid] == target){
        return mid;
    } else if (nums[mid] > target){
        return BFS(nums, left, mid - 1, target);
    } else {
        return BFS(nums, mid + 1, right, target);
    }
}


int search(int* nums, int numsSize, int target){
    return BFS(nums, 0, numsSize-1, target);
}

需要注意的点:

  • upper bound 的定义,因为我的编程习惯是index都得是valid的,所以我会选择使用right = size of the array - 1,如果直接等于size,虽然这题没有用但可能会遇到需要使用nums[right]的情况,所以我会make sure right index一定是valid的
  • 当确定right = size(nums) - 1后,while loop的condition就很清楚了,需要是left <= right,最直观的理解方法为corner case: nums = [5], target = 5。如果你选择使用left < right,你需要在while loop前首先确认在array size = 1的情况下,target是否存在在array中。否则程序并不会compare target with the array,直接return 1。
  • mid的定义方式需要是left + (right - left)/2 而不是单纯的(right - left)/2,因为当while loop开始后,left可能不再为0,致使通过(right - left)/2算出的mid不为真正的mid index,所以需要加上新的left,把mid shift到正确的位置。

27. 移除元素 Remove Element

题目链接  |  文章链接  |  视频链接  |

从一个数组中原地移除所有等于val的元素并返回移除后的数组长度k。

因为题目很明确说了新数组size k之后的元素是什么没有意义,是什么都无所谓,所以很快就想到了如何解答这道题,看了文章和视频解析才知道这种方法属于“双指针”,可谓是误打误撞完成了要求。

Python解法

class Solution(object):
    def removeElement(self, nums, val):
        """
        :type nums: List[int]
        :type val: int
        :rtype: int
        """
        k = 0
        for i in range(len(nums)):
            if nums[i] != val:
                nums[k] = nums[i]
                k+=1
        return k

C++解法

class Solution {
public:
    int removeElement(vector& nums, int val) {
        int k = 0;
        for (int i = 0; i < nums.size(); i++){
            if (nums[i] != val){
                nums[k] = nums[i];
                k++;
            }
        }
        return k;
    }
};

C解法

int removeElement(int* nums, int numsSize, int val){
    int k = 0;
    for (int i = 0; i < numsSize; i++){
        if (nums[i] != val){
            nums[k] = nums[i];
            k++;
        }
    }
    return k;
}

附加练习题

35. 搜索插入位置 Search Insert Position

题目链接

排序好的且唯一的数组和目标,如果找到目标返回目标index,否则返回目标应该被插入的index位置,runtime必须符合O(log N)。

这题与704其实基本相同,只不过需要在找不到目标时返回插入目标所需要的index位置,回想我们的代码,while loop condition (left <= right),在最后一次进入循环时:

  • left == right

也就是说:

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

进入if判断,如果此时target比nums[mid]大,left最终会为mid+1,target确实应该在这里插入

如果此时target比nums[mid]小,right = mid - 1 = left - 1, target则应该在此时的left位置插入,因为在这次循环中我们发现nums[left] == nums[mid] > target,所以我们的function应该return left,而不是right (此前的循环中判断过了nums[left - 1] < target)。

  • 最终:nums[left - 1] < target < nums[left],所以应该在left位置插入target

Python解法

class Solution(object):
    def searchInsert(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left = 0
        right = len(nums) - 1

        while left <= right :
            mid = int(left + (right - left)/2)
            if nums[mid] == target:
                return mid
            elif nums[mid] > target:
                right = mid - 1
            else:
                left = mid + 1

        return left

34. 在排序数组中查找元素的第一个和最后一个位置 Find First and Last Position of Element in Sorted Array

题目链接  |  文章链接

一个按照非递减顺序排列的整数数组(并没有说是否唯一)和一个目标,找到目标在该数组开始与结束位置,若没有找到则返回 [-1, 1],runtime必须符合O(log N)。

写了一个O(N)的解法,没想到testcase过了,看来leetcode的testcase也不是很精准,也可能是我对Big O概念还是不熟悉,以下是我的O(N)解法:

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        start = 0
        count = 0
        for i in range(len(nums)):
            if (nums[i] == target):
                if start == 0 and count == 0:
                    start = i
                count += 1

        end = start + count - 1
        if count != 0:
            return [start, end]  
        else: 
            return [-1, -1]

这题的关键点在于元素非唯一同时要求O(logN)的运算速度,所以需要使用二分法进行解答,但需要一定的改写。

我们需要两个点:

  • 目标所在最左的起点
  • 目标所在最右的终点

二分法找到mid后进行左右分散

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        left = 0
        right = len(nums) - 1

        while (left <= right):
            mid = int(left + (right - left) / 2)
            if nums[mid] == target:
                start = end = mid
                while start >= 0  and nums[start] == target:
                    start-=1
                while end <= len(nums)-1 and nums[end] == target:
                    end+=1
                return [start+1, end-1]
            elif nums[mid] > target:
                right = mid - 1
            else:
                left = mid + 1

        return [-1, -1]

左右边界二分法

class Solution(object):

    def lower_bound(self, nums, target):
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = int((left + right) / 2)
            if nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return left

    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        start = self.lower_bound(nums, target)
        if start == len(nums) or nums[start] != target:
            return [-1, -1]
        end = self.lower_bound(nums, target + 1) - 1
        return [start, end]
        

写到这里才发现在Python中可以使用mid = (left + right) // 2,因为//与/是有不同的:

  • “在Python中“/”表示浮点数除法,返回浮点结果,也就是结果为浮点数,而“//”在Python中表示整数除法,返回不大于结果的一个最大的整数,意思就是除法结果向下取整。” —— 百度知道

你可能感兴趣的:(代码随想录刷题训练营,算法,数据结构)