LeetCode Cookbook 数组习题(4)
篇接上回,开始!
127. 单词接龙 * (典型BFS)
题目链接:127. 单词接龙
题目大意:字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> … -> sk:
- 每一对相邻的单词只差一个字母。
- 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
- sk == endWord
- 给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。
解题思路:看这篇blog Leetcode 127. Word Ladder
- 主要学习: (1)字符串数组的正则表达;(2)BFS的编写的流程。
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if endWord not in wordList:
return 0
neighbors = collections.defaultdict(list)
for word in wordList:
for i in range(len(word)):
neighbors[word[:i]+'*'+word[i+1:]].append(word)
visited = set()
q = collections.deque()
q.append((beginWord,1))
while q:
word,step = q.popleft()
if word == endWord:
return step
for i in range(len(word)):
for nei in neighbors[word[:i]+'*'+word[i+1:]]:
if nei not in visited:
q.append((nei,step+1))
visited.add(nei)
return 0
126. 单词接龙 II
题目链接:126. 单词接龙 II
题目大意:是 127. 单词接龙 的提升版本需要返回全部的最短的转换序列。
解题思路:看这篇blog Leetcode 126. Word Ladder II (python+cpp)
- 不用考虑其他花里胡哨的算法 专注于BFS和helper的编写 注意使用的语言是py3 那就专注于 py3
class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
def append_path(start,end,curr_path,level,length):
if level>length:
return
curr_path.append(end)
if end==start:
ans.append(curr_path[::-1].copy())
for parent in path[end]:
append_path(start,parent,curr_path,level+1,length)
curr_path.pop()
adj = collections.defaultdict(list)
for word in wordList:
for i in range(len(word)):
adj[word[:i]+'*'+word[i+1:]].append(word)
visited = set()
q = collections.deque()
q.append((beginWord,1))
path = collections.defaultdict(set)
length=0
while q:
word,k = q.popleft()
if word == endWord:
length = k
break
if word not in visited:
visited.add(word)
for i in range(len(word)):
neighbors = word[:i]+'*'+word[i+1:]
for neigh in adj[neighbors]:
if neigh not in visited:
path[neigh].add(word)
q.append((neigh,k+1))
ans = []
append_path(beginWord,endWord,[],1,length)
return ans
128. 最长连续序列 *
题目链接:128. 最长连续序列
题目大意:给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
解题思路: 典型 哈希 表题目,建立的字典 是数组中每一个的值(val)与左右的邻居个数(cur_length)。
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
hash_dict = dict()
max_length = 0
for num in nums:
# print(hash_dict)
if num not in hash_dict:
L = hash_dict.get(num-1,0)
R = hash_dict.get(num+1,0)
cur_length = 1+L+R
if cur_length > max_length:
max_length = cur_length
hash_dict[num] = cur_length
hash_dict[num-L] = cur_length
hash_dict[num+R] = cur_length
# print(hash_dict)
return max_length
152. 乘积最大子数组
题目链接:152. 乘积最大子数组
题目大意:给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。测试用例的答案是一个 32-位 整数。子数组 是数组的连续子序列。
解题思路: 动态规划题,细节看代码注释,其实这道题还可以求连续的最小乘积。
class Solution:
def maxProduct(self, nums: List[int]) -> int:
# 注意这道题 没说正负
n = len(nums)
numMin,numMax,ans = nums[0],nums[0],nums[0]
for i in range(1,n):
num = nums[i]
# 一个动态变化的问题
# 如果当前数字是正数 没必要做交换
# 如果当前数字是负数 需要最大最小值交换一下 毕竟 负负得正
if num < 0:
numMax,numMin = numMin,numMax
numMax = max(num,numMax*num)
numMin = min(num,numMin*num)
ans = max(ans,numMax)
print(numMax,numMin)
return ans
153. 寻找旋转排序数组中的最小值 *
题目链接:153. 寻找旋转排序数组中的最小值
题目大意:已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
- 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
- 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
- 注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
- 给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
解题思路: 二分模板的灵活应用,模板就是与右边界死磕到底,即使下一题 154. 寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/) 的去重也是与右边界进行死磕。
- 令mid与最右侧的R比较重新确定区间边界
if nums[mid] >= nums[R]:
- 注意,这不是寻找target的例题,所以需要遍历mid所在的数组值,即
R = mid
。
class Solution:
def findMin(self, nums: List[int]) -> int:
n = len(nums)
if n==1: return nums[0]
L,R = 0,n-1
# 这办法绝了 真的好用啊!!!
while L<R:
mid = L+(R-L)//2
if nums[mid] >= nums[R]:
L = mid+1
else:
R = mid
return nums[L]
154. 寻找旋转排序数组中的最小值 II
题目链接:154. 寻找旋转排序数组中的最小值 II
题目大意: 区别于153. 寻找旋转排序数组中的最小值,这里的数组含有重复元素。给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
解题思路: 同上一题,需要注意去重操作,与右边界R 死磕到底,即: if nums[mid] == nums[R]: R -= 1
。
class Solution:
def findMin(self, nums: List[int]) -> int:
L,R = 0,len(nums)-1
while L <R:
mid = L + (R-L)//2
if nums[mid] == nums[R]:
R -= 1
elif nums[mid] > nums[R]:
L = mid+1
else:
R = mid
return nums[L]
162. 寻找峰值 *
题目链接:162. 寻找峰值
题目大意:峰值元素是指其值严格大于左右相邻值的元素。给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。你可以假设 nums[-1] = nums[n] = -∞ 。你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
解题思路:二分模板灵活应用,注意这是与相邻元素的比较。
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
L,R = 0,len(nums)-1
while L<R:
mid = L+(R-L)//2
# 就使劲找呗 往上爬坡
if nums[mid] < nums[mid+1]:
L = mid+1
else:
R = mid
return L
852. 山脉数组的峰顶索引
题目链接:
题目大意:符合下列属性的数组 arr 称为 山脉数组 :(1)arr.length >= 3;(2)存在 i(0 < i < arr.length - 1)使得:arr[0] < arr[1] < … arr[i-1] < arr[i],arr[i] > arr[i+1] > … > arr[arr.length - 1];(3)给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1] 的下标 i 。
解题思路: 与162. 寻找峰值一模一样的解题思路,代码也没什么变动。
class Solution:
def peakIndexInMountainArray(self, arr: List[int]) -> int:
L,R = 0,len(arr)-1
while L<R:
mid = L+(R-L)//2
if arr[mid] < arr[mid+1]:
L = mid+1
else:
R = mid
return L
167. 两数之和 II - 输入有序数组
题目链接:167. 两数之和 II - 输入有序数组
题目大意:给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
- 以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
- 你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
- 你所设计的解决方案必须只使用常量级的额外空间。
解题思路:双指针常规操作。
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
n = len(numbers)
id1,id2 = 0,n-1
while id1 < n and id2 >= 0 and id1<id2:
ret = numbers[id1] + numbers[id2]
if ret == target:
return [id1+1,id2+1]
elif ret < target:
id1 += 1
else:
id2 -= 1
169. 多数元素*
题目链接:169. 多数元素
题目大意:给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
解题思路: 摩尔投票,算是贪心做法吧!有时间多看看,其实看来也属于一个动态维护。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
ans,cnt = 0,0
for num in nums:
if cnt == 0:
ans,cnt = num,1
else:
if num == ans: cnt += 1
else: cnt -= 1
return ans
209. 长度最小的子数组
题目链接:209. 长度最小的子数组
题目大意:给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
解题思路: 动态规划,指定滑窗的起始点 start 与 终止点 end,当 < target 时,窗口延展 end += 1;当 >= target 后,缩小窗口,start += 1.
- 注意,需要动态地管理 中间值也就是 窗口的总和 total。
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
n = len(nums)
if n==1: return 1 if nums[0] >= target else 0
start,end,total = 0,0,0
ans = n+1
while end < n:
total += nums[end]
# print(start)
while total >= target:
# 与答案进行比较
ans = min(ans,end-start+1)
# 起始点移除
total -= nums[start]
start += 1
end += 1
return ans if ans != n+1 else 0
216. 组合总和 III
题目链接:216. 组合总和 III
题目大意:找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
- (1) 只使用数字1到9;
- (2) 每个数字 最多使用一次。
- 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
解题思路: 注意与39. 组合总和和 40. 组合总和 II进行联合学习 这道题使用回溯还是比较简单的
- 不过,注意
tmp+[c]
不能写在 if 条件之前,会导致回溯失败的窘境。
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
def backTrace(start: int,tmp: List[int],target: int) -> None:
for i in range(start,9):
c = candidate[i]
# print(tmp,k)
if c == target and len(tmp)==k-1:
ans.append(tmp+[c])
return
if c < target and len(tmp) < k:
backTrace(i+1,tmp+[c],target-c)
else:
return
candidate = [1,2,3,4,5,6,7,8,9]
if n < 6: return []
ans = []
backTrace(0,[],n)
return ans
217. 存在重复元素
题目链接:217. 存在重复元素
题目大意:给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
解题思路: (1)集合特性;(2)哈希表;(3)collections的计数器。
- (1)集合特性
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
return len(set(nums)) != len(nums)
- (2)哈希表
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
hash_map = dict()
for num in nums:
if num not in hash_map:
hash_map[num] = 1
else:
return True
return False
- (3)collections的计数器
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
counter = collections.Counter(nums)
for num in nums:
if counter[num] > 1:
return True
return False
总结
这次的题目和二分法(153. 寻找旋转排序数组中的最小值、154. 寻找旋转排序数组中的最小值 II、162. 寻找峰值、852. 山脉数组的峰顶索引)、动态规划(128. 最长连续序列、152. 乘积最大子数组、209. 长度最小的子数组 ),尤其是这两道题127. 单词接龙和126. 单词接龙 II非常值得好好的进行专题归纳总结,现在我不求每道题的用时和占用内存都非常优秀,我只想得到一个不错的、能让我接受又容易记忆复现的方法,继续加油吧! 努力,奋斗!