classSolution: deftwoSum(self, numbers: List[int], target: int) -> List[int]: N = len(numbers) L, R =0, N-1 while L < R: while L <R and numbers[L] + numbers[R] <target: L += 1 while L < R and numbers[L] + numbers[R] > target: R -= 1 if L< R and numbers[L] + numbers[R] == target: return [L+1, R+1] return []
classSolution: defreverseString(self, s: List[str]) -> None: """ Do not return anything, modify s in-place instead. """ L, R = 0, len(s)-1 while L < R: s[L], s[R] = s[R], s[L] L += 1 R -=1
'''思路2. 使用双指针''' classSolution: defreverseVowels(self, s: str) -> str: N = len(s) ll = list(s) L, R = 0, N-1 while L < R: while L < R and ll[L] notin'aeiouAEIOU': L += 1 while L < R and ll[R] notin'aeiouAEIOU': R -=1 if L<R: ll[L], ll[R] = ll[R], ll[L] L +=1 R -=1 return''.join(ll)
classSolution: defisPalindrome(self, s: str) -> bool: # 先将所有大写转成小写 s = s.lower() # 初始化双指针 L, R = 0, len(s)-1
while L < R: while L < R andnot s[L].isalnum(): # 对于非字母数字字符, 直接不考虑, 直接移动指针 L += 1 while L < R andnot s[R].isalnum(): R -= 1 if L < R: if s[L] == s[R]: L += 1 R -= 1 else: # 如果发现不相等的情况,说明左右不对称 returnFalse returnTrue
classSolution: defexchange(self, nums: List[int]) -> List[int]: L, R = 0, len(nums)-1 while L < R: while L < R and nums[L] % 2 == 1: # 题目要求奇数在前,所以如果是奇数,左指针就不用反转,直接指针把L指针右移 L += 1 while L < R and nums[R] % 2 == 0: # 题目要求偶数在后,所以如果是偶数,右指针就不用反转,直接指针把R指针左移 R -=1 # 前面两个循环执行完毕后,说明L遇到的是偶数,R遇到的是奇数, # 此时交换一下位置即可 if L < R: nums[L], nums[R] = nums[R], nums[L] L += 1 R -= 1
L, R = 0, len(people)-1 cnt = 0 while L < R: # 如果重量超过限制,就先让重的人单独坐一条船走 while L < R and people[L] + people[R] > limit: R -= 1 cnt += 1 # 如果重量不超限制, 就两个人一起走 while L < R and people[L] + people[R] <= limit: R -= 1 L += 1 cnt += 1 # 如果最后还剩下一个人,就让他坐一条船走 if L == R: cnt += 1 return cnt
classSolution: deftriangleNumber(self, nums: List[int]) -> int: N =len(nums) if N < 3: return0
# 先从大到小排序 nums.sort(reverse=True) res = 0 for i inrange(0,N-2): # 当前 i 位置的数左为最长边 lng; 另外两个小边就在[i+1, N-1]区间找 L, R = i+1, N-1 while L<R: # 目标是期望 小边之和 > 长边,即 a+b > c # lng, mid, sht = nums[i], nums[L], nums[R]
# 如果 mid + sht 不够大,说明sht应该增大,即R侧应该往左移一次,找一个更大的sht if nums[i] >= nums[L] + nums[R]: R -= 1 else: # lng < mid + sht # 如果此时能满足条件, R位置充当最短边sht,则R左侧的更大的数更能成为sht # 即从 L+1 ~ R位置的所有数都可以成为最短边 sht(使得mid + sht更大) # 与mid (L位置的值)一起构造一对小边; sht可取的个数为 => R - L res += R - L # 计数完之后,再将 L 右移一位,即 mid 减小一点, # 这样会使得 mid + sht变小 # 看变小后的和是否还能大于lng L += 1 # 使用双指针的核心就在这里, L和R靠拢时, mid+sht 的值是在缩小的 # 但只要大于lng, L和R遍历的范围就能够满足条件 return res
更简洁的 code 写法:
classSolution: deftriangleNumber(self, nums: List[int]) -> int: N = len(nums) if N < 3: return0 nums.sort(reverse=True) res = 0 for i inrange(0, N-2): large = nums[i] L, R = i+1, N-1 while L < R: while L < R and nums[L] + nums[R] <= large: R -= 1# 如果两边之和小于等于长边, 就说明最小边应该增大 if L < R: res += R- L L += 1 # 由于L 增大后, 中边其实缩小了, 所以之前R右侧数字因比R位置还小, 所以更不满足条件 return res
nums.sort() # 升序排列 N = len(nums) res=[] for i inrange(0, N-2): if i > 0and nums[i] == nums[i-1]: continue# 如果 三元组的第一个数字重复, 可以不用考虑 if nums[i] > 0: break# 如果三元组的第一个数组都大于0, 由于已经升序排列, 所以数字之和必不为0 L, R = i+1, N-1 while L < R: if L > i+1and nums[L] == nums[L-1]: L += 1 continue# 如果在i不变的情况下,三元组的第二个数组重复,也可以不用考虑
while L < R and nums[L] + nums[R] > -nums[i]: R -= 1# 如果两数之和大于了目标值, 说明应该减小较大数 if L < R and nums[L] + nums[R] == -nums[i]: res.append([nums[i], nums[L], nums[R]]) L += 1 # 因为 L 右移之后, 数字变大, 所以原来 R右侧更大的数字更不会满足条件了, R可以先不变 return res
N = len(nums) nums.sort() res = [] # 相比于三数之和问题,只是多加了一层循环嵌套而已 for i inrange(N-3): if i > 0and nums[i] == nums[i-1]: continue for j inrange(i+1, N-2): if j>i+1and nums[j] == nums[j-1]: continue L, R = j+1, N-1 while L < R: if L >j+1and nums[L] == nums[L-1]: L +=1 continue while L < R and nums[L] + nums[R] > target - nums[i] -nums[j]: R -= 1 if L <R and nums[i] + nums[j] + nums[L] + nums[R] == target: res.append([nums[i], nums[j], nums[L], nums[R]]) L += 1
classSolution: defsortedSquares(self, nums: List[int]) -> List[int]: ''' 暴力解法: res = [num**2 for num in nums] return sorted(res) ''' N = len(nums) res = [0] * N L, R, idx = 0, N - 1, N-1 while L <= R: while L<=R andabs(nums[L]) > abs(nums[R]): # 如果 L 位置的数更远,就将其平方结果装入 res数组的末尾 res[idx] = nums[L]**2 L += 1 idx -=1 while L<=R andabs(nums[L]) <= abs(nums[R]): # 如果 R 位置的数更远,就将其平方结果装入 res数组的末尾 res[idx] = nums[R]**2 R -=1 idx -=1 return res
classSolution: defsortColors(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ '''荷兰国旗问题''' N = len(nums) L, R = -1, N i = L+1 while i < R: if L < R and nums[i] < 1: nums[i], nums[L+1] = nums[L+1], nums[i] L+=1 i+=1 elif L < R and nums[i] == 1: i+=1 else: nums[i], nums[R-1] = nums[R-1], nums[i] R -=1
比如【 从左往右遍历时,考察的是 L 位置】, 可以肯定 L_max 一定是 L 左侧最高边界: 此时 R_max 是 R 右侧的最高边界,并不一定是 L 右侧的最高边界 但是 R_max 它是不是 L位置 的右侧最高边界重要吗?来讨论一下
情况1. if R_max > L_max, then H = L_max, 即 H 取值和 R_max 无关,原因如下: 如果 L~R 之间,还有更高的边界可以作为L的右边界,那 L_max 依旧是更矮的,不影响 H 取值是 L_max 如果 L~R 之间,不存在更高的边界,那 R_max 就是 L 的最高右边界,哪怕 L~R区间都是更矮的也不影响,反正有 R_max 作为右边界兜底,故 H 取值还是更矮的 L_max
但是反其道而行之!!!对于当前条件:L_max > R_max,无法判断 L 位置的 H 取值。但是却可以判断 R 位置的 H 取值!这正是我们讨论【从左往右遍历时,考察的是 L 位置】 时的情况1 如果我们现在 【从 右往左遍历 时,考察的是 R 位置】,那么
L_max > R_max 这个情况 就是 R 位置的情况1:此时 R 位置的 H = R_max
对于 R 位置的情况2:条件是 R_max > L_max, 同理,无法确定 L~R 之间是否有更适合作为 R 位置左边界的值 但是,它又转换成了 对于 L 位置的情况1.
对于 R_max == L_max 的情况: 如果考察的是 L 位置,H 仍然能取 L_max 如果考察的是 R 位置,H 仍然能取 R_max。 仍然按照两个位置情况1的思路去理解 在写代码时,只需要将这种情况固定划分到考察 L 或者 R 的一种之中去就行 这样整个思路就能串通起来了, 由于只需要遍历一次数组,时间复杂度 O(N), 只有有限个辅助变量,空间复杂度O(1)。
classSolution: deftrap(self, height: List[int]) -> int: N = len(height) # 最左和最右的柱子只能当边界,无法装水 # 柱子一定要至少有3个才能装水 water = 0 if N <3: return water
L_max, R_max = height[0], height[N-1] L, R = 1, N-2 while L <= R : if L_max <= R_max: # 这是 从左往右遍历的 L 位置的情况1:H = L_max L_max = max(L_max, height[L]) water += L_max - height[L] L += 1 else: # 这是 从右往左遍历的 r 位置的情况1: H = R_max R_max = max(R_max, height[R]) water += R_max - height[R] R -= 1 return water
classSolution: deflongestMountain(self, arr: List[int]) -> int: N = len(arr) width = 0 i = 1 while i < N-1: L, R = i-1, i+1 # 先判断是否满足左右都下降的趋势 if arr[i] >arr[L] and arr[i] >arr[R]: while L> 0and arr[L-1] < arr[L]: # L 一直尽可能地往左延伸 L -= 1 while R< N-1and arr[R+1] < arr[R]: # R 一直尽可能的往右延伸 R += 1 width = max(width, R-L+1) i = R # 如果之前存在山,那么就是(L,i,R)这一段, 所以 i 到 R 之间是下降趋势 return width
classSolution: defcompress(self, chars: List[str]) -> int: N = len(chars) if N <= 1: return N
s, f = 0, 1 old = 0# 需要设置一个变量来记录重复区域的起始index while f <= N: if f==N or chars[f] != chars[s]: # 此时f已经走出了重复区域来到了新的区域(f==N时代表list已经遍历完) w = f-old # 则上一段重复区域的长度就是 f 减去上一段重复区域的起始index: old if w>=2: # 如果该区域长度至少为2, 则说明要添加数字 for ch inlist(str(w)): s+=1 chars[s] = str(ch) if f < N: # 把新区域的字符添加上 s+=1 chars[s] = chars[f] old = f # 现在已经处理完上一段重复区域, 则old更新为新重复区域的起始index f += 1 return s+1
defremoveDuplicates(self, nums: List[int]) -> int: N = len(nums) s, f = 0, 1 begin = 0
while f <= N: # 这里的code写法和前面两道题不同,算是拓展一下思路 # 这里是f和它前一位比较, 来判断重复区域是否结束 if f == N or nums[f] != nums[f-1]: # f-1 就是重复区域的末尾index nums[s] = nums[f-1] s += 1 cnt = f - begin begin = f if cnt >= 2: nums[s] = nums[f-1] s += 1
思路:这个题虽然没有显式的使用快慢指针,但是思想是借鉴了快慢指针的。 假设我们要找3个数a,b,c满足 a < b < c 的条件,那么a应该尽可能的小,b也应该在大于a的情况下尽可能的小,这样才容易去找到满足条件的c。 那么,比方说我们如何找到一个数组中,最小的数呢(不调用min)??那就是设定一个极大的初始值,遍历一道数组,在遍历过程中发现更小的数,就更新,最终就能找到这个最小的数。 借鉴这个思想,我们的代码如下
classSolution: defincreasingTriplet(self, nums: List[int]) -> bool: a = b = float('inf') for num in nums: if num <= a: # 这样能够保证在遍历过的数字中,a一定是最小的那个数 a = num elif num <=b: # elif隐含的条件是不满足上面的条件,即不满足小于等于a # 所以b一定能取到在遍历过的数目当中,位于a后面的,比a大的数当中,最小的一个数 b = num else: # 在继续变量的过程中,只要遇到一个数不满足上面的条件,即比a和b都大 # 这个数就能够充当 c 的角色 # c = num returnTrue returnFalse
classSolution: deffindMaxConsecutiveOnes(self, nums: List[int]) -> int: L, R =-1, 0 # 慢指针用来标记连续1区域的左边界, 快指针用来判断当前位置是否是1 gap = 0 for R inrange(0, len(nums)): if nums[R] == 1: gap = max(gap, R-L) else: L = R return gap