题目列表
11. 盛最多水的容器
42. 接雨水
15. 三数之和
16. 最接近的三数之和
18. 四数之和
26. 删除有序数组中的重复项
27. 移除元素
75. 颜色分类
167. 两数之和 II - 输入有序数组
2024.04.06
11. 盛最多水的容器
题目
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明: 你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
- n == height.length
- 2 <= n <= 105
- 0 <= height[i] <= 104
思路
- 从图可知,接水量=Math.min(left, right) * (rightIndex - leftIndex)
- 可以用双指针
- 因为要找最大值,而且双指针相向而行(宽度在变小),只有最小高度变大才可能有最大值,所以移动高度相对较低的一端
答案
class Solution {
public int maxArea(int[] height) {
int length = height.length;
if (length < 2) {
return 0;
}
int left = 0;
int right = length - 1;
int leftH = height[left];
int rightH = height[right];
int result = 0;
while (left < right) {
int h = Math.min(leftH, rightH);
result = Math.max(result, h * (right - left));
if (leftH == h) {
left++;
// 此处快速收敛一下,因为leftH >= height[left],就不可能更大
while (left < right && leftH >= height[left]) {
left++;
}
leftH = height[left];
} else {
right--;
// 同left处
while (left < right && rightH >= height[right]) {
right--;
}
rightH = height[right];
}
}
return result;
}
}
42. 接雨水
题目
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 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 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
提示:
- n == height.length
- 1 <= n <= 2 * 104
- 0 <= height[i] <= 105
思路
和上一题的区别是,上一题认为每个位置的高度都是0,本题是有一个基础高度
所以不能简单的用high * length,要逐个计算高度
整体思路和上一题类似,区别是每个格子能盛水的量是,Math.min(左max,右max)
答案
class Solution {
public int trap(int[] height) {
int length = height.length;
if (length < 2) {
return 0;
}
int left = 0;
int right = length - 1;
int leftHMax = height[left];
int rightHMax = height[right];
int result = 0;
// 因为计算高度在判断后进行。判断后会进行一次位移,left和right会重合,这里不需要left <= right
while (left < right) {
int minMax = Math.min(leftHMax, rightHMax);
if (minMax == leftHMax) {
left++;
if (height[left] < minMax) {
result += minMax - height[left];
} else {
leftHMax = height[left];
}
} else {
right--;
if (height[right] < minMax) {
result += minMax - height[right];
} else {
rightHMax = height[right];
}
}
}
return result;
}
}
15. 三数之和
题目
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例 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 <= nums.length <= 3000
- -105 <= nums[i] <= 105
思路
- 循环每一个数,然后变为newTarget = target - nums[i]的两数之和
- 排序之后,循环每一个数,然后双指针。
- newTarget = target - nums[i]。left + right = newTarget。
- 因为排序,left向右则和变大,right向左则和变小
- 综上,用双指针
答案
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int length = nums.length;
sorted(nums, 0, length - 1);
List<List<Integer>> result = new ArrayList<>();
for (int i = 0; i < length - 1; i++) {
if (nums[i] > 0) {
return result;
}
// 去除重复元素
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int target = -nums[i];
int left = i + 1;
int right = length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去除重复元素,这里用left和left+1对比,而不是用left和left-1对比,是为了后续移动left统一操作
while (left < right && nums[left] == nums[left+1]) {
left++;
}
// 去除重复元素
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
return result;
}
// 顺便练习下,有重复元素的快排(颜色分类用到了)
public void sorted(int[] nums, int left, int right) {
if (left >= right) {
return;
}
// min指向小于element元素的下一个值(不包含)[left, mid)是小于element的
// mid指向当前遍历元素[min, mid)是和element相等的值
// max是指向第一个比element大的元素的左边的元素,(max, right] 比element大
// [mid, max]是未遍历元素
int min = left, mid = min + 1, max = right;
int element = nums[min];
while (mid <= max) {
int current = nums[mid];
if (element == current) {
mid++;
} else if (element < current) {
swap(nums, mid, max--);
} else {
swap(nums, min++, mid++);
}
}
//因为min和max都是开区间,所以,这里做一个位移
sorted(nums, left, min - 1);
sorted(nums, max + 1, right);
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
2024.04.07
16. 最接近的三数之和
题目
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
输入:nums = [0,0,0], target = 1
输出:0
提示:
- 3 <= nums.length <= 1000
- -1000 <= nums[i] <= 1000
- -104 <= target <= 104
思路
同上三数之和,可以作为练习题
答案
class Solution {
public int threeSumClosest(int[] nums, int target) {
int length = nums.length;
int result = 0;
sorted(nums, 0, length - 1);
int min = Integer.MAX_VALUE;
for (int i = 0; i < length; i++) {
int nums1 = nums[i];
int left = i + 1;
int right = length - 1;
while (left < right) {
int sum = nums1 + nums[left] + nums[right];
// 判断差值
if (Math.abs(sum - target) < min) {
min = Math.abs(sum - target);
result = sum;
}
// 找到直接截断
if (sum == target) {
return sum;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
return result;
}
private void sorted(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int element = nums[left];
int min = left, mid = min + 1, max = right;
while (mid <= max) {
int current = nums[mid];
if (current == element) {
mid++;
} else if (current < element) {
swap(nums, min++, mid++);
} else {
swap(nums, mid, max--);
}
}
sorted(nums, left, min - 1);
sorted(nums, max + 1, right);
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
18. 四数之和
题目
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
- 0 <= a, b, c, d < n
- a、b、c 和 d 互不相同
- nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
提示:
- 1 <= nums.length <= 200
- -109 <= nums[i] <= 109
- -109 <= target <= 109
思路
和三数之和类似
区别是
- 需要两次循环
- length条件不一样
- nums[i]的条件不一样,可能相加之后溢出
答案
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
int length = nums.length;
if (length < 4) {
return result;
}
sorted(nums, 0, length - 1);
for (int i = 0; i < length - 3; i++) {
long first = nums[i];
// 快速截断,已经超过目标值了
if (first + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
break;
}
// 快速截断,不可能达成
if (first + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < length - 2; j++) {
// 同上
if (first + nums[j] + nums[j + 1] + nums[j + 2] > target) {
break;
}
// 同上
if (first + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int second = nums[j];
int left = j + 1;
int right = length - 1;
while (left < right) {
long sum = first + second + nums[left] + nums[right];
if (sum == target) {
result.add(Arrays.asList((int) first, second, nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
}
return result;
}
private void sorted(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int element = nums[left];
int min = left, mid = min + 1, max = right;
while (mid <= max) {
int current = nums[mid];
if (current == element) {
mid++;
} else if (current < element) {
swap(nums, min++, mid++);
} else {
swap(nums, mid, max--);
}
}
sorted(nums, left, min - 1);
sorted(nums, max + 1, right);
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
26. 删除有序数组中的重复项
题目
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
- 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
- 返回 k 。
判题标准:
系统会用下面的代码来测试你的题解:
int[] nums = […]; // 输入数组
int[] expectedNums = […]; // 长度正确的期望答案
int k = removeDuplicates(nums); // 调用
assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}
如果所有断言都通过,那么您的题解将被 通过。
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
提示:
- 1 <= nums.length <= 3 * 104
- -104 <= nums[i] <= 104
- nums 已按 非严格递增 排列
思路
- 一个size记录新的大小
- 如果下一个元素和当前元素相等,size就不动
- 否则size+1,并把当前位置的元素复制过来
答案
class Solution {
public int removeDuplicates(int[] nums) {
int size = 0;
// 看提示,可以确认大小范围
int last = -100000;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == last) {
continue;
} else {
nums[size++] = nums[i];
last = nums[i];
}
}
return size;
}
}
27. 移除元素
题目
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
提示:
- 0 <= nums.length <= 100
- 0 <= nums[i] <= 50
- 0 <= val <= 100
思路
和上一题相同。区别是,size变更的条件
上一题是last和当前不相等
这个一题是val和当前不相等
答案
class Solution {
public int removeElement(int[] nums, int val) {
int size = 0;
for (int i = 0; i < nums.length; i++) {
int current = nums[i];
if (current == val) {
continue;
} else {
nums[size++] = nums[i];
}
}
return size;
}
}
75. 颜色分类
题目
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
- n == nums.length
- 1 <= n <= 300
- nums[i] 为 0、1 或 2
进阶:
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
思路
重复元素排序
元素
class Solution {
public void sortColors(int[] nums) {
sorted(nums, 0, nums.length - 1);
}
private void sorted(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int element = nums[left];
int min = left, mid = min + 1, max = right;
while (mid <= max) {
int current = nums[mid];
if (element == current) {
mid++;
} else if (current < element) {
swap(nums, min++, mid++);
} else {
swap(nums, mid, max--);
}
}
sorted(nums, left, min - 1);
sorted(nums, max + 1, right);
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
167. 两数之和 II - 输入有序数组
题目
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
提示:
- 2 <= numbers.length <= 3 * 104
- -1000 <= numbers[i] <= 1000
- numbers 按 非递减顺序 排列
- -1000 <= target <= 1000
- 仅存在一个有效答案
思路
三数之和的简单版本
答案
class Solution {
public int[] twoSum(int[] numbers, int target) {
int l = 0, r = numbers.length - 1;
while (l < r) {
int current = numbers[l] + numbers[r];
if (current > target) {
r--;
} else if (current < target) {
l++;
} else {
// 题目要求从1开始
return new int[] { l + 1, r + 1 };
}
}
return new int[2];
}
}