数组经典题目

一、二分查找

题目704.二分查找.
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

题目中的前提条件有:
1.数组为有序数组
2.数组中无重复元素

数组有序是使用二分法的前提,而且如果有重复元素,二分查找返回的元素下标可能不是唯一的,这些都是使用二分法的前提,看到题目满足如上条件时,可以考虑一下是不是可以使用二分法。

二分法的思想虽然简单,但涉及边界条件,需要清楚定义区间,也就是定义不变量,在二分查找的过程中(即while),每一次边界的处理都要坚持根据区间的定义来操作。

一般来说可以使用左闭右闭,即[left,right],定义target在这个区间里。
因此需要注意以下两点:

  1. while(left <= right),这里使用<=,因为left==right是有意义的
  2. if(nums[mid] > target)时,right赋值为mid-1,因为当前的nums[mid]不可能等于target,相反left就该赋值为mid+1

题解:

class Solution:
   def search(self, nums: List[int], target: int) -> int:
       left, right = 0, len(nums) - 1
       while left <= right:
           mid = (left + right) // 2
           if nums[mid] == target:
               return mid
           elif nums[mid] > target:
               right = mid - 1
           else:
               left = mid + 1
       return -1

理解之后可以做一下以下题目:
35.搜索插入位置.
34. 在排序数组中查找元素的第一个和最后一个位置.
69. x 的平方根.
367. 有效的完全平方数.

二、双指针法

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

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

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

如何删除数组中的元素?
数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
直观的做法就是暴力解法,一个for循环遍历数组,一个for循环更新数组,发现要移除的元素就将其后方数组集体向前移动。
很明显暴力解法的时间复杂度是O(n^2),通常我们可以用到快慢指针的方法,在一个循环内完成两个循环的工作。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        if len(nums) == 0:
            return 0
        i = 0   #指向要赋值的位置
        for j in range(len(nums)):  
            if nums[j] != val:  #当前要处理的元素不等于val,即为输出数组中的一个元素
                nums[i] = nums[j]   #把输出数组直接写在输入数组上
                i += 1  #指针右移
        
        return i

双指针法相关题目:
26. 删除有序数组中的重复项.
283. 移动零.
977. 有序数组的平方.

三、滑动窗口

题目209. 长度最小的子数组.
给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

暴力解法,使用两个for循环,不断寻找符合条件的子序列。这里可以使用数组操作的另一种方法:滑动窗口。

滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
滑动窗口主要需要确定三点内容:

  1. 窗口内是什么?
  2. 如何移动窗口的起始位置?
  3. 如何移动窗口的结束位置?

对于本题,窗口就是 满足其和 ≥ target 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于target了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        res = float("inf")  #要选出长度最小的,先定义一个无限大的值
        sum = 0 #滑动窗口数值之和
        i = 0   #滑动窗口起始位置
        sub_len = 0 #滑动窗口长度

        for j in range(len(nums)):
            sum += nums[j]
            #用while每次更新起始位置,并比较子序列是否符合条件
            while sum >= target:
                sub_len = j - i + 1
                res = min(res, sub_len)
                #变更子序列的起始位置
                sum -= nums[i]
                i += 1
        
        return 0 if res == float("inf") else res

最长窗口模板

for(枚举选择)
    右边界
    while(不符合条件)
        左边界
    更新结果

三、模拟行为

题目: 59. 螺旋矩阵 II.
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
数组经典题目_第1张图片
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

模拟行为并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。
模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

由外向内一圈一圈这么画下去,每一圈都要按照统一的规则(左闭右开,或者左开右闭的原则)。

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        left, right, up, down = 0, n-1, 0, n-1
        matrix = [[0] * n for _ in range(n)]    #初始矩阵
        num = 1

        while left <= right and up <= down:
            #填充左到右
            for i in range(left, right+1):
                matrix[up][i] = num
                num += 1
            up += 1

            #填充上到下
            for i in range(up, down+1):
                matrix[i][right] = num
                num += 1
            right -= 1

            #填充右到左
            for i in range(right, left-1, -1):
                matrix[down][i] = num
                num += 1
            down -= 1

            #填充下到上
            for i in range(down, up-1, -1):
                matrix[i][left] = num
                num += 1
            left += 1
        
        return matrix

相关题目:
54. 螺旋矩阵.

你可能感兴趣的:(LeetCode刷题,算法与数据结构)