力扣算法面试150题——个人笔记——复习用
双指针
第一题:
125. 验证回文串https://leetcode.cn/problems/valid-palindrome/
题目内容
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个回文串。
字母和数字都属于字母数字字符。
给你一个字符串s,如果它是回文串,返回true;否则,返回false。
示例 1: 输入: s = "A man, a plan, a canal: Panama" 输出:true 解释:"amanaplanacanalpanama" 是回文串。 示例 2: 输入:s = "race a car" 输出:false 解释:"raceacar" 不是回文串。 示例 3: 输入:s = " " 输出:true 解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。 由于空字符串正着反着读都一样,所以是回文串。提示:
1 <= s.length <= 2 * 105s仅由可打印的 ASCII 字符组成
思路
两个指针初始化,一根指向开头一根指向结尾。
若左指针对应内容不为单词,则右移1;右指针同理,若对应内容不为单词,则左移1
若开头等于结尾,则左边右移1,右边左移1。
结束判定:当左指针<右指针时
代码
class Solution: def isPalindrome(self, s: str) -> bool: # 先全部转小写 s = s.lower() # 双指针 i = 0 j = len(s)-1 while i<j: # 若当前指向内容不为字母,则移动指针 if not s[i].isalnum(): i += 1 continue if not s[j].isalnum(): j -= 1 continue if s[i] != s[j]: return False i += 1 j -= 1 return True第二题:
392. 判断子序列https://leetcode.cn/problems/is-subsequence/
题目内容
给定字符串s和t,判断s是否为t的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
示例 1: 输入:s = "abc", t = "ahbgdc" 输出:true 示例 2: 输入:s = "axc", t = "ahbgdc" 输出:false提示:
0 <= s.length <= 1000 <= t.length <= 10^4- 两个字符串都只由小写字符组成。
思路
两个字符串s和t,分别用一根指针i和j。若i指向内容等于j指向内容,则i右移1,j右移1;若i指向内容不等于j指向内容,则i不动,j右移1。因此。分析发现,不管怎么样j都会右移1,而i不一定。
那么最终结束的判定条件就是i能否等于len(s),若等于,则True,否则False
附:也可以使用for循环的方式遍历t,然而时间复杂度更高,效率不及双指针。
代码
class Solution: def isSubsequence(self, s: str, t: str) -> bool: i = 0 j = 0 lengs = len(s) lengt = len(t) # while 循环判定范围 while i<=lengs-1 and j<=lengt-1: if s[i] == t[j]: i += 1 # j 指针不管怎么样都要右移 j += 1 # 结束条件 if i == lengs: return True else: return False“进阶”思路
目前的版本(双指针法)的时间复杂度是 O(∣s∣+∣t∣),如果有 K 个 s需要匹配,总时间复杂度就是 O(K⋅∣t∣)。当 K 很大且 t 很长时,每次都要遍历一遍 t,效率就很低了。
可以先对 t 进行一次预处理,建立一个“索引”。再用二分查找匹配,若能找到则更新位置,找不到则返回False
第三题
167. 两数之和 II - 输入有序数组https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/
题目内容
给你一个下标从1开始的整数数组numbers,该数组已按非递减顺序排列,请你从数组中找出满足相加之和等于目标数target的两个数。如果设这两个数分别是numbers[index1]和numbers[index2],则1 <= index1 < index2 <= numbers.length。
以长度为 2 的整数数组[index1, index2]的形式返回这两个整数的下标index1和index2。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1: 输入:numbers = [2,7,11,15], target = 9 输出:[1,2] 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。 示例 2: 输入:numbers = [2,3,4], target = 6 输出:[1,3] 解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。 示例 3: 输入:numbers = [-1,0], target = -1 输出:[1,2] 解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。提示:
2 <= numbers.length <= 3 * 104-1000 <= numbers[i] <= 1000numbers按非递减顺序排列-1000 <= target <= 1000- 仅存在一个有效答案
思路
一个数组,两个指针,分别指向头和尾,因为数组提前说好了,非递减顺序排列,且只对应唯一解。也就是说,右边的数字一定是大于等于左边的数字的。因此,若当前值>目标值,右指针左移1,同理,若当前值<目标值,那么左指针右移1。
代码
class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: i = 0 j = len(numbers) - 1 while i < j: if numbers[i] + numbers[j] < target: i+=1 elif numbers[i] + numbers[j] > target: j-=1 else: return [i+1,j+1]第四题
11. 盛最多水的容器https://leetcode.cn/problems/container-with-most-water/
题目内容
给定一个长度为n的整数数组height。有n条垂线,第i条线的两个端点是(i, 0)和(i, height[i])。
找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1: 输入:[1,8,6,2,5,4,8,3,7] 输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 示例 2: 输入:height = [1,1] 输出:1提示:
n == height.length2 <= n <= 1050 <= height[i] <= 104
思路
一个数组,两个指针,分别指向头和尾。
能盛多少水,就看长 * 宽。长就是两个指针对应的内容的最小值min(height),宽就是两根指针的距离(j-i),更新条件就是判断左右的高低,左边低就往右移动(因为宽边小了,想要最终的面积更大,必须移动短的那一根,如果短的板不动的话,取到的水永远不会比上次多),同理右边低就往左移,知道i>=j结束。最终返回max_ans即为最大值。
代码
class Solution: def maxArea(self, height: List[int]) -> int: i = 0 j = len(height)-1 ans = 0 while i<j: # 最终的面积 ans = max(ans, min(height[i],height[j]) * (j-i)) if height[i]<height[j]: i+=1 else: j-=1 return ans第五题
15. 三数之和https://leetcode.cn/problems/3sum/
题目内容
给你一个整数数组nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != j、i != k且j != k,同时还满足nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1: 输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。 示例 2: 输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。 示例 3: 输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。提示:
3 <= nums.length <= 3000-105 <= nums[i] <= 105
思路
先对整个列表从小到大排序,然后固定一个数i,对于剩下的两个数,双指针分别指向头j(i+1)和尾k(n-1)
先对i进行去重(第一次尝试写的时候犯的错,漏了 i),接着进行计算,若三数之和小于0,则左指针右移,因为j的左侧一定都比当前的值小;同理若三数之和大于0,则右指针左移,因为k的右侧一定都比当前的值大;若等于0,则记录答案。然后左右指针各自均移动。
值得注意的是(第一次尝试写的时候犯的错),在记录完答案并移动左右指针之后,还要对j、k去重。且这一步必须在记录完答案之后进行。否则会导致漏掉某些解。即去重逻辑应该是在找到一个有效三元组之后,为了寻找下一个可能解时才执行。在没有确定当前组合是否有效之前,不应该随意跳过指针。
代码
class Solution: def threeSum(self, nums: list[int]) -> list[list[int]]: # 先从小到大排序,便于后续使用双指针 newnums = sorted(nums) n = len(nums) ans = [] for i in range(n-2): # i 去重 if i>0 and newnums[i] == newnums[i-1]: continue # 双指针分别指向头和尾 j = i + 1 k = n - 1 while j<k: # 结果小于0,则左指针右移 if newnums[i]+newnums[j]+newnums[k]<0: j+=1 # 结果大于0,则右指针左移 elif newnums[i]+newnums[j]+newnums[k]>0: k-=1 else: # 记录结果 ans.append([newnums[i], newnums[j], newnums[k]]) j+=1 k-=1 # 进一步j、k去重 while j<k and newnums[j] == newnums[j-1]: j+=1 while j<k and newnums[k] == newnums[k+1]: k-=1 return ans