81、搜索旋转排序数组 II
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
解:这道题与33题相似,但33题中不存在重复元素,而本题中存在重复元素.这里我们有一种简便方法,即不去找最大的元素,而是直接找target。
class Solution:
def search(self, nums: List[int], target: int) -> bool:
if not nums:
return False
n=len(nums)
left=0
right=n-1
while left<=right:
mid=(left+right)//2
if nums[mid]==target:
return True
if nums[mid]>nums[right]:#逆序,但0-mid是顺序
if nums[mid]>target and nums[left]<=target:
right=mid-1
else:
left=mid+1
elif nums[right]>nums[mid]:#顺序
if nums[mid]<target and nums[right]>=target:
left=mid+1
else:
right=mid-1
else:
right=right-1#只减一,因为有可能是[1,1,target,1]这种情况,没有办法根据mid缩减right,但因为right的值与mid的相等,故都不等于target,right可以减1
if nums[left]==target:
return True
else:
return False
执行用时:32 ms, 在所有 Python3 提交中击败了98.16%的用户
内存消耗:13.9 MB, 在所有 Python3 提交中击败了32.79%的用户
当然这道题也可以先转成set去重,然后按照33题的思路去做。
82、删除排序链表中的重复元素 II
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
解:一个数不相等等价于它和前后两个数都不相等,如果满足这个条件的head,令ans.next=head,然后继续遍历,直到它位于末尾,便只需跟前一个比较,然后决定是否加入,最后ans.next=None这个封口是一定要做的,否则ans.next后面仍然是head的链,会有重复的元素。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
res=ListNode(None)
ans=res
if head.val!=head.next.val:
if not head.next.next:
return head
else:
res.next=head
res=res.next
if not head.next.next:
return None
while head.next.next:
if head.val!=head.next.val and head.next.val!=head.next.next.val:
res.next=head.next
res=res.next
head=head.next
if head.val!=head.next.val:
res.next=head.next
res=res.next
res.next=None#否则[1,2,2]时因为ans是连接head的,虽然没有赋值,但ans.next.val=2
return ans.next
执行用时:48 ms, 在所有 Python3 提交中击败了80.51%的用户
内存消耗:13.8 MB在所有 Python3 提交中击败了14.61%的用户
还有一个笨方法,就是把有重复的元素的值加进列表里,然后再遍历一遍,删除重复的。
83、删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
解:这道题就简单多了,只需要顺序遍历,每一个haed和head.next比较,如果相等,则head.next=head.next.next,否则继续遍历
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
ans=head
while head.next:
if head.next.val==head.val:
head.next=head.next.next
else:
head=head.next
return ans
执行用时:52 ms, 在所有 Python3 提交中击败了60.23%的用户
内存消耗:13.7 MB, 在所有 Python3 提交中击败了50.06%的用户
84、柱状图中最大的矩形 hard
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
解:这道题和装水的题可以说是互为镜像,上道题是在波谷之后找到波峰,这题是在波谷之后找波谷。
解法一:因为找不到头绪,所以就想到用动态规划法解决问题,设dp[i][j]是heights[i]到[j]的矩形的面积,事实上面积为lowest*(j-i+1),所以新加入的dp[i][j+1]也可以计算,dp[i+1][j]也可以计算,然后找到最大的即可。
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
if not heights:
return 0
n=len(heights)
maxS=heights[0]
dp=[[0 for _ in range(n)] for _ in range(n)]
for i in range(n):
dp[i][i]=heights[i]
if heights[i]>maxS:
maxS=heights[i]
for i in range(n-1):
for j in range(i+1,n):
dp[i][j]=min(dp[i][j-1]//(j-i),heights[j])*(j-i+1)
if dp[i][j]>maxS:
maxS=dp[i][j]
return maxS
但在进行到倒数第三个示例时会超时,所以这种遍历的操作是不行的,还是要从其内在规律着手。
解法二:中心生成法
这时可以后退一步,不考虑二维的dp[i][j],而是考虑一维的dp[i],即包含i的最大面积,这个需要向两边推进,直到找到比heights[i]小的才停止(不能包含比它小的),这样i就是底,也即数组中的最短边和矩形的高。因为最终答案一定会存在一个最短边,所以这样做是能够找到答案的
这样可以遍历两次,一次求左端点集合,一次求右端点的集合,这样就是O(n)了。此外还可以对该算法进行优化,事实上内层循环没必要一步一步移动,可以直接将j -= 1 改成 j = left[j], j +=1 改成 j = right[j]。
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
left=[-1]*n
right=[n] * n
maxS=0
for i in range(1,n):
j=i-1
while j>=0 and heights[j]>=heights[i]:
j=left[j]
left[i]=j
for i in range(n-2,-1,-1):
j=i+1
while j<n and heights[j]>=heights[i]:
j=right[j]
right[i]=j
for i in range(n):
maxS=max(maxS,heights[i]*(right[i]-left[i]-1))
return maxS
执行用时:92 ms, 在所有 Python3 提交中击败了14.30%的用户
内存消耗:15.6 MB, 在所有 Python3 提交中击败了66.09%的用户
相对来讲该方法还是很慢,因为要做3次循环
解法3:
若是单调递增栈,则从栈底到栈顶的元素是严格递增的,也就是说进栈的元素越来越大。元素进栈过程:对于单调递增栈,若当前进栈元素为e,从栈顶开始遍历元素,把大于e的元素弹出栈,直接遇到一个小于等于e的元素或者栈为空为止,然后再把e压入栈中。
从左到右遍历柱子,对于每一个柱子,我们想找到第一个高度小于它的柱子,那么我们就可以使用一个单调递增栈来实现。 如果柱子大于栈顶的柱子,那么说明不是我们要找的柱子,我们把它塞进去继续遍历,如果比栈顶小,那么我们就找到了第一个小于的柱子。 对于栈顶元素,其右边第一个小于它的就是当前遍历到的柱子,左边第一个小于它的就是栈中下一个要被弹出的元素,因此以当前栈顶为最小柱子的面积为当前栈顶的柱子高度 * (当前遍历到的柱子索引 - 1 - 栈中下一个要被弹出的元素索引 - 1 + 1)
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n=len(heights)
heights=[0]+heights+[0]
stack=[]
maxS=0
for i in range(n + 2):
while stack and heights[stack[-1]]>heights[i]:
maxS=max(maxS,heights[stack.pop(-1)]*(i-stack[-1]-1))
stack.append(i)#每个i都进去过,说明没有漏
return maxS
执行用时:68 ms, 在所有 Python3 提交中击败了75.63%的用户
内存消耗:15.8 MB, 在所有 Python3 提交中击败了36.70%的用户
85、最大矩形
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
解:这种乱七八糟的还是首先要想动态规划。
乍看下去动态规划很难,但是我们可以先从第一行分析,对i,dp[i]=dp[i-1],也就是说dp[i]是从i开始向左数1的个数。我们可以先按行遍历赋值,然后dp[i][j]为max(它的行数*1,min(它的行数,上一行的行数)*2,…)这样就能够完成赋值。
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
if not matrix:
return 0
m=len(matrix)
n=len(matrix[0])
dp=[[0]*n for _ in range(m)]
maxS=0
for i in range(m):
for j in range(n):
if matrix[i][j]=='0':
continue
if j>0:
dp[i][j]=dp[i][j-1]+1
else:
dp[i][j]=1
width=dp[i][j]
for k in range(i,-1,-1):
width=min(width, dp[k][j])
maxS=max(maxS, width*(i-k+1))
return maxS
执行用时:2668 ms, 在所有 Python3 提交中击败了8.63%的用户
内存消耗:14.4 MB, 在所有 Python3 提交中击败了62.15%的用户
但是不出所料,动态规划由于是遍历,所以速度极慢,时间复杂度为O(m**2*n)
解法二:
注意到本题跟上一题的相似性,当固定一列之后这一列加上上面的元素就构成了一个柱状图,每一个点的高度是从它往上连续1的个数(不为1的点算作高度为零)
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
if not matrix:
return 0
m=len(matrix)
n=len(matrix[0])
maxS=0
colhigh=[0]*n
for i in range(m):
for j in range(n):
if matrix[i][j]=='1':
colhigh[j]=colhigh[j]+1
else:
colhigh[j]=0
maxS=max(maxS, self.largestRectangleArea(colhigh))
return maxS
def largestRectangleArea(self, heights: List[int]) -> int:
n=len(heights)
heights=[0]+heights+[0]
stack=[]
maxS=0
for i in range(n + 2):
while stack and heights[stack[-1]]>heights[i]:
maxS=max(maxS,heights[stack.pop(-1)]*(i-stack[-1]-1))
stack.append(i)
return maxS
这样复杂度变为O(m*n)
执行用时:92 ms, 在所有 Python3 提交中击败了89.67%的用户
内存消耗:14.3 MB在所有 Python3 提交中击败了79.51%的用户