优选算法100 题 ——1 双指针

优算

1. 双指针

1.1 移动零

题目链接:移动零

其可以归类为 数组划分/数组分块 问题,一般使用 双指针算法 进行解题。

针对这道题,假定两个指针 cur / dest

作用分别是

cur: 从左向右遍历数组;dest: 表示已处理区间内( [0,cur - 1] ),某一特征元素的最后一个位置。

这两个指针将数组划分成了三个部分,

[0, dest] 非零数 [dest + 1, cur - 1] 零 [cur, n -1] 待处理数据

在 cur 从左向右遍历的过程中,

遇到 0: cur++;

遇到非 0: dest++(因为又有新的非零元素了);交换;cur++;

当 cur 遍历完数组,三个区域就只剩两个区域,即 dest及其左边的非零数;它右边的非零数。

java 复制代码
class Solution {
    public void moveZeroes(int[] nums) {
        int len = nums.length;
        for (int cur = 0, dest = -1; cur < len; cur++) {
            if (nums[cur] != 0) {
                dest++;
                int tmp = nums[cur];
                nums[cur] = nums[dest];
                nums[dest]= tmp;
            }
        }
    }
}

这个双指针进行数组分块,同样是 快速排序 的核心步骤。只不过 dest 表示 目标值

1.2 复写零

题目链接: 1089.复写零

同样使用双指针。

先完成 "异地" 操作,然后优化成双指针下的 "就地" 操作。

假定你整出了个新数组(题目要求在原数组的基础上操作),

这是 "异地" ,那么 "就地" 中,同样是这样移动指针。

设想如果从前开始复制,那么就会导致原数组元素被覆盖,后续全都是错的。像这样:

原数组中的 2 ,直接被覆盖了。

那从后先前试一试,假定 cur 指向 4 (即复写完成后,数组末尾值),dest 值为 n (数组长度),发现可以完成。

那么,找到最后一个值就很关键了。这里同样使用双指针来完成,不过不对数组元素进行操作。

四个步骤:

  1. 先看 cur 的值

  2. dest 走几步

  3. dest 是否到达末尾

  4. cur++

找到之后,"从后向前"完成复写操作。

这里有边界条件需要处理,如果给定的数组是 【1,0,2,3,0,4】,那么当 cur 走到原数组最后一个 0 时,dest 走两步就会超出数组长度。

针对这种情况,这样处理:

  1. 将 n - 1 处的置为 0

  2. dest -= 0

  3. cur--

java 复制代码
class Solution {
    public void duplicateZeros(int[] arr) {
        int cur = 0, dest = -1, n = arr.length;
        while(cur < n) {
            if (arr[cur] != 0) {
                dest++;
            } else {
                dest += 2;
            }
            if (dest >= n - 1) break; 
            cur++;

        }
        if (dest == n) {
            arr[n - 1] = 0;
            cur--;
            dest -= 2;
        }
        while (cur >= 0) {
            if (arr[cur] != 0) {
                arr[dest--] = arr[cur];

            } else {
                arr[dest--] = 0;
                arr[dest--] = 0;
            }
             cur--;
        }
    }
}

1.3 快乐数

题目链接: 202. 快乐数

由题目 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1 知存在下面这种过程。

看起来是两种,但是可以抽象成一种:

快乐数圈上全是 1

发现和链表是否有环 这一问题图形一样。其实,原理也是一样的。使用快慢双指针 来解决。当快慢指针相遇时,通过判断相遇点是否为一来判断是否为快乐数。这里没有指针,所以就把 数作为指针 ,而题目的变换关系 f f f 作为 .next 操作。

为什么一定成环? -- 鸽巢原理

n 的最大值为 2,147,483,647. 这里为了好算,记为 9,999,999,999.一共十个九;

那么n 经过 f f f 变换 最大值即为 81 × 10 = 810 . 取值范围 [0, 810].

假定经过了 811次变化, 那么至少存在一个数出现了两次(即成环了).

