数组(Array)
485:最大连续1的个数
输入:[1,1,0,1,1,1]
输出:3
解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.
- 方法1:遍历计数法
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
if nums is None or len(nums)==0:
return 0
count = 0
result = 0
for num in nums:
if num == 1:
count += 1
result = max(result, count)
else:
count = 0
return result
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
if nums is None or len(nums)==0:
return 0
count = 0
result = 0
for num in nums:
if num == 1:
count += 1
#result = max(result, count)
else:
result = max(result, count)
count = 0
return max(result,count)
- 方法2:动态规划
计算过程
nums[i] | 1 | 1 | 0 | 1 | 1 | 1 |
---|---|---|---|---|---|---|
dp[i] | 1 | 2 | 0 | 1 | 2 | 3 |
(1)初始状态:dp[0] = 0
(2)状态转移方程
当nums[i] = 0 时,dp[i] = nums[i]
当nums[i] = 1 时,dp[i] = dp[i-1] +1
(3)计算结果:输出max(dp[i])
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
if nums is None or len(nums)==0:
return 0
dp = [0]*len(nums)
dp[0] = nums[0]
for i in range(1,len(nums)):
if nums[i] == 1:
dp[i] = dp[i-1] + 1
else:
dp[i] = 0
return max(dp)
283-移动零
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
要求:在原数组上操作
- 方法1:暴力法
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
for i in range(len(nums)):
for j in range(i, len(nums)):
if nums[i] == 0 and nums[j] != 0:
nums[i], nums[j] = nums[j], nums[i]
- 方法2:原地修改
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
index = 0
for num in nums:
if num != 0:
nums[index] = num
index += 1
for i in range(index, len(nums)):
nums[i] = 0
27-移除元素
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
- 方法1:原地修改
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# n = nums.count(val)
# for i in range(n):
# nums.remove(val)
# return len(nums)
# while val in nums:
# nums.pop(nums.index(val))
# return len(nums)
index = 0
for num in nums:
if num != val:
nums[index] = num
index += 1
return index
- 双指针
交换之前:nums = [3, 2, 2, 3]
交换之后:nums = [2, 2, 3, 3] 把val移向右端
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 双指针
if nums == None or len(nums) == 0:
return 0
left = 0
right = len(nums)-1
while (left < right):
while (left < right and nums[left] != val):
left += 1
while (left < right and nums[right] == val):
right -= 1
# 当左指针指向的数是val且右指针指向的数不是val
nums[left], nums[right] = nums[right], nums[left]
if nums[left] == val:
return left
else:
return left + 1
链表(Linked List)
203-移除链表元素
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
输入:head = [], val = 1
输出:[]
输入:head = [7,7,7,7], val = 7
输出:[]
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
prev = dummy
while head is not None:
if head.val == val:
prev.next = head.next
head = head.next
else:
prev = head
head = head.next
return dummy.next
206-反转链表
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
## 使用迭代法
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur: # 当cur==null时结束循环
temp = cur.next # 存储原来的cur.next位置
cur.next = pre
pre = cur
cur = temp
return pre
### 使用递归
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next: #递归终止条件
return head
p = self.reverseList(head.next)
head.next.next = head
head.next = None
return p
160-相交链表
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 思路:采用双指针,分别指向两链表的头节点
# 指针A先遍历完链表headA,再开始遍历headB
# 指针B先遍历完链表headB,再开始遍历headA
A, B = headA, headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A
队列(Queue)
933-请求次数
输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:[null, 1, 2, 3, 3]
class RecentCounter:
def __init__(self):
self.q = collections.deque()
def ping(self, t: int) -> int:
self.q.append(t)
while self.q[0] < t - 3000:
self.q.popleft()
return len(self.q)
239-滑动窗口最大值
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 ; 3
1 [3 -1 -3] 5 3 6 7 ; 3
1 3 [-1 -3 5] 3 6 7 ; 5
1 3 -1 [-3 5 3] 6 7 ; 5
1 3 -1 -3 [5 3 6] 7 ; 6
1 3 -1 -3 5 [3 6 7] ; 7
- 方法1:求出窗口子数组,然后求出每个子数组的最大值
- 时间复杂度:O((n−k+1)k)=O(nk);运行超时:49 / 61 个通过测试用例
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
subListNum = len(nums) - (k-1) # 窗口的数量
temp = []
for i in range(subListNum):
maxNum = max(nums[i:i+k])
temp.append(maxNum)
return temp
- 方法2:使用优先队列-大根堆
- 时间复杂度:O(logn)
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# 使用优先队列
# 注意 Python 默认的优先队列是小根堆,因此将数值边为负数,从而实现大根堆
q = [(-nums[i], i) for i in range(k)]
heapq.heapify(q)
# q = [(value, index)]
n = len(nums)
ans = [-q[0][0]]
for i in range(k, n):
heapq.heappush(q, (-nums[i], i))
while q[0][1] <= i - k: # 去除超出窗口的左侧元素
heapq.heappop(q)
ans.append(-q[0][0])
return ans
栈(Stack)
20-有效的括号
输入:s = "()[]{}"
输出:true
class Solution:
def isValid(self, s: str) -> bool:
# 定义一个栈
stack = []
for i in s:
if i == '(' or i == '[' or i == '{':
stack.append(i)
else:
if len(stack) == 0:
return False
else:
temp = stack.pop()
if i == ')':
if temp != '(':
return False
if i == ']':
if temp != '[':
return False
if i == '}':
if temp != '{':
return False
if len(stack) == 0:
return True
else:
return False
- 优化
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 != 0: # 取余数
return False
dicts = {')':'(', ']':'[', '}':'{'}
stack = []
for i in s:
if i in dicts:
if not stack or stack[-1] != dicts[i]:
return False
stack.pop()
else:
stack.append(i)
return not stack
496-下一个更大元素
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
## 方法1:使用两个栈
res = []
stack = [] # 初始化一个栈
for num in nums2:
stack.append(num)
for num in nums1:
temp = [] # 定义一个临时的栈
isFound = False
max = -1
while len(stack) !=0 and not isFound:
top = stack.pop()
if top > num:
max = top
elif top == num:
isFound = True
temp.append(top)
res.append(max)
while len(temp) != 0:
stack.append(temp.pop())
return res
# 方法2:使用栈和哈希表
res = []
stack = []
hashTable = {}
for num in nums2:
while (len(stack) != 0 and num > stack[-1]):
temp = stack.pop()
hashTable[temp] = num
stack.append(num)
while len(stack) != 0:
temp = stack.pop()
hashTable[temp] = -1
for val in nums1:
res.append(hashTable.get(val)) # nums1中的值对应的哈希表数据
return res
哈希表(Hash Table)
217-存在重复元素
输入: [1,2,3,1]
输出: true
- 方法1:数组排序法
- 排序之后的数组查找速度较快,相邻元素比较
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
if len(nums) == 0:
return False
nums.sort()
prev = nums[0]
for i in range(1, len(nums)):
if nums[i] == prev:
return True
else:
prev = nums[i]
return False
- 方法2:集合法
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
if len(nums) == 0:
return False
# 方法2
numsList = nums
numsSet = set(nums)
if len(numsList) == len(numsSet):
return False
else:
return True
- 方法3:哈希表法
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
if len(nums) == 0:
return False
# 方法3
mapping = {}
for num in nums:
if num not in mapping:
mapping[num] = 1
else:
mapping[num] = mapping.get(num) + 1
for val in mapping.values():
if val > 1:
return True
return False
389-找不同
输入:s = "abcd", t = "abcde"
输出:"e"
解释:'e' 是那个被添加的字母。
class Solution:
def findTheDifference(self, s: str, t: str) -> str:
if len(s) == 0:
return t
mapping = [0]*26 # 存储26个小写字母
for i in s:
temp = ord(i) - 97 # 字母转换为ASCII
mapping[temp] -= 1
for j in t:
temp = ord(j) - 97
mapping[temp] += 1
for k in range(len(mapping)):
if mapping[k] == 1:
return chr(k+97) # ASCII转换为字符
496-下一个更大元素
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
- 使用一个栈和一个哈希表。
- 栈用于数据元素的进出
- 哈希表用于按照检索格式存储数据
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
mapping = {}
stack = []
res = []
for num2 in nums2:
while len(stack) != 0 and num2 > stack[-1]:
temp = stack.pop()
mapping[temp] = num2
stack.append(num2)
while len(stack) != 0:
temp = stack.pop()
mapping[temp] = -1
for val in nums1:
res.append(mapping.get(val))
return res
集合(Set)
217-存在重复元素
输入: [1,2,3,1]
输出: true
- 方法1:数组排序法
- 排序之后的数组查找速度较快,相邻元素比较
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
if len(nums) == 0:
return False
nums.sort()
prev = nums[0]
for i in range(1, len(nums)):
if nums[i] == prev:
return True
else:
prev = nums[i]
return False
- 方法2:集合法
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
if len(nums) == 0:
return False
# 方法2
numsList = nums
numsSet = set(nums)
if len(numsList) == len(numsSet):
return False
else:
return True
705-设计哈希集合
输入:
["MyHashSet", "add", "add", "contains", "contains", "add", "contains", "remove", "contains"]
[[], [1], [2], [1], [3], [2], [2], [2], [2]]
输出:
[null, null, null, true, false, null, true, null, false]
解释:
MyHashSet myHashSet = new MyHashSet();
myHashSet.add(1); // set = [1]
myHashSet.add(2); // set = [1, 2]
myHashSet.contains(1); // 返回 True
myHashSet.contains(3); // 返回 False ,(未找到)
myHashSet.add(2); // set = [1, 2]
myHashSet.contains(2); // 返回 True
myHashSet.remove(2); // set = [1]
myHashSet.contains(2); // 返回 False ,(已移除)
class MyHashSet:
def __init__(self):
"""
Initialize your data structure here.
"""
self.hashSet = [0]*1000001
def add(self, key: int) -> None:
self.hashSet[key] = 1 # 加入元素并标记为1
def remove(self, key: int) -> None:
self.hashSet[key] = 0 # 删除元素并标记为0
def contains(self, key: int) -> bool:
"""
Returns true if this set contains the specified element
"""
return bool(self.hashSet[key])
树(Tree)
144-树的前序遍历
- 递归法(递归终止条件)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
def preorder(root:TreeNode):
# 递归的终止条件,根节点不存在
if not root:
return
# 前序遍历:根-左-右
res.append(root.val)
preorder(root.left)
preorder(root.right)
res = []
preorder(root)
return res
- 迭代法
# 使用迭代
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
if not root:
return res
stack = []
node = root
while stack or node:
while node:
res.append(node.val)
stack.append(node)
node = node.left
# 向上寻找根节点
node = stack.pop()
node = node.right
return res
94-二叉树的中序遍历
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
def inorder(root:TreeNode):
if not root:
return
inorder(root.left)
res.append(root.val)
inorder(root.right)
res = []
inorder(root)
return res
145-二叉树的后序遍历
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
def posterorder(root:TreeNode):
if not root:
return
posterorder(root.left)
posterorder(root.right)
res.append(root.val)
res = []
posterorder(root)
return res
堆(heap)
215-数组中第k大元素
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
- 方法1:排序法
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
# 排序法
nums.sort(reverse=True)
return nums[k-1]
- 方法2:最大堆法
import heapq # 使用建堆的函数
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
# 最大堆法
heap = []
heapq.heapify(heap) # 建堆
for num in nums:
heapq.heappush(heap, num*(-1)) # 逆序
while k > 1:
heapq.heappop(heap)
k -= 1
return heapq.heappop(heap)*(-1)
692-前k个高频单词
输入: ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i" 和 "love" 为出现次数最多的两个单词,均为2次。
注意,按字母顺序 "i" 在 "love" 之前。
import heapq
class Solution:
def topKFrequent(self, words: List[str], k: int) -> List[str]:
# 创建哈希表,用于统计每个单词出现的次数
mapping = {}
for word in words:
if word not in mapping:
mapping[word] = 0
mapping[word] = mapping[word] + 1
print(mapping)
# 创建前k大的堆
heap = []
for key, value in mapping.items():
heapq.heappush(heap, Node(key, value))
if len(heap) > k:
heapq.heappop(heap)
res = []
while len(heap) > 0:
temp = heapq.heappop(heap)
print(temp.key, ' ', temp.value)
res.append(temp.key)
print(res)
res.reverse()
return res
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
# 当两个单词出现的次数相同时,按照字母顺序排序,出现前面的优先
# 当两个单词出现的次数不同时,按照字母出现频率排序,频次大者优先
def __lt__(self, nxt):
if self.value == nxt.value:
return self.key > nxt.key
else:
return self.value < nxt.value
print()标准化输出结果
{'i': 2, 'love': 2, 'leetcode': 1, 'coding': 1}
love 2
i 2
['love', 'i']
双指针(Two Pointers)
141-环形链表
- 方法1:使用一个哈希表
- 思路:使用一个哈希表来存储所有已经访问过的节点,如果该节点存在于哈希表中,则说明该链表是环形链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
seen = set()
while head:
if head in seen:
return True
seen.add(head)
head = head.next
return False
- 方法2:快慢指针
- 思路:快指针每次移动一步,慢指针每次移动两步,如果快指针追上慢指针,则为环形链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head or not head.next:
return False
slow = head
fast = head.next
while slow != fast:
if not fast or not fast.next:
return False
slow = slow.next
fast = fast.next.next
return True
881-救生艇
输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)
class Solution:
def numRescueBoats(self, people: List[int], limit: int) -> int:
if people is None or len(people) == 0:
return 0
# 先排序
people.sort()
print(people)
# 双指针
left = 0
right = len(people) - 1
count = 0
while left <= right:
if people[left] + people[right] <= limit:
left += 1
right -= 1
count += 1
return count
二分查找(Binary Search)
704-二分查找
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
注:数组nums是升序的
- 方法1:依次遍历
class Solution:
def search(self, nums: List[int], target: int) -> int:
for i in range(len(nums)):
if nums[i] == target:
return i
return -1
- 方法2:二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
if nums is None or len(nums) == 0:
return -1
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right-left)//2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
return mid
return -1
35-搜索插入位置
输入: nums = [1,3,5,6], target = 5
输出: 2
注:给定数组是有序的
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right-left)//2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left += 1
else:
right -= 1
return left # 思考为什么返回left
162-寻找峰值
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
可以假设 nums[-1] = nums[n] = -∞
- 方法1:考虑峰值下降的瞬间
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
for i in range(len(nums)-1):
if nums[i] > nums[i+1]:
return i
return len(nums)-1
- 方法2:双指针
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
left = 0
right = len(nums) - 1
while left < right:
mid = left + (right-left)//2
if nums[mid] > nums[mid+1]:
right = mid # 峰值在左,抛弃右边的值
else:
left = mid + 1 # 峰值在右
return left
74-搜索二维矩阵
- 方法1:按照行\列依次遍历查找
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if matrix is None or len(matrix) == 0:
return False
row = len(matrix)
col = len(matrix[0])
for i in range(row):
for j in range(col):
if matrix[i][j] == target:
return True
return False
- 方法2:使用二分法查找
- 将二维矩阵转为一维矩阵的方法
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if matrix is None or len(matrix) == 0:
return False
row = len(matrix)
col = len(matrix[0])
# 二维矩阵转为一维矩阵
l = 0
r = row*col - 1
while l <= r:
mid = l + (r-l)//2
x = mid//col
y = mid % col
element = matrix[x][y]
if element == target:
return True
elif element < target:
l += 1
else:
r -= 1
return False
滑动窗口(Sliding Window)
209-长度最小的子数组
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
注:如果不存在符合条件的子数组,返回 0 。
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
if nums is None or len(nums) == 0:
return 0
res = len(nums) + 1 # 给一个不可能出现的最大值进行初始化
i = j = 0
total = 0
while i < len(nums):
total = total + nums[i] # 窗口增大直到满足条件
i += 1
while total >= target: # 记录窗口大小并滑动窗口
res = min(res, i-j)
total = total - nums[j]
j += 1
if res == len(nums) + 1:
return 0
else:
return res
1456-定长子串中元音的最大数目
输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。
class Solution:
def maxVowels(self, s: str, k: int) -> int:
if s == None or len(s) == 0 or k > len(s):
return 0
res = 0
count = 0
hashset = {'a','e','i','o','u'}
for i in range(k):
if s[i] in hashset:
count += 1
res = max(res, count)
for j in range(k, len(s)):
if s[j-k] in hashset: # 窗口滑动左侧边界字符的变化
count -= 1
if s[j] in hashset: # 窗口滑动右侧边界字符的变化
count += 1
res = max(res, count)
return res
递归(Recursion)
509-斐波拉契数列
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
- 方法1:递归
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
res = self.fib(n-1) + self.fib(n-2)
return res
- 方法2:记忆化算法
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
a = 0
b = 1
for i in range(1, n):
res = a + b
a = b
b = res
return res
206-反转链表
- 方法1:使用递归
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next: #递归终止条件
return head
p = self.reverseList(head.next)
head.next.next = head
head.next = None
return p
- 方法2:
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
dummy = ListNode(0)
dummy.next = head
while head is not None and head.next is not None:
dnext = dummy.next #存储 1
hnext = head.next #存储 2
dummy.next = hnext #将2临时链到dummy上
head.next = hnext.next #将1链到3上
hnext.next = dnext #第2链到1上
return dummy.next
344-反转字符串
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
注:不能使用额外的空间,必须在原地进行修改
- 方法1:双指针
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
# 双指针
left = 0
right = len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
- 方法2:递归
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
# 递归
if s == None or len(s) == 0:
return
left = 0
right = len(s) - 1
recursion(s, left, right)
def recursion(s, left, right):
# 递归终止条件
while left > right:
return
recursion(s, left+1, right-1)
s[left], s[right] = s[right], s[left]
分治法(Devide and Conquer)
169-多数元素
输入:[3,2,3]
输出:3
注:数组非空且存在数组中出现次数 大于 n/2 的元素
- 方法1:排序法
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[len(nums)//2]
- 方法2:哈希表统计法
class Solution:
def majorityElement(self, nums: List[int]) -> int:
mapping = {}
# 字典初始化
for num in nums:
mapping[num] = 0
# 计数统计
for num in nums:
mapping[num] += 1
return max(mapping, key = mapping.get)
# dic = defaultdict(int)
# for num in nums:
# dic[num] += 1
# return max(dic, key=dic.get)
- 方法3:投票法
# 思路:不同的两个数相互抵消,最后剩下的那个数就是众数
class Solution:
def majorityElement(self, nums: List[int]) -> int:
count = 0
candinate = None
for num in nums:
if count == 0: # 当两个数字全部抵消,选出新的候选者
candidate = num
if num == candidate:
count += 1
else: # 当两个数不相等时票数减少,相当于不相等数相互抵消
count -= 1
return candidate
- 方法4:分治法
class Solution:
def majorityElement(self, nums: List[int]) -> int:
return self.getMajority(nums, 0, len(nums)-1)
def getMajority(self, nums, left, right):
if left == right: # 指向了同一个数
return nums[left]
# 进行二分
mid = (left + right)//2
leftMajority = self.getMajority(nums, left, mid)
rightMajority = self.getMajority(nums, mid+1, right)
# 相等时为众数
if leftMajority == rightMajority:
return leftMajority
# 不相等时需要比较元素的出现频率
leftcount = 0
rightcount = 0
for i in range(left,right+1):
if nums[i] == leftMajority:
leftcount += 1
elif nums[i] == rightMajority:
rightcount += 1
# 选出频率较大的数
if leftcount > rightcount:
return leftMajority
else:
return rightMajority
53-最大子序和
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
- 方法1:贪心算法
# 思路:若当前指针所指元素之前的和小于0,则丢弃当前元素之前的数列
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
total = nums[0]
res = nums[0]
for i in range(1, len(nums)):
total = max(nums[i], total+nums[i])
res = max(total, res)
return res
- 方法2:动态规划
思路:若前一个元素大于0,则将其加到当前元素上
变换前: -2 1 -3 4 -1 2 1 -5 4
变换后: -2 1 -2 4 3 5 6 1 5 --> max(nums)
状态转移方程:f(i) = max{f(i-1) + nums[i], nums[i]}
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
for i in range(1, len(nums)):
if nums[i-1] > 0:
nums[i] = nums[i] + nums[i-1] #动态规划会求出每一个情况下的结果
return max(nums)
- 方法3:分治法
class Solution:
# Leetcode 53. Maximum Subarray
# Divide And Conquer
# N is the size of nums
# Time Complexity: O(N)
# Space Complexity: O(logN)
def maxSubArray(self, nums: List[int]) -> int:
return self.getMax(nums, 0, len(nums)-1)
def getMax(self, nums, l, r):
if l == r:
return nums[l]
mid = l + (r-l)//2
leftSum = self.getMax(nums, l, mid)
rightSum = self.getMax(nums, mid+1, r)
crossSum = self.crossSum(nums, l, r)
return max(leftSum, rightSum, crossSum)
def crossSum(self, nums, l, r):
mid = l + (r-l)//2
# from mid to leftmost
leftSum = nums[mid]
leftMax = leftSum
for i in range(mid-1, l-1, -1):
leftSum += nums[i]
leftMax = max(leftMax, leftSum)
# from mid to rightmost
rightSum = nums[mid+1]
rightMax = rightSum
for i in range(mid+2, r+1):
rightSum += nums[i]
rightMax = max(rightMax, rightSum)
return leftMax + rightMax
215-数组中的第K个最大的元素
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
- 方法1:排序法
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
nums.sort()
return nums[-k]
- 方法2:大根堆
import heapq
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
# 1 建堆初始化
heap = []
heapq.heapify(heap)
# 2 将数组中的值构造为大根堆
for num in nums:
heapq.heappush(heap, num*(-1))
# 输出前k-1个大的堆顶元素
while k > 1:
heapq.heappop(heap)
k -= 1
return heapq.heappop(heap)*(-1)
回溯法(Backtracing)
https://leetcode-cn.com/problems/subsets/solution/hui-su-suan-fa-by-powcai-5/
22-括号生成
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
注:有效括号组合需满足:左括号必须以正确的顺序闭合。
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
self.backTracing(n, result, 0, 0, '')
return result
def backTracing(self, n, result, left, right, s):
'''
n:输入的匹配括号数目
result:运行结果
left:左括号的个数
right:右括号的个数
s:初始字符串,临时结果
'''
# 回溯终止条件:当左括号的个数小于右括号的个数
if left < right:
return
# 对满足条件的匹配结果进行输出
if left == right == n:
result.append(s)
return
# 先添加左括号,条件必须是小于n
if left < n:
self.backTracing(n, result, left+1, right, s+'(')
# 再添加右括号,条件必须是右括号数目小于左括号的数目
if right < left:
self.backTracing(n, result, left, right+1, s+')')
78-子集
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
- 方法1:扩展法
求解过程:
result = [[]]
result = [[], [1]]
result = [[], [1], [2], [1,2]]
result = [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]
- 方法1:扩增法
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = [[]] # 临时数组的设置!!!
for num in nums:
temp = []
for cur in result:
temp.append(cur+[num]) # 把新的数添加到已经生成的每一个子集
for val in temp:
result.append(val) # 及时更新result
return result
- 方法2:回溯法
求解思路:
空集:[]
长度为1的子集:[1]; [2]; [3]
长度为2的子集:[1,2]; [1,3]; [2,3]
长度为3的子集:[1,2,3]
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
# 回溯法
result = [[]]
for i in range(1, len(nums)+1):
self.backTracing(nums, result, i, 0, [])
return result
def backTracing(self, nums, result, length, index, subset):
'''
length:子集的长度
index:索引位置
subset:临时子集
'''
# 递归终止条件
if len(subset) == length:
result.append(subset[:])
return
for i in range(index, len(nums)):
subset.append(nums[i])
# 通过 i+1 实现只遍历 i 索引后面的元素
self.backTracing(nums, result, length, i+1, subset)
subset.pop() # 因为要向上继续查找,所以要及时删除!!!
- 方法3:DFS
77-组合
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
注:组合可以是任意顺序输出结果
- 方法1:回溯法
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
result = []
self.backTracing(n, k, result, 1, [])
return result
def backTracing(self, n, k, result, begin, ls):
'''
begin:开始查找的头元素
ls:临时数组
'''
if len(ls) == k:
result.append(ls[:])
return
for i in range(begin, n+1):
ls.append(i)
self.backTracing(n, k, result, i+1, ls)
ls.pop()
- 方法2:自带组合函数-itertools
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
return list(itertools.combinations(range(1,n+1),k))
itertools模块中combinations的用法
Python itertools模块combinations(iterable, r)方法可以创建一个迭代器,返回iterable中所有长度为r的子序列,返回的子序列中的项按输入iterable中的顺序排序。
import itertools
result = list(itertools.combinations(range(4), 2))
print(result)
>> [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
输出一组数据的子序列
list1 = [1,2,3]
list2 = []
for i in range(len(list1)+1):
iter = list(itertools.combinations(list1, i))
list2.append(iter)
print(list2)
>> [[()], [(1,), (2,), (3,)], [(1, 2), (1, 3), (2, 3)], [(1, 2, 3)]]
46-全排列
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
result = []
visited = {}
for num in nums:
visited[num] = False
self.backTracing(nums, result, visited, [])
return result
def backTracing(self, nums, result, visited, ls):
'''
visited:用于记录是否已经访问过数组元素
ls:临时数组
'''
# 递归终止条件
if len(ls) == len(nums):
result.append(ls[:])
return
for num in nums:
if not visited[num]:
ls.append(num)
visited[num] = True
self.backTracing(nums, result, visited, ls)
ls.pop()
visited[num] = False
字符串重排
输出字符串的不同排列数
输入:ABA
输出:ABA、AAB、BAA
解题思路:
先把每个字符当成唯一出现过一次,计算所有排列数;再统计重复出现的字母,除去每个字母的排列次数
对于ABA,当成三个不同字符则排列数为:
S总 = 321 = 3!
其中A出现两次,排列数为:SA = 2!,
B出现一次,排列数为:SB = 1!,
最终计算得:S=S总/(SASB)=321/(21*1)=3
import math
from collections import Counter
def func(s):
lst = [x for x in Counter(s).values() if x>1]
print(lst)
s1 = 1
for x in lst:
s1 *= math.factorial(x)
return math.factorial(len(s))//s1
回溯算法=DFS+剪枝
八皇后、数独
深度优先搜索(Depth First Search)
78-子集
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
- 方法:DFS
使用递归,一条路走到底,不撞南墙不回头
求解顺序
先加入空集: []
深层次搜索:[1], [1,2], [1,2,3]
深层次探索:[2], [2,3]
深层次探索:[3]
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = []
self.dfs(nums, result, 0, [])
return result
def dfs(self, nums, result, index, subset):
result.append(subset[:])
# 递归终止条件,直到长度等于nums的长度
if index == len(nums):
return
for i in range(index, len(nums)):
subset.append(nums[i])
self.dfs(nums, result, i+1, subset)
subset.pop()
200-岛屿数量
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
# dfs
if grid is None or len(grid) == 0:
return 0
result = 0
row = len(grid) # 行数
col = len(grid[0]) # 列数
# 找岛屿,同化
for i in range(row):
for j in range(col):
if grid[i][j] == '1':
result += 1
self.dfs(grid, i, j, row, col) # 同化:把 1 变为 0
return result
def dfs(self, grid, x, y, row, col):
# 递归的终止条件
if x < 0 or y < 0 or x >= row or y >= col or grid[x][y] == '0':
return
grid[x][y] = '0' # 将 ‘1’变为 ‘0’
# 上\下\左\右 查找可变为‘0’的区域
self.dfs(grid, x-1, y, row, col)
self.dfs(grid, x+1, y, row, col)
self.dfs(grid, x, y-1, row, col)
self.dfs(grid, x, y+1, row, col)
广度优先搜索(Breadth First Search)
938-二叉树的搜索范围
给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和
输入:root = [10,5,15,3,7,null,18], low = 7, high = 15
输出:32
- 方法1:递归法
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rangeSumBST(self, root: TreeNode, low: int, high: int) -> int:
# 递归法
result = 0
# 递归的终止条件
if root is None:
return 0
leftSum = self.rangeSumBST(root.left, low, high) # 左子树符合条件的和
rightSum = self.rangeSumBST(root.right, low, high) # 右子树符合条件的和
result = leftSum + rightSum
if root.val >= low and root.val <= high: # 条件符合的根结点值
result += root.val
return result
- 方法2:BFS
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rangeSumBST(self, root: TreeNode, low: int, high: int) -> int:
result = 0
queue = []
queue.append(root)
while len(queue) > 0: # 大循环控制整体结束
size = len(queue)
while size > 0: # 小循环控制每一层是否遍历完成
cur = queue.pop()
if cur.val >= low and cur.val <= high:
result += cur.val
if cur.left is not None:
queue.append(cur.left)
if cur.right is not None:
queue.append(cur.right)
size -= 1
return result
102-二叉树的层序遍历
输入:[3,9,20,null,null,15,7],
输出:[
[3],
[9,20],
[15,7]
]
- 方法1:BFS
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
# 方法1:广度优先搜索-按照层进行遍历
result = []
if root is None:
return result
queue = [] # 辅助队列
queue.append(root)
while len(queue) > 0:
size = len(queue) # size控制遍历的层数
list = []
while size > 0:
cur = queue.pop(0)
list.append(cur.val)
if cur.left is not None:
queue.append(cur.left)
if cur.right is not None:
queue.append(cur.right)
size = size - 1
result.append(list)
return result
- 方法2:DFS
求解过程:
树的层数=子数组的个数
第一次循环:[3], [9], [15]
第二次循环:[3], [9,20],[15,7]
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
# 方法2:深度优先搜索-一条路走到黑
result = []
if root is None:
return result
self.dfs(root, result, 0) # 从第零层开始
return result
'''
node: 当前的节点
level:当前的层数
'''
# 递归终止条件
if node is None:
return
if level > len(result)-1: # 当前的层数大于result数组的长度-1时(0>-1)
result.append([]) # 添加一个空数组,保证可以添加该层的元素
result[level].append(node.val) # 添加元素到指定的层
if node.left is not None:
self.dfs(node.left, result, level+1)
if node.right is not None:
self.dfs(node.right, result, level+1)
107-二叉树的层序遍历
给定一个二叉树,返回其节点值自底向上的层序遍历。
[
[15,7],
[9,20],
[3]
]
- 方法1:BFS+链表法
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
from collections import deque
class Solution:
def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
# # BFS
result = []
if root is None:
return result
queue = deque([]) # 创建辅助链表插入值,减小时间复杂度
queue.append(root)
temp = deque([])
while len(queue) > 0:
size = len(queue)
ls = []
while size > 0:
cur = queue.popleft() # 链表取出第一个元素
ls.append(cur.val)
if cur.left is not None:
queue.append(cur.left)
if cur.right is not None:
queue.append(cur.right)
size = size - 1
temp.appendleft(ls[:]) # 插入第一个位置
return list(temp)
- 方法2:DFS+结果反转
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
#DFS
result = []
if root is None:
return result
self.dfs(root, result, 0)
result.reverse()
return result # 把结果反转之后输出
def dfs(self, node, result, level):
if node is None: # 递归的终止条件
return
if level > len(result) - 1:
result.append([])
result[level].append(node.val)
if node.left is not None:
self.dfs(node.left, result, level+1)
if node.right is not None:
self.dfs(node.right, result, level + 1)
200-岛屿数量
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
# bfs
if grid is None or len(grid) == 0:
return 0
result = 0
row = len(grid)
col = len(grid[0])
q = [] # 辅助队列
for i in range(0, row):
for j in range(0, col):
if grid[i][j] == '1':
result = result + 1
q.append([i,j])
grid[i][j] = '0'
while len(q) > 0:
cur = q.pop(0) # 出队
x = cur[0]
y = cur[1]
# 如果位置坐标为‘1’同化之后添加坐标至队列中,
# 如果同化全部完成,坐标只会出列
if x-1 >= 0 and grid[x-1][y] == '1':
grid[x-1][y] = '0'
q.append([x-1, y])
if y-1 >= 0 and grid[x][y-1] == '1':
grid[x][y-1] = '0'
q.append([x, y-1])
if x+1 < row and grid[x+1][y] == '1':
grid[x+1][y] = '0'
q.append([x+1, y])
if y+1 < col and grid[x][y+1] == '1':
grid[x][y+1] = '0'
q.append([x, y+1])
return result
- 总结:广度优先搜索用到了队列,深度优先搜索用到了递归
并查集(Union Find)
200-岛屿数量
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
# 并查集算法
if grid == None or len(grid) == 0:
return 0
row = len(grid)
col = len(grid[0])
waters = 0 # 水域
uf = UnionFind(grid) # 实例化类
for i in range(0, row):
for j in range(0, col):
if grid[i][j] == '0':
waters = waters + 1
# 实现同化
else:
directions = [[0,1], [0,-1], [1,0], [-1,0]]
for d in directions:
x = i + d[0]
y = j + d[1]
if x>=0 and x<row and y>=0 and y<col and grid[x][y]=='1':
uf.union(x*col+y, i*col+j)
# 输出:(岛屿数+水域数) - 水域数
return uf.getCount() - waters
class UnionFind:
def __init__(self, grid):
# 由二维数组构建一个一维数组
row = len(grid)
col = len(grid[0])
self.count = row * col
self.root = [-1] * row * col # 长度
for i in range(0, row * col):
self.root[i] = i # 每一个树都有自己独立的祖先
# 寻找祖先
def find(self, x):
if x == self.root[x]:
return self.root[x]
else:
self.root[x] = self.find(self.root[x])
return self.root[x]
# 只有找到最终的祖先后才union
def union(self, x, y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
self.root[rootx] = rooty
self.count -= 1
# 返回合并之后的 岛屿数+水域数
def getCount(self):
return self.count
547
贪心(Greedy)
322
1217-玩筹码
数轴上放置了一些筹码,每个筹码的位置存在数组 chips 当中。
你可以对 任何筹码 执行下面两种操作之一(不限操作次数,0 次也可以):
将第 i 个筹码向左或者右移动 2 个单位,代价为 0。
将第 i 个筹码向左或者右移动 1 个单位,代价为 1。
最开始的时候,同一位置上也可能放着两个或者更多的筹码。
返回将所有筹码移动到同一位置(任意位置)上所需要的最小代价。
输入:chips = [1,2,3]
输出:1
解释:第二个筹码移动到位置三的代价是 1,第一个筹码移动到位置三的代价是 0,总代价为 1。
- 方法1:规律法
- 因为移动两个单位的代价为0
因此把在偶数位置上的所有筹码移动到同一个偶数位置上所用的代价为0
同理可得奇数位置上的所有筹码移动到同一个奇数位置上所用的代价为0
所以可以实现将筹码移动到相邻的两个奇数和偶数位置上面 - 因为移动一个单位的代价为1
所以要想代价最小,就把位置上较小数目的筹码移动到较多数目筹码的位置上
class Solution:
def minCostToMoveChips(self, position: List[int]) -> int:
first = 0 # 奇数位置
second = 0 # 偶数位置
for p in position:
if p % 2 == 0: # 偶数个数
first += 1
else: # 奇数个数
second += 1
return min(first, second)
- 方法2:贪心法
先用代价为0的方法进行移动,再用代价为1的方式进行移动
class Solution:
def minCostToMoveChips(self, position: List[int]) -> int:
zero = 0
for p in position:
if p % 2 == 0:
zero += 1
two = len(position) - zero
return min(zero, two)
55-跳跃游戏
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步(<=2),从下标 0 到达下标 1;然后再从下标 1 跳 3 步(<=3)到达最后一个下标。
- 贪心法:每次维护可以跳到的最远距离
class Solution:
def canJump(self, nums: List[int]) -> bool:
max_distance = nums[0] # 设定可以达到的最大坐标
for i in range(1, len(nums) - 1):
if i <= max_distance: # 表示当前坐标可以达到
max_distance = max(max_distance, i + nums[i]) # 更新可以达到的最远坐标
else:
break
return max_distance >= len(nums) - 1
class Solution:
def canJump(self, nums: List[int]) -> bool:
max_distance = 0
for i in range(len(nums)):
if i <= max_distance:
max_distance = max(max_distance, i+nums[i])
if max_distance >= len(nums) - 1:
return True
return False
45-跳跃游戏Ⅱ
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
- 方法1:贪心
如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。
class Solution:
def jump(self, nums: List[int]) -> int:
n = len(nums)
maxPos, end, step = 0, 0, 0
for i in range(n-1):
if i <= maxPos:
maxPos = max(maxPos, nums[i] + i)
if i == end:
end = maxPos
step += 1
return step
- 方法2:动态规划
class Solution:
def jump(self, nums: List[int]) -> int:
# 动态规划
n = len(nums)
dp = [n+1]*n
dp[0] = 0
for i in range(n):
for j in range(1, nums[i]+1):
if i + j >= n:
return dp[len(dp)-1]
dp[i+j] = min(dp[i+j], dp[i]+1)
return dp[len(dp)-1]
记忆化搜索(Memorization)
剑指offer10-斐波拉契数列
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
a = 0
b = 1
for i in range(2, n+1):
# res = a + b
# a = b
# b = res
a, b = b, a+b
return b % 1000000007
#答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
322-零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
- 记忆化搜索
F(S)=F(S−C)+1
动态规划(Dynamic Programming)
509-斐波拉契数列
输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
- 方法1:动态规划
当n=0时,f(0)=0; 当n=1时,f(1)=1
当n>1时: dp[i] = dp[i-1] + dp[i-2]
class Solution:
def fib(self, n: int) -> int:
# 方法3:动态规划
if n < 2:
return n
dp = [0]*(n+1)
dp[0] = 0
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
62-不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
输入:m = 3, n = 7
输出:28
- 方法:动态规划
求解思路:
当m=0,n=0时:dp[0][0]=0
状态方程:dp[i][j] = dp[i-1][j] + dp[i][j-1]
当前路径 = 上方表格路径+左方表格路径
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0 for i in range(n)] for j in range(m)]
dp[0][0] = 1
for i in range(m):
for j in range(n):
# 左边有有效值
if i-1 >= 0 and i-1 < m:
dp[i][j] = dp[i][j] + dp[i-1][j]
# 右边有有效值
if j-1 >= 0 and j-1 < n:
dp[i][j] = dp[i][j] + dp[i][j-1]
return dp[m-1][n-1]
121-买卖股票的最佳时机
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
- 方法1:暴力求解-两层循环
# 时间复杂度较高,计算超时
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
ans = 0
for i in range(n):
for j in range(i+1, n):
ans = max(ans, prices[j] - prices[i])
return ans
- 方法2:遍历一次
对于股票购买,最大利润就是“低价买入,高价卖出”
所以,只需要找出所给数据的最大值和最小值,但需要注意的是,最小值必须在最大值之前
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if prices is None:
return 0
min_price = max(prices) # 初始最小价格这么设定的目的是为了返回利润是0的情况
max_profit = 0
for p in prices:
if p < min_price: # 找到最小值
min_price = p
elif p - min_price > max_profit: # 找到最小值的情况下求利润
max_profit = p - min_price
return max_profit
class Solution:
def maxProfit(self, prices: List[int]) -> int:
min_price = max(prices) + 1
max_profit = 0
for p in prices:
min_price = min(min_price, p)
max_profit = max(max_profit, p-min_price)
return max_profit
- 方法3:动态规划
状态方程:dp[i]=max(dp[i−1],prices[i]−minprice)
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
if n == 0:
return 0
dp = [0]*n
min_price = prices[0]
for i in range(1, n):
min_price = min(min_price, prices[i])
dp[i] = max(dp[i-1], prices[i]-min_price)
return dp[-1]
70-爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
输入:2
输出:2
解释:有两种方法可以爬到楼顶。1 阶 + 1 阶;2 阶
- 方法1:数学归纳法
# n = 1: 1
# n = 2: 2 1+1=2
# n = 3: 3 1+1+1=1+2=2+1
# n = 4: 5 1+1+1+1=1+2+1=2+2=2+1+1=1+1+2
# n = n: f(n) = f(n-1) + f(n-2)
class Solution:
def climbStairs(self, n: int) -> int:
# 方法1:数学归纳法
if n <= 2:
return n
res = 0
n1 = 1
n2 = 2
for i in range(3, n+1):
res = n1 + n2
n1 = n2
n2 = res
return res
- 方法2:动态规划
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 2:
return n
dp = [0]*n
dp[0] = 1
dp[1] = 2
for i in range(2, n):
dp[i] = dp[i-1] + dp[i-2]
return dp[-1]
279-完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
- 动态规划:
状态转移方程:dp[i] = min(dp[i], dp[i-j*j]+1)
class Solution:
def numSquares(self, n: int) -> int:
# n=1 1 1=1
# n=2 1 2=1+1
# n=3 3 3=1+1+1
# n=4 4 4=1+1+1+1
# n=5 2 5=4+1
dp = [0]*(n+1)
for i in range(1, n+1):
dp[i] = i
j = 1
while i-j*j >= 0:
dp[i] = min(dp[i], dp[i-j*j]+1)
j += 1
return dp[n]
322-零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
- 方法:动态规划
f(i) | 最小硬币数量 |
---|---|
f(i) | i<0时忽略f(i) |
f(0) | 0 |
f(1) | 1:f(1)=min(f(1-1), f(1-2), f(1-5))+1=1 |
f(2) | 1:f(2)=min(f(2-1), f(2-2), f(2-5))+1=1 |
f(3) | 2:f(3)=min(f(3-1), f(3-2), f(3-5))+1=2 |
f(4) | 2:f(4)=min(f(4-1), f(4-2), f(4-5))+1=2 |
... | ... |
f(11) | 1:f(11)=min(f(11-1), f(11-2), f(11-5))+1=3 |
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
# 初始化动态数组,每一项为无穷大
dp = [float('inf')]*(amount+1)
dp[0] = 0
for coin in coins:
for i in range(coin, amount+1):
dp[i] = min(dp[i], dp[i-coin]+1)
if dp[amount] != float('inf'):
return dp[amount]
else:
return -1
前缀树/字典树(Trie)
208-实现Trie(前缀树)
- Trie() 初始化前缀树对象。
- void insert(String word) 向前缀树中插入字符串 word 。
- boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
- boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]
class Trie:
def __init__(self):
self.child = collections.defaultdict(dict) # 用字典存储单词
def insert(self, word: str) -> None:
nownode = self.child
for s in word:
if s not in nownode.keys():
nownode[s] = collections.defaultdict(dict) # 创建新的节点
nownode = nownode[s]
nownode['#'] = '#' #有一定的局限性 前提是单词里不能有结束符
def search(self, word: str) -> bool:
nownode = self.child
for s in word:
if s in nownode.keys():
nownode = nownode[s]
else:
return False
return '#' in nownode.keys() # 以标识符结尾
def startsWith(self, prefix: str) -> bool:
nownode = self.child
for s in prefix:
if s in nownode.keys():
nownode = nownode[s]
else:
return False
return True
720-字典中最长字串
输入:
words = ["w","wo","wor","worl", "world"]
输出:"world"
解释:
单词"world"可由"w", "wo", "wor", 和 "worl"添加一个字母组成。
输入:
words = ["a", "banana", "app", "appl", "ap", "apply", "apple"]
输出:"apple"
解释:
"apply"和"apple"都能由词典中的单词组成。但是"apple"的字典序小于"apply"。
- 方法:循环比较
class Solution:
def longestWord(self, words: List[str]) -> str:
if words is None or len(words)==0:
return ''
result = ''
hashset = set(words) # 集合之后加快计算速度
for word in words:
# 当更长的字符串出现 或者 当两个字符串长度相等
if len(word)>len(result) or (len(word)==len(result) and word < result):
# 长度符合规则时比较公共前缀
isWord = True
l = len(word)
for i in range(0, l):
if word[0:i+1] not in hashset: # 不满足公共前缀
isWord = False
break
if isWord:
result = word
else:
result = result
return result
- 方法2:前缀树
class Solution:
def longestWord(self, words: List[str]) -> str:
if words is None or len(words)==0:
return ''
root = Trie()
# 1 将所有元素添加到字典树里面
for word in words:
cur = root
for c in word:
if c in cur.children:
cur = cur.children[c]
else:
newnode = Trie()
cur.children[c] = newnode # 添加新的节点
cur = newnode
cur.val = word # 存入当前的字符串
cur.isEnd = True # 标记字符串结束
# 查找最长单词
result = ''
for word in words:
cur = root
if (len(word) > len(result) or (len(word)==len(result) and word<result)):
isWord = True
for c in word:
cur = cur.children[c]
if not cur.isEnd: # 不满足公共前缀
isWord = False
break
result = word if isWord else result
return result
class Trie:
def __init__(self):
self.children = {}
self.isEnd = False
self.val = ''
692-前k个高频单词
输入: ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i" 和 "love" 为出现次数最多的两个单词,均为2次。
注意,按字母顺序 "i" 在 "love" 之前。
- 方法1:字典法
class Solution:
def topKFrequent(self, words: List[str], k: int) -> List[str]:
word_freq = collections.Counter(words)
rec = list(word_freq.keys())
rec.sort(key = lambda x: (-1 * word_freq[x], x))
return rec[:k]
- 方法2:堆排序法
class Solution:
def topKFrequent(self, words: List[str], k: int) -> List[str]:
words_count = Counter()
for word in words: words_count[word] += 1
return list(map(lambda x: x[0], heapq.nsmallest(k, words_count.items(), key=lambda x: (-x[1], x[0]))))