# 注:下面按照算法类别由浅入深,把下面罗列的这些题刷完,并且多看这些题不同的解法(国际版mostvotes),看懂之后估计就不会有太大的问题啦~
整体框架
数据结构:
一维:
基础: 数组array(string),链表linked list
高级 : 栈stack,队列queue,双端队列deque,集合set,映射 map(hash or map), etc
二维:
基础:树tree,图graph
高级: 二叉搜索树binary search tree(red-black tree,AVL),堆heap,并查集disjoint set,字典树Trie
特殊:
位运算Bitwise,布隆过滤器BloomFilter
LRU Cache
算法:
递归Recursion
搜索Search: 深度优先搜索Depth first search,广度优先搜索Breadth first search
动态规划 Dynamic Programming
二分查找 Binary Search
贪心 Greedy
数组&链表部分
一、数组部分
数组的常见写法: Java,C++: int a[50] ; Python: list = []
注:高级数据语言可以不指定类型语言
数组底层硬件实现,有一个内存管理器,每当申请一个数组时,计算机会在内存中开辟一段连续的地址,每一个地址都可以直接通过内存管理器直接访问,访问任意一个位置,时间复杂度都是一致的都是O(1)
数组的插入和删除都需要O(n)的时间复杂度
看一下Java源码,数组插入和删除的操作过程:
http://developer.classpath.org/doc/java/util/ArrayList-source.html
329: /**
330: * Appends the supplied element to the end of this list.
331: * The element, e, can be an object of any type or null.
332: *
333: * @param e the element to be appended to this list
334: * @return true, the add will always succeed
335: */
336: public boolean add(E e)
337: {
338: modCount++;
339: if (size == data.length)
340: ensureCapacity(size + 1);
341: data[size++] = e;
342: return true;
343: }
数组添加一个元素e,modCount是一个计数器,ensureCapacity保证数组的容量,末尾插入一个元素e
345: /**
346: * Adds the supplied element at the specified index, shifting all
347: * elements currently at that index or higher one to the right.
348: * The element, e, can be an object of any type or null.
349: *
350: * @param index the index at which the element is being added
351: * @param e the item being added
352: * @throws IndexOutOfBoundsException if index < 0 || index > size()
353: */
354: public void add(int index, E e)
355: {
356: checkBoundInclusive(index);
357: modCount++;
358: if (size == data.length)
359: ensureCapacity(size + 1);
360: if (index != size)
361: System.arraycopy(data, index, data, index + 1, size - index);
362: data[index] = e;
363: size++;
364: }
指定位置index处插入元素e, checkBoundInclusive-检查是都越界,ensureCapacity保证数组容量,arraycopy-对数组序列进行移动
463: /**
464: * Checks that the index is in the range of possible elements (inclusive).
465: *
466: * @param index the index to check
467: * @throws IndexOutOfBoundsException if index > size
468: */
469: private void checkBoundInclusive(int index)
470: {
471: // Implementation note: we do not check for negative ranges here, since
472: // use of a negative index will cause an ArrayIndexOutOfBoundsException,
473: // a subclass of the required exception, with no effort on our part.
474: if (index > size)
475: throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
476: + size);
477: }
checkBoundInclusive检查是否越界
160: /**
161: * Guarantees that this list will have at least enough capacity to
162: * hold minCapacity elements. This implementation will grow the list to
163: * max(current * 2, minCapacity) if (minCapacity > current). The JCL says
164: * explictly that "this method increases its capacity to minCap", while
165: * the JDK 1.3 online docs specify that the list will grow to at least the
166: * size specified.
167: *
168: * @param minCapacity the minimum guaranteed capacity
169: */
170: public void ensureCapacity(int minCapacity)
171: {
172: int current = data.length;
173:
174: if (minCapacity > current)
175: {
176: E[] newData = (E[]) new Object[Math.max(current * 2, minCapacity)];
177: System.arraycopy(data, 0, newData, 0, size);
178: data = newData;
179: }
180: }
ensureCapacity-确保数组的容量
数组的删除操作和插入操作同理,也会用到相应的函数
366: /**
367: * Removes the element at the user-supplied index.
368: *
369: * @param index the index of the element to be removed
370: * @return the removed Object
371: * @throws IndexOutOfBoundsException if index < 0 || index >= size()
372: */
373: public E remove(int index)
374: {
375: checkBoundExclusive(index);
376: E r = data[index];
377: modCount++;
378: if (index != --size)
379: System.arraycopy(data, index + 1, data, index, size - index);
380: // Aid for garbage collection by releasing this pointer.
381: data[size] = null;
382: return r;
383: }
二、链表部分(Linked List)
链表部分弥补前面数组部分的缺点(插入和删除时间复杂度过高)
每一个元素都需要定义一个class,头指针用head表示,尾指针用tail表示,最后一个元素next指针指向空,因为没有next指针啦;如果tail 的next指针指向head时,为循环链表
Java-Linked List源码:
http://developer.classpath.org/doc/java/util/LinkedList-source.html
链表的插入和删除操作没有引起链表的群移操作,也不需要复制元素
操作
时间复杂度(linkedList vs Array)
prepend(前向插入)
O(1) vs O(1)
append(后向插入) O(1) vs O(1)
lookup(查询)
O(n) vs O(1)
insert(插入)
O(1) vs O(n)
delete(删除)
O(1) vs O(n)
从图中可以看出,查询操作时间复杂度为O(n),也是链表的问题所在,所以可以看出并没有一种完美的数据结构,根据不同的需求用相应的数据结构即可
三、跳表
常见于redis中,链表的缺点在于查询时间复杂度为O(n),对链表查询进行加速,中心思想-空间换时间
原始链表-时间复杂度:O(n) 简单优化:添加头尾指针 ,多添加指针会更快
一级索引加速,速度可以加快,还可以增加第二级索引,更快加快速度,通常增加log2n级索引,跳表时间复杂度为logn。例如一个链表原始长度为1024,按照原始查询方式需要查1024次,如果按照跳表进行查询,只需要查询10次。增加和删除操作,跳表需要更新一次。跳表的时间复杂度为O(logn),空间复杂度为O(n)。
redis应用跳表参考链接:
https://redisbook.readthedocs.io/en/latest/internal-
datastruct/skiplist.html
四、实战题目
题目1:
Leetcode-283-移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,
同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
解法一:双指针从前到后进行遍历,遇到不是0的元素往前移,0元素放在后面
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
j = 0
for i in range(len(nums)):
if nums[i]!=0:
nums[j] = nums[i]
if i!=j:
nums[i] = 0
j += 1
效率:
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 52 ms 14.5 MB Python3
解法二:遍历数组,构建一个索引,遇到不是零的元素,则添加到数组前面,
直到遍历完数组,index-至数组末尾添加0
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
index = 0
for i in range(len(nums)):
if nums[i]!=0:
nums[index] = nums[i]
index +=1
for i in range(index,len(nums)):
nums[i] = 0
性能:
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 88 ms 14.4 MB Python3
Most votes:
// Shift non-zero values as far forward as possible
// Fill remaining space with zeros
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0) return;
int insertPos = 0;
for (int num: nums) {
if (num != 0) nums[insertPos++] = num;
}
while (insertPos < nums.length) {
nums[insertPos++] = 0;
}
}
# in-place
def moveZeroes(self, nums):
zero = 0 # records the position of "0"
for i in xrange(len(nums)):
if nums[i] != 0:
nums[i], nums[zero] = nums[zero], nums[i]
zero += 1
题目2
Leetcode-11-盛最多水的容器
给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。
在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,
容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
解法一、暴力解法:
class Solution:
def maxArea(self, height: List[int]) -> int:
max_area = 0
for i in range(len(height)):
for j in range(i+1,len(height)):
max_area = max(max_area,min(height[j],height[i])
*(j-i))
return max_area
性能:超时
解法二、双指针
数组左侧一个指针,右侧一个指针,两个指针向中间移动,直到两者相遇;
算指针高度较小的指针的高度,指针移动。
class Solution:
def maxArea(self, height: List[int]) -> int:
i,j,max_area = 0,len(height)-1,0
while i<j:
if height[i] < height[j]:
max_area = max(max_area,height[i]*(j-i))
i += 1
else:
max_area = max(max_area,height[j]*(j-i))
j -= 1
return max_area
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 232 ms 14.7 MB Python3
MostVotes:
class Solution(object):
def maxArea(self, height):
"""
:type height: List[int]
:rtype: int
"""
MAX = 0
x = len(height) - 1
y = 0
while x != y:
if height[x] > height[y]:
area = height[y] * (x - y)
y += 1
else:
area = height[x] * (x - y)
x -= 1
MAX = max(MAX, area)
return MAX
int maxArea(vector<int>& height) {
int water = 0;
int i = 0, j = height.size() - 1;
while (i < j) {
int h = min(height[i], height[j]);
water = max(water, (j - i) * h);
while (height[i] <= h && i < j) i++;
while (height[j] <= h && i < j) j--;
}
return water;
}
题目3
Leetcode-70-爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入:2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
# 解法
class Solution:
# 只保存最近的三个值
def climbStairs(self, n: int) -> int:
if n<=2:
return n
else:
f1,f2,f3 = 1,2,3
for i in range(3,n+1):
f3 = f1 + f2
f1 = f2
f2 = f3
return f3
时间复杂度O(n),空间复杂度为O(1)
Time Submitted Status Runtime Memory Language
28 minutes ago Accepted 104 ms 13 MB python
官方给出两个比较新颖的解法:
Binets方法 && 斐波那契公式,详见
https://leetcode-cn.com/problems/climbing-stairs/solution
/pa-lou-ti-by-leetcode/
题目4
Leetcode-15-三数之和
精选题解(https://leetcode-cn.com/problems/3sum/solution/pai-xu-shuang-zhi-zhen-zhu-xing-jie-shi-python3-by/):
排序 + 双指针
本题的难点在于如何去除重复解。
算法流程:
特判,对于数组长度 nn,如果数组为 nullnull 或者数组长度小于 3,返回 []。
对数组进行排序。
遍历排序后数组:
若 nums[i]>0nums[i]>0:因为已经排序好,所以后面不可能有三个数加和等于 0,直接返回结果。
对于重复元素:跳过,避免出现重复解
令左指针 L=i+1,右指针 R=n-1,当 L<R时,执行循环:
当 nums[i]+nums[L]+nums[R]==0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,R 移到下一位置,寻找新的解
若和大于 0,说明 nums[R]太大,RR 左移
若和小于 0,说明 nums[L]太小,LL 右移
复杂度分析
时间复杂度:O(n^2),数组排序O(NlogN),遍历数组O(N),双指针遍历O(n),总体O(NlogN)+O(n)+O(n)*O(n) => O(n^2)
空间复杂度: O(1)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n=len(nums)
res=[]
if(not nums or n<3):
return []
nums.sort()
res=[]
for i in range(n):
if(nums[i]>0):
return res
if(i>0 and nums[i]==nums[i-1]):
continue
L=i+1
R=n-1
while(L<R):
if(nums[i]+nums[L]+nums[R]==0):
res.append([nums[i],nums[L],nums[R]])
while(L<R and nums[L]==nums[L+1]):
L=L+1
while(L<R and nums[R]==nums[R-1]):
R=R-1
L=L+1
R=R-1
elif(nums[i]+nums[L]+nums[R]>0):
R=R-1
else:
L=L+1
return res
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 1508 ms 16.6 MB Python3
题目5
Leetcode-206-反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
解法一:双指针迭代
思想,申请两个指针,一个指针指向空,一个指针指向头结点,然后遍历链表,每个元素指向空指针方向
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur:
item = cur.next
cur.next = pre
pre = cur
cur = item
return pre
时间复杂度为O(n),空间复杂度为O(1)
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 36 ms 14.2 MB Python3
递归-精选题解(https://leetcode-cn.com/problems/reverse-linked-list/solution/dong-hua-yan-shi-206-fan-zhuan-lian-biao-by-user74/)
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 递归终止条件是当前为空,或者下一个节点为空
if(head==None or head.next==None):
return head
# 这里的cur就是最后一个节点
cur = self.reverseList(head.next)
# 这里请配合动画演示理解
# 如果链表是 1->2->3->4->5,那么此时的cur就是5
# 而head是4,head的下一个是5,下下一个是空
# 所以head.next.next 就是5->4
head.next.next = head
# 防止链表循环,需要将head.next设置为空
head.next = None
# 每层递归函数都返回cur,也就是最后一个节点
return cur
题目6
Leetcode-24-两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
first_node = head
second_node = head.next
first_node.next = self.swapPairs(second_node.next)
second_node.next = first_node
return second_node
题目7
Leetcode-141-环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。
解法一:ListNode放在集合里,遍历列表,如果元素出现在集合里,则表示构成环
class Solution:
def hasCycle(self, head: ListNode) -> bool:
set_ = set()
while head:
if head in set_:
return True
set_.add(head)
head = head.next
return False
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 48 ms 16.5 MB Python3
解法二:快慢指针
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head or not head.next:
return False
i,j = head,head.next
while j and j.next:
if i==j:
return True
i,j = i.next,j.next.next
return False
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 76 ms 16.3 MB Python3
题目8
Leetcode-142-环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
se = set()
node = head
while node is not None:
if node in se:
return node
else:
se.add(node)
node = node.next
return node
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 60 ms 16.8 MB Python3
题目9
Leetcode-25-K 个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明 :
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换
优选解法-参考https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/kge-yi-zu-fan-zhuan-lian-biao-by-powcai/
题目10
Leetcode-26-删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
i = 0
for j in range(1,len(nums)):
if nums[i]!=nums[j]:
i +=1
nums[i] = nums[j]
return i + 1
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 88 ms 14.9 MB Python3
题目11
Leetcode-189-旋转数组
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k %= n
for i in range(k):
nums.insert(0,nums.pop())
题目12
Leetcode-21-合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if l1 is None:
return l2
elif l2 is None:
return l1
elif l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next,l2)
return l1
else:
l2.next = self.mergeTwoLists(l1,l2.next)
return l2
题目13
Leetcode-88-合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
方法一:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
nums1[:] = sorted(nums1[:m] + nums2[:n])
时间复杂度O(n+m)log(n+m),空间复杂度O(1)
方法二:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
res = []
p1,p2 = 0,0
nums1[:] = nums1[:m]
while p1 < m and p2 < n:
if nums1[p1] < nums2[p2]:
res.append(nums1[p1])
p1 += 1
else:
res.append(nums2[p2])
p2 += 1
if p1 < m:
res[p1+p2:] = nums1[p1:]
if p2 < n:
res[p1+p2:] = nums2[p2:]
nums1[:] = res[:]
时间复杂度O(n+m),空间复杂度O(m)
方法三:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
p1,p2,p = m-1,n-1,m+n-1
while p1 >= 0 and p2 >= 0:
if nums1[p1] < nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
nums1[:p2+1] = nums2[:p2+1]
时间复杂度O(m+n),空间复杂度O(1)
题目14
Leetcode-1-两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
map_ = {}
for idx,num in enumerate(nums):
map_[num] = idx
for i,num in enumerate(nums):
j = map_.get(target-num)
if j is not None and i!=j:
return [i,j]
题目15
Leetcode-66-加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
return list(map(int, list(str(int(''.join(map(str, digits))) + 1))))
栈、队列部分
一、栈和队列的优先特性
Stack & Queue 关键点
Stack: 先入后出:添加、删除皆为O(1)
Queue: 先入先出:添加、删除皆为O(1)
查询为O(n),元素是无序的
双端队列(Deque):栈和队列的结合体,可以从最前面或者最后面push或pop出来,插入和删除是O(1),查询是O(n)
Stack、Queue、Deque的工程实现
Java Stack 源码:http://developer.classpath.org/doc/java/util/Stack-source.html
Java Queue 源码:http://fuseyism.com/classpath/doc/java/util/Queue-source.html
1、Leetcode-20-有效的符号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
class Solution:
def isValid(self, s: str) -> bool:
dic = {'(':')','{':'}','[':']','a':'a'}
res = ['a']
if len(s) == 0:
return True
if len(s)%2==1:
return False
for ch in s:
if ch in dic:
res.append(ch)
elif dic[res.pop()]!=ch:
return False
return len(res)==1
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 20 ms 13.2 MB Python3
Mostvotes
class Solution:
# @return a boolean
def isValid(self, s):
stack = []
dict = {"]":"[", "}":"{", ")":"("}
for char in s:
if char in dict.values():
stack.append(char)
elif char in dict.keys():
if stack == [] or dict[char] != stack.pop():
return False
else:
return False
return stack == []
2、Leetcode-155-最小栈
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) -- 将元素 x 推入栈中。
pop() -- 删除栈顶的元素。
top() -- 获取栈顶元素。
getMin() -- 检索栈中的最小元素
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack_1,self.stack_2 = [],[]
def push(self, x: int) -> None:
self.stack_1.append(x)
if len(self.stack_2) ==0 or x<= self.stack_2[-1]:
self.stack_2.append(x)
else:
self.stack_2.append(self.stack_2[-1])
def pop(self) -> None:
if self.stack_1:
self.stack_2.pop()
return self.stack_1.pop()
def top(self) -> int:
if self.stack_1:
return self.stack_1[-1]
def getMin(self) -> int:
if self.stack_2:
return self.stack_2[-1]
3、Leetcode-84-柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积
输入: [2,1,5,6,2,3]
输出: 10
精选题解:
(https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/zhao-liang-bian-di-yi-ge-xiao-yu-ta-de-zhi-by-powc/)
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
stack = []
heights = [0] + heights + [0]
res = 0
for i in range(len(heights)):
while stack and heights[stack[-1]] > heights[i]:
tmp = stack.pop()
res = max(res, (i - stack[-1] - 1) * heights[tmp])
stack.append(i)
return res
4、Leetcode-239-滑动窗口的最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
if len(nums) * k == 0:
return []
return [max(nums[i:i+k]) for i in range(0,len(nums)-k+1)]
Mostvotes可查看:https://leetcode.com/problems/sliding-window-maximum/discuss/?currentPage=1&orderBy=most_votes&query=
5、Leetcode-641-设计循环双端队列
设计实现双端队列。
你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
Mostvotes:
(https://leetcode.com/problems/design-circular-deque/discuss/154055/python3-using-list-easy-to-understand)
class MyCircularDeque:
def __init__(self, k):
"""
Initialize your data structure here. Set the size of the deque to be k.
:type k: int
"""
self._size = 0
self._front, self._rear = 0, 0
self._capacity = k
self._data = [-1] * k
def insertFront(self, value):
"""
Adds an item at the front of Deque. Return true if the operation is successful.
:type value: int
:rtype: bool
"""
if self.isFull():
return False
if self.isEmpty():
self._data[self._front] = value
else:
self._front = (self._front - 1) % self._capacity
self._data[self._front] = value
self._size += 1
return True
def insertLast(self, value):
"""
Adds an item at the rear of Deque. Return true if the operation is successful.
:type value: int
:rtype: bool
"""
if self.isFull():
return False
if self.isEmpty():
self._data[self._rear] = value
else:
self._rear = (self._rear + 1) % self._capacity
self._data[self._rear] = value
self._size += 1
return True
def deleteFront(self):
"""
Deletes an item from the front of Deque. Return true if the operation is successful.
:rtype: bool
"""
if self.isEmpty():
return False
self._data[self._front] = -1
self._front = (self._front + 1) % self._capacity
self._size -= 1
if self.isEmpty():
self._rear = self._front
return True
def deleteLast(self):
"""
Deletes an item from the rear of Deque. Return true if the operation is successful.
:rtype: bool
"""
if self.isEmpty():
return False
self._data[self._rear] = -1
self._rear = (self._rear - 1) % self._capacity
self._size -= 1
if self.isEmpty():
self._front = self._rear
return True
def getFront(self):
"""
Get the front item from the deque.
:rtype: int
"""
return self._data[self._front]
def getRear(self):
"""
Get the last item from the deque.
:rtype: int
"""
return self._data[self._rear]
def isEmpty(self):
"""
Checks whether the circular deque is empty or not.
:rtype: bool
"""
return self._size == 0
def isFull(self):
"""
Checks whether the circular deque is full or not.
:rtype: bool
"""
return self._size == self._capacity
6、Leetcode-42-接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
下面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)
Mostvotes
https://leetcode.com/problems/trapping-rain-water/discuss/17554/Share-my-one-pass-Python-solution-with-explaination
def trap(self, bars):
if not bars or len(bars) < 3:
return 0
volume = 0
left, right = 0, len(bars) - 1
l_max, r_max = bars[left], bars[right]
while left < right:
l_max, r_max = max(bars[left], l_max), max(bars[right], r_max)
if l_max <= r_max:
volume += l_max - bars[left]
left += 1
else:
volume += r_max - bars[right]
right -= 1
return volume
哈希表、映射、集合部分
哈希表(Hash table),也叫散列表,是根据关键码值(key value)而直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做哈希表(或散列表)。
哈希表复杂度分析:插入、删除、大部分情况下时间复杂度都是O(1),最坏 情况哈比表的长度比较小,导致多次哈希碰撞,时间复杂度退化成O(n)
1、Leetcode-242-有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
解法:
1、暴力,sorted,两个字符串是否相等 O(NlogN)
2、hash,map --> 统计每个字符的频次
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
dict_1 = {}
dict_2 = {}
for item in s:
dict_1[item] = dict_1.get(item,0) + 1
for item in t:
dict_2[item] = dict_2.get(item,0) + 1
return dict_2 == dict_1
提交时间 提交结果 执行用时 内存消耗 语言
几秒前 通过 56 ms 13.5 MB Python3
2、Leetcode-49-字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
import collections
dic = collections.defaultdict(list)
for c in strs:
count = [0] * 26
for _ in c:
count[ord(_) - ord('a')] += 1
dic[tuple(count)].append(c)
return dic.values()
树、二叉树、二叉搜索树部分
二维数据结构-树和图,区别是否构成环
链表是特殊化的树,树是特殊化的图
二叉树遍历 Pre-order/In-order/Post-order
1、前序(Pre-order):根-左-右
2、中序(In-order): 左-根-右
3、后序(Post-order): 左-右-根
二叉搜索树(Binary Search Tree),查询等操作时间复杂度都是log(n),最坏情况是变成链表形式复杂度变为O(n)
二叉搜索树,也称有序二叉树、排序二叉树,是指一颗空树或者具有下列性质的二叉树:
-
左子树所有节点的值均小于它的根节点的值
-
右子树所有节点的值均大于它的根节点的值
-
重复:左右子树也分别为二叉查找树
1、Leetcode-94-二叉树中序遍历
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解法一:递归:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
self.helper(root,res)
return res
def helper(self,root:TreeNode,res:List):
if root:
if root.left:
self.helper(root.left,res)
res.append(root.val)
if root.right:
self.helper(root.right,res)
解法二:颜色标记
(参考:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
white,Gary = 0,1
res = []
stack = [(white,root)]
while stack:
color,node = stack.pop()
if node is None:continue
if color == white:
stack.append((white,node.right))
stack.append((Gary,node))
stack.append((white,node.left))
else:
res.append(node.val)
return res
2、Leetcode-144-二叉树前序遍历
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
self.helper(root,res)
return res
def helper(self,root:TreeNode,res:List):
if root:
res.append(root.val)
if root.left:
self.helper(root.left,res)
if root.right:
self.helper(root.right,res)
3、Leetcode-590-N叉树的后序遍历
官方题解(https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/solution/ncha-shu-de-hou-xu-bian-li-by-leetcode/)
"""
# Definition for a Node.
class Node:
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
class Solution:
def postorder(self, root: 'Node') -> List[int]:
if root is None:
return []
stack,output = [root],[]
while stack:
root = stack.pop()
if root:
output.append(root.val)
for c in root.children:
stack.append(c)
return output[::-1]
复杂度分析:
时间复杂度:时间复杂度:O(M),其中 M 是 N 叉树中的节点个数。每个节点只会入栈和出栈各一次。
空间复杂度:O(M)。在最坏的情况下,这棵 N 叉树只有 2 层,所有第 2 层的节点都是根节点的孩子。将根节点推出栈后,需要将这些节点都放入栈,共有 M - 1个节点,因此栈的大小为O(M)
4、Leetcode-589-N叉树的前序遍历
"""
# Definition for a Node.
class Node:
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
class Solution:
def preorder(self, root: 'Node') -> List[int]:
if root is None:
return []
stack,output = [root],[]
while stack:
root = stack.pop()
output.append(root.val)
stack.extend(root.children[::-1])
return output
时间复杂度与空间复杂度如上分析所示
5、Leetcode-429-N叉树的层次遍历
"""
# Definition for a Node.
class Node:
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
class Solution:
def levelOrder(self, root: 'Node') -> List[List[int]]:
if root is None:
return []
result,previous_layer = [],[root]
while previous_layer:
current_layer = []
result.append([])
for node in previous_layer:
result[-1].append(node.val)
current_layer.extend(node.children)
previous_layer = current_layer
return result
复杂度分析:
时间复杂度:O(n)。n指的是节点的数量。
空间复杂度:O(n)。
泛型递归、树的递归
递归
递归-循环
通过函数体来进行的循环
Python 代码模板
def recursion(level,param1,param2,...):
# 终止条件
if level > MAX_LEVEL:
process_result
return
# 过程逻辑
process(level,data...)
# 下一层
self.recursion(level+1,p1,..
思维要点:
1、不要人肉递归
2、拆解可重复解决的问题(重复子问题)
3、数学归纳法思维
1、Leetcode-70-爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入:2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
class Solution:
# 只保存最近的三个值
def climbStairs(self, n: int) -> int:
if n<=2:
return n
else:
f1,f2,f3 = 1,2,3
for i in range(3,n+1):
f3 = f1 + f2
f1 = f2
f2 = f3
return f3
2、Leetcode-22-括号生成
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
def recursion(s='',left=0,right=0):
if len(s)==2*n:result.append(s)
if left < n:
recursion(s + '(',left+1,right)
if left > right:
recursion(s + ')',left,right+1)
recursion()
return result
3、Leetcode-226-翻转二叉树
翻转一棵二叉树。
示例:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
root.left,root.right = root.right,root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
4、Leetcode-98-验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def helper(node,lower = float("-inf"),upper = float("inf")):
if not node:
return True
val = node.val
if val <= lower or val >= upper:
return False
if not helper(node.left,lower,val):
return False
if not helper(node.right,val,upper):
return False
return True
return helper(root)
时间复杂度为O(N),空间复杂度为O(N)
5、Leetcode-104-二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left) + 1, self.maxDepth(root.right)+1)
时间复杂度为O(n),空间复杂度为O(logn),logn为树的高度
6、Leetcode-111-二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度2
方法1:
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:return 0
if not root.left:return self.minDepth(root.right) + 1
if not root.right:return self.minDepth(root.left) + 1
return min(self.minDepth(root.left),self.minDepth(root.right)) + 1
方法2:
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
children = [root.left,root.right]
if not any(children):
return 1
min_d = float("inf")
for c in children:
if c:
min_d = min(self.minDepth(c),min_d)
return min_d + 1
时间复杂度为O(n),空间复杂度为O(logn)
7、Leetcode-297-二叉树的序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例:
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
官方题解(https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/solution/er-cha-shu-de-xu-lie-hua-yu-fan-xu-lie-hua-by-leet/)
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
def rserialize(root,string):
if root is None:
string += "None,"
else:
string += str(root.val) + ","
string = rserialize(root.left,string)
string = rserialize(root.right,string)
return string
return rserialize(root,"")
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
def rdeserialize(l):
if l[0] == "None":
l.pop(0)
return None
root = TreeNode(l[0])
l.pop(0)
root.left = rdeserialize(l)
root.right = rdeserialize(l)
return root
data_list = data.split(',')
root = rdeserialize(data_list)
return root
时间复杂度为O(n),空间复杂度为O(n)
# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))
8、Leetcode-236-二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
官方题解(https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/er-cha-shu-de-zui-jin-gong-gong-zu-xian-by-leetcod/)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def __init__(self):
self.ans = None
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
def recurse_tree(current_node):
if not current_node:
return False
left = recurse_tree(current_node.left)
right = recurse_tree(current_node.right)
mid = current_node == p or current_node == q
if mid + left + right >= 2:
self.ans = current_node
return mid or left or right
recurse_tree(root)
return self.ans
时间复杂度O(n),空间复杂度O(n)
9、Leetcode-105-从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if len(inorder) == 0:
return None
root = TreeNode(preorder[0])
mid = inorder.index(preorder[0])
root.left = self.buildTree(preorder[1:mid+1],inorder[:mid])
root.right = self.buildTree(preorder[mid+1:],inorder[mid+1:])
return root
10、Leetcode-77-组合
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
def backtrack(first = 1,curr = []):
if len(curr) == k:
output.append(curr[:])
for i in range(first,n+1):
curr.append(i)
backtrack(i+1,curr)
curr.pop()
output = []
backtrack()
return output
11、Leetcode-46-全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [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]]:
def backtrack(first=0):
if first == n:
output.append(nums[:])
for i in range(first,n):
nums[first],nums[i] = nums[i],nums[first]
backtrack(first+1)
nums[i],nums[first] = nums[first],nums[i]
n = len(nums)
output = []
backtrack()
return output
分治、回溯部分
分治、回溯是递归的一种,本质是将重复性问题分解以及最后组合每个子问题的结果
分治代码模板:
def divide_conquer(problem,param1,param2,...):
# 递归终止条件
if problem is None:
print_result
return
# 准备数据
data = prepare_data(problem)
subproblems = split_problem(problem,data)
# 分治 子问题
subresult1 = self.divide_conquer(subproblems[0],p1,...)
subresult2 = self.divide_conquer(subproblems[1],p1,...)
subresult3 = self.divide_conquer(subproblems[2],p1,...)
.....
# 收集子结果产生最后的结果
result = process_result(subresult1,subresult2,..)
# 清除当前层的状态
回溯,是简单的一层一层去试探
1、Leetcode-50-Pow(x,n)
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
class Solution:
def myPow(self, x: float, n: int) -> float:
def helper(x,n):
if n==0:
return 1.0
if n%2==0:
return helper(x*x,n/2)
else:
return helper(x*x,(n-1)/2)*x
if n<0:
n = -n
return 1/helper(x,n)
return helper(x,n)
2、Leetcode-78-子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
results = [[]]
for num in nums:
newsets = []
for subset in results:
new_subset = subset + [num]
newsets.append(new_subset)
results.extend(newsets)
return results
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = [[]]
for i in nums:
res = res + [[i] + num for num in res]
return res
3、Leetcode-169-多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [3,2,3]
输出: 3
分治法
class Solution:
def majorityElement(self, nums: List[int]) -> int:
def majority_element_rec(first,end):
if first == end:
return nums[first]
mid = (end-first)//2 + first
left = majority_element_rec(first,mid)
right = majority_element_rec(mid+1,end)
if left == right:
return left
left_count = sum([1 for i in range(first,end+1) if nums[i]==left])
right_count = sum([1 for i in range(first,end+1) if nums[i]==right])
return left if left_count > right_count else right
return majority_element_rec(0,len(nums)-1)
时间复杂度O(nlogn),空间复杂度O(n)
4、Leetcode-17-电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
phone = {"2":["a","b","c"],
"3":["d","e","f"],
"4":["g","h","i"],
"5":["j","k","l"],
"6":["m","n","o"],
"7":["p","q","r","s"],
"8":["t","u","v"],
"9":["w","x","y","z"]}
output = []
def backtrack(combinations,next_digits):
if len(next_digits)==0:
output.append(combinations)
else:
for letter in phone[next_digits[0]]:
backtrack(combinations + letter,next_digits[1:])
if digits:
backtrack("",digits)
return output
5、Leetcode-51-N皇后问题
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
官方题解(https://leetcode-cn.com/problems/n-queens/solution/nhuang-hou-by-leetcode/)
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
def could_place(row, col):
return not (cols[col] + hill_diagonals[row - col] + dale_diagonals[row + col])
def place_queen(row, col):
queens.add((row, col))
cols[col] = 1
hill_diagonals[row - col] = 1
dale_diagonals[row + col] = 1
def remove_queen(row, col):
queens.remove((row, col))
cols[col] = 0
hill_diagonals[row - col] = 0
dale_diagonals[row + col] = 0
def add_solution():
solution = []
for _, col in sorted(queens):
solution.append('.' * col + 'Q' + '.' * (n - col - 1))
output.append(solution)
def backtrack(row = 0):
for col in range(n):
if could_place(row, col):
place_queen(row, col)
if row + 1 == n:
add_solution()
else:
backtrack(row + 1)
remove_queen(row, col)
cols = [0] * n
hill_diagonals = [0] * (2 * n - 1)
dale_diagonals = [0] * (2 * n - 1)
queens = set()
output = []
backtrack()
return output
深度优先搜索、广度优先搜索
遍历搜索:在树(图/状态集)中寻找特定节点
搜索-遍历:
-
每个节点都要访问一次
-
每个节点仅仅要访问一次
-
对于节点的访问顺序不限:深度优先、广度优先
示例代码:
def dfs(node):
if node in visited:
return
visited.add(node)
dfs(node.left)
dfs(node.right)
深度优先搜索
DFS代码-递归写法:
visited = set()
def dfs(node,visited):
if node in visited:
return
visited.add(node)
for next_node in node.children():
if not next_node in visited:
dfs(next node,visited)
DFS代码-非递归写法
def DFS(self,tree):
if tree.root is None:
return []
visited,stack = [],[tree.root]
while stack:
node = stack.pop()
visited.add(node)
process(node)
nodes = generage_related_node(node)
stack.push(nodes)
广度优先搜索
BFS代码-递归
visited = set()
def dfs(node,visited):
visited.add(node)
for next_node in node.children():
if next_node not in visited:
dfs(next_node,visited)
BFS代码-非递归
dfs BFS(graph,start,end):
queue = []
queue.append([start])
visited.add(start)
while queue:
node = queue.pop()
visited.add(node)
process(node)
nodes = generate_related_nodes(node)
queue.push(nodes)
1、Leetcode-102-二叉树的层次遍历
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
解法一:
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
ans,level = [],[root]
while root and level:
ans.append([node.val for node in level])
LRpair = [(node.left,node.right) for node in level]
level = [leaf for LR in LRpair for leaf in LR if leaf]
return ans
解法二:BFS
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
from collections import deque
if not root:
return []
queue,res = deque([root]),[]
while queue:
cur_level,size = [],len(queue)
for i in range(size):
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
cur_level.append(node.val)
res.append(cur_level)
return res
2、Leetcode-433-最小基因变化
一条基因序列由一个带有8个字符的字符串表示,其中每个字符都属于 "A", "C", "G", "T"中的任意一个。
假设我们要调查一个基因序列的变化。一次基因变化意味着这个基因序列中的一个字符发生了变化。
例如,基因序列由"AACCGGTT" 变化至 "AACCGGTA" 即发生了一次基因变化。
与此同时,每一次基因变化的结果,都需要是一个合法的基因串,即该结果属于一个基因库。
现在给定3个参数 — start, end, bank,分别代表起始基因序列,目标基因序列及基因库,请找出能够使起始基因序列变化为目标基因序列所需的最少变化次数。如果无法实现目标变化,请返回 -1。
注意:
起始基因序列默认是合法的,但是它并不一定会出现在基因库中。
所有的目标基因序列必须是合法的。
假定起始基因序列与目标基因序列是不一样的。
示例 1:
start: "AACCGGTT"
end: "AACCGGTA"
bank: ["AACCGGTA"]
返回值: 1
示例 2:
start: "AACCGGTT"
end: "AAACGGTA"
bank: ["AACCGGTA", "AACCGCTA", "AAACGGTA"]
返回值: 2
示例 3:
start: "AAAAACCC"
end: "AACCCCCC"
bank: ["AAAACCCC", "AAACCCCC", "AACCCCCC"]
返回值: 3
BFS
class Solution:
def minMutation(self, start: str, end: str, bank: List[str]) -> int:
bank = set(bank)
if end not in bank:
return -1
q = [(start,0)]
change = {'A':'TCG','T':'ACG','C':'ATG','G':'ATC'}
while q:
node,step = q.pop(0)
if node == end:
return step
for i,v in enumerate(node):
for j in change[v]:
new = node[:i] + j + node[i+1:]
if new in bank:
q.append((new,step+1))
bank.remove(new)
return -1
3、Leetcode-22-括号生成
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
def dfs(leftRemain,rightRemain,path,res):
if leftRemain > rightRemain or leftRemain < 0 or rightRemain < 0:
return
if leftRemain == 0 and rightRemain == 0:
res.append(path)
return
dfs(leftRemain - 1, rightRemain, path + "(", res)
dfs(leftRemain, rightRemain-1, path + ")", res)
dfs(n,n,"",res)
return res
4、Leetcode-515-在每个树行中找到最大值
您需要在二叉树的每一行中找到最大的值。
示例:
输入:
1
/ \
3 2
/ \ \
5 3 9
输出: [1, 3, 9]
class Solution:
def largestValues(self, root: TreeNode) -> List[int]:
from collections import deque
queue = deque([root])
res = []
if not root:
return []
while queue:
cur_level,size = [],len(queue)
for i in range(size):
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
cur_level.append(node.val)
res.append(max(cur_level))
return res
5、Leetcode-127-单词接龙
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
wordList.append(endWord)
queue = collections.deque([[beginWord,1]])
while queue:
word,length = queue.popleft()
if word == endWord:
return length
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i+1:]
if next_word in wordList:
wordList.remove(next_word)
queue.append([next_word,length+1])
return 0
6、Leetcode-126-单词接龙II
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
MostVotes(https://leetcode.com/problems/word-ladder-ii/discuss/40482/Python-simple-BFS-layer-by-layer)
class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
wordList = set(wordList)
res = []
layer = {}
layer[beginWord] = [[beginWord]]
while layer:
newlayer = collections.defaultdict(list)
for w in layer:
if w == endWord:
res.extend(k for k in layer[w])
else:
for i in range(len(w)):
for c in 'abcdefghijklmnopqrstuvwxyz':
neww = w[:i]+c+w[i+1:]
if neww in wordList:
newlayer[neww]+=[j+[neww] for j in layer[w]]
wordList -= set(newlayer.keys())
layer = newlayer
return res
7、Leetcode-200-岛屿数量
给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
MostVotes(https://leetcode.com/problems/number-of-islands/discuss/56340/Python-Simple-DFS-Solution)
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
if not grid:
return 0
count = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
self.dfs(grid, i, j)
count += 1
return count
def dfs(self, grid, i, j):
if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j] != '1':
return
grid[i][j] = '#'
self.dfs(grid, i+1, j)
self.dfs(grid, i-1, j)
self.dfs(grid, i, j+1)
self.dfs(grid, i, j-1)
8、Leetcode-529-扫雷游戏
让我们一起来玩扫雷游戏!
给定一个代表游戏板的二维字符矩阵。 'M' 代表一个未挖出的地雷,'E' 代表一个未挖出的空方块,'B' 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字('1' 到 '8')表示有多少地雷与这块已挖出的方块相邻,'X' 则表示一个已挖出的地雷。
现在给出在所有未挖出的方块中('M'或者'E')的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板:
如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X'。
如果一个没有相邻地雷的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的方块都应该被递归地揭露。
如果一个至少与一个地雷相邻的空方块('E')被挖出,修改它为数字('1'到'8'),表示相邻地雷的数量。
如果在此次点击中,若无更多方块可被揭露,则返回面板。
示例 1:
输入:
[['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'M', 'E', 'E'],
['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'E', 'E', 'E']]
Click : [3,0]
输出:
[['B', '1', 'E', '1', 'B'],
['B', '1', 'M', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]
MostVotes(https://leetcode.com/problems/minesweeper/discuss/137802/Python-DFS-solution)
class Solution:
def updateBoard(self, board: List[List[str]], click: List[int]) -> List[List[str]]:
if not board:
return []
m, n = len(board), len(board[0])
i, j = click[0], click[1]
# If a mine ('M') is revealed, then the game is over - change it to 'X'.
if board[i][j] == 'M':
board[i][j] = 'X'
return board
# run dfs to reveal the board
self.dfs(board, i, j)
return board
def dfs(self, board, i, j):
if board[i][j] != 'E':
return
m, n = len(board), len(board[0])
directions = [(-1,-1), (0,-1), (1,-1), (1,0), (1,1), (0,1), (-1,1), (-1,0)]
mine_count = 0
for d in directions:
ni, nj = i + d[0], j + d[1]
if 0 <= ni < m and 0 <= nj < n and board[ni][nj] == 'M':
mine_count += 1
if mine_count == 0:
board[i][j] = 'B'
else:
board[i][j] = str(mine_count)
return
for d in directions:
ni, nj = i + d[0], j + d[1]
if 0 <= ni < m and 0 <= nj < n:
self.dfs(board, ni, nj)
二分查找部分
二分查找的前提
1、目标函数单调性(单调递增或递减)
2、存在上下界(bound)
3、能够通过索引访问(index accessible)
代码模板:
left,right = 0,len(array)-1
while left <= right:
mid = (left + right)/2
if array[mid] == target:
# find the target
break or return result
elif array[mid] < target:
left = mid + 1
else:
right = mid - 1
1、Leetcode-69-x的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
二分查找法
class Solution:
def mySqrt(self, x: int) -> int:
left,right = 0,x//2 + 1
while left < right:
mid = left + (right-left+1)//2
if mid*mid > x:
right = mid -1
else:
left = mid
return left
牛顿法
class Solution:
def mySqrt(self, x: int) -> int:
r = x
while r*r > x:
r = (r+x//r)//2
return r
2、Leetcode-367-有效的完全平方数
给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。
说明:不要使用任何内置的库函数,如 sqrt。
示例 1:
输入:16
输出:True
示例 2:
输入:14
输出:False
class Solution:
def isPerfectSquare(self, num: int) -> bool:
x = num
while x*x>num:
x = (x + num//x)//2
if x*x==num:return True
return False
3、Leetcode-33-搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
class Solution:
def search(self, nums: List[int], target: int) -> int:
left,right = 0,len(nums)-1
while left < right:
mid = left + (right - left)//2
if nums[0] <= nums[mid] and (target > nums[mid] or target < nums[0]):
left = mid + 1
elif(target > nums[mid] and target < nums[0]):
left = mid + 1
else:
right = mid
if left == right and nums[left] == target: return left
return -1
4、编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if len(matrix)==0:
return False
i,row,col = 0,len(matrix)-1,len(matrix[0])-1
while i <= row and 0 <=col:
if matrix[i][col]>target:
col -= 1
elif matrix[i][col]==target:
return True
else:
i += 1
return False
5、Leetcode-153-寻找旋转数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0
class Solution:
def findMin(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
left,right = 0,len(nums)-1
if nums[right] > nums[0]:
return nums[0]
while left <= right:
mid = left + (right - left)//2
if nums[mid] > nums[mid+1]:
return nums[mid+1]
if nums[mid-1] > nums[mid]:
return nums[mid]
if nums[mid] > nums[0]:
left = mid + 1
else:
right = mid - 1
贪心算法部分
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望导致结果是全局最好或最优的算法。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解,这种子问题最优解成为最优子结构。贪心算法与动态规划的不同在于它对每个子问题的解决方案都作出选择不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
1、Leetcode-455-分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
你可以假设胃口值为正。
一个小朋友最多只能拥有一块饼干。
示例 1:
输入: [1,2,3], [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
贪心算法
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
child = 0
cookie = 0
while child < len(g) and cookie < len(s):
if g[child] <= s[cookie]:
child += 1
cookie += 1
return child
2、Leetcode-122-买股票的最佳时机II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
total = 0
for i in range(len(prices)-1):
if prices[i+1] > prices[i]:
total += (prices[i+1]-prices[i])
return total
3、Leetcode-55-跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
class Solution:
def canJump(self, nums: List[int]) -> bool:
endLength = len(nums) - 1
for i in range(len(nums)-1,-1,-1):
if nums[i] + i >= endLength:
endLength = i
return endLength == 0
4、Leetcode-860-柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
示例 1:
输入:[5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five,ten = 0,0
for bill in bills:
if bill == 5:
five += 1
elif bill == 10:
if not five:return False
five -= 1
ten += 1
else:
if five and ten:
five -= 1
ten -= 1
elif five>=3:
five -= 3
else:
return False
return True
5、Leetcode-874-模拟行走机器人
机器人在一个无限大小的网格上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令:
-2:向左转 90 度
-1:向右转 90 度
1 <= x <= 9:向前移动 x 个单位长度
在网格上有一些格子被视为障碍物。
第 i 个障碍物位于网格点 (obstacles[i][0], obstacles[i][1])
如果机器人试图走到障碍物上方,那么它将停留在障碍物的前一个网格方块上,但仍然可以继续该路线的其余部分。
返回从原点到机器人的最大欧式距离的平方。
示例 1:
输入: commands = [4,-1,3], obstacles = []
输出: 25
解释: 机器人将会到达 (3, 4)
class Solution:
def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int:
dx,dy,x,y = 0,1,0,0
distance = 0
obs_dict = {}
for obs in obstacles:
obs_dict[tuple(obs)] = 0
for com in commands:
if com == -2:
dx,dy = -dy,dx
elif com == -1:
dx,dy = dy,-dx
else:
for j in range(com):
next_x = x + dx
next_y = y + dy
if (next_x,next_y) in obs_dict:
break
x,y = next_x,next_y
distance = max(distance,x*x+y*y)
return distance
6、Leetcode-45-跳跃游戏II
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
class Solution:
def jump(self, nums: List[int]) -> int:
if nums.count(1) == len(nums):return len(nums)-1
def fun(n):
if not n:return 0
for k,v in enumerate(n):
if v + k >= len(n):return fun(n[:k]) + 1
return fun(nums[:-1])
动态规划部分
动态规划和递归或者分治没有根本上的区别(关键看有无最优的子结构)
共性:找到重复子问题
差异性:最优子结构、中途可以淘汰次优解
Bottom Up- 自底向上
F[n] = F[n-1] + F[n-2]
a[0] = 0, a[1] = 1
for i in range(2,n+1):
a[i] = a[i-1] + a[i-2]
a[n]
0,1,1,2,3,5,8,13
状态转移方程(DP方程)
opt[i,j] = opt[i+1,j] + opt[i,j+1]
完整逻辑:
if a[i,j]='空地':opt[i,j] = opt[i+1,j] + opt[i,j+1]
else opt[i,j]=0
动态规划关键点:
1、最优子结构 opt[n] = best_od(opt[n-1],opt[n-2],...)
2、存储中间状态: opt[i]
3、递推公式:
Fib: opt[i] = opt[n-1] + opt[n-2]
二维路径: opt[i,j] = opt[i+1][j] + opt[i][j+1](且判断a[i,j]是否空地)
1、Leetcode-62-不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
方法:动态规划
思路:第一行 + 第一列都是在边界,所以只能为1 + 动态方程
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0]*n]*m
for i in range(m):
dp[i][0] = 1
for j in range(n):
dp[0][j] = 1
for i in range(1,m):
for j in range(1,n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
2、Leetcode-1143-最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace",它的长度为 3
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
if not text1 or not text2:
return 0
m,n = len(text1),len(text2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
if text1[i-1] == text2[j-1]:
dp[i][j] = 1 + dp[i-1][j-1]
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
return dp[m][n]
3、Leetcode-120-三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:
"""
dp[i][j] = triangle[i][j] + min(dp[i+1][j],dp[i+1][j+1])
"""
dp = triangle
for i in range(len(triangle)-2,-1,-1):
for j in range(len(triangle[i])):
dp[i][j] += min(dp[i+1][j],dp[i+1][j+1])
return dp[0][0]
4、Leetcode-53-最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
for i in range(1,len(nums)):
nums[i] = max(nums[i-1],0) + nums[i]
return max(nums)
5、Leetcode-152-乘积最大子序列
给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
class Solution:
def maxProduct(self,nums:List[int])->int:
ma,mi,res = nums[0]
if nums[i]<0:ma,mi = mi,ma
for i in range(len(nums)):
ma = max(ma*nums[i],nums[i])
mi = min(mi*nums[i],nums[i])
res = max(res,ma)
return res
6、Leetcode-322-零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
"""
f(n) = min{f(n-k),k in [1,2,5]} + 1
"""
MAX = float("inf")
dp = [0] + [MAX] * amount
for i in range(1,amount+1):
dp[i] = min([dp[i-c] if i-c >=0 else MAX for c in coins]) + 1
return [dp[amount],-1][dp[amount]==MAX]
7、Leetcode-198-打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
class Solution:
def rob(self, nums: List[int]) -> int:
cur,pre = 0,0
for num in nums:
cur,pre = max(pre + num, cur),cur
return cur
8、Leetcode-213-打家劫舍-II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
class Solution:
def rob(self, nums: List[int]) -> int:
def my_rob(nums):
cur,pre = 0,0
for num in nums:
pre,cur = cur,max(pre+num,cur)
return cur
return max(my_rob(nums[:-1]),my_rob(nums[1:])) if len(nums)!=1 else nums[0]
动态规划解决股票系列问题
状态转移框架;
base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -float("inf")
状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])
case-1(k=1的情况): Leetcode_121_买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
dp_i_0,dp_i_1 = 0,float("-inf")
for i in range(len(prices)):
dp_i_0 = max(dp_i_0,dp_i_1 + prices[i])
dp_i_1 = max(dp_i_1,-prices[i])
return dp_i_0
case_2(k=正无穷的情况):Leetcode_122_买卖股票的最佳时机II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
分析: 在k=正无穷的情况下,上述状态转移方程
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])
转化为
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k][0] - prices[i])
==>
dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1],dp[i-1][0] - prices[i])
code:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
dp_i_0,dp_i_1 = 0,-float("inf")
for i in range(len(prices)):
temp = dp_i_0
dp_i_0 = max(dp_i_0,dp_i_1 + prices[i])
dp_i_1 = max(dp_i_1,temp - prices[i])
return dp_i_0
case_3(添加手续费):Leetcode_714_买卖股票最佳时机含手续费
给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每次交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
示例 1:
输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp_i_0,dp_i_1 = 0,-float("inf")
for price in prices:
item = dp_i_0
dp_i_0 = max(dp_i_0,dp_i_1+price)
dp_i_1 = max(dp_i_1,dp_i_0-price-fee)
return dp_i_0
case_4(隔一天才能购买):Leetcode_309_最佳买卖股票时机含冷冻期
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
思路:
状态转移方程:
dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1],dp[i-2][0] - prices[i])
注:i天购买时,从i-2状态转移
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp_i_0,dp_i_1,da_pre = 0,float("-inf"),0
for i in range(n):
temp = dp_i_0
dp_i_0 = max(dp_i_0,dp_i_1 + prices[i])
dp_i_1 = max(dp_i_1,da_pre - prices[i])
da_pre = temp
return dp_i_0
case_5(k=2情况下):leetcode_123_买卖股票的最佳时机III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
k = 1,2,状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])
=>
p[i][1][0] = max(dp[i-1][1][0],dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1],dp[i-1][0][0] - prices[i])
p[i][2][0] = max(dp[i-1][2][0],dp[i-1][2][1] + prices[i])
dp[i][2][1] = max(dp[i-1][2][1],dp[i-1][1][0] - prices[i])
class Solution:
def maxProfit(self, prices: List[int]) -> int:
dp_i10,dp_i11,dp_i20,dp_i21 = 0,-float("inf"),0,-float('inf')
for price in prices:
dp_i10 = max(dp_i10,dp_i11 + price)
dp_i11 = max(dp_i11,-price)
dp_i20 = max(dp_i20,dp_i21 + price)
dp_i21 = max(dp_i21,dp_i10 - price)
return dp_i20