0-1的算法学习之路 数组篇

数学系学生学习算法第一天,代码随想录Day1 Day2
这里主要记录了数组相关的内容。对于数学系学生来说,去理清楚逻辑(算法的数学原理是什么,具体的流程是什么)不是一件很难的事情,重点在于代码能力

数组

数组基础

数组是一种线性数据结构,用于存储相同类型的元素集合。其特点包括:

  • 固定大小:多数编程语言中数组长度在创建时确定,不可动态改变(除非使用动态数组)
  • 连续内存:元素在内存中连续存储,支持随机访问(通过索引直接访问任意元素)。
  • 索引从零开始:多数语言中,数组第一个元素的索引为 0,最后一个为 length-1

二分法查找

简而言之,有一个目标target,取中间,如果大于目标,则再在左侧取中间。如此反复用,一直取到等于为止。

704二分法查找

题目描述

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1。
你必须编写一个具有 O(log n) 时间复杂度的算法。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

解决方法

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

思考 为什么这里的mid需要+1或-1?
如果 nums[mid] < target:说明 target 肯定在 mid 的右侧,mid 不可能是答案
所以更新为 left = mid + 1
如果 nums[mid] > target:说明 target 肯定在 mid 的左侧,mid 不可能是答案
所以更新为 right = mid - 1
左闭右闭 [left, right] while left <= right left = mid + 1 / right = mid - 1 ✅ 必须跳过
左闭右开 [left, right) while left < right left = mid + 1 / right = mid ❌ 不需要跳过

27删除元素

题目描述

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。

解题思路

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        i = 0
        for n in nums:
            if n != val:
                i +=1   # i 先加 1,表示有效长度增加
# 然后把当前元素放到 nums[i-1] 的位置(因为索引从 0 开始)
                nums[i-1] = n  
# 这一步其实是在原地修改数组,把所有不等于 val 的元素依次往前放             
        return i

977有序数组的怕平方

题目描述

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
双指针法(Two Pointers)
思路核心

由于原数组是有序的(升序),但可能包含负数,因此平方后数组的最大值一定出现在数组的两端(因为负数变正后可能很大)。

我们可以使用两个指针:

  • left 指向数组的起始位置
  • right 指向数组的末尾位置

然后从两端向中间比较平方值,每次选择较大的那个放入结果数组的末尾,从而构建一个升序的平方数组。

算法步骤详解
  1. 初始化结果数组:创建一个与原数组等长的结果数组 result,用于存放平方后并排序的值。
  2. 设置三个指针
    • left = 0(左指针)
    • right = n - 1(右指针)
    • i = n - 1(从后往前填充结果数组)
  3. 循环比较
    • 计算 nums[left]nums[right] 的平方
    • 将较大的平方值填入 result[i]
    • 移动对应指针(left += 1right -= 1
    • i -= 1,继续向前填充
  4. 循环结束条件:当 left > right 时,循环结束

from typing import List

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        result = [0] * n
        left, right = 0, n - 1
        i = n - 1  # 从后往前填充结果数组

        while left <= right:
            left_sq = nums[left] ** 2
            right_sq = nums[right] ** 2

            if left_sq > right_sq:
                result[i] = left_sq
                left += 1
            else:
                result[i] = right_sq
                right -= 1
            i -= 1

        return result

209 长度最小子数组

滑动窗口

像是一条毛毛虫,固定最左侧一段,逐次向后移动,计算期中的和,之后和目标值比较。如果小于目标值,则向后继续移动,如果大于目标值,则停止,之后收缩最左端。

float('inf"的作用

举一个生活中的例子:找最便宜的汉堡
假设你今天要去麦当劳、肯德基、汉堡王这三家店买汉堡。
你不知道哪家最便宜,所以你先在纸上写了一个非常大的数字:999999。
然后你去:
麦当劳:汉堡15元
15 < 999999 → 更新纸上为 15
肯德基:汉堡18元
18 > 15 → 不更新
汉堡王:汉堡12元
12 < 15 → 更新纸上为 12
最后你纸上写的是:12,也就是最便宜的汉堡价格
解题思路

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n = len(nums)
        left, right, num = 0, 0, 0
        min_length = float('inf')
        while right < n:
            num += nums[right] 
            # 这段代码的意思是,一个一个将右侧的值加起来
            while num >= target:
                length = right - left + 1
                if length < min_length:
                    min_length = length
                num -= nums[left]
            # 这段代码的意思是,超出的值减去最左侧的值
                left += 1
            # 在经过中间一个while判断过后,right更新数值
            right += 1
        return min_length if min_length != float('inf') else 0

59 螺旋矩阵

旋转矩阵的核心,就是一步一步,根据判断条件,把值放入新的数组。其中,while、if的用法对我来说有点搞

while、if的用法

无限循环:while True: 创建了一个无限循环,这意味着如果没有遇到 break 语句,程序会一直执行下去。在这种情况下,它是用来模拟“一圈一圈”地遍历整个矩阵的。
控制遍历方向和边界调整:通过在每个方向(从左到右、从上到下、从右到左、从下到上)遍历后调整相应的边界值(如 t, b, l, r),并检查是否需要终止循环,实现了对矩阵元素的有序访问。
如何结束循环
条件判断与终止:每次完成一个方向的遍历之后,都会进行一次或多次边界检查(例如 if t > b: break 或 if l > r: break)。这些检查是为了确定当前剩余的未遍历部分是否足够形成一个完整的圈。如果不足以形成完整的一圈,则意味着所有元素都已被遍历完毕,此时应终止循环。
为什么使用 while True 而不是直接用条件控制?
简化逻辑:虽然可以用 while l <= r and t <= b: 这样的条件来控制循环,但使用 while True: 加上显式的 break 条件可以使逻辑更加清晰明了。每个方向遍历结束后立即进行边界检查,并决定是否继续下一个方向的遍历或者退出循环。
灵活性:这种方法提供了更大的灵活性,特别是在处理不同形状的矩阵时(比如矩形而非正方形),可以根据具体情况灵活调整边界和遍历顺序。
总之,while True: 在这里的作用是提供一个框架,在这个框架内按照预定的方向顺序遍历矩阵,并通过适当的边界调整和条件判断确保遍历过程能够正确且高效地完成,同时避免不必要的重复计算或越界错误。

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix: return []
        l, r, t, b, res = 0, len(matrix[0]) - 1, 0, len(matrix) - 1, []
        while True:
            for i in range(l, r + 1): 
                res.append(matrix[t][i]) # left to right             
            t += 1
            if t > b: break

            for i in range(t, b + 1): 
                res.append(matrix[i][r]) # top to bottom
            r -= 1
            if l > r: break

            for i in range(r, l - 1, -1): 
                res.append(matrix[b][i]) # right to left
            b -= 1
            if t > b: break

            for i in range(b, t - 1, -1): 
                res.append(matrix[i][l]) # bottom to top
            l += 1
            if l > r: break
        return res

这里的核心还是一步一步来,但是我的代码能力太弱了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值