283移动零
题目
思路解析
那我们就把不为0的数字都放在数组前面,然后数组后面的数字都为0就行了
代码
class Solution {
public void moveZeroes(int[] nums) {
int left = 0;
for (int num : nums) {
if (num != 0) {
nums[left++] = num;
// left最后会变成数组中不为0的数的个数
}
}
for (int i = left; i < nums.length; i++) {
nums[i] = 0;
}
return;
}
}
11盛最多水的容器
题目
思路解析
如果中间的水高度小,宽度又小,那肯定不会比蓝色这个区域大
如果中间的高度>=它的高度,但我们的宽度小
但我们容纳的水宽度变小高度不变
也不会比蓝色的面积大
因为中间的任何线都无法和他构成更大的容器了
因为我们 计算的高度是取决于短板的 ,宽度是变化的(收缩宽度甚至变小)
所以我们有个left左指针,right右指针
然后我们不断收缩,哪边高度小,哪边就开始收缩,因为计算的高度是取决于短板的
代码
class Solution {
public int maxArea(int[] height) {
int max = 0;
int left = 0, right = height.length - 1;
while (left < right) {
// right - left 是我们的长度
// 如果右边的高度大一点我们就 left++, 如果左边的高度大一点我们就 right-- 这样子不断收缩
max = height[left] < height[right] ?
Math.max(max, (right - left) * height[left++]) :
Math.max(max, (right - left) * height[right--]);
}
return max;
}
}
15三数之和
题目
三个数的和+起来为0
同时i!=j!=k
思路解析
我们要求的结果是nums【a】+nums【b】+nums【c】=0
我们的思路是for循环遍历a,然后求b,c
用两个指针操作,一个向右找一个向左找
我们先对数组进行排序,Arrays,sort()
如果我们的nums【0】>0,说明没负数,那说明我们的和不可能为0了我们直接return
然后判断去重,例如nus【i】==nums【i-1】我们就跳过,因为我们的nums【i-1】已经计算过答案了,我们【1,1,-2,0】我们没必要再利用第二个1了,所以我们直接去重
然后我们要判断和
因为我们的数组排序后是从小到大的
所以如果我们sum<0,我们就left++
如果我们的sum>0,我们就right--
如果我们找到了答案,我们就双指针收缩,left++,right--(双指针搜索前,我们要看看我们的判断条件,例如我们是找到最右边的left和最左边的right,这样子我们双指针收缩的时候,我们就不会得到重复的答案)
代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
// 先对数组进行排序
Arrays.sort(nums);
// 如果排序后最小的元素都大于 0,不可能存在三个数之和为 0 的情况
if (nums.length > 0 && nums[0] > 0) {
return result;
}
// 遍历数组,固定一个数作为三元组的第一个数
for (int i = 0; i < nums.length; i++) {
// 去重:如果当前数和前一个数相同,跳过
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
// 使用双指针法寻找另外两个数
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
// 和大于 0,右指针左移
right--;
} else if (sum < 0) {
// 和小于 0,左指针右移
left++;
} else {
// 找到一个三元组
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 对第二个数和第三个数去重
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
// 双指针同时收缩
left++;
right--;
}
}
}
return result;
}
}
42接雨水
题目
我们要求下雨的时候我们能接多少水
思路解析
我们有三种解法,前后缀分解,双指针解法,单调栈写法
前后缀分解
我们要计算能接多少水,就要计算左边木板的高度和右边木板的高度,这两个高度取最小值
左边木板的高度取决于左边的最大高度
因为高于这个高度的水,他是会从左边流出去的
而低于这个高度的水,他是不会流出去的
右边的木板高度同理,它取决于右边的最大高度
我们要有一个数组要从左到右,存储前缀的最大值
还要有一个数组从右到左,存储后缀的最大值
我们可以Max(上一个前缀最大值,当前高度),这样子来取我们的前缀最大值
从左到右的前缀最大值
后缀最大值同理
然后我们把水桶里面能接的水都加起来,这样子我们就得到了答案
双指针(可以进行空间优化)
优化的点:我们不利用两个数组去收集我们的前后缀和,而是通过双指针不断移动来模拟max前缀和max后缀
因为我们的这个位置能收集的水取决于左边的最大高度和右边的最大高度之间的最小高度
所以我们单个位置单个位置收集
答案为:(左边的最大高度和右边的最大高度之间的最小高度)-当前高度
但一开始例如最左边节点,我们的左边的最大高度
最右边节点,我们的右边的最大高度是固定的
所以我们左边右边一个一个收集节点,然后通过左右指针更新我们的最大左右高度
单调栈
我们相当于把坑填平了,所以我们只要记录5和4就好了
例如2这个节点
我们5和4下标之间距离是宽度
Min(5,4)之间的最小值和当前节点的高度的差 就是高度
所以我们除了知道栈顶那个数是什么,还要知道栈顶那个数是什么
找上一个更大元素,在找的过程中填坑
如果收集的时候出现了5,3,6这种中间有凹的能收集的情况,我们就算这个位置
所以我们弄一个栈,每次出现凹凸情况的时候我们就收集雨水
代码
前后缀分解
class Solution {
public int trap(int[] height) {
int n = height.length;
int[] preMax = new int[n]; // preMax[i] 表示从 height[0] 到 height[i] 的最大值
preMax[0] = height[0];
for (int i = 1; i < n; i++) {
preMax[i] = Math.max(preMax[i - 1], height[i]);
}
int[] sufMax = new int[n]; // sufMax[i] 表示从 height[i] 到 height[n-1] 的最大值
sufMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
sufMax[i] = Math.max(sufMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans += Math.min(preMax[i], sufMax[i]) - height[i]; // 累加每个水桶能接多少水
}
return ans;
}
}
==
双指针
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0;
int right = height.length - 1;
int preMax = 0; // 前缀最大值,随着左指针 left 的移动而更新
int sufMax = 0; // 后缀最大值,随着右指针 right 的移动而更新
while (left < right) {
preMax = Math.max(preMax, height[left]);
sufMax = Math.max(sufMax, height[right]);
ans += preMax < sufMax ? preMax - height[left++] : sufMax - height[right--];
}
return ans;
}
}
单调栈
class Solution {
public int trap(int[] height) {
int result = 0;
int n = height.length;
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 这个的意思是,如果收集的时候出现了5,3,6这种中间有凹的能收集的情况,我们就算这个位置
while (!deque.isEmpty() && height[i] >= height[deque.peek()]) {
int temp = height[deque.pop()];
// 如果队列为空了,我们就跳出循环
if (deque.isEmpty()) {
break;
}
int left = deque.peek();
int dh = Math.min(height[left], height[i]) - temp; // 面积的高
result += dh * (i - left - 1);
}
deque.push(i);
}
return result;
}
}