剪枝,减少不必要的计算
283. 移动零
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
第一印象:使用一个辅助数组,同时以0进行初始化,遍历nums,依次将非零元素复制到辅助数据中;最后将辅助数组赋值给原数组。
但是,题目要求不复制数组原地修改数据进行操作。
想办法直接修改原数组,我们使用两个指针,p1、p2分别表示新数组中可以插入元素的下标,当前数组中遍历的元素;遍历数组时,找到非零元素同时将元素保存到p1下–通过交换元素实现,如果是非零元素,则继续遍历。
p1 = 0
for p2 in range(len(nums)):
if nums[p2] != 0:
nums[p1], nums[p2] = nums[p2], nums[p1]
p1 += 1
class Solution(object):
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
if not nums:
return 0
j = 0
for i in range(len(nums)):
if nums[i]:
nums[j], nums[i] = nums[i], nums[j]
j += 1
11. 盛最多水的容器
输入:[1,8,6,2,5,4,8,3,7]
**输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
朴素思路:查找所有可以盛水的容器,计算容量,找到最多水的容器。使用双层循环。
容器的容量 = 两边的短板高度 x 容器长度,
result = 0
for i in range(N):
for j in range(i+1, N):
temp = min(nums[i], nums[j]) * (j - i)
result = max(result, temp)
return result
这种思路中,计算所有可能的容器,因此复杂度过高。所以,优化思路是减少循环次数,去掉不必要的解。同样采用双指针(ps:双循环也算一种双指针),left,right初始化为0、N-1,数组的起始和终止位置–容器长度最大。此时计算一下容量,之后移动left和right中的小值。
left, right = 0, N - 1
result = 0
while left < right:
temp = min(nums[left], nums[right]) * (right - left)
result = max(result, temp)
if nums[left] < nums[right]:
left += 1
else:
right -=1
return result
class Solution:
def maxArea(self, height: List[int]) -> int:
i, j, res = 0, len(height) - 1, 0
while i < j:
temp = min(height[i], height[j]) * (j - i)
res = max(res, temp)
if height[i] < height[j]:
i += 1
else:
j -= 1
return res
15.三数之和
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
**解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
朴素思路:3层循环/三指针,查找所有3元组,判断和是否等于0,通过限制i、j、k的取值范围保证可以保证不重复取值,但是无法保证没有重复的三元组,所以一种思路就是找到所有解最后去重。
缺点是时间复杂度过高。
另一种思路,类似于两数之和, 外围一层循环,内层循环查找两数之和等于0-nums[i]的数组,如果找到,加入到结果中,同时进行去重操作,将数组中前后等于当前元素的下标都跳过(所以,最先一步是排序,保证相同元素都连续出现,方便后续进行去重操作),这里去重之后,就不用最后对结果去重。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if n < 3:
return []
nums.sort()
res = []
for i in range(n):
if nums[i] > 0: # 剪枝:第一个元素>0,则一定不会出现对应的3元组;排序后
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]])
L = L + 1
R = R - 1
while L < R and nums[L] == nums[L-1]:#去重
L = L + 1
while L < R and nums[R] == nums[R+1]:#去重
R=R-1
elif nums[i] + nums[L] + nums[R] >0:
R = R - 1
else:
L = L + 1
return res
42. 接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
**输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
我们需要做两点:1.判断当前位置能否可以接雨水?2. 如果可以,雨水容量是多少
能否接:需要判断当前点是否为一个凹点,即当前点的左边、右边是否有高于当前位置的元素,如果有,其容量 = min(max_left, max_right) - height[i]
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
ml = [0] * len(height)
mr = [0] * len(height)
for i in range(1, len(height)):
ml[i] = max(ml[i - 1], height[i - 1])
for i in range(len(height) - 2, -1, -1):
mr[i] = max(mr[i + 1], height[i + 1])
for i in range(1, len(height) - 1):
tmp = min(ml[i], mr[i])
if height[i] < tmp:
ans += tmp - height[i]
return ans
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
int left = 0, right = n - 1;
int leftMax = height[left], rightMax = height[right];
++left, --right;
int ans = 0;
while(left <= right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if(leftMax < rightMax) {
ans += leftMax - height[left];
left++;
}else {
ans += rightMax - height[right];
right--;
}
}
return ans;
}
};