目录
1、盛最多水的容器
题目要求
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
题目分析
设面积为area,设左下标为l,右下标为r。由图可知,假设height[r] < height[l],则面积的计算公式为:
area = height[r] * (r - l)
在遇到有两个变量在一个数组中时,可以考虑使用双指针。创建两个指针 l 和 r,一个代表左下标,一个代表右下标,从数组的两边开始遍历。由于长方形的高由小的那条边决定,因此可以让 r 和 l 所对应的数组的值进行比较,小的一方的下标向内移动一位,再计算面积的值。与移动前相比,若面积更大,则取移动后的面积,若更小,则取移动前的面积,随后再重复以上操作直到 l > r 。如图所示:
l 和 r 从数组两边开始遍历,计算每一次遍历的面积。
l < r ,l 向右移动一位,计算面积的值,与移动前的面积进行比较。
代码演示
//双指针,总是移动数字较小的那个指针
public static int maxArea2(int[] height) {
int l = 0, r = height.length - 1;
int ans = 0;
while (l < r) {
int area = Math.min(height[l], height[r]) * (r - l);
ans = Math.max(ans, area);
if (height[l] <= height[r]) {
++l;
}
else {
--r;
}
}
return ans;
}
总结:
将面积问题分解成边的大小问题,遇到数组与两个变量,优先考虑双指针+贪心。
2、跳跃游戏Ⅰ
题目要求
给定一个非负整数数组 nums ,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标。
示例:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
题目分析
数组中的元素代表可跳跃的最大长度,若计算每一次的跳跃地点,需要根据元素的值来划定遍历的范围,然后遍历范围内所有可跳跃的地点,通过比较所能到达的最远距离来确定最佳的跳跃地点。这种做法非常繁琐。
不妨换个想法,若当前的点最远可以到达的位置大于等于数组中的最后一个位置,说明可以到达。那么我们每次都只跳一格,计算当前所能到达的最远距离,比跳跃之前大,则更新最远可以到达的位置,当大于等于数组中的最后一个位置时返回true。
代码演示
//正确思路
/**
* 定义一个cover,表示当前元素能跳的最大覆盖范围,每次我都只往右跳一格
* 然后更新cover范围,将当前索引的覆盖范围和上一次的覆盖范围cover相比,
* 两者中的最大值就是更新后的cover。当最大范围>=数组最后一个索引时,返回true
*/
public static boolean canJump1(int[] nums) {
int cover = 0;
int num = nums.length-1;
for (int i = 0; i <= cover; i++) {
cover = Math.max(cover,i+nums[i]);
if(cover >= num){
return true;
}
}
return false;
}
总结:
做该题时如果直观的思想实现起来太麻烦,可以尝试改变一下需要考虑的对象。从考虑跳跃的坐标变成考虑跳跃的最远距离。
3、跳跃问题Ⅱ
题目要求
给你一个非负整数数组 nums
,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。假设你总是可以到达数组的最后一个位置。
示例:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。 从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
题目分析
由题可得,我们需要计算出跳跃到终点的次数,但这并不意味着需要计算出每次跳跃最合适的地点,因为非常繁琐,需要考虑诸多因素。因此和“跳跃问题Ⅰ”一样,我们同样考虑最远距离。
由于目标是使用最少的跳跃次数到达数组的最后一个位置,那么可以依次遍历数组内的每一个元素,遍历时计算他们所能到达最远的位置,即可以到达的最远下标,然后进行比较。若比上一个元素跳得远,则更新最远距离。当我们遍历到当前最远距离所在的下标时,意味着我们跳了一次。
代码演示
public static int jump(int[] nums) {
int count = 0;
int jump = 0;
int farthest = 0;
//如果访问最后一个元素,在边界正好为最后一个位置的情况下,会增加一次「不必要的跳跃次数」,例如{0},因此不必访问最后一个元素。
for (int i = 0; i < nums.length-1; i++) {
farthest = Math.max(farthest,i + nums[i]);
if(i == jump) {
jump = nums[farthest];
count++;
}
}
return count;
}
总结:
这道题是典型的贪心算法,通过局部最优解得到全局最优解。