文章目录
- 算法概述
- 奇偶数字归位
- 寻找重复数字
- 接雨水
- 救生艇问题
算法概述
设置两个指针的技巧,其实这种说法很宽泛,似乎没什么可总结的
- 有时候所谓的双指针技巧,就单纯是代码过程用双指针的形式表达出来而已。
没有单调性(贪心)方面的考虑 - 有时候的双指针技巧包含单调性(贪心)方面的考虑,牵扯到可能性的取舍。
对分析能力的要求会变高。其实是先有的思考和优化,然后代码变成了双指针的形式。3)所以,双指针这个“皮”不重要,分析题目单调性(贪心)方面的特征,这个能力才重要。
常见的双指针类型:
3. 同向双指针
4. 快慢双指针
5. 从两头往中间的双指针
6. 其他
奇偶数字归位
leetcode922.按奇偶排序数组2
题目分析 :
设置两个指针, 一个指针指向奇数位置, 一个指向偶数位置, 然后看数组的最后一个位置进行发货, 如果是奇数发往奇数位置, 奇数位指针前进两步, 如果是偶数发往偶数位置, 偶数指针前进两步, 如果有一个指针越界了就说明数组已经排好了, 因为数组中奇数跟偶数的个数是一致的, 代码实现如下
class Solution {
//简单的双指针的解法, 给出来一个奇数位的指针, 偶数位的指针
public int[] sortArrayByParityII(int[] nums) {
//简单的发货的逻辑(只盯着最后一个位置看)
for(int i = 1, j = 0; i < nums.length && j < nums.length; ){
//如果最后一个数是奇数就跟奇数位交换, 奇数指针+2, 偶数同理
if((nums[nums.length - 1] & 1) == 1){
swap(nums, i, nums.length - 1);
i += 2;
}else{
swap(nums, j, nums.length - 1);
j += 2;
}
}
return nums;
}
//交换的方法
private void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
寻找重复数字
leetcode287.寻找重复数
题目分析:
这道题其实是链表里面的快慢指针寻找入环节点的题是一样的(不是很好理解), 看自己的理解了, 我们只提供一下代码思路, 寻找入环节点的步骤就是快指针一次走两步, 慢指针一次走一步, 然后快慢指针相遇, 重置一个指针为0位置, 然后快慢指针一次都走一步, 最后相遇的位置就是第一个入环的节点
class Solution {
//快慢指针法求解第一个入环节点(快指针走两步, 慢指针走两步)
public int findDuplicate(int[] nums) {
int slow = nums[0];
int fast = nums[nums[0]];
//找到第一个相遇的位置
while(fast != slow){
fast = nums[nums[fast]];
slow = nums[slow];
}
//重置其中一个指针为起始位置, 进行同速率移动
slow = 0;
while(slow != fast){
slow = nums[slow];
fast = nums[fast];
}
return fast;
}
}
接雨水
leetcode42.接雨水
题目分析(初始版本) :
这个题就是典型的逐渐优化代码从而转化为一个双指针的形式的例子, 我们的核心是想要找到一个位置可以存储多少的水量, 可以得知, 这个其实是由左右两侧的最大值的最小值决定的
如图所示, 三条黑色柱子从左到右代表着待求位置的左侧最大值, 待求位置, 和待求位置的右侧的最大值, 所以我们只要找到最大值中的较小的那个然后减去待求的高度就是该位置存储的水量, 事实真的是这样吗 ? 其实不然, 因为如果最大值中的最小值小于中间的值该位置就不会存储水…
核心公式 :
water = Math.max(0, Math.min(lmax, rmax) - height)
所以我们需要对每一个位置的左右两侧的最值进行查询, 所以我们需要生成两个预处理的结构, 代码实现如下
class Solution {
//双指针(对撞双指针)解决接雨水问题(一维的接雨水)
public int trap(int[] height) {
//基础的版本(运用辅助空间)
int len = height.length;
//开始生成预处理结构
int[] LMHeight = new int[len];
LMHeight[0] = height[0];
for(int i = 1; i < len; i++){
LMHeight[i] = Math.max(height[i], LMHeight[i - 1]);
}
int[] RMHeight = new int[len];
RMHeight[len - 1] = height[len - 1];
for(int i = len - 2; i >= 0; i--){
RMHeight[i] = Math.max(RMHeight[i + 1], height[i]);
}
//下面的求解的过程就是找到一个位置可以存储几格水
int ans = 0;
for(int i = 1; i < len - 1; i++){
ans += Math.max(0, Math.min(RMHeight[i + 1], LMHeight[i - 1]) - height[i]);
}
return ans;
}
}
双指针版本的优化就是
我们定义两个指针, 每次我们都去结算最小的那一侧, 如下图所示
对于左侧l指针指向的数据, 左侧的最大值是确定的, 但是右侧的最大值是不确定的(但是肯定大于等于当前的右侧最大值), 而对于右侧r指针指向的数据, 右侧的最大值是确定的, 但是左侧的最大值是不确定的(但是肯定大于等于当前的左侧最大值), 所以此时结算右侧r指针的数据, 因为r指针的数据瓶颈就是右侧的rmax…分析完毕, 左侧同理, 代码实现如下
class Solution {
//双指针(对撞的指针)解决双指针的问题
public int trap(int[] height) {
//下面是优化过后的接雨水的解法 --> 时间复杂度O(n), 空间复杂度O(1)
int ans = 0;
//创建的两个指针
int left = 1;
int right = height.length - 2;
//左右侧的最大值(对于左指针来说, 左侧的最大值是真实的, 右侧的是虚拟的, 右侧指针同理)
int lmax = height[0];
int rmax = height[height.length - 1];
while(left <= right){
if(lmax < rmax){
//此时结算左侧
ans += Math.max(0, lmax - height[left]);
lmax = Math.max(lmax, height[left++]);
}else {
//此时结算右侧
ans += Math.max(0, rmax - height[right]);
rmax = Math.max(rmax, height[right--]);
}
}
return ans;
}
}
救生艇问题
leetcode881.救生艇
题目分析 :
这道题就有一点贪心的感觉在里面了, 给定两个指针l, r 一个指向左侧位置一个指向右侧位置, 然后如果 peo[l] + peo[r] <= limit, l++, r–, 如果peo[r] > limit, r–; 这样就可以确保每一步都不会亏, 最后返回的船数就是最好的结果
class Solution {
//比较经典的双指针(对撞双指针)加上贪心的应用
public int numRescueBoats(int[] people, int limit) {
//首先进行一部排序(这个也是决定时间复杂度的一步, O(n * logn))
Arrays.sort(people);
//返回的答案
int ans = 0;
for(int l = 0, r = people.length - 1; l <= r; ){
int sum = l == r ? people[l] : people[l] + people[r];
if(sum > limit){
//此时最大的自己单独一个船
r--;
}else{
//此时这一轮的最大值跟最小值一个船
r--;
l++;
}
ans++;
}
return ans;
}
}