算法练习Day1 (Leetcode/Python)

数组专题

数组特点在内存地址中连续,不能单独删除,只能覆盖。

对于数组问题,注意是否有重复,是否有序。

方法上可以考虑二分法、双指针法、暴力解法等。

易错点是数组的边界范围,数组的index是从0开始。注意index的整数运算。

704 二分查找 (二分法)

Example:

Input: nums = [-1,0,3,5,9,12], target = 9
Output: 4
Explanation: 9 exists in nums and its index is 4

思路:

数组有序且无重复,可以考虑二分法

Solution:

方法一:二分法(recursive binary search implementation)

1)注意!新的搜索区间选择。我喜欢用“左闭右闭”的区间,即区间内最左或者最右的元素都可能是mid被判断到。每次算mid的时候用整除,这样相当于是向下取整,即floor运算,永远”四舍“。

E,g. [0,1,2,3,4,5]

mid=(0+5) // 2 = 2, 则取出第三个元素和target比较,

如果小于target,下一次应该从 (mid,end], 即 [mid+1,end]去搜索。因为mid这个元素已经判断过不是,下一次的搜索范围要跳过,记得对mid+1。

如果大于target,下一次应该从 [start,mid-1] 去搜索。同上,记得对mid-1。

2)注意!循环截止条件。

start>end

因为新的区间左右边界的值都是有可能被考虑的,所以在start==end的时候,也是可以再算一次mid来判断的,如果还不对,下一次mid+1或者mid-1就start>end了,也就该跳出循环了。

具体来说,每次算mid的时候用整除,这样相当于是向下取整,即floor运算,永远”四舍“。在start与end相邻的时候(start=end-1),start会作为mid被取到。此时如果start<target的话,新的start=mid+1,也就是start=start+1,就和end相等了,即下一个搜索区间start == end,再执行一次搜索操作。这一次start==end==mid。如果start>target的话,end=mid-1,也就是end = start-1,即 end<mid,符合递归终止条件,新的搜索范围不再有效,跳出循环,终止程序。

class Solution(object):
    def search(self, nums, target):
        return self.binsearch(nums, 0, len(nums)-1, target) 
    
    def binsearch(self, nums, start, end, target):
        # Step1: 当前区间是否合理,判断是否满足跳出循环的条件
        if start > end:
            return -1 # 按照题目要求,return -1  

        # Step2: 当前区间合理,判断中值,看是否要继续二分。
        mid = (start + end) // 2 # 注意整数除法用 //, 浮点型除法用 / 
        # Step2.1:找到啦,return返回。
        if nums[mid] == target:
            return mid
        # Step2.2:要找新的区间继续搜索
        elif nums[mid] > target:
            return self.binsearch(nums, start, mid-1, target)
        else:
            return self.binsearch(nums, mid+1, end, target) # return 不要忘记啊

时间复杂度: O(logn)。因为每一步因为每一步算法都会将搜索空间分成两半。

空间复杂度: 由于递归调用堆栈,O(logn)。否则的话是 O(1)。

27 移除元素 (双指针法)

Example: 

Input: nums = [0,1,2,2,3,0,4,2], val = 2
Output: 5, nums = [0,1,4,0,3,_,_,_]

分析:

数组在存储地址上是连续的,删除掉数组的一个元素,后面的元素都需要递补。

Solution:

方法一:暴力解法

两层循环嵌套,一个for循环遍历数组,一个for循环把被删元素之后的元素更新。

算法复杂度:O(n^2)。两次for循环,最坏的情况是要移动元素1+2+3+...+n = n*(n+1)/2 次。

方法二:快慢指针

快指针遍历每个值,慢指针记录有效值。有效值记下来;没用的无效值,覆盖掉就好。

快慢指针是同向双指针,也可以用异向双指针,右侧指针始终指向一个有效值,但凡左指针指向了无效值,就把右侧的指针指向的值替换给左指针对应的位置。

    def removeElement(self, nums: List[int], val: int) -> int:
        cnt = 0 # 用cnt来记录有效的位数
        for i in range(len(nums)):
            if nums[i] != val: #当前快指针所指向的数据有效,记录下来!
                nums[cnt] = nums[i]
                cnt += 1
        return cnt 

时间复杂度:O(n),每一个元素被处理常数项次数。

空间复杂度:额外O(1),存储cnt,原地修改原数组。

#############################################################################

74. Search a 2D Matrix

You are given an m x n integer matrix matrix with the following two properties:

  • Each row is sorted in non-decreasing order.
  • The first integer of each row is greater than the last integer of the previous row.

Given an integer target, return true if target is in matrix or false otherwise.

You must write a solution in O(log(m * n)) time complexity.

