:数组板块简单题)
零基础入门刷题记录。这篇帖子按题号整理了力扣数组板块简单题的解题思路重点记录新手最容易踩的坑和通用套路。每道题都附带我自己踩过的错误希望能帮到同样从零开始的朋友。目录一、通用套路速查二、新手高频错误清单三、逐题记录四、Python 语法小抄五、复杂度入门一、通用套路速查刷完这批题我总结出几个反复出现的套路模板。刷题的本质就是建立自己的套路库——遇到新题先想这像我见过的哪一类。套路适用场景核心思想复杂度双指针快慢数组原地删除/整理去重、移除元素slow管写fast/i管读各扫一遍不回头O(n)二分查找有序数组查找/定位每次看中间一次砍掉一半O(log n)递归树的构建/遍历、可拆成同类子问题函数自己调用自己处理更小的数据看具体题递推DP雏形用前面算好的结果推后面如杨辉三角用上一行推下一行O(n)边走边记最优贪心/DP求最大/最小值如最大利润用几个变量记住目前为止的最优O(n)异或消重找只出现一次的数相同的数异或0剩下的就是答案O(n)二、新手高频错误清单这些是我反复犯的错误单独拎出来建议做成检查清单每次报错先对照排查。1.和搞混是赋值把右边给左边是判断相等if/while里的比较永远用写成直接语法报错。2.range和切片的语法不通用range(a, b)→ 用逗号生成数字序列 a, a1, …, b-1nums[a:b]→ 用冒号取数组的一段a 到 b-1不包含 b把冒号写进range会报语法错误。三、逐题记录14. 最长公共前缀思路以第一个字符串为基准逐列比较所有字符串的同一位置字符遇到不一致就返回。踩坑if :永远是 False空字符串为假判断无意义。某个字符串比前缀短时strs[j][i]会越界 → 比较前先判断i len(strs[j])。短路求值技巧把越界检查放在or前面左边为真就不算右边。classSolution:deflongestCommonPrefix(self,strs:List[str])-str:ifnotstrs:returnlengthlen(strs[0])numlen(strs)foriinrange(length):cstrs[0][i]forjinrange(num):ifilen(strs[j])orstrs[j][i]!c:# 安全检查放前面returnstrs[0][0:i]returnstrs[0]26. 删除有序数组中的重复项思路快慢指针。slow记录已放好的不重复元素末尾位置fast/i往前扫遇到新值就写进来。踩坑返回的是去重后的长度整数不是数组。不要边删边遍历会越界用覆盖代替删除。classSolution:defremoveDuplicates(self,nums:List[int])-int:ifnotnums:return0slow0foriinrange(1,len(nums)):ifnums[i]!nums[slow]:slow1nums[slow]nums[i]returnslow1口诀i一直往前走不回头slow只在遇到新值时才前进一步。27. 移除元素思路同样是快慢指针。slow是写指针i是读指针凡是要保留的不等于 val就写到slow位置。踩坑这题不需要主动 breakfor跑完自然结束。只需一个if保留就写slow前进不保留就自动跳过没有多余分支。classSolution:defremoveElement(self,nums:List[int],val:int)-int:slow0foriinrange(len(nums)):ifnums[i]!val:nums[slow]nums[i]slow1returnslow读写指针的区别i读负责遍历每个元素slow写负责记录下一个该写的位置只在需要保留时移动。所以slow总是 ≤i落在后面 → 这就是慢的由来。35. 搜索插入位置二分查找入门思路二分查找。前提是数组有序。用left/right框定范围mid取中间每次砍掉一半。核心动作nums[mid] target→ 返回midnums[mid] target→ 目标在右半left mid 1nums[mid] target→ 目标在左半right mid - 1踩坑二分两大铁律边界一定要跨过 midmid1/mid-1否则范围缩不动 → 死循环 → 超时。返回的是下标mid/left不是值nums[mid]。没找到时返回left循环结束时left正好停在第一个 ≥ target 的位置就是插入点。classSolution:defsearchInsert(self,nums:List[int],target:int)-int:left0rightlen(nums)-1whileleftright:mid(leftright)//2ifnums[mid]target:returnmidelifnums[mid]target:leftmid1else:rightmid-1returnleft# 没找到返回插入位置108. 将有序数组转换为二叉搜索树递归入门前置概念二叉搜索树BST左子树全比根小右子树全比根大每个节点都满足。平衡左右高度差不多树最矮查找最快。节点结构一个节点 一个值val 左孩子left 右孩子right没有孩子时为None。思路递归取数组正中间的数当根左半数组搭左子树右半数组搭右子树。因为每次取中间左右元素个数几乎一样多树自然平衡。递归三要素停止条件数组空了返回None空树。自我调用self.xxx(左半)和self.xxx(右半)——同一个方法处理更小的数据。self.是类里调用自己方法的固定前缀。踩坑空树返回None不是0返回0会让上层拿到整数报int object has no attribute left。左右子树是两行别复制粘贴忘了把left改成right。classSolution:defsortedArrayToBST(self,nums:List[int])-Optional[TreeNode]:ifnotnums:returnNonemidlen(nums)//2rootTreeNode(nums[mid])root.leftself.sortedArrayToBST(nums[:mid])root.rightself.sortedArrayToBST(nums[mid1:])returnroot理解递归的钥匙想象两面镜子间无数个自己但每层镜子都缩小一点缩到看不见就停了。递归就是每次传更小的数据直到触底返回。118. 杨辉三角思路递推一行行往下建。每一行头尾是1中间的数 上一行相邻两数之和。用已建好的上一行result[i-1]来算当前行。踩坑row [1]*(i1)先造一行全1头尾天然是1不用管。中间循环range(1, i)只改中间头尾的1不动。下标是result[i-1][j-1] result[i-1][j]上一行的左上右上。classSolution:defgenerate(self,numRows:int)-List[List[int]]:result[]foriinrange(numRows):row[1]*(i1)forjinrange(1,i):row[j]result[i-1][j-1]result[i-1][j]result.append(row)returnresult递推 动态规划的雏形用前面算好的结果推后面。你已经在用DP思想了只是还没系统学。119. 杨辉三角 II思路和118几乎一样只是最后返回第rowIndex行。举一反三的迁移练习。踩坑要第rowIndex行下标从0起循环必须range(rowIndex 1)别少建一行。取最后一行用result[-1]-1表示最后一个不用len()-1。classSolution:defgetRow(self,rowIndex:int)-List[int]:result[]foriinrange(rowIndex1):row[1]*(i1)forjinrange(1,i):row[j]result[i-1][j-1]result[i-1][j]result.append(row)returnresult[-1]121. 买卖股票的最佳时机思路边走边记最优 / 贪心从左往右扫一遍用两个变量记录min_price到目前为止的最低买入价遇到更低的就更新max_profit到目前为止的最大利润踩坑关键认知买入价不是定死的遇到更便宜的就更新min_price。很多人卡在死守第一个价格。如果一路下跌无法盈利返回0初始值不是报错。别用双层循环O(n²)会超时一次遍历就够O(n)。classSolution:defmaxProfit(self,prices:List[int])-int:min_priceprices[0]max_profit0forpinprices:ifpmin_price:min_pricep# 遇到更低买点更新elifp-min_pricemax_profit:max_profitp-min_price# 今天卖能赚更多更新returnmax_profit口诀买入价会变遇到更便宜的就更新每天算一次今天卖能赚多少留最大的。136. 只出现一次的数字思路异或消重把所有数异或起来。异或性质相同的两个数异或 02 ^ 2 0任何数异或0 它自己1 ^ 0 1所以重复的两两抵消只剩下单独出现的那个。只扫一遍O(n)。classSolution:defsingleNumber(self,nums:List[int])-int:result0forninnums:result^nreturnresult走一遍[2,2,1]0^22 → 2^20 → 0^11结果 1。备选能过但慢O(n²)用count找出现次数为1的数。适合先拿下题目但不是最优。classSolution:defsingleNumber(self,nums:List[int])-int:foriinrange(len(nums)):ifnums.count(nums[i])1:returnnums[i]异或找单独数是经典套路值得记进笔记本。四、Python 语法小抄刷题过程中攒下的一行代码全是高频操作需求一行代码说明数组末尾加元素nums.append(x)直接放不用包装合并两个数组nums1 nums2拼接返回新列表取最后一个元素nums[-1]-1是最后一个-2倒数第二造长度n全0的列表[0] * n需要按下标赋值时用空列表nums []Python 列表动态不用定长度数字位数len(str(n))转字符串数长度负数含负号数字拆位入数组[int(c) for c in str(n)]先str再逐字符int10的n次方10 ** n**是幂运算删除第 k 个元素del nums[k]后面元素自动前移数组排序nums.sort()原地排序取最大值max(nums)直接取不用先排序统计元素次数nums.count(x)返回 x 出现的次数注释多行Ctrl /MacCmd /选中后按再按取消复合赋值简写x n即x x nx ^ n即x x ^ n规律?都是变量 变量 ? 右边的简写 - * ^同理。五、复杂度入门复杂度不是题目给的是分析代码得出的。方法数一下每个元素被处理了几次。复杂度怎么数典型题O(log n)每次砍一半最多查 log n 次二分查找35O(n)每个元素扫一遍双指针26/27、异或136O(n²)每个 i 都要 j 跑一整轮n×n暴力双层循环注意区分两件事题目要求的复杂度如35题写明必须 O(log n)这是给你的限制。代码实际的复杂度写完后自己分析代码得出题目不会告诉你。现阶段只需建立意识写完代码粗略想一下每个元素大概被碰了几次。碰一次是 O(n)每次砍半是 O(log n)嵌套循环是 O(n²)。先有这个感觉就够了。写在最后刷题的一点心得与同样零基础的朋友共勉代码短 ≠ 简单往往相反。短代码是想通之后的结晶难的从来不是写是想。想不到某个解法很正常不是你笨是你还没见过这个套路。刷题就是在建立套路库库越大越多题变成哦这个我见过。第一遍靠学第二遍靠记第三遍才靠想。现阶段能看懂、能理解、能迁移就是最重要的进步。别用能不能秒想到评判自己。持续更新中欢迎交流指正。