11. 盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
我的思路:
双指针m、n,m从前往后,n从后往前。宽度n-m一定是越来越小,故只需要判断高度是否增大,不增大则肯定不能超过之前的容积。
执行用时:5 ms, 在所有 Java 提交中击败了43.66%的用户
内存消耗:51.8 MB, 在所有 Java 提交中击败了33.11%的用户
class Solution {
public int maxArea(int[] height) {
int result =(height.length-1)*Math.min(height[0],height[height.length-1]);
int m=0,n=height.length-1;
while(m<n){
if(height[m]>height[n]){
n--;
if(height[n]<=height[n+1]){
continue;
}
}else{
m++;
if(height[m]<=height[m-1]){
continue;
}
}
int s = (n-m)*Math.min(height[m],height[n]);
if(s>result){
result = s;
}
}
return result;
}
}
参考写法:三元
我们每次向内移动短板,所有的消去状态都不会导致丢失面积最大值
执行用时:4 ms, 在所有 Java 提交中击败了76.25%的用户
内存消耗:52.1 MB, 在所有 Java 提交中击败了7.67%的用户
class Solution {
public int maxArea(int[] height) {
int i = 0, j = height.length - 1, res = 0;
while(i < j){
res = height[i] < height[j] ?
Math.max(res, (j - i) * height[i++]):
Math.max(res, (j - i) * height[j--]);
}
return res;
}
}
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
我的思路:
三层循环,暴力解题。但没有去重的方法。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
if(nums.length<3){
return result;
}
for(int i = 0;i<nums.length;i++){
for(int j = i+1;j<nums.length;j++){
for(int k = j+1;k<nums.length;k++){
if(nums[i]+nums[j]+nums[k]==0&&nums[i]!=nums[k]&&nums[j]!=nums[k]){
List<Integer> ans = new ArrayList<Integer>();
ans.add(nums[i]);
ans.add(nums[j]);
ans.add(nums[k]);
result.add(ans);
}
}
}
}
return result;
}
}
参考思路:
1、枚举到的三元数组abc满足a<=b<=c,保证[a,b,c],而非[b,a,c];
2、三重循环中,相邻枚举元素不能重复
3、优化三重循环,a+b+c=0,b增大时,c必然需要减小,b,c构成双指针。
我的改进代码:
执行用时:25 ms, 在所有 Java 提交中击败了57.76%的用户
内存消耗:42.6 MB, 在所有 Java 提交中击败了38.77%的用户
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
int length = nums.length;
Arrays.sort(nums);
for (int i = 0; i < length - 2; i++) {
int a = i + 1;
int b = length - 1;
//去重
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
while (a < b) {
int sum = nums[i] + nums[a] + nums[b];
if (sum == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[a]);
list.add(nums[b]);
result.add(list);
a++;
b--;
//nums[i]和nums[a]相同,nums[b]必然唯一,去重
while(a<b&&nums[a]==nums[a-1]){
a++;
}
while(b>a&&nums[b]==nums[b+1]){
b--;
}
} else if (sum > 0) {
b--;
} else if (sum < 0) {
a++;
}
}
}
return result;
}
}
优化:
执行用时: 22 ms
内存消耗: 42.3 MB
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
int length = nums.length;
Arrays.sort(nums);
//因为排序规则,i为最小值,必然有nums[i]<=0才满足nums[i] + nums[a] + nums[b] = 0;
for (int i = 0; i < length - 2 && nums[i] <= 0; i++) {
int a = i + 1;
int b = length - 1;
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
while (a < b) {
int sum = nums[i] + nums[a] + nums[b];
//先判断多数情况,减少开销
if (sum > 0) {
b--;
} else if (sum < 0) {
a++;
}else if (sum == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[a]);
list.add(nums[b]);
result.add(list);
//放在双指针变化前,先跳过重复项,再移动
while(a<b&&nums[a]==nums[a+1]){
a++;
}
while(b>a&&nums[b]==nums[b-1]){
b--;
}
a++;
b--;
}
}
}
return result;
}
}
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
我的思路:
与15题相似,一次遍历枚举+双指针
双指针实现
- 利用 Arrays.sort(nums) 对数组进行排序。 初始化一个用于保存结果的值 result = nusm[0] + nums[1]
- nums[2] (不要自己设初值,直接从数组中抽取三个元素,假设这是最接近的三数之和,然后再更新就是了)。 利用下标 i 对数组进行遍历,此时就是在固定第一个元素,注意,下标 i 的边界为 i < nums.length-2,否则设置指针的时候会出现数组越界。
- 每次遍历的过程中设置两个指针,分别是 left = i + 1、right = nums.length - 1。 检查 sum => nums[i] + nums[left] + nums[right]与 target 的距离,如果该距离比之前保存的 result 与> target 的距离更小,就更新 result。
- 然后就是移动双指针。 如果 sum 的值比 target 大,那么我们让> right–,因为数组是有序的,right --会使得下一次的 sum 更小,也就更接近 target 的值 同理,如果 sum 的值> target 小,那么我们让 left++。· left++ 和 right-- 的界限自然是 left != right,如果 left> == right,说明我们已经将所有的元素都遍历过一遍了。 重复上面的操作,直到i循环结束为止,返回 result。
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
int ans = nums[0] + nums[1] + nums[n - 1];
for (int i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) i++;
int left = i + 1;
int right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == target) {
return sum;
}
int a = (target - ans > 0) ? target - ans : ans - target;
if (target - sum < a && sum - target < a) {
ans = sum;
}
if (sum < target) {
left++;
} else if (sum > target) {
right--;
}else {
break;
}
}
}
return ans;
}
}
题目中无需去重,但实际工作中大数据量去重优化很有必要。
18. 四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
思路:排序,两次枚举+双指针
以为有更高级的算法,没有做。第二次做的时候必须要动手实现。
参考代码:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> quadruplets = new ArrayList<List<Integer>>();
if (nums == null || nums.length < 4) {
return quadruplets;
}
Arrays.sort(nums);
int length = nums.length;
for (int i = 0; i < length - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
break;
}
if (nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
for (int j = i + 1; j < length - 2; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
break;
}
if (nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
int left = j + 1, right = length - 1;
while (left < right) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
quadruplets.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
}
return quadruplets;
}
}