双指针算法
11. 盛最多水的容器 - 力扣(LeetCode)
优化解法:用 left 和 right 两个指针从两端向中心收缩,一边收缩一边计算 [left, right] 之间的矩形面积,取最大的面积值即是答案。
其实是优化区间选择 直接丢弃体积一定会减小的区间 (通过指针移动 直到两个指针重合
遍历数组一遍O(n)
class Solution {
public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int res = 0; while (left < right) { // [left, right] 之间的矩形面积
int cur_area = Math.min(height[left], height[right]) * (right - left);
res = Math.max(res, cur_area); // 双指针技巧,移动较低的一边
if (height[left] < height[right]) {
left++; }
else { right--;
}
} return res;
}
}
15. 三数之和 - 力扣(LeetCode)
- 关键: 三个数的下标互不相等,和为0 返回三元数组
- 答案中不可以包含重复的三元组
- 难点:三元组去重
暴力解法:排序+暴力枚举+利用SET去重,会超时
- 错误代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);//排序
HashSet<List<Integer>> hash = new HashSet<>();
int[] record = new int[3];
List<List<Integer>> ret = new ArrayList<>();
for(int i = 0;i < nums.length;i++){
for(int j = 0;j < nums.length;j++){
for(int k = 0;k < nums.length;k++){
while(i != j && i != k && j!=k){
if(nums[i] + nums[j] + nums[k] == 0){
record.add(hash.add(nums[i],nums[j],nums[k]))
}
}
}
}
}
return ret.add(record);
}
}
错误分析
- 使用 HashSet 存储 List:
HashSet 不适合直接存储 List,因为它需要元素具有 hashCode() 和 equals() 方法的正确实现来确保唯一性。而 List 的 hashCode() 和 equals() 方法默认是基于对象内存地址的,这意呀着即使两个 List 包含相同的元素,它们也被视为不同的对象。 - 使用 while 循环进行条件判断:
在三层嵌套循环中使用 while 循环进行条件判断是不必要的,且逻辑上也不正确。您应该直接在 if 语句中检查条件。 - 添加元素到 List 和 HashSet 的方式错误:
record.add(hash.add(nums[i],nums[j],nums[k]))
这行代码是错误的,因为 HashSet 没有add(int, int, int)
方法,且record
是一个 int 数组,不能添加 List 或其他对象。 - 返回结果的处理:
return ret.add(record);
这行代码也是错误的,因为add()
方法返回的是布尔值(表示是否成功添加),而不是 List。您应该直接返回ret
。 - 去重和排序问题:
由于您已经对数组进行了排序,可以利用这一点来避免重复和减少不必要的计算。
数组有序考虑二分查找 双指针
- 双指针能让复杂度低一个指数
优化思路:指针到一个数t,剩下有序数组存两个数,和为t的相反数 两数之和
重难点:去重 两个地方,不走重复的数 ,避免越界
去重逻辑的思考
a的去重
说到去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]
a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。
但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。
其实不一样!
都是和 nums[i]进行比较,是比较它的前一个,还是比较它的后一个。
如果我们的写法是 这样:
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!
所以这里是有两个重复的维度。
那么应该这么写:
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。
这是一个非常细节的思考过程。
b与c的去重
while (right > left) {
if (nums[i] + nums[left] + nums[right] > 0) {
right--;
// 去重 right
while (left < right && nums[right] == nums[right + 1]) right--;
} else if (nums[i] + nums[left] + nums[right] < 0) {
left++;
// 去重 left
while (left < right && nums[left] == nums[left - 1]) left++;
} else {
}
}
但细想一下,这种去重其实对提升程序运行效率是没有帮助的。
拿right去重为例,即使不加这个去重逻辑,依然根据 while (right > left)
和 if (nums[i] + nums[left] + nums[right] > 0)
去完成right-- 的操作。
多加了 while (left < right && nums[right] == nums[right + 1]) right--;
这一行代码,其实就是把 需要执行的逻辑提前执行了,但并没有减少 判断的逻辑。
最直白的思考过程,就是right还是一个数一个数的减下去的,所以在哪里减的都是一样的。
所以这种去重 是可以不加的。 仅仅是 把去重的逻辑提前了而已。
- 正确答案
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.length; i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}