java 复制代码
class Solution {
    public int getSum(int n) {
        int sum = 0;
        while (n > 0) {
            int t = n % 10;
            sum += (t * t);
            n /= 10;
        }
        return sum;
    }
    public boolean isHappy(int n) {
       int slow = n,fast = n;
       do {
        slow = getSum(slow);
        fast = getSum(getSum(fast));
       } while (slow != fast);
       return slow == 1;
    }
}

1.4 盛水最多的容器

题目链接: 11. 盛水最多的容器

方法一: 暴力枚举; 但是该题会超时。

方法二: 利用单调性,双指针解决。

先从示例一中取一小段。

此时较大值即为 两端中的较小值 × 长度。移动指向最小值的指针,则又可以确定一个体积值。这一连串的体积值中的最大值即为所求。

java 复制代码
class Solution {
    public int maxArea(int[] height) {
        int max = 0, width = 0, high = 0;
        int left = 0, right = height.length - 1;
        while (left < right) {
            high = Math.min(height[left],height[right]);
            width = right - left;
            if (max < width * high) {
                max = width * high;
            }
            if (height[left] > height[right]) {
                right--;
            } else {
                left++;
            }
        }
        return max;
    }
}

1.5 有效三角形的个数

题目链接: 611. 有效三角形的个数

解法一:暴力枚举

使用三层循环嵌套,完成。时间复杂度为 O( n 3 n^3 n3)

解法二:利用单调性,使用双指针解题

先对数组进行排序。这时只需要比较一次即可 例如 a < = b < = c a <= b <= c a<=b<=c ; 只要证明 a + b > = c a + b >= c a+b>=c 即可。暴力枚举则要比较三次。

对数组排序后,就可以利用它的单调性解题。

  1. 锚定最大值

  2. 在最大值左侧区域,使用双指针,快速统计出符合要求的三元组的个数

时间复杂度为 O( n 2 n^2 n2)

解法二的完整时间复杂度为 n l o g n + n 2 nlogn + n^2 nlogn+n2 ,相较于解法一的 (3n³) (常系数 3 通常来源于三角形不等式的三个条件检查) ,有了质的飞跃。

java 复制代码
class Solution {
    public int triangleNumber(int[] nums) {
        Arrays.sort(nums);
        int len = nums.length,count = 0;
        for (int i = len - 1; i >= 2; i--) {
            int left = 0, right = i - 1;
            while(left < right) {
                if (nums[left] + nums[right] > nums[i]) {
                    count += (right - left);
                    right--;
                } else {
                    left++;
                }
            }
        }
        return count;
    }
}

1.6 和为 s 的两个数

题目链接: LCR179. 查找总价格为目标值的两个商品

解法一:暴力解法 使用两个循环嵌套。 时间复杂度 O( n 2 n^2 n2)

解法二:利用单调性,使用双指针解题

时间复杂度 O( n n n) ;解题思想同前题相同。

java 复制代码
class Solution {
    public int[] twoSum(int[] price, int target) {
        int left = 0, right = price.length - 1;
        while (left < right) {
            int sum = price[right] + price[left];
            if (sum > target) {
                right--;
            } else if (sum < target) {
                left++;
            } else {
                return new int[] {price[left],price[right]};
            }
        }
        return new int[2];
    }
}

1.7 三数之和

题目链接: 15. 三数之和

通过题目值要对结果进行去重。

可以先得到结果在排序去重,也可以先排序再去重。

解法一:排序 + 暴力枚举 + set特性去重

java 复制代码
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums); // 排序,方便去重和剪枝
        Set<List<Integer>> result = new HashSet<>(); // 利用Set自动去重
        int n = nums.length;

        for (int i = 0; i < n - 2; i++) {
            if (nums[i] > 0) break; // 提前终止,因为数组已排序,后续不可能有解
            for (int j = i + 1; j < n - 1; j++) {
                for (int k = j + 1; k < n; k++) {
                    if (nums[i] + nums[j] + nums[k] == 0) {
                        // 将满足条件的三元组排序后存入Set(去重关键)
                        List<Integer> triplet = Arrays.asList(nums[i], nums[j], nums[k]);
                        result.add(triplet);
                    }
                }
            }
        }
        return new ArrayList<>(result); // 转换为List返回
    }
}

