- 记录于2019年5月18日
11. 盛最多水的容器
① 题目描述
- 给定 n 个非负整数
a1,a2,...,an
,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i
的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 - 说明:你不能倾斜容器,且 n 的值至少为 2。
- 示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
② 暴力求解
- 盛水容器的体积,是由
start和end中的较小值
与start和end之间的distance
决定,与中间的line高度无关(中间的line不是容器壁,较高的line不会阻碍容器装水)。
public int maxArea(int[] height) {
int result = 0;
for (int i = 0; i < height.length - 1; i++) {
for (int j = i + 1; j < height.length; j++) {
int dis = j - i;
int temp = Math.min(height[i], height[j]) * dis;
if (temp > result) {
result = temp;
}
}
}
return result;
}
③ 双指针法
- 具体示意图见力扣上的solution。
- 总体思想:
① 使用left的right两个指针,分别指向line数组的左边和右边;最初我们考虑由最外围两条线段构成的区域,但是为了获得area的最大化,我们应该继续寻找更长的两条线段之间的区域。
② 如果向内侧移动较长line的指针,新的区域受限于较短line而不会获得任何增加;所以应该向内侧移动较短line的指针,移动指向较短线段的指针尽管造成了矩形宽度的减小,但却可能会有助于面积的增大。
③ 因为移动较短线段的指针会得到一条相对较长的线段,这可以克服由宽度减小而引起的面积减小。 - 代码如下:
public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int result = 0;
while (left < right) {
int temp = Math.min(height[left], height[right]) * (right - left);
result = (result < temp) ? temp : result;
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return result;
}
15. 三数之和
① 题目描述
- 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在
三个元素 a,b,c ,使得 a + b + c = 0
?找出所有满足条件且不重复的三元组。 - 注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[ [-1, 0, 1], [-1, -1, 2] ]
② 使用暴力解法(Time Limit Exceeded)
- 使用3层循环,查找满足条件的3个数。需要使用
isDuplicate()
方法,避免结果中包含重复的三元组。 - 时间复杂度:n 表示 num 的个数,三个循环
O(n³)
,而 isInList 也需要O(n)
,总共就是O(n^4)
。
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
int len = nums.length;
if (len < 3 || nums == null) {
return result;
}
for (int i = 0; i < len - 2; i++) {
for (int j = i + 1; j < len - 1; j++) {
for (int k = j + 1; k < len; k++) {
if (nums[i] + nums[j] + nums[k] == 0) {
List<Integer> temp = new ArrayList<>();
temp.add(nums[i]);
temp.add(nums[j]);
temp.add(nums[k]);
if (!isDuplicate(result, temp)) {
result.add(temp);
}
}
}
}
}
return result;
}
public boolean isDuplicate(List<List<Integer>> res, List<Integer> target) {
Collections.sort(target);
for (int i = 0; i < res.size(); i++) {
List<Integer> temp = res.get(i);
Collections.sort(temp);
boolean flag = false;
for (int j = 0; j < 3; j++) {
if (temp.get(j) != target.get(j)) {
flag = true;
break;
}
}
if (!flag) {
return true;
}
}
return false;
}
③ 使用多指针(有人说是双指针,我更倾向于它是三指针)
- 先对nums进行sort,然后用一个指针i从头开始遍历nums,作为第一个数;使用left指针
(left=i+1)
和right指针(right=n-1)
,固定后面的区域,通过夹逼查找满足要求的num,构成三元组。 - 如果三个指针指向的数相加为零,开始移动指针:
① 当nums[left] == nums[left + 1]
时,left要向内侧移动,直至遇到新的不同的数停止,避免构成的三元组重复;right指针也是如此。
② 指针i作为固定的三元组中的第一个数,也要跳过重复的数字,直至遇到新的不同的数字停止。 - 如果
sum > 0
,说明right指针指向的数过大,向内侧移动right指针。 - 如果
sum < 0
,说明left指针指向的数过小,向内侧移动left指针。
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
int len = nums.length;
if (len < 3 || nums == null) {
return result;
}
Arrays.sort(nums);
for (int i = 0; i < len - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1, right = len - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
List<Integer> temp = new ArrayList<>();
temp.add(nums[i]);
temp.add(nums[left]);
temp.add(nums[right]);
result.add(temp);
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
} else if (sum > 0) {
right--;
} else
left++;
}
}
return result;
}