前言:
本文收录下以下专栏:
力扣Leetcode数组类中等组(python版本)(专栏)http://t.csdnimg.cn/PVDQO
结合目录快速定位!!!
标题下面有免费配套讲解资源可下载使用!
本篇LeetCode中等组的题解,会持续更新!
许多代码其实考察的是一个数学建模的问题,抽象成一个数学问题就能很好解决了!!
每一部分尽量留一个问题给读者自行解决!
11.盛最多水的容器
题目:![5f507584bde1466c9a9e0b65d21c95cd.png](https://i-blog.csdnimg.cn/blog_migrate/f2b5804b9705b964bb61aae708f12f50.png)
题解思路:
先根据题目进行分析,就是一个求体积最大的问题,抽象成二维平面实际上就是一个求最大面积的问题:
首先假设有两个指针 i,j 从数组的两端开始,他们所指向的长条高度分别为:
height[i] ,height[j]
根据木板效应,题中能装多少水是取决于我们最短的木板,所以我们要求的面积为:
上述式子为什么是min(height[i],height[j]呢?
接下来我们要想去求最大值,让 i ,j 把每一个位置试遍显然会超时,我们要采取其他办法:
我们如果只挪动高的木板,由于木板效应,存放的水只有变少有可能不变,绝不可能变多!:
分三种情况讨论:
第一种,高的木板里面的木板高度一样,由于长度(横坐标变化)显然水变少;
第二种,高的木板里面的木板高度变得更高,我们能装多少水是由短板决定,存放水变少;
第三种高木板里面是短木板(比他自己短),这种情况变少:
如果我们选择短板移动,那么效果就完全不一样了:
第一种情况短板内高度变高,这时候存水可能变少也有可能变多:
第二种情况短板高度不变,这种情况肯定变少!
第三种情况短板内高度更低:这种情况也是变少:
综上所述:要是存水面积变大我们只能采取移动短板的方法,换言之我们要找到存水最大的方法,就是把 i,j 两个指针从头开始向内移动,每次移动较短的那一个,直到 i,j 相遇,期间肯定能找对最大值。
本题完整代码:
class Solution:
def maxArea(self, height: List[int]) -> int:
# 初始化两个指针 i=0, j=len(height)-1
i, j = 0, len(height) - 1
maxArea = 0
while i != j:
if height[i] < height[j]:
min_height = height[i]
# print('i = {}, j = {}'.format(i, j))
i += 1
else:
min_height = height[j]
# print('i = {}, j = {}'.format(i, j))
j -= 1
# 这里长度变成 j - i + 1 是因为前面会多减一个1,造成结果有问题!!!
maxArea = max(maxArea, (j - i + 1) * min_height)
# print('maxArea = {},min_height = {}'.format(maxArea, min_height), )
return maxArea
可能遇到的问题:
- 无脑暴力循环,中等组一般会超时
- 移动了高指针,找错了最大值
- 最后计算 maxArea 没注意前面指针减 1 了,可能会有问题
- 数学抽象能力较弱
改进的空间:
中间的 min_height 可以尝试不用变量存起来,可以节省一定空间(留给读者)
15.三数之和:
题目:
题解思路:
首先暴力解法,三个for循环肯定能解,不过肯定会超时,排除。
想要排除重复的情况,可以利用多指针来剔除重复的解。(这在降低时间复杂度中很常用)
然后就是对数组进行排序(直接库函数就行),一个无序的数组,我们很难有什么好的办法,这一步也成为数据的预处理,数据在一开头进行预处理,一般能为我们节省大量的时间。
现在我们得到一个排好序的数组了, 然后仿照三层 for 的暴力解法,设三个指针 k ,i , j ,k指针用 for 循环来移动, i,j指针在while循环中移动。
k指针从头开始,i从k+1开始,j从数组末尾开始,其中,k成为固定指针,在一个for循环中位置不变,i,j在一个for循环中可变:
我们在一个for循环中,如果nums[k]+nums[i]+nums[j]的值小于0如上图,那么移动左指针i,在nums[i]与nums[i+1]值不同的情况下,可以使得我们的三数之和可以变大:
如果 nums[k]+nums[i]+nums[j] 的值大于0 ,在nums[j]与nums[j-1] 的值不同的情况下,可以使我们的sum三数之和值变小:
如果 nums[k]+nums[i]+nums[j] 的值加起来正好使0 ,那就是我们的答案:
对于题解我们还不能直接存入到我们的结果数组之前,还需要检查是否重复! (这里体现了我们对数组预排序的好处。
当我们的i、j相遇的时候,这个for循环就能结束了。
然后对k用一个for循环使他能走便每一个位置,这样不会漏解。
还有一个减少时间消耗的办法:
当我们nums[k]的值大于0时可以直接退出,后面的三者之和一定大于0,没有题解。
这样一来有暴力解法的时间复杂度O(N^3)变成了O(N^2)降低了复杂度。
本题完整代码:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 对数组排序
nums.sort()
new_nums = []
# 特例
if len(nums) < 3 or nums is None:
return []
for k in range(len(nums) - 1):
# 结束条件
if nums[k] > 0:
break
i, j = k + 1, len(nums) - 1
while True:
if i == j:
break
sum_nums = nums[k] + nums[i] + nums[j]
if sum_nums == 0:
temp_nums = [nums[k], nums[i], nums[j]] # 注意顺序,用来判断是否重复的解
# print("k,i,j,temp_nums:",k,i,j,temp_nums)
if temp_nums not in new_nums:
new_nums.append(temp_nums)
i += 1
# 移动指针
elif sum_nums > 0:
j -= 1
elif sum_nums < 0:
i += 1
return list(new_nums)
可能遇到的问题:
- 我们有三个指针,但题目给的数组元素小于三或者是一个空数组要特判!
- for循环里面有一个while循环,while一定要注意有退出条件!
- 重复的解要剔除!
改进的空间:
我们这里剔除重复元素的办法是用库函数(in)来检测,我们可以自己在函数中设计指针移动,使其解肯定不重复,这样可以再进一步减少时间复杂度。(留给读者)
16.最接近的三数之和:
题目:![3e278992a37d449698852943fc393297.png](https://i-blog.csdnimg.cn/blog_migrate/bab6a5450af23cd8b9dcd235bb1db493.png)
题解思路:
这题看上去就和15题差不多,无非就是一个是能等于target,一个可能等于target可能不等于,要找一个最接近的值,我们在上题的基础上,只要增加一个临时变量temp来记录目前最接近的值,每次比较都要进行更新值的操作即可。三指针按照上题所述,正常进行移位即可。
我们这里还能略微优化一下,如果temp为0说明题目给的数组正好有三个数之和等于target,这时候直接返回答案即可。
本题完整代码:
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
temp = inf
for k in range(len(nums)):
i, j = k + 1, len(nums) - 1
while i < j:
s = nums[k] + nums[i] + nums[j]
if s == target:
return s
# 更新值
if abs(s - target) <= abs(temp - target):
# print('s,k,i,j', s, nums[k], nums[i], nums[j])
temp = s
# 移动指针
if s <= target:
i += 1
elif s > target:
j -= 1
return temp
可能遇到的问题:
- 更新值的时候要明确知道比较的是谁离 target 比较近,要带绝对值
- 移动指针的时候要注意,我们是根据我们的三数之和与 target 大小比较来进行移动指针
- 移动指针的时候要考虑 = 的条件,不然会有死循环
- temp初始化要为最大值,不然后面比较会出问题
改进的空间:
中途还有部分小细节可以减少一点重复计算的步骤
例如和15题一样,如果nums[k]的值都比target大,那么直接退出循环,因为后面的结果会渐行渐远(留给读者)
18.四数之和
题目:![2db92582938f454180c91e149c78154e.png](https://i-blog.csdnimg.cn/blog_migrate/508385045e00b75668762fd585d23a6d.png)
题解思路:
这题和上面三数排序之和差不多,首先对原数组排序,方便后期去判断重复的解。
然后开设四个指针 m , n ,i , j指向首尾四个位置,形如下图:
按照上面的逻辑,m , n由for循环来改变他们的值,i ,j 称为移动指针,根据四数之和与 target 大小关系,如果四数之和 s 比 target 小那么移动 i 指针,大于,移动 j 指针, 相等,保存结果并且判断是否重复。
本题完整代码:
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
# 预处理、排序
nums.sort()
new_nums = []
for m in range(len(nums)):
for k in range(len(nums)):
# n 从末尾开始
n = len(nums) - k - 1
i, j = m + 1, n - 1
while i < j:
s = nums[m] + nums[i] + nums[j] + nums[n] # 注意顺序,用来判断是否重复
if s < target:
i += 1
elif s > target:
j -= 1
elif s == target:
temp_nums = [nums[m], nums[i], nums[j], nums[n]] # 注意顺序
if temp_nums not in new_nums:
new_nums.append(temp_nums)
j -= 1
return new_nums
可能遇到的问题:
- 注意 n 要从末尾开始,使得四个指针逐渐向中间靠拢
- 判断循环终止条件的时候,容易把位置处于数组中间的解给漏了(常错)
- 最好进行特例特判,把长度小于 4 的数组特判空解
改进的空间:
本题没做任何剪枝处理,我们仿照前面的题目,如果有 nums[m]大于 target 那么直接退出,后面一定全部值都比 target 大,以此类推进行适量剪枝操作。(留给读者)
未完待续: