美文网首页
二、数组及LeetCode典型题目分析

二、数组及LeetCode典型题目分析

作者: 奔向算法的喵 | 来源:发表于2019-01-31 17:49 被阅读0次

    一、线性表、数组基础

    很多问题就是在一个数组中解决的
    栈、队列、堆的底层都是基于数组,封装的数据结构,后面我们会遇到很灵活的算法问题。本质就是在数组里面做操作。
    下面我们来讲一个简单算法,二分查找法:

    class Solution(object):
        def BinarySearch(self, arr, target):
            # [l, r]的区间采用二分查找法
            l, r = 0, len(arr) -1
            while l <= r:
                mid = l+(r-l)//2
                if target == arr[mid]:
                    return mid
                elif target > arr[mid]:
                    l = mid +1
                else:
                    r = mid -1
            return -1    
    

    由于我们选用了[l, r]这个区间,那么进行循环的时候,控制条件l是可以和r相等的,当target<arr[mid]的时候,说明目标在区间的左边,r就应该等于mid-1(因为是开区间,所以是mid-1),同理我们可以知道l的取值就是mid+1了。
    mid = l+(r-l)//2的写法是为了防止数值的溢出,要是写成(l+r)//2的形式,那么当r和l都非常的大的时候,就可能出现数值溢出的问题。
    时间复杂度:O(logn)
    空间复杂度:O(1)

    二、一些思路及LeetCode代表题

    1、移除元素

    LeetCode 283题:移动零

    给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
    示例:
    输入: [0,1,0,3,12]
    输出: [1,3,12,0,0]
    说明:
    必须在原数组上操作,不能拷贝额外的数组。
    尽量减少操作次数。

    思路一:三个for循环解决问题。用一个格外的数组来存取nums中的非0元素,然后一一赋值到nums中,nums剩下的位置全部赋予0就行了。
    时间复杂度:O(n)
    空间复杂度:O(n)

    class Solution:
        def moveZeroes(self, nums):
            """
            :type nums: List[int]
            :rtype: void Do not return anything, modify nums in-place instead.
            """
            nonZeros= []
            for i in nums:
                if i:
                    nonZeros.append(i)
            for i in range(len(nonZeros)):
                nums[i] = nonZeros[i]
            
            for i in range(len(nonZeros),len(nums)):
                nums[i]=0          
    

    这种思路的话,多用了一个数组,带来了空间复杂度O(n),下面我们就针对这一点进行一个优化。
    思路二:采用快慢指针的思想,用慢指针k来指向非零元素,用快指针来遍历该数组。循环完成之后,那么[0,...,k)中都是非0元素。我们后面需要做的就是将后面的元素赋值为0,这样就完成了这个思路
    时间复杂度:O(n)
    空间复杂度:O(1)

    class Solution:
        def moveZeroes(self, nums):
            """
            :type nums: List[int]
            :rtype: void Do not return anything, modify nums in-place instead.
            """
            k = 0
            for index,item in enumerate(nums):
                if item:
                    nums[k]=item
                    k+=1
            for i in range(k, len(nums)):
                nums[i]=0
    

    想到这里来了之后,我们就会思考,还能不能将这个算法变得更好呢?答案是yes。
    思路三:我们还是采用了快慢指针的操作,然后将非0元素和0元素进行一个互换,这样的话,就一个for循环就完成了整个操作。[0,...,k)中都是非0元素, [k,...,len(nums)]就是0元素了。
    时间复杂度:O(n)
    空间复杂度:O(1)

    class Solution:
        def moveZeroes(self, nums):
            """
            :type nums: List[int]
            :rtype: void Do not return anything, modify nums in-place instead.
            """
            k = 0
            for index, item in enumerate(nums):
                if item:
                    nums[k], nums[index] = nums[index],nums[k]
                    k+=1     
    

    到了这里之后,我们还能不能够进行进一步的优化呢?答案是yes。因为很多时候,我们的优化都是针对的特殊情况。

    class Solution:
        def moveZeroes(self, nums):
            """
            :type nums: List[int]
            :rtype: void Do not return anything, modify nums in-place instead.
            """
            k = 0
            for index, item in enumerate(nums):
                if item:
                    if index!=k:
                        nums[k], nums[index] = nums[index],nums[k]
                        k+=1
                    else:
                        k+=1    
    

    下面还有一些类似于改思想的题目:
    LeetCode 26, 27, 80
    小伙伴们可以自己去尝试一下

    2、对撞指针法

    LeetCode 167 两数之和 II - 输入有序数组
    给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
    函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
    说明:
    返回的下标值(index1 和 index2)不是从零开始的。
    你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
    示例:
    输入: numbers = [2, 7, 11, 15], target = 9
    输出: [1,2]
    解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
    思路一:暴力解法,进行双层的遍历,然后时间复杂度是O(n^2),遍历i,j,检测i和j所对应的是不是target。要是一时没有想法,就能给出结果,这仍然是一个可行的解决,解决完,提交的话,LeetCode会告诉大家,这是会超时的。数据的规模为10000,运行起来就显得比较慢了,数据规模要是100000,就不能在我们容仍的时间内完成这个问题。因此我们必须要有更高效的解法。
    时间复杂度:O(n^2)
    空间复杂度:O(1)
    思路二:采用二分搜索法,第一层循环进行数组的遍历,针对每个元素,找剩下部分找target-element是不是存在,存在返回就行了。然后试了一下,是accept的。因为前面讲了二分查找法,大家思考一下,这个方法还是挺简单的。
    时间复杂度:O(nlogn)
    空间复杂度:O(1)

    class Solution:
        def twoSum(self, numbers, target):
            """
            :type numbers: List[int]
            :type target: int
            :rtype: List[int]
            """
            n = len(numbers)
            for index, item in enumerate(numbers):
                if index<n-1:
                    l, r = index+1, n-1
                    while l<=r:
                        mid = l+(r-l)//2
                        if numbers[mid]==target-item:
                            return [index+1, mid+1]
                        elif numbers[mid]>target-item:
                            r=mid-1
                        else:
                            l=mid+1
            return None        
    

    到这里之后,我们想一想还有没有更加高效的方法来解决这个问题呢?答案是yes的。下面就是用对撞指针的方式来解决这个问题
    思路三:因为我们的数组是排序过的,然后左边的小,右边的大,左边的开始记为i,右边的开始记为j,然后看nums[i]和nums[j]的大小,要是他们相等,那么正好i和j就是我们想找的,要是nums[i]+nums[j]<target,那么就说明了nums[i]是小了,那么i就需要向前进,要是nums[i]+nums[j]>target了,就说了nums[j]大了,那么j就需要向前面走了。知道了这个思路之后,我们下面就来进行一个实现:
    时间复杂度:O(n)
    空间复杂度:O(1)

    class Solution(object):
        def twoSum(self, numbers, target):
            """
            :type numbers: List[int]
            :type target: int
            :rtype: List[int]
            """
            l, r = 0, len(numbers)-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
    

    采用了对撞指针的题目还有:
    Leetcode 11, 125, 344, 345
    大家可以去尝试一下这些简单、中等的题目哈

    3、滑动窗口法

    Leetcode 209:长度最小的子数组
    给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
    示例:
    输入: s = 7, nums = [2,3,1,2,4,3]
    输出: 2
    解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
    进阶:
    如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。
    思路一:第一种思路就是暴力法,采用三次循环来达到目的,这样的解决方法的时间复杂度是就是O(n3),可以将这种方法优化到O(n2)去。
    思路二:采用滑动窗口法,
    时间复杂度:O(n)
    空间复杂度:O(1)

    class Solution:
        def minSubArrayLen(self, s, nums):
            """
            :type s: int
            :type nums: List[int]
            :rtype: int
            """
            sum = 0
            l,r=0,-1
            res=len(nums)+1
            
            while l<len(nums):
                if r+1<len(nums) and sum<s:
                    r+=1
                    sum+=nums[r]
                else:
                    sum-=nums[l]
                    l+=1
                if sum>=s:
                    res = min(res, r-l+1)
    
            if res==len(nums)+1:
                return 0;
    
            return res
    

    欢迎各位小伙伴批评指正哈。
    类似的题目如下:
    Leetcode 3, 76, 438

    持续更新中...
    数据结构与算法系列博客:
    一、数据结构与算法概述
    二、数组及LeetCode典型题目分析
    三、链表(Linked list)以及LeetCode题
    四、栈与队列(Stack and Queue
    五、树(Trees)
    六、递归与回溯算法
    七、动态规划
    八、排序与搜索
    九、哈希表
    参考资料:
    1、https://coding.imooc.com/class/82.html
    2、https://leetcode-cn.com/problemset/all/

    相关文章

      网友评论

          本文标题:二、数组及LeetCode典型题目分析

          本文链接:https://www.haomeiwen.com/subject/ulclsqtx.html