在LeetCode做简单数组题的第1天
标签:#LeetCode #数组 #双指针 #数据结构 #刷题复盘
阅读时长:5分钟
前言
本人是一只学过数据结构但死活不理解咋用的绝望蘑菇,今日初到力扣,半小时做了第88题【合并两个有序数组】,发现了一个绝望的事实:课堂上学过双指针的思路,但是自己写的时候想不出,只能依赖AI
这篇随笔用于记录我的思考过程、踩坑、排错,总结我将如何把一个算法思路真正固化下来。
一、题目描述(合并两个有序数组)
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组
nums1中。为了应对这种情况,nums1的初始长度为m + n,其中前m个元素表示应合并的元素,后n个元素为0,应忽略。nums2的长度为n。
测试示例1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
测试示例2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
二、蘑菇的简单思路(复制+排序)
看到题目,认真思考后的想法:把nums2的元素全部复制到nums1后面预留的0位置,之后直接对nums1排序。
代码:
class Solution:
def merge(self, nums1: list[int], m: int, nums2: list[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
for i in range(n):
nums1[m+i] = nums2[i]
nums1.sort()
计算时间复杂度:
- for循环:时间复杂度(O(n))
- sort排序:Timsort算法,时间复杂度O((m+n)log(m+n))
备注:算法的时间复杂度取代码里最高阶的部分,所以整体复杂度是 O((m+n)log(m+n))。
三、本题的最优思路:从后往前双指针
思路推理过程
- 什么是数组有序:题目中,两个数组都是非递减有序,而结果返回的nums1数组也是非递减有序。非递减有序,就是数组的有效元素顺序递增,小数在头大数在尾,可参见示例1。
- 如何利用数组有序的特点:题目本质是,把两个递增数组(对nums1而言不太恰当,因为它尾部预留了全0空间),就较长的那个数组(nums1)去就地合并,得到一个新的递增数组(新nums1),那么,我们会在白纸上先圈出两个数组的有效位的尾部(即最大值)进行比较,选中较大的那个放到nums1的尾部,然后,划掉选中的大数,并往前一位去圈出本数组里的新大数,继续与另一个数组的大数比较,直到一个数组圈完为止。
- 用代码去理解刚才的思路:我们设置两个指针分别指向
nums1、nums2的有效位的尾部(p1、p2),以及一个指向nums1实际尾部的指针(p3),循环条件为p1或p2大于等于0,在循环内部,设置if判断,条件为若nums1[p1]>nums[p2],则nums1[p3]等于nums1[p1],否则,nums1[p3]等于nums1[p2],并且每次循环,值更大的数的指针,p1或p2,必须减去1,让指针重新指向剩下的数组里那个新的最大值,同时,p3也要减去1,因为当前循环中指向的位置已经放入一个最大值,那下一次循环就必须把下一次的最大值放到这次指向的前一位,这样才能得到一个递增数组。 - 写代码第一步,设置3个指针变量:
p1:nums1有效元素的最后一位下标m-1p2:nums2有效元素的最后一位下标n-1p3:当前写入位置,合并完成数组的最后一位m+n-1
- 主循环:只要
p1和p2都大于等于0,就持续比较,把更大的值放到nums1[p3],对应指针左移,指针p3左移。 - 收尾循环:主循环结束之后,如果nums2还有剩下没有合并的元素该怎么办呢?应知剩下的元素不仅是最小的哪些,而且是递增的,可直接复制到nums1前头去。如果是nums1有剩余元素呢?因为我们要的就是合并后的nums1,那些数本身就在正确的原位,无需处理。
nums1之所以在尾部预留出全零空间,就是指引我们去想到从后往前这个转换。
五、我的bug(低级代码错误)
按照双指针的思路,我写出了代码,但是提交无法通过样例:
class Solution:
def merge(self, nums1: list[int], m: int, nums2: list[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
p1 = m-1
p2 = n-1
p3 = m+n-1
while p1 >= 0 and p2 >=0:
if p1 > p2: # ❗BUG在这里!
nums1[p3] = nums1[p1]
p1 -= 1
p3 -= 1
else:
nums1[p3] = nums2[p2]
p2 -= 1
p3 -= 1
while p2 >=0:
nums1[p3] = nums2[p2]
p2 -= 1
p3 -= 1
问题分析
我错误地比较指针下标p1、p2本身的大小,而不是比较指针指向的数组元素的值 nums1[p1] 和 nums2[p2]。
用样例手动复现错误:
nums1 = [1,2,3,0,0,0],nums2 = [2,5,6]
初始 p1=2,p2=2,p3=5
第一轮判断 p1>p2 → 2>2 为false,取nums2[2]=6,数组变成[1,2,3,0,0,6]
第二轮p1=2,p2=1,p1>p2 → 2>1 为true,错误取nums1[2]=3,放到p3=4的位置。
数组变成[1,2,3,0,3,6],这一步就已经出错,后续全部结果跑偏。
bug总结:
p1只是数组下标编号;nums1[p1]才是下标对应的数组数值。
写双指针判断时,一定要问自己:我比较的是下标,还是下标对应的元素?
六、完整AC代码
class Solution:
def merge(self, nums1: list[int], m: int, nums2: list[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
p1 = m - 1
p2 = n - 1
p3 = m + n - 1
# 主循环,两个数组都还有元素时
while p1 >= 0 and p2 >= 0:
if nums1[p1] > nums2[p2]:
nums1[p3] = nums1[p1]
p1 -= 1
else:
nums1[p3] = nums2[p2]
p2 -= 1
p3 -= 1
# 如果nums2还有剩余元素,继续拷贝
while p2 >= 0:
nums1[p3] = nums2[p2]
p2 -= 1
p3 -= 1
✅ 时间复杂度:(O(m+n)),只遍历两个数组一遍
✅ 空间复杂度:(O(1)),原地修改,不额外开辟数组
样例手动推演:
nums1 = [1,2,3,0,0,0], nums2=[2,5,6]
p1=2(val=3), p2=2(val=6), p3=5
- 3<6 → nums1[5]=6,p2=1,p3=4
- 3<5 → nums1[4]=5,p2=0,p3=3
- 3>2 → nums1[3]=3,p1=1,p3=2
- 2==2 → nums1[2]=2,p2=-1,p3=1
主循环结束,p2=-1,第二个while不执行
最终nums1 =[1,2,2,3,5,6],符合预期。
七、如何记住一个算法思路?(本次学习最大收获)
误区:看懂代码 ≠ 能独立写出代码。看懂是跟着别人的思路走;独立写,需要自己完成完整推理。
-
记住场景
两个有序数组,原地合并到第一个数组,第一个数组尾部预留空位 → 优先思考从后往前双指针。 -
算法的推理链条
从前向后合并会覆盖原有数据 → 改为尾部写入 → 每次选最大值放到末尾 → 设置3个指针 → 主循环合并 → 处理nums2剩余元素。
记住「为什么要反向遍历」,而不是记住有3个指针。
-
纸笔模拟优先于写代码
拿到题目,先拿草稿纸画数组方框,标注指针,手动模拟2轮完整流程,再动手写代码。指针类题目,画图能规避绝大多数下标bug。 -
分模块写代码
分步写:初始化指针 → 主循环 → 收尾循环。写完一块验证一块。 -
变形自测检验是否真理解
做完原题给自己提问:
- 如果数组是非递增有序,思路怎么变?
- 如果nums1没有预留空位,不能原地修改怎么办?
- 如果nums2为空数组,代码是否正常运行?
灵活变通,而不是背题。
- 间隔重复复习
把学习记录成一篇博客,是为了每天有空扒出来读读画画,不能指望蘑菇脑袋进化到一夜之间内化任何哪怕简单的算法。
八、总结
第一次刷题给我的感受:想学算法就不能不动手,否则get不到适用场景,没学过数据结构也不行,因为如果当年没学,我还得翻书查什么是时间复杂度。
双指针这道题,在于场景分析和方向转换,还有写代码时细心区分下标和数组元素。
刷题的价值,不在于AC,而在于发现自己思维的盲区、手写代码的低级失误,把思路真正内化(祝见过了记住了的总有用上的一日)。
浙公网安备 33010602011771号