LeetCode刷题——数组(python语言)

LeetCode刷题——数组(python语言)

一、数组

1.1 数组的定义

数组是具有一定顺序关系的若干对象组成的集合,组成数组的对象称为数组元。其中向量对应一维数组,矩阵对应二维数组。

1.2 数组的存储特点

  1. 数组在内存中按照顺序连续存储
  2. 二维数组的分配按照行(C,C++,C#)来分配
  3. 数组名表示的是数组的首地址,是常量

二、刷题

2.1 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]

提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

2.1.1 解法1 暴力枚举
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        answer = []
        for i in range(len(nums)):#第一个值
            for j in range(i+1,len(nums)):#因为a+b==b+a 所以第二值来自第一个值之后
                if(nums[i]+nums[j]==target):#是否等于待查找的值
                    answer.append(i)
                    answer.append(j)
                    return answer#加入索引,返回

#2836 ms 15.3 MB
#优点:简单、易懂
#缺点:时间复杂度为O(n2)

2.1.2 解法二 更改数组的索引,值不变,查询每一个元素对应的值(target-num)是否存在。
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hashMap = {}#用字典来记录,值为索引,数组索引为值
        for i,num in enumerate(nums):#枚举,一次取索引和值
               if hashMap.get(target-num) is not None:#对应值是否存在
                      return [hashMap.get(target-num), i]#存在返回
               hashMap[num] = i#不存在存储

#36 ms 15.9 MB amazing!!
#优点:快,时间复杂度为O(n)
#缺点:以空间换时间,较难理解

2.2 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:
输入:nums = []
输出:[]

示例 3:
输入:nums = [0]
输出:[]

提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105

思路:整体的思路是遍历找到所有等于的零的三元组,然后去重。但是暴力枚举时间复杂度过高,所以先对数据排序,再定一个元素,其余之和为其负值。

2.2.1 解法一 双指针(left和right),先对数据排序
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        answer = []
        nums.sort()#对列表排序
        for i in range(len(nums)):
            target = -nums[i] #第一值的相反数,找后两个值的和位对应
            left = i + 1 #左指针
            right = len(nums) - 1 #右指针
            while(left<right): 
                sum = nums[left] + nums[right] #求和
                if(sum == target):
                    if([nums[i],nums[left],nums[right]] not in answer):
                        answer.append([nums[i],nums[left],nums[right]])#去重
                    left += 1
                    right -= 1
                elif(sum < target):#比预期值小,增大sum,左指针右移
                    left += 1
                else:#比预期值大,减小sum,右指针左移
                    right -= 1 
        return answer

#3236 ms 17.4 MB
#优点:时间复杂度为O(n2),相比于暴力枚举减少一级时间复杂度。
#缺点:速度较慢。没有考虑各种情况。

2.2.2 解法二 优化各种情况

1、对于数组为空或者小于3直接返回空。
2、遍历到正数时,由于数据排序,后面的两个相加也不会是0,直接返回
3、排序后相邻一样的数,不再匹配,第一个数直接跳过,第二个数多加1。这样也不会存在重复。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        
        n=len(nums)
        res=[]
        if(not nums or n<3):#数组长度小于3或者为空直接返回
            return []
        nums.sort()#数组排序
        res=[]#结果
        for i in range(n):
            if(nums[i]>0):#如果第一个数大于零,后两个数的大小不可能小于零
                return res
            if(i>0 and nums[i]==nums[i-1]):#相同的值,跳过
                continue
            L=i+1
            R=n-1
            while(L<R):
                if(nums[i]+nums[L]+nums[R]==0):
                    res.append([nums[i],nums[L],nums[R]])
                    while(L<R and nums[L]==nums[L+1]):#相同的值,跳过
                        L=L+1
                    while(L<R and nums[R]==nums[R-1]):#相同的值,跳过
                        R=R-1
                    L=L+1
                    R=R-1
                elif(nums[i]+nums[L]+nums[R]>0):
                    R=R-1
                else:
                    L=L+1
        return res

#640 ms 17.7 MB
#优点:速度快。
#缺点:考虑多种情况。

2.3 最接近的三数之和

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

示例 2:
输入:nums = [0,0,0], target = 1
输出:0

提示:
3 <= nums.length <= 1000
-1000 <= nums[i] <= 1000
-104 <= target <= 104

2.3.1 解法:采用双指针法,避免暴力枚举的三重循环,还是先对数组排序,看选择的是否接近目标值,然后调整大小。
class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        nums.sort()
        answer = nums[0] + nums[1] + nums[2]#至少三个元素的初始解
        for i in range(len(nums)):
            head = nums[i]#还是第一个元素
            left = i + 1#左指针
            right = len(nums) - 1#右指针
            while(left<right):
                sum = nums[left] + nums[right] + head#求和
                if(abs(sum - target) < abs(answer - target)):#比较误差
                    answer = sum#最优解更换
                if(sum > target):#比目标值大,右指针左移
                    right -= 1
                elif(sum < target):#比目标值小,左指针右移
                    left += 1
                else:
                    return answer
        return answer

#196 ms 15.3 MB
#优点:速度快
#缺点:不太直观,首先要懂得双指针的套路

2.4 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。

for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100

2.4.1 解法1 调用python的list函数remove,判断是否存在val,remove删除
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        while(val in nums):
            nums.remove(val)
        return len(nums)

#24 ms 14.9 MB
#优点:实现简单,速度还行
#没用算法思想:容易丢饭碗(doge)

2.4.2 解法2 双指针,我们把数组重新赋值。left代表新数组的索引,right遍历旧的数组,如果旧的数组出现不是删除的数,赋值给新数组
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left = 0#左指针代表新数组的索引
        for right in range(len(nums)):#遍历旧数组
            if(nums[right]!=val):#如果不是要删除的数
                nums[left] = nums[right]#赋值给新数组
                left += 1 #新数组加一
        return left

2.5删除有序数组中的重复项

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:
为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。

for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:
0 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按升序排列

2.5.1 解法1 因为有序数组,统计出连续相同的数字,然后一一删除
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        delete_num = []
        for i in range(len(nums)-1):
            if(nums[i]==nums[i+1]):
               delete_num.append(nums[i])#找到所有相同的数字的数,存储
        for i in delete_num:#根据名单一一删除
            nums.remove(i)
        return len(nums)

#1112 ms 15.8 MB
#优点:实现简单
#算法思想不强,速度较慢,容易丢饭碗(doge)

2.5.2 解法2 根据序号移动元素,因为是有序数组,所以设置序号index从0开始,i从1开始。不同的话错位赋值,值不变,如果错位值相同,continue。下次赋值将会把重复的后一位覆盖,最为核心的思想是index与i在没有重复的情况下永远错一位,重复的话i多加1,将会覆盖重复元素。
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        n = len(nums)
        index=0#第一个索引,左指针
        for i in range(1,n):#第二个索引,右指针
            if nums[index]==nums[i]:
                continue
            nums[index+1] = nums[i]#默认情况下,左指针比右指针小1,该方法不会改变数组内容,如果出现continue,则index少加1,导致错位赋值。
            index += 1#左指针加1,去重复的数字也加1
        return index+1 

#32 ms 15.9 MB
#优点:实现简单
#缺点:算法思想较难理解。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值