双指针之习题分析
一、两数之和——有序数组
(一)、题目需求
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
(二)、解法
public class Solution {
public static void main(String[] args) {
System.out.println(new Solution().twoSum(new int[]{
-1, 0
}, -1));
}
public int[] twoSum(int[] numbers, int target) {
if (numbers == null || numbers.length == 0) {
return numbers;
}
int start = 0;
int end = numbers.length - 1;
int[] result = {-1, -1};
while (start + 1 < end) {
if ((numbers[start] + numbers[end]) == target) {
result[0] = start + 1;
result[1] = end + 1;
break;
} else if ((numbers[start] + numbers[end]) > target) {
end -= 1;
} else {
start += 1;
}
}
return result;
}
}
(三)、代码分析
结束指针开始位置指向数组的最后一位;开始指针开始位置指向数组的第一位。
进行开始指针以及结束指针指向的值的判断:
1、情况1:开始指针与结束指针指向的值相加刚好等于目标值,直接退出循环。
2、情况2:开始指针与结束指针指向的值相加大于目标值,由于数组为有序数组,所以将结束指针前移一位。
3、情况3:开始指针与结束指针指向的值相加小于目标值,由于数组为有序数组,所以将开始指针后移一位。
while (start + 1 < end) {
if ((numbers[start] + numbers[end]) == target) {
break;
} else if ((numbers[start] + numbers[end]) > target) {
end -= 1;
}else {
start +=1;
}
}
(四)、流程图
1、初始状态,输入数组以及目标值
2、设置开始指针和结束指针
3、相加大于目标值,结束指针前移一位
4、相加等于目标值,退出循环
二、三数之和
(一)、题目需求
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
(二)、解法
public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length == 0) {
return result;
}
Arrays.sort(nums);
int len = nums.length;
for (int i = 0; i < len - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int start = i + 1;
int end = len - 1;
while (start < end) {
if (nums[i] + nums[start] + nums[end] == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[start]);
list.add(nums[end]);
start++;
end--;
result.add(list);
while (start < end && nums[start] == nums[start - 1]) {
start++;
}
while (start < end && nums[end] == nums[end + 1]) {
end--;
}
} else if (nums[i] + nums[start] + nums[end] > 0) {
end--;
} else {
start++;
}
}
}
return result;
}
}
(三)、代码分析
1、由于输入数组为无序数组,首先将该数组进行排序操作。
Arrays.sort(nums);
2、进行for循环判断,设立“i”只到达len-2,预留后面两个位置给其余两个数字。同时进行判断,假如当前“i”的值与前一个值相同,则说明情况相同,无需进行重复操作,直接跳出该层循环。
for (int i = 0; i < len - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// ......
}
3、设置start指针和end指针:start取i的后一位,end取当前数组的最后一位。
int start = i + 1;
int end = len - 1;
4、进行判断
- 情况1:当i对应的值加上开始指针指向的值以及结束指针指向的值等于0时,创建List集合,将其加入结果集中。并将开始指针后移一位,结束指针前移一位。
- 假如开始指针移动后,数值与移动前相同,则继续后移
- 假如结束指针移动后,数值与移动前相同,则继续前移
- 情况2:当i对应的值加上开始指针指向的值以及结束指针指向的值大于0时,说明总和过大,则需要减小该和,因此将结束指针前移一位。
- 情况3:当i对应的值加上开始指针指向的值以及结束指针指向的值小于0时,说明总和过小,则需要增大该和,因此将开始指针后移一位。
while (start < end) {
if (nums[i] + nums[start] + nums[end] == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[start]);
list.add(nums[end]);
start++;
end--;
result.add(list);
while (start < end && nums[start] == nums[start - 1]) {
start++;
}
while (start < end && nums[end] == nums[end + 1]) {
end--;
}
} else if (nums[i] + nums[start] + nums[end] > 0) {
end--;
} else {
start++;
}
}
(四)、流程图
1、初始状态
2、进行数组排序
3、开始for循环,并设置start指针与end指针。
4、开始指针与结束指针相加小于0,因此开始指针后移一位
5、不断进行判断,由于3个数值相加一直小于0,所以开始指针移动至结束指针前一位。再进行一次判断仍小于0,start指针将移动至结束指针位置,此时第一层循环结束
6、第二层循环开启:i后移一位,同时重新设置开始指针与结束指针
7、3个数值相加等于0因此,开始指针后移一位,结束指针前移一位。第二次判断,仍等于0,此时start指针将后移一位,结束指针将前移一位,即两者交换了指向的位置,此时开始指针大于结束指针,所以第二层循环结束。
8、开启第三层循环:i+1,同时重新设置开始指针与结束指针。由于i+1后,所对应的值与前一位相同,所以无需重复判断,直接退出第三层循环。
9、开启第4层循环,i+1,同时重新设置开始指针与结束指针。由于3个数值相加大于0,所以结束指针将前移一位,此时开始指针与结束指针将相等,所以第四层循环退出。for循环结束。
三、验证三角形
(一)、题目需求
给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。
示例 1:
输入: [2,2,3,4]
输出: 3
解释:
有效的组合是:
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3
(二)、解法
public class Solution {
public static void main(String[] args) {
System.out.println(new Solution().triangleNumber(new int[]{
1, 2, 3, 4, 5, 6
}));
}
public int triangleNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
Arrays.sort(nums);
int total = 0;
for (int i = nums.length - 1; i >= 2; i--) {
int start = 0;
int end = i - 1;
while (start < end) {
if (nums[start] + nums[end] > nums[i]) {
total += (end - start);
end--;
} else {
start++;
}
}
}
return total;
}
}
(三)、代码分析
1、排序数组
Arrays.sort(nums);
2、开始for循环,并将i初始值设为数组的最后一位,结束值为第三位,预留两位数值作为三角形的另外两条边的判断。并设置开始指针与结束指针
for (int i = nums.length - 1; i >= 2; i--) {
int start = 0;
int end = i - 1;
// ......
}
3、进行判断:
- 情况1:开始指针与结束指针相加大于i对应的值,由于数组已进行了排序操作。因此假如开始指针与结束指针相加已大于i对应的值,则开始指针至结束指针中间的值皆大于等于开始指针,因此中间的值与结束指针相加同样大于i,因此三角形的数量需加上结束指针与开始指针的差。
- 情况2:开始指针与结束指针相加小于i对应的值,由于数组已进行了排序操作。所以需要增大开始指针与结束指针的和,因此增大开始指针。
while (start < end) {
if (nums[start] + nums[end] > nums[i]) {
total += (end - start);
end--;
} else {
start++;
}
}
(四)、流程图
1、初始状态,并进行数组排序
2、开启for循环,start指针对应的值加end指针对应的值大于i对应的值,因此三角形数量加上结束指针与开始指针的差,同时结束指针前移一位
3、start指针对应的值加end指针对应的值等于i对应的值,因此开始指针后移一位
4、start指针等于结束指针,退出while循环,第一层for循环结束
5、开启第二轮for循环,i前移一位,重新设置start指针与end指针。start指针对应的值加end指针对应的值大于i对应的值,因此三角形数量加上结束指针与开始指针的差,同时结束指针前移一位
6、start指针等于结束指针,退出while循环,此时i已经等于数组的第三位数,所以for循环结束
四、存水问题
(一)、题目需求
给定 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
(二)、解法
public class Solution {
public static void main(String[] args) {
System.out.println(new Solution().trap(new int[]{0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1}));
}
public int trap(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
int start = 0;
int end = height.length - 1;
int startHeight = height[start];
int endHeight = height[end];
int total = 0;
while (start < end) {
if (startHeight <= endHeight) {
if (startHeight > height[start + 1]) {
total += startHeight - height[start + 1];
} else {
startHeight = height[start + 1];
}
start++;
} else {
if (endHeight > height[end - 1]) {
total += endHeight - height[end - 1];
} else {
endHeight = height[end - 1];
}
end--;
}
}
return total;
}
}
(三)、代码分析
1、初始化开始指针、结束指针、开始的最高高度,结束的最高高度。
int start = 0;
int end = height.length - 1;
int startHeight = height[start];
int endHeight = height[end];
int total = 0;
2、进行判断:
- 情况1:开始高度小于结束高度:
- 假如开始高度大于start指针指向的下一位对应的高度,则说明start指针指向的下一位格子可以存储的水量为开始高度减去该格的高度。
- 假如开始高度小于start指针指向的下一位对应的高度,则说明开始高度需要更新,因此更新开始高度
- 情况2:开始高度大于等于结束高度:
- 假如结束高度大于end指针指向的上一位对应的高度,则说明end指针指向的上一位格子可以存储的水量为结束高度减去该格的高度。
- 假如结束高度小于end指针指向的上一位对应的高度,则说明结束高度需要更新,因此更新结束高度
while (start < end) {
if (startHeight <= endHeight) {
if (startHeight > height[start + 1]) {
total += startHeight - height[start + 1];
} else {
startHeight = height[start + 1];
}
start++;
} else {
if (endHeight > height[end - 1]) {
total += endHeight - height[end - 1];
} else {
endHeight = height[end - 1];
}
end--;
}
}
(四)、流程图分析
1、初始状态
2、设置开始指针,结束指针,开始高度,结束高度
3、开始高度小于结束高度,因此结束指针前移一位,同时更新开始高度
4、开始指针指向的值的下一个值小于开始高度,因此将结果+1,同时开始指针后移
5、开始指针指向的值的下一个大于开始高度,因此更新开始高度,同时开始指针后移
6、结束指针小于开始指针,结束指针前移,同时结束高度进行更新
7、开始指针不断后移,total不断进行相加,直到开始指针指向的下一个值大于开始高度,更新开始高度,同时开始指针后移一位
8、结束指针前移,同时total进行运算,直至结束指针位于开始指针的后一位,退出循环