解法二:排序 + 双指针

  1. 排序

  2. 固定一个数 a;因为已经排过序,所以 a <= 0。否则,三数之和不可能为零。

  3. 在 a 后面的区间内运用双指针,找到两数和为 -a。

细节问题:

  • 去重

找到一种结果后,left 和 right 指针要跳过重复元素。完成一次双指针运算后,i也要跳过重复元素。注意:不要越界

  • 不漏

找到一种结果后,left++, right-- 缩小区间,继续寻找。

java 复制代码
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        int len = nums.length;
        List<List<Integer>> ret = new ArrayList<>();
        for (int i  = 0; i < len - 2; ) {
            if(nums[i] > 0) break;
            int left = i + 1, right = len - 1, target = -nums[i];
            while (left < right) {
                int sum = nums[left] + nums[right];
                if (sum > target) {
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    ret.add(new ArrayList<Integer> (Arrays.asList(nums[i],nums[left],nums[right])));
                    left++;
                    right--;
                    while (left < right && nums[left] == nums[left - 1]) left++;
                    while (left < right && nums[right] == nums[right + 1]) right--;
                }
            }
            i++;
            while (i < len - 2 && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
}

1.8 四数之和

题目链接: 18. 四数之和

解法一:同 1.7 但是是四层嵌套

解法二:仍是 排序+双指针

  • 先固定一个 a,再在右侧区域固定一个 b。

  • 然后在 b 右侧区域用双指针找到 两数之和 为 target - a - b

  • 同样,要做到不重(这里a,b都要跳过重复元素),不漏

注意事项:如果是target是负数时 target - a - b 该计算可能超出范围。所以要强转为 long ,转一个数就行了编译器会自动转其他数,不要转结果因为结果已经是超限的结果。

java 复制代码
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> list = new ArrayList<>();
        Arrays.sort(nums);
        int len = nums.length;
        for (int a = 0; a < len - 3;) {
            for (int b = a + 1; b < len - 2; ) {
                int left = b + 1, right = len - 1;
                long aim = (long)target - nums[a] - nums[b];
                while (left < right) {
                    int sum = nums[left] + nums[right];
                    if (sum < aim) {
                        left++;
                    } else if (sum > aim) {
                        right--;
                    } else {
                        list.add(Arrays.asList(nums[a],nums[b],nums[left],nums[right]));
                        left++;
                        right--;
                        while (left < right && nums[left] == nums[left - 1]) left++;
                        while (left < right && nums[right] == nums[right + 1]) right--;
                    }
                }
                b++;
                while (b < len - 2 && nums[b] == nums[b - 1]) b++;
            }
            a++;
            while (a < len - 3 && nums[a] == nums[a - 1]) a++;
        }
        return list;
    }
}
相关推荐
77qqqiqi2 小时前
算法——数学基础
算法
啊?啊?2 小时前
7 排序算法通关指南:从 O (n²)(选择 / 冒泡)到 O (nlogn)(快排 / 归并)+ 计数排序
数据结构·算法·排序算法
张较瘦_2 小时前
[论文阅读] 算法 | 抗量子+紧凑!SM3-OTS:基于国产哈希算法的一次签名新方案
论文阅读·算法·哈希算法
芒克芒克2 小时前
LeetCode 面试经典 150 题:多数元素(摩尔投票法详解 + 多解法对比)
算法·leetcode·面试
wow_DG2 小时前
【Vue2 ✨】Vue2 入门之旅 · 进阶篇(二):虚拟 DOM 与 Diff 算法
开发语言·javascript·vue.js·算法·前端框架
和光同尘 、Y_____2 小时前
BRepMesh_IncrementalMesh 重构生效问题
c++·算法·图形渲染
sali-tec3 小时前
C# 基于halcon的视觉工作流-章32-线线测量
开发语言·人工智能·算法·计算机视觉·c#
lingran__3 小时前
速通ACM省铜第一天 赋源码(The Cunning Seller (hard version))
c++·算法
塔中妖3 小时前
【华为OD】数字游戏
算法·游戏·华为od