难度较大题型--LeetCode
寻找两个有序数组的中位数
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
解法
参考网上的题解:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/shuang-zhi-zhen-by-powcai/
首先,我们理解什么中位数:指的是该数左右个数相等。
比如:odd : [1,| 2 |,3],2 就是这个数组的中位数,左右两边都只要 1 位;
even: [1,| 2, 3 |,4],2,3 就是这个数组的中位数,左右两边 1 位;
那么,现在我们有两个数组:
num1: [a1,a2,a3,…an]
nums2: [b1,b2,b3,…bn]
[nums1[:left1],nums2[:left2] | nums1[left1:], nums2[left2:]]
只要保证左右两边 个数 相同,中位数就在 | 这个边界旁边产生。
如何找边界值,我们可以用二分法,我们先确定 num1 取 m1 个数的左半边,那么 num2 取 m2 = (m+n+1)/2 - m1 的左半边,找到合适的 m1,就用二分法找。
当 [ [a1],[b1,b2,b3] | [a2,…an],[b4,…bn] ]
我们只需要比较 b3 和 a2 的关系的大小,就可以知道这种分法是不是准确的!
- a2 < b3:说明a2不够大,nums1应该再往左边多分一些,即m1应该再加大些
- a2 >= b3: a2有可能太大了,所以m1可以少一些;也有可能当前的位置正好,所以right = m1
例如:我们令:
nums1 = [-1,1,3,5,7,9]
nums2 =[2,4,6,8,10,12,14,16]
当 m1 = 4,m2 = 3 ,它的中位数就是median = (num1[m1] + num2[m2])/2
时间复杂度:O(log(min(m,n)))O(log(min(m,n)))
简单排序方法:
class Solution(object):def findMedianSortedArrays(self, nums1, nums2):""":type nums1: List[int]:type nums2: List[int]:rtype: float"""nums1.extend(nums2)nums1 = sorted(nums1)len_num = len(nums1)if len_num % 2 == 0:tmp = int(len_num / 2)re = (float)(nums1[tmp-1] + nums1[tmp])return re/2else:tmp = int((len_num-1) / 2)return nums1[tmp]
class Solution:def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:n1 = len(nums1)n2 = len(nums2)if n1 > n2:return self.findMedianSortedArrays(nums2,nums1)k = (n1 + n2 + 1)//2left = 0right = n1while left < right :m1 = left +(right - left)//2m2 = k - m1if nums1[m1] < nums2[m2-1]:left = m1 + 1else:right = m1m1 = leftm2 = k - m1 c1 = max(nums1[m1-1] if m1 > 0 else float("-inf"), nums2[m2-1] if m2 > 0 else float("-inf") )if (n1 + n2) % 2 == 1:return c1c2 = min(nums1[m1] if m1 < n1 else float("inf"), nums2[m2] if m2 <n2 else float("inf"))return (c1 + c2) / 2
找出第 k 小的距离对
给定一个整数数组,返回所有数对之间的第 k 个最小距离。一对 (A, B) 的距离被定义为 A 和 B 之间的绝对差值。
示例 1:
输入:
nums = [1,3,1]
k = 1
输出:0
解释:
所有数对如下:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
因此第 1 个最小距离的数对是 (1,1),它们之间的距离为 0。
提示:
2 <= len(nums) <= 10000.
0 <= nums[i] < 1000000.
1 <= k <= len(nums) * (len(nums) - 1) / 2.
解法
参考网上题解:https://leetcode-cn.com/problems/find-k-th-smallest-pair-distance/solution/hei-ming-dan-zhong-de-sui-ji-shu-by-leetcode/
由于第 k 小的距离一定在 [0, W = max(nums) - min(nums)] 中,我们在这个区间上进行二分。对于当前二分的位置 mid,统计距离小于等于 mid 的距离对数量,并根据它和 k 的关系调整区间的上下界。
具体实现的时候,我们可以使用双指针来计算出所有小于等于 mid 的距离对数目。我们维护 left 和 right,其中 right 通过循环逐渐递增,left 在每次循环中被维护,使得它满足 nums[right] - nums[left] <= mid 且最小。
这样对于 nums[right],以它为右端的满足距离小于等于 mid 的距离对数目即为 right - left。我们在循环中对这些 right - left 进行累加,就得到了所有小于等于 mid 的距离对数目。
class Solution(object):def smallestDistancePair(self, nums, k):""":type nums: List[int]:type k: int:rtype: int"""nums = sorted(nums)l, r = 0, nums[-1] - nums[0]while l < r:mid = (l + r) // 2cnt = self.count(nums, mid)if cnt < k:l = mid + 1else:r = midreturn ldef count(self, nums, k):cnt = 0l = 0for r, x in enumerate(nums):while x - nums[l] > k:l += 1cnt += r - lreturn cnt
分割数组的最大值
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
注意:
数组长度 n 满足以下条件:
1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)
示例:
输入:
nums = [7,2,5,10,8]
m = 2
输出:
18
解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
解法
首先分析题意,可以得出结论,结果必定落在 [max(nums), sum(nums)] 这个区间内,因为左端点对应每个单独的元素构成一个子数组,右端点对应所有元素构成一个子数组。
然后可以利用二分查找法每次猜测一个答案,然后模拟一下划分子数组的过程,可以得到用这个mid值会一共得到的子区间数cnt,然后比较cnt和m的关系,来更新区间范围。
class Solution(object):def splitArray(self, nums, m):""":type nums: List[int]:type m: int:rtype: int"""if len(nums) == m:return max(nums)l, r = max(nums), sum(nums)while l < r:mid = (l + r) // 2cnt, tmp = 1, 0 for x in nums:tmp += xif tmp > mid:tmp = xcnt += 1if cnt > m:l = mid + 1else:r = midreturn l
存在重复元素 III
给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。
示例 1:
输入: nums = [1,2,3,1], k = 3, t = 0
输出: true
示例 2:
输入: nums = [1,0,1,1], k = 1, t = 2
输出: true
示例 3:
输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false
解法
参考:https://leetcode-cn.com/problems/contains-duplicate-iii/solution/li-yong-tong-de-yuan-li-onpython3-by-zhou-pen-chen/
首先,定义桶的大小是t+1, nums[i]//(t+1)决定放入几号桶,这样在一个桶里面的任意两个的绝对值差值都<=t
例如t=3, nums=[0 ,5, 1, 9, 3,4],那么0号桶就有[0,1,3],1号桶就有[4,5],2号桶就有[9]
先不考虑索引差值最大为K的限制,那么遍历nums每一个元素,并把他们放入相应的桶中,有两种情况会返回True
要放入的桶中已经有其他元素了,这时将nums[i]放进去满足差值<=t
可能存在前面一个桶的元素并且与nums[i]的差值<=t 或者 存在后面一个桶的元素并且与nums[i]的差值<=t
根据返回True的第一个条件,可以知道前后桶的元素最多也只能有一个。
接着考虑限制桶中的索引差最大为K,当i>=k的时候:
我们就要去删除存放着nums[i-k]的那个桶(编号为nums[i-k]//(t+1))
这样就能保证遍历到第i+1个元素时,全部桶中元素的索引最小值是i-k+1,就满足题目对索引的限制了
class Solution(object):def containsNearbyAlmostDuplicate(self, nums, k, t):""":type nums: List[int]:type k: int:type t: int:rtype: bool"""if k < 0 or t < 0 :return Falsedic = {}size = t + 1for i in range(len(nums)):x = nums[i] // sizeif x in dic:return Truedic[x] = nums[i]if x - 1 in dic and abs(dic[x - 1] - nums[i]) <= t:return Trueif x + 1 in dic and abs(dic[x + 1] - nums[i]) <= t:return Trueif i >= k:dic.pop(nums[i - k] // size)return False
三种取值的排序
一个数组里面只有’r’,‘g’,'b’三种字符随机排序,如果用In-place的方法让这个数组排好顺序,排序后的样子是[r,r,r,g,g,g,b,b,b]
解法
- 暴力:用哈希法存下每种元素的个数,最后直接赋值
- 三个指针:明确在 p0 左侧维护 r, p1 右侧维护 g, p2 右侧维护 b, 然后使用 p0 朝右扫描,如果扫到 g 很容易处理,如果扫到 b 就需要把 p1, p2 各自移动一位,并在移动过程中注意维护各个区间中元素成分是否正确
def rgbSort(arr):n = len(arr)p1 = p2 = n - 1p0 = 0while p0 <= p1:if arr[p0] == 'g':arr[p0], arr[p1] = arr[p1], arr[p0]p1 -= 1elif arr[p0] == 'b':arr[p0], arr[p1] = arr[p1], arr[p0]arr[p1], arr[p2] = arr[p2], arr[p1]p2 -= 1p1 -= 1else:p0 += 1return arr
第 K 个最小的素数分数
一个已排序好的表 A,其包含 1 和其他一些素数. 当列表中的每一个 p<q 时,我们可以构造一个分数 p/q 。
那么第 k 个最小的分数是多少呢? 以整数数组的形式返回你的答案, 这里 answer[0] = p 且 answer[1] = q.
示例:
输入: A = [1, 2, 3, 5], K = 3
输出: [2, 5]
解释:
已构造好的分数,排序后如下所示:
1/5, 1/3, 2/5, 1/2, 3/5, 2/3.
很明显第三个最小的分数是 2/5.
输入: A = [1, 7], K = 1
输出: [1, 7]
注意:
A 长度的取值范围在 2 — 2000.
每个 A[i] 的值在 1 —30000.
K 取值范围为 1 —A.length * (A.length - 1) / 2
解法
https://leetcode-cn.com/problems/k-th-smallest-prime-fraction/solution/di-k-ge-zui-xiao-de-su-shu-fen-shu-by-leetcode/
方法一:二分查找
under(x) 用于求解小于 x 的分数数量,这是一个关于 x 的单调增函数,因此可以使用二分查找求解。
算法:使用二分查找找出一个 x,使得小于 x 的分数恰好有 K 个,并且记录其中最大的一个分数。
我们的二分搜索与其他的二分搜索方法类似:初始有区间 [lo, hi],中心点 mi = (lo + hi) / 2.0。如果小于 mi 的分数数量小于 K,更新区间为 [mi, hi],否则更新为 [lo, mi]。
under(x) 函数有两个目的:返回小于 x 的分数数量以及小于 x 的最大分数。在 under(x) 函数中使用滑动窗口的方法:对于每个 primes[j],找出最大的 i 使得 primes[i] / primes[j] < x。随着 j (和 primes[j])的增加, i 也会随之增加。
方法二:使用一个堆记录所有以 primes[j] 为分母且未被弹出的最小分数。依次从堆中弹出 K-1 个元素,此时堆顶的分数就是结果。
算法:在 Python 中,使用 (fraction, i, j) 表示分数 fraction = primes[i] / primes[j]。如果下一个分数有效(即 i+1 < j),那么使用当前分数时,将下一个分数压入堆中。
方法一:
class Solution(object):def kthSmallestPrimeFraction(self, primes, K):from fractions import Fractiondef under(x):# Return the number of fractions below x,# and the largest such fractioncount = best = 0i = -1for j in xrange(1, len(primes)):while primes[i+1] < primes[j] * x:i += 1count += i+1if i >= 0:best = max(best, Fraction(primes[i], primes[j]))return count, best# Binary search for x such that there are K fractions# below x.lo, hi = 0.0, 1.0while hi - lo > 1e-9:mi = (lo + hi) / 2.0count, best = under(mi)if count < K:lo = mielse:ans = besthi = mireturn ans.numerator, ans.denominator
class Solution(object):#Note - this solution may TLE.def kthSmallestPrimeFraction(self, A, K):pq = [(A[0] / float(A[i]), 0, i) for i in xrange(len(A) - 1, 0, -1)]for _ in xrange(K-1):frac, i, j = heapq.heappop(pq)i += 1if i < j:heapq.heappush(pq, (A[i] / float(A[j]), i, j))return A[pq[0][1]], A[pq[0][2]]
算法题:名人问题,给出最优解法
有n个人他们之间认识与否用邻接矩阵表示(1表示认识,0表示不认识),并A认识B并不意味着B认识A,也就意味着是个有向图。如果一个人是名人,他必须满足两个条件,一个是他不认识任何人,另一个是所有人必须都认识他。
解法
如果a认识b,则a不会是名人;如果a不认识b,则b不会是名人。因此每询问一次a是否认识b,都可以排除掉一个人,所以在O(n)时间内就可以排除掉n-1个人。 最后还要检查确认,是否其他人都认识这个人,以及这个人都不认识其他人。
def findCelebrity(int n):celebrity = 0for i in range(1, n):if knows(celebrity, i):celebrity = ifor i in range(n):if (celebrity != i and knows(celebrity, i)):return -1if (celebrity != i and !knows(i, celebrity)):return -1return celebrity
丑数
把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
解法
- 暴力:把每个数字逐个遍历判断是否是丑数的方式。
- 多指针法:使用空间换时间。我们使用一个数组保存每个丑数,然后生成下一个丑数。使用了3个指针,分别指向最后一个进行×2,×3,×5操作后会大于当前最大的丑数的位置。那么需要找到下一个丑数的时候,一定会是这三个指针指向的丑数进行对应操作的结果之一。因此,每次都更新三个指针指向下一次操作就会变成最大值的位置。就能一直生成下一丑数。
# -*- coding:utf-8 -*-
class Solution:def GetUglyNumber_Solution(self, index):# write code hereif index < 1:return 0res = [1]t2 = t3 = t5 = 0nextNum = 1while nextNum < index:minNum = min(res[t2] * 2, res[t3] * 3, res[t5] * 5)res.append(minNum)if res[t2] * 2 <= minNum:t2 += 1if res[t3] * 3 <= minNum:t3 += 1if res[t5] * 5 <= minNum:t5 += 1nextNum += 1return res[nextNum - 1]
判断某个数是否为丑数:
暴力破解,思路非常简单,首先除2,直到不能整除为止,然后除5到不能整除为止,然后除3直到不能整除为止。 最终判断剩余的数字是否为1,如果是1则为丑数,否则不是丑数。
class Solution:def isUgly(self, num):""":type num: int:rtype: bool"""while num >0 and num%2==0:num /= 2while num >0 and num%3==0:num /= 3while num >0 and num%5==0:num /= 5return True if num == 1.0 else False
跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
解法
贪心算法:设置一个当前能到达的最远位置,从头开始遍历,只有当i<=maxPos时,才表示可以到达i位置,这时我们更新maxPos,判断其能否到达尾部,若不能,返回false,能则返回true。
class Solution(object):def canJump(self, nums):""":type nums: List[int]:rtype: bool"""if not nums:return FalsemaxPos = 0for i in range(len(nums)):if maxPos >= i:maxPos = max(maxPos, i + nums[i])if maxPos >= len(nums) - 1:return Truereturn False
跳跃游戏 II
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [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, 0for i in range(n - 1):if maxPos >= i:maxPos = max(maxPos, i + nums[i])if i == end:end = maxPosstep += 1return step
跳跃游戏III
这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。请你判断自己是否能够跳到对应元素值为 0 的 任意 下标处。注意,不管是什么情况下,你都无法跳到数组之外。
示例 1:
输入:arr = [4,2,3,0,3,1,2], start = 5
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案:
下标 5 -> 下标 4 -> 下标 1 -> 下标 3
下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3
示例 2:
输入:arr = [4,2,3,0,3,1,2], start = 0
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案:
下标 0 -> 下标 4 -> 下标 1 -> 下标 3
解法
参考:https://blog.nowcoder.net/n/54abe7064f0c44e786081a0530647582
思路:广度优先搜索:初始时将 start 加入队列。在每一次的搜索过程中,我们取出队首的节点 u,它可以到达的位置为 u + arr[u] 和 u - arr[u]。如果某个位置落在数组的下标范围 [0, len(arr)) 内,并且没有被搜索过,则将该位置加入队尾。只要我们搜索到一个对应元素值为 0 的位置,我们就返回 True。在搜索结束后,如果仍然没有找到符合要求的位置,我们就返回 False。
class Solution {public:bool canReach(vector<int>& arr, int start) {if (arr[start] == 0) {return true;}int n = arr.size();vector<bool> used(n);queue<int> q;q.push(start);used[start] = true;while (!q.empty()) {int u = q.front();q.pop();if (u + arr[u] < n && !used[u + arr[u]]) {if (arr[u + arr[u]] == 0) {return true;}q.push(u + arr[u]);used[u + arr[u]] = true;}if (u - arr[u] >= 0 && !used[u - arr[u]]) {if (arr[u - arr[u]] == 0) {return true;}q.push(u - arr[u]);used[u - arr[u]] = true;}}return false;}
};
最大正方形
在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
示例:
输入:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
输出: 4
解法
- 暴力法:由于正方形的面积等于边长的平方,因此要找到最大正方形的面积,首先需要找到最大正方形的边长,然后计算最大边长的平方即可:遍历矩阵中的每个元素,每次遇到 1,则将该元素作为正方形的左上角;确定正方形的左上角后,根据左上角所在的行和列计算可能的最大正方形的边长(正方形的范围不能超出矩阵的行数和列数),在该边长范围内寻找只包含 1的最大正方形;每次在下方新增一行以及在右方新增一列,判断新增的行和列是否满足所有元素都是 1。时间复杂度:O(mn*min(m,n)^2),其中 m 和 n 是矩阵的行数和列数。
- 动态规划:用 dp(i, j)表示以 (i, j) 为右下角,且只包含 1 的正方形的边长最大值。如果我们能计算出所有 dp(i, j) 的值,那么其中的最大值即为矩阵中只包含 1 的正方形的边长最大值,其平方即为最大正方形的面积。对于每个位置 (i, j)(i,j),检查在矩阵中该位置的值:
如果该位置的值是 0,则 dp(i, j) = 0,因为当前位置不可能在由 1 组成的正方形中;如果该位置的值是 1,则 dp(i, j) 的值由其上方、左方和左上方的三个相邻位置的 dpdp 值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1,状态转移方程如下:
dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1
此外,还需要考虑边界条件。如果 i 和 j 中至少有一个为 0,则以位置 (i, j)为右下角的最大正方形的边长只能是 1,因此 dp(i, j) = 1。
解法一:
class Solution:def maximalSquare(self, matrix: List[List[str]]) -> int:if len(matrix) == 0 or len(matrix[0]) == 0:return 0maxSide = 0rows, columns = len(matrix), len(matrix[0])for i in range(rows):for j in range(columns):if matrix[i][j] == '1':# 遇到一个 1 作为正方形的左上角maxSide = max(maxSide, 1)# 计算可能的最大正方形边长currentMaxSide = min(rows - i, columns - j)for k in range(1, currentMaxSide):# 判断新增的一行一列是否均为 1flag = Trueif matrix[i + k][j + k] == '0':breakfor m in range(k):if matrix[i + k][j + m] == '0' or matrix[i + m][j + k] == '0':flag = Falsebreakif flag:maxSide = max(maxSide, k + 1)else:breakmaxSquare = maxSide * maxSidereturn maxSquare
解法二:
class Solution(object):def maximalSquare(self, matrix):""":type matrix: List[List[str]]:rtype: int"""if not matrix or not matrix[0]:return 0max_side, res = 0, 0 n, m = len(matrix), len(matrix[0])dp = [[0 for i in range(m)] for j in range(n)]for i in range(n):for j in range(m):if matrix[i][j] == '1':if i == 0 or j == 0:dp[i][j] = 1else:dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j -1]) + 1max_side = max(max_side, dp[i][j])res = max_side * max_sidereturn res
柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:
输入: [2,1,5,6,2,3]
输出: 10
解法
- 枚举「宽」,我们可以使用两重循环枚举矩形的左右边界以固定宽度 w,此时矩形的高度 h,就是所有包含在内的柱子的「最小高度」,对应的面积为 w * h。时间复杂度均为 O(N^2)
- 枚举「高」,我们可以使用一重循环枚举某一根柱子,将其固定为矩形的高度 hh。随后我们从这跟柱子开始向两侧延伸,直到遇到高度小于 hh 的柱子,就确定了矩形的左右边界。如果左右边界之间的宽度为 ww,那么对应的面积为 w * h。时间复杂度均为 O(N^2)
- 单调递增栈操作规则:如果新的元素比栈顶元素大,就入栈;如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小。总体思路:对于一个高度,如果能得到向左和向右的边界;那么就能对每个高度求一次面积;遍历所有高度,即可得出最大面积;使用单调栈,在出栈操作时得到前后边界并计算面积
解法一:
class Solution {public:int largestRectangleArea(vector<int>& heights) {int n = heights.size();int ans = 0;// 枚举左边界for (int left = 0; left < n; ++left) {int minHeight = INT_MAX;// 枚举右边界for (int right = left; right < n; ++right) {// 确定高度minHeight = min(minHeight, heights[right]);// 计算面积ans = max(ans, (right - left + 1) * minHeight);}}return ans;}
};
解法二:
class Solution {public:int largestRectangleArea(vector<int>& heights) {int n = heights.size();int ans = 0;for (int mid = 0; mid < n; ++mid) {// 枚举高int height = heights[mid];int left = mid, right = mid;// 确定左右边界while (left - 1 >= 0 && heights[left - 1] >= height) {--left;}while (right + 1 < n && heights[right + 1] >= height) {++right;}// 计算面积ans = max(ans, (right - left + 1) * height);}return ans;}
};
解法三:
class Solution:def largestRectangleArea(self, heights: List[int]) -> int:n = len(heights)left, right = [0] * n, [0] * nmono_stack = list()for i in range(n):while mono_stack and heights[mono_stack[-1]] >= heights[i]:mono_stack.pop()left[i] = mono_stack[-1] if mono_stack else -1mono_stack.append(i)mono_stack = list()for i in range(n - 1, -1, -1):while mono_stack and heights[mono_stack[-1]] >= heights[i]:mono_stack.pop()right[i] = mono_stack[-1] if mono_stack else nmono_stack.append(i)ans = max((right[i] - left[i] - 1) * heights[i] for i in range(n)) if n > 0 else 0return ans
最大矩阵
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例:
输入:
[
[“1”,“0”,“1”,“0”,“0”],
[“1”,“0”,“1”,“1”,“1”],
[“1”,“1”,“1”,“1”,“1”],
[“1”,“0”,“0”,“1”,“0”]
]
输出: 6
解法
动态规划 - 使用柱状图的优化:我们可以以常数时间计算出在给定的坐标结束的矩形的最大宽度。我们可以通过记录每一行中每一个方块连续的“1”的数量来实现这一点。每遍历完一行,就更新该点的最大可能宽度。
然后就类似于上一题:
求出每一层的 heights[] 然后传给上一题的函数就可以了。时间复杂度 : O(NM),空间复杂度 : O(M)
class Solution:# Get the maximum area in a histogram given its heightsdef leetcode84(self, heights):stack = [-1]maxarea = 0for i in range(len(heights)):while stack[-1] != -1 and heights[stack[-1]] >= heights[i]:maxarea = max(maxarea, heights[stack.pop()] * (i - stack[-1] - 1))stack.append(i)while stack[-1] != -1:maxarea = max(maxarea, heights[stack.pop()] * (len(heights) - stack[-1] - 1))return maxareadef maximalRectangle(self, matrix: List[List[str]]) -> int:if not matrix: return 0maxarea = 0dp = [0] * len(matrix[0])for i in range(len(matrix)):for j in range(len(matrix[0])):# update the state of this row's histogram using the last row's histogram# by keeping track of the number of consecutive onesdp[j] = dp[j] + 1 if matrix[i][j] == '1' else 0# update maxarea with the maximum area from this row's histogrammaxarea = max(maxarea, self.leetcode84(dp))return maxarea
单词接龙 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”]
]
示例 2:
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出: []
解释: endWord “cog” 不在字典中,所以不存在符合要求的转换序列。
解法
把开始单词当作开始节点,结束单词当作结束节点,现在要解决的问题就是开始节点和结束节点是否有连接,求最短路径。
首先,如果结束单词一开始就不在单词列表里,可以直接返回,因为本身就不存在这样的节点。
其次,求最短路径,最容易想到的就是BFS解法,这个从起始节点开始遍历,如果找到了结束节点,代表找到了结果,同时这个肯定是最优解(路径最短)。
注意:此时我们并不能直接返回答案,因为要求的所有最短路径,所以我们要把这一层的所有满足结果都返回。
现在我们还有2个问题:
- 如何通过某个节点找到它关联的节点,也就是遍历边,我们需要完成这个方法def edges(word),接收一个单词,返回它关联的所有单词
- 遍历到了结束节点后,如何查到路径。
解决方案:
- 直接根据题目意思来,每个字母都从a-z变换,如果在单词列表里,代表是有效节点,可以返回
- 我们在BFS的时候实际上本来就已经知道了路径,所以完全可以把路径存在节点里面,你可以想象一下,你BFS的时候队列里是[word1,word2],原本word1,word2只存储了单词信息,那我可不可以把路径也存到节点里面,变成这样[[parentparent1, parent1,word1],[parentparent2, parent2,word2]],这样一来,我们既可以知道单词信息,同时也可以知道单词从头开始的遍历路径。
BFS的时间复杂度是O(n),n代表单词列表个数,总的时间复杂度是O(26nc)。和普通BFS也没啥区别,就是2个改动,1在节点里保存了路径,2你要写一个能遍历下个节点的方法
class Solution:def findLadders(self, beginWord, endWord, wordList):se=set(wordList)if not endWord in se:return []def edges(word):arr=list(word)for i in range(len(arr)):c=arr[i]for j in range(97,123):arr[i]=chr(j)newWord=''.join(arr)if newWord in se and not newWord in marked:yield newWordarr[i]=cres=[]marked=set()queue=[[beginWord]]while queue:temp=[]found=Falsefor words in queue:marked.add(words[-1])for words in queue:for w in edges(words[-1]):v=words+[w]if w == endWord:res.append(v)found=Truetemp.append(v)if found: #找到就不再遍历了,即使再有endWord,路径也会更长breakqueue=tempreturn res
优化一:
我们可以把每个单词看作可以hash成c个值,c代表单词长度,比如单词’hit’,可以hash成’it’,'ht’,'hi*'这3个值,如果2个单词的hash值有重合,说明这2个单词是可以通过改1个字母变成另外一个的,所以我们的寻找边方法def edges(word)可以优化成这样,此方法将寻找边的O(26*c)优化成了O©
hash=collections.defaultdict(list)for word in wordList:for i in range(len(word)):hash[word[:i]+"*"+word[i+1:]].append(word) #初始化:将单词列表的所有元素全部hash保存进字典def edges(word):for i in range(len(word)):for newWord in hash[word[:i]+'*'+word[i+1:]]: #遍历该单词的所有hash,从字典里取出关联的单词if not newWord in marked:yield newWord
优化二:双向BFS
因为已经知道了开始节点和结束节点,所以可以从2头开始遍历,哪头的节点少,就遍历哪头,如果2头的节点重合了(有交集)说明连上了,遍历结束。
如果一直没交集,任何一头走到末尾就可以结束,代表全部节点都遍历完成了。
这里为了更好的取交集,begin和end都用了set来表示,同时把路径单独存储在了path字典中,一旦出现交集,从最后一个节点,依据path的存储,直到找到开始节点,能出现交集,说明一定能找到开头。
class Solution:def findLadders(self, beginWord, endWord, wordList):if not endWord in wordList:return []hash=collections.defaultdict(list)for word in wordList:for i in range(len(word)):hash[word[:i]+"*"+word[i+1:]].append(word)def edges(word):for i in range(len(word)):for newWord in hash[word[:i]+'*'+word[i+1:]]:if not newWord in marked:yield newWorddef findPath(end):res=[]for curr in end:for parent in path[curr[0]]:res.append([parent]+curr)return resmarked=set()path=collections.defaultdict(set)begin=set([beginWord])end=set([endWord])forward=Truewhile begin and end:if len(begin)>len(end):begin,end=end,beginforward=not forwardtemp=set()for word in begin:marked.add(word)for word in begin:for w in edges(word):temp.add(w)if forward:path[w].add(word)else:path[word].add(w)begin=tempif begin&end:res=[[endWord]]while res[0][0]!=beginWord:res=findPath(res)return resreturn []
正则表达式匹配
给定s1, s2, (s1只包含正则表达式匹配中的 .* ? 和正常字符,s2只包含正常字符),判定s1 s2是否匹配。.代表任意一个字符,*代表长度,?代表前面的一个字符。*代表重复次数大于等于0,? 代表重复次数 0-1
下面是两个匹配成功的例子:
s1: abc.*abc
s2: abcabc
s1: abc?abc
s2: abcabc
解法
外面一层三个 if else 选择支在判断是不是有 * ? 这两种重复标记,每个选择支里面两个选择支判断当前字符是否匹配成功
简易版:https://blog.csdn.net/qq_20141867/article/details/80910724
def match(s, partten, i, j):if i == len(s) and j == len(partten):return Trueelif i == len(s) or j == len(partten):return Falseif j + 1 < len(partten) and partten[j + 1] == '*':if partten[j] == '.' or partten[j] == s[i]:return match(s, partten, i + 1, j) or \match(s, partten, i + 1, j + 2) or \match(s, partten, i, j + 2)else:return match(s, partten, i, j + 2)elif j + 1 < len(partten) and partten[j + 1] == '?':if partten[j] == '.' or partten[j] == s[i]:return match(s, partten, i + 1, j + 2) or match(s, partten, i, j + 2)else:return match(s, partten, i, j + 2)else:if partten[j] == '.' or partten[j] == s[i]:return match(s, partten, i + 1, j + 1)else:return Falsedef main():print(match("", "", 0, 0))print(match("a", "a", 0, 0))print(match("a", ".", 0, 0))print(match("a", ".*", 0, 0))print(match("a", "a?", 0, 0))print(match("abcabc", "abc.*abc", 0, 0))print(match("abcabc", "abc?abc", 0, 0))print(match("abxxxacc", "abc.*abc", 0, 0))print(match("abcdabc", "abc?abc", 0, 0))if __name__ == "__main__":main()
判断图是否是树
给出 n 个节点,标号分别从 0 到 n - 1 并且给出一个 无向 边的列表 (给出每条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树
样例
给出n = 5 并且 edges = [[0, 1], [0, 2], [0, 3], [1, 4]], 返回 true.
给出n = 5 并且 edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]], 返回 false.
解法
用BFS去做,如果一个图是树的话,它的结点个数是n,那么它的边的个数一定是n-1,并且这n个结点一定是联通的,联通的意思就是从某个结点出发遍历一遍这个图,得到的集合里有n个结点。因为只给了边的数组,所以我们需要自己来构建图的数据结构。
class Solution:"""@param n: An integer@param edges: a list of undirected edges@return: true if it's a valid tree, or false"""def validTree(self, n, edges):# write your code hereif(n==1):return Trueif(len(edges)!=n-1):return Falsedic={}for i in range(n):dic[i]=set() #集合里存储与该点连接的点def getGraph(dic,n,edges): #获得图for i in range(len(edges)):u=edges[i][0]v=edges[i][1]dic[u].add(v)dic[v].add(u)return dicgraph=getGraph(dic,n,edges) #dic{i:set}from queue import Queueq=Queue()q.put(0)traversal=set() #记录已经遍历过的点while(q.qsize()!=0):node=q.get()for neighbor in graph[node]:if(neighbor in traversal):continueq.put(neighbor)traversal.add(neighbor)return(len(traversal)==n)s = Solution()
print(s.validTree(5,[[0, 1], [0, 2], [0, 3], [1, 4]]))
难度较大题型--LeetCode相关推荐
- Leetcode 1143.最长公共子序列(求长度)
Time: 20190906 Type: Medium 题目描述 给定两个字符串text1和text2,返回它们的最长公共子序列. 子序列是通过原字符串删除一些字符(可以不删)生成的新的字符串,不改变 ...
- Leetcode 300题AC的刷题总结(C与C++)
文章目录 前引闲聊 力扣刷题心路历程 Day1 全部看题解 看思路 Day2 - Day7 基本上看题解 怀疑自己是弱智 Day8 - Day15 诶~有些题自己可以做出来了 Day16 - Day2 ...
- 数据分析SQL常考题型、大厂例题及面试要点
文章目录 1. 数据分析岗位技能要求 1.1 哪一个工具是数据分析师的核心工具 1.2 对于数据分析师来说,是否需要建模能力 1.3 数据分析岗位对业务有什么要求 1.4 数据的特点 1.5 优秀数据 ...
- 【LeetCode 总结】Leetcode 题型分类总结、索引与常用接口函数
文章目录 零. Java 常用接口函数 一. 动态规划 二. 链表 三. 哈希表 四. 滑动窗口 五. 字符串 六. DFS.BFS 七. 二分法 八. 二叉树 九. 偏数学.过目不忘 and 原地算 ...
- 面试官系列 - LeetCode链表知识点题型总结
文章目录 前言 知识点 什么是链表 类别 单向链表 循环链表 双向链表 双向循环链表 与数组的性能对比 优缺点 常用技巧 题型总结 基本操作 删除类 翻转类题型 合并链表 环形链表 拆分链表 排序链表 ...
- LeetCode常见题型——树
1. 算法思想 作为(单)链表的升级版,我们通常接触的树都是二叉树(binary tree),即每个节点最多有 两个子节点:且除非题目说明,默认树中不存在循环结构. struct TreeNode { ...
- leetcode适合做面试的那些题型
文章目录 前言 二分查找 [模板题] [容易] [中等] [困难] 单链表 [模板题] [简单] [困难] 栈 [模板题] [简单] 队列 [中等] 二叉树 [简单] [中等] 双指针 [模板题] [ ...
- 一文通数据结构与算法之——链表+常见题型与解题策略+Leetcode经典题
文章目录 1 链表 1.1 常见题型及解题策略 1.1.1 LeetCode中关于链表的题目有以下五种类型题: 1.1.2 解题策略 1.2 链表的基本内容 1.2.1 链表的基本结构: 1.2.2 ...
- 用这样的方法,我解决了leetcode的大部分的这种题型!
点个赞,看一看,好习惯!本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了 3 个月总结的一线大厂 Java 面试总结,本 ...
最新文章
- 微信小程序php后台支付,微信小程序 支付功能实现PHP实例详解
- C语言实现pid算法(附完整源码)
- ELK+Kafka 企业日志收集平台(一)
- Elasticsearch Mapping 详解
- poj 1182 食物链 (并查集)
- spring framwork解析
- joomla 3.6 mysql 版本_Joomla是否支持MariaDB数据库?
- ajax跨域post请求的java代理实现
- 点击list view中一行内容可以在combox中显示_在后台添加新的产品/文章分类,如何在模板中显示产品列表/文章列表?如何调用分类?...
- 马上上线!谷歌与苹果联手抗疫,打造基于蓝牙设备的接触史回溯 | 凌云时刻...
- oracle 平均值 最大值,Oracle分析函数三——SUM,AVG,MIN,MAX,COUNT
- Android 修改系统时间代码
- java写的网络版斗地主_JAVA网络版斗地主游戏
- Python基础简答题
- 数据库学习——10-13-聚合函数+GROUP BY+HAVING学习
- Oracle 中的各种读
- 171029 windows10 桌面美化
- jzoj 1307. Jail
- 安装完Vmware-tools后找不到共享文件夹的解决办法-Ubuntu 18有效
- iPhone、iPad、Android UI常用设计尺寸