思路:此题类似,只不过是从2Dmatrix中搜索,重复两遍二分法罢了。先按照第一列的值找到合适的行,再在那行按列搜索。关键点在于行搜索完后,如果第一列就已经找到target值,自然return True,如果没有的话,就是不满足while start <= end的条件,也就是end<start。这时开启第二个二分法,要对end行进行搜索,而不是start。仔细想想看,是因为这时候已经end<start了,其实意思是要在end到start的区间继续搜索(不过这对第一个二分法不再成立了,因为两个数之间没有更多数可以搜索了),这也就是第二个二分法需要搜索的区间,也就是end所在的行。

这道题还有一个点是,求2D list的size,要用len(matrix), len(matrix[0])。list这个object是没有shape() attribute的。

另外就是,时隔一个月再写这道题,一不小心把end的起始直接写成了m而不是m-1,index的问题虽小,但实在要注意。

class Solution(object):
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        m,n = len(matrix), len(matrix[0])
        start = 0 
        end = m-1
        if start == end:
            pass
        else:
            while start <= end:
                mid = (start+end)//2
                mid_val = matrix[mid][0]
                if mid_val == target:
                    return True
                elif mid_val < target:
                    start = mid + 1
                else: 
                    end = mid - 1
        start_row = end 
        #print(start)
        start, end = 0, n-1
        
        if start == end:
            if target == matrix[start_row][0]:
                return True
            else:
                return False


        while start <= end:
            mid = (start+end)//2
            mid_val = matrix[start_row][mid]
            if mid_val == target:
                return True
            elif mid_val < target:
                start = mid + 1
            else: 
                end = mid - 1
        return False

2055. Plates Between Candles

There is a long table with a line of plates and candles arranged on top of it. You are given a 0-indexed string s consisting of characters '*' and '|' only, where a '*' represents a plate and a '|' represents a candle.

You are also given a 0-indexed 2D integer array queries where queries[i] = [lefti, righti] denotes the substring s[lefti...righti] (inclusive). For each query, you need to find the number of plates between candles that are in the substring. A plate is considered between candles if there is at least one candle to its left and at least one candle to its right in the substring.

  • For example, s = "||**||**|*", and a query [3, 8] denotes the substring "*||**|". The number of plates between candles in this substring is 2, each of the two plates has at least one candle in the substring to its left and right.

Return an integer array answer where answer[i] is the answer to the ith query.

思路:用二分法找出要求的query中的蜡烛之间夹着的盘子。把蜡烛的index记录下来,然后在这个蜡烛index的list中找出query中的index,如果找不到(也就是query中的index其实指向了盘子),那就找到相邻的index(二分法时right和left每次只+/-1最后肯定会指向一个和target index相邻的蜡烛index),然后求这两个最后找出来的index之间的距离,然后减去这两个index在蜡烛index的list中的距离,得到的就是所求盘子的个数。

注意,这道题目里不是直接用二分法去找原list的值,而是把原list的值转成index,然后再用二分法去找。

如果找不到蜡烛的index而要用相邻的,这里是return left还是right最好举例子想清楚。

class Solution:
    def just_larger_than(self, candle_idx, target):
        left, right = 0, len(candle_idx)-1
        while left<=right:
            m = (left+right)//2
            if candle_idx[m] == target:
                return m
            if candle_idx[m] > target:
                right = m-1 
            else:
                left = m+1
        return left

    def just_smaller_than(self, candle_idx, target):
        left, right = 0, len(candle_idx)-1
        while left<=right:
            m = (left+right)//2
            if candle_idx[m] == target:
                return m
            if candle_idx[m] > target:
                right = m-1
            else:
                left = m+1
        return right


    def platesBetweenCandles(self, s, queries):
        #brute force
        n = len(s)
        candles = []
        out = []
        for i in range(n):
            if s[i]=='|':
                candles.append(i)    
        
        for left,right in queries:
            #find the index of the first candle of the given interval, in the candles array
            firstIndx = self.just_larger_than(candles,left)
            
            #find the index of the last candle of the given interval, in the candles array
            lastIndx = self.just_smaller_than(candles,right)
            
            #if no candles in the given interval 
            if firstIndx>=len(candles) or candles[lastIndx]>right or candles[firstIndx]<left:
                out.append(0)
                continue

            #number of candles
            num_candles = lastIndx-firstIndx-1 
            # print("num candles in range",num_candles)
            
            #number of items between first and last candle - this interval would contain both plates and candles
            items = candles[lastIndx]-candles[firstIndx]-1
            
            #Remove the candles from the above interval to get the plates
            num_plates = max(0,items-num_candles)
            out.append(num_plates)
        
        return out

Reference: 

代码随想录

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值