一文了解什么是数组及其经典考察题目

前言

一文带你了解数组的基础,并且其经常考察的思维算法。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于代码随想录同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!

数组

数组理论基础

什么是是数组?数组就是存放在计算机连续空间上的相同类型数据的集合。其便利性在于我们可以直接通过数组下标访问其元素,但也正是因为其连续存储的特性,我们需要对其实现增删操作的时候需要连续处理其他的元素。

数组的特性:

数组的下标是从0开始的

数组的存储空间是连续的

修改数组元素的方式是覆盖而不是直接删除其地址

数组的经典考察的题目

二分法

理论

应用场景:当存在一个有序数组且无重复元素的(以升序为例),我们需要从中找到我们想要的元素的位置该怎么办最简单?

首先我们当然可以遍历整个数组,一个一个比较找到元素的位置,但是这样处理未免太过麻烦,同时最坏的情况下的时间复杂度为O(n).

这个时候我们看其题目的特性,其是一个有序的数组,且无重复元素。那我们可以每次比较其中间值,当中间值比该元素大的时候,说明其肯定在中间值的左半区间,反之则肯定在右半区间。该方法处理的时间复杂度为O(logn)

其思想很简单,也很容易实现,但是其最经常困扰大家的一个问题就是边界带不带等号的问题,即到底是while(left<right)还是while(left<=right),并且当右边界改变的时候到底是right=middle还是right=middle-1的问题。

其实无论是while(left<right)还是while(left<=right)两种都是可以作为判定条件的,那么其具体有什么不同呢?

我们来看,当判断条件是while(left<right)时,其实质就是从[left,right)左闭右开区间去找符合要求的值

这里使用的是<,其left=right在区间内是没有意义的,那么我们每次比较完target之后呢,if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,为什么这样,因为我们已经比较过了其nums[middle]的值是不符合我们需要的,而我们是从左闭右开的区间去找,那我们更新right就应该令right 更新为 middle,倘若更新为middle-1,我们未比较过nums[middle-1]是不是等于target的,所以就是会出现问题的。

其实现代码为(这里为python代码):

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)  # 定义target在左闭右开的区间里,即:[left, right)

        while left < right:  # 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            middle = left + (right - left) // 2

            if nums[middle] > target:
                right = middle  # target 在左区间,在[left, middle)中
            elif nums[middle] < target:
                left = middle + 1  # target 在右区间,在[middle + 1, right)中
            else:
                return middle  # 数组中找到目标值,直接返回下标
        return -1  # 未找到目标值

当判断条件是while(left<=right)时,其实质就是从[left,right] 左闭右闭区间去找符合要求的值

同理来看这里使用的是=,其left=right在区间内是有意义的,那么我们每次比较完target之后呢,if (nums[middle] > target) right 更新为 middle-1,因为当前nums[middle]不等于target,为什么这样,因为我们已经比较过了其nums[middle]的值是不符合我们需要的,而我们是从左闭右闭的区间去找,那我们更新right就应该令right 更新为 middle-1。

其实现代码为(这里为python代码):

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1  # 定义target在左闭右闭的区间里,[left, right]

        while left <= right:
            middle = left + (right - left) // 2
            
            if nums[middle] > target:
                right = middle - 1  # target在左区间,所以[left, middle - 1]
            elif nums[middle] < target:
                left = middle + 1  # target在右区间,所以[middle + 1, right]
            else:
                return middle  # 数组中找到目标值,直接返回下标
        return -1  # 未找到目标值

实战练手

本题目来源于力扣

其解答为:

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

        while (left<=right):

            middle=(left+right)//2

            if target>nums[middle]:
                left+=1
            elif target<nums[middle] :
                right-=1
            else:
                return middle
        return left

双指针法

理论

应用场景:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

最直接的方法就是两个for循环,一个for循环用来找值为val的,一个for循环用来移动数组位置。最直接的方法当然会更加复杂,其时间复杂度为O($n^2$).

当然我们也可以用双指针法,用一个for循环来完成两个for循环的事情。其时间复杂度为O(n)

首先定义快慢指针:

快指针:用来寻找值不为val的元素

慢指针:用来更新新数组的下标

其含义一定要理解。具体什么意思:fast一直在数组循环,当找到值不为val的元素,就与slow交换,当fast遍历完整个数组,那么slow所代表的数组以内就都会是符合要求的值了。

其实现代码为:

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 快慢指针
        fast = 0  # 快指针
        slow = 0  # 慢指针
        size = len(nums)
        while fast < size:  # 不加等于是因为,a = size 时,nums[a] 会越界
            # slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow

实战

本题目来源于力扣

其实现为:

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        slow,fast=0,1
        while fast<len(nums):
            if nums[slow]!=nums[fast]:
                nums[slow+1]=nums[fast]
                slow=slow+1
            fast+=1
        return slow+1

滑动窗口法

应用场景:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

这道题目暴力解法当然是 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是O($n^2$)。

主要讲述滑动窗口法,其实其思想有些类似于双指针法。其核心就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。对于这道题,我们首先定义其left和right,让right一直向右移动累加,直至出现满足题意要求的区间,当然若是超出数组范围其无直接返回0.当出现满足题意区间后,left移动,缩小区间看是否能够继续满足,满足则继续移动。当不满足时,right开始向右移动,一直进行,直至right到达边界并且区间最小时终止。

其实现代码为:

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        l = len(nums)
        left = 0
        right = 0
        min_len = float('inf')
        cur_sum = 0 #当前的累加值
        
        while right < l:
            cur_sum += nums[right]
            
            while cur_sum >= s: # 当前累加值大于目标值
                min_len = min(min_len, right - left + 1)
                cur_sum -= nums[left]
                left += 1
            
            right += 1
        
        return min_len if min_len != float('inf') else 0

前缀和法

应用场景:给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。

此题何解?使用前缀和法,非常巧妙。当然我们可以直接一个for循环从指定区间加完即可,但是当有特别多特别多的区间需要去求和的时候怎么办,每次都是从头加一遍,会浪费大量的计算内存,因为其出现的很多都是重复过的计算。所以前缀和法出现了,我们使用另一个数组用来记录其下标前的所有总和,当求区间时直接该数组的对应下标的元素相减即可。

其实现代码为:

import sys
input = sys.stdin.read

def main():
    data = input().split()
    index = 0
    n = int(data[index])
    index += 1
    vec = []
    for i in range(n):
        vec.append(int(data[index + i]))
    index += n

    p = [0] * n
    presum = 0
    for i in range(n):
        presum += vec[i]
        p[i] = presum

    results = []
    while index < len(data):
        a = int(data[index])
        b = int(data[index + 1])
        index += 2

        if a == 0:
            sum_value = p[b]
        else:
            sum_value = p[b] - p[a - 1]

        results.append(sum_value)

    for result in results:
        print(result)

if __name__ == "__main__":
    main()
posted @ 2025-04-06 17:49  carpell  阅读(63)  评论(0)    收藏  举报