双指针算法周总结

双指针算法

文章目录

  • 双指针算法
    • 概念剖析
    • 实战练习
      • [643. 子数组最大平均数 I - 力扣(LeetCode)](https://leetcode.cn/problems/maximum-average-subarray-i/)
      • [61. 旋转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/rotate-list/description/)
      • [481. 神奇字符串 - 力扣(LeetCode)](https://leetcode.cn/problems/magical-string/)
      • [11. 盛最多水的容器 - 力扣(LeetCode)](https://leetcode.cn/problems/container-with-most-water/description/)
      • [42. 接雨水 - 力扣(LeetCode)](https://leetcode.cn/problems/trapping-rain-water/description/)
    • 总结

概念剖析

废话不多说,直接进入正题

  • 第一点需要明确,双指针不是用指两个c语言中的指针去操作,其他没有指针的语言也可以实现,是两个“指针”不断位移来解决问题的一种算法,比如经常谈及的滑动窗口(特殊在两个指针移动时二者的距离始终不变)就是双指针算法的一种,熟悉的快速排序归并排序在实现过程中也运用了该思想

  • 大部分使用双指针的算法都可以用两层循环去暴力(遍历数组)解决,那么双指针的优势在哪里呢?
    O(n)的效率,朴素做法通常为O(n^2),大部分算法题是有时间限制的,暴力枚举通常无法通过所有测试点

  • 而根据两个指针的方向不同,也可以分为同向指针相向指针两种,像滑动窗口就是同向,同向中自然会涉及到移动的速度,就有了快慢指针法的概念,不过今天的实战中并没有该部分的典型习题,说着其实还挺像物理中的追及相遇问题

概念就是这么简单,具体的还需要在实战中体会

实战练习

先来一道经典的滑动窗口热热身

643. 子数组最大平均数 I - 力扣(LeetCode)

double findMaxAverage(int* nums, int numsSize, int k) {
    int sum = 0;
    int max;
    for (int left = 0; left <= numsSize - k; left++) {
        if (left == 0) {
            for (int i = 0; i < k; i++) {
                sum += nums[i];
            }
            max = sum;
        }
        else {
            sum = sum - nums[left - 1] + nums[left + k - 1];
            max = fmax(max, sum);
        }
    }
    return (double)max / k;
}

单纯的暴力遍历,求子数组的最大平均数,长度固定,即求子数组最大和

计算最大和除了最开始需要循环求出第一组数的和,后来依靠双指针的思想,窗口每移动一次只需要加上右边进来的元素,并减去左边出去的元素即可

热身完毕,数据的载体并不一定是数组,有一种反转链表的方法叫双指针法,同样也是双指针思想的应用,这里不对其做具体的解释,来看一道其他的题

61. 旋转链表 - 力扣(LeetCode)

这题真简单,和双指针能有什么关系,上来就写直接过

struct ListNode* rotateRight(struct ListNode* head, int k) {
    struct ListNode *tep = head;
    while (k >= 0) {
        tep = tep->next;
        if (tep == NULL) {
            tep = head;
        }
        k--;
    }
    return tep;
}

显然这是错的,因为这个链表不是环,这样写只能找到正确的头,会把头在原本链表中前面的数据全部丢失,如果是环这道题也没必要出了,所有我们可以考虑将链表变成环再断开,这样问题就得以解决了

理清思路后还有一点需要注意 在为了效率用k % n得出新的k的时,必须用n减去这个值,否则出来的结果总是会少移一位

struct ListNode* rotateRight(struct ListNode* head, int k) {
    if (k == 0 || head == NULL || head->next == NULL) {
        return head;
    }
    int n = 1;
    struct ListNode *tail = head;
    while (tail->next != NULL) {
        tail = tail->next;
        n++;
    }
    k = n - k % n;
    if (k == n) {
        return head;
    }
    tail->next = head;
    while (k--) {
        tail = tail->next;
    }
    struct ListNode *result = tail->next;
    tail->next = NULL;
    return result;
}

有人又要问了,这里面也没见leftright,怎么能叫双指针解法,双指针是一种思想,其本身并没有太多的限制,这道题体现为headtail,在两个指针的作用下连接成环,完成这最关键的一步

当然还有些题连载体都没有给你,比如下来这道,这个题与其他题不同的一点在于它没有给要进行操作的所有数据,只给了规律,这就需要先构造出原数组了

481. 神奇字符串 - 力扣(LeetCode)

好神奇,一神奇就蒙了,相信这是绝大数人见到此题的第一反应

它的问法本身是极其简单的,这就说明这道题的考察点实际上在构造这一步,这题既然都在双指针算法的文章下出现了,毫无疑问双指针的运用就是核心了

那么问题来了,怎么用?

有一点和上一个题很相似,没有传统意义上的leftright指针,现在的问题转化为两个指针往哪放

构建一个不断延伸的数组,且我们已经知道了前几位和具体的规律,那么每次延伸时只需要关注下一位即可,则必定有一个指针始终先一步指到下一位提供可以构造的位置

构造好了还需要算出1的个数,则另一个指针必须指向当前最右边的位置,让构造出的数字能顺利进行统计

int magicalString(int n) {
    if (n < 4) {
        return 1;
    }
    char s[n + 1];
    memset(s, 0, sizeof(s));
    s[0] = '1', s[1] = '2', s[2] = '2';//构建初始数列
    int cnt = 1, size, tep;
    int i = 2, j = 3;
    while (j < n) {
        size = s[i] - '0';
        tep = 3 - (s[j - 1] - '0');
        while (size > 0 && j < n) {
            s[j++] = '0' + tep;
            if (tep == 1) {
                cnt++;
            }
            size--;
        }
        i++;
    }
    return cnt;
}

tep是每次需要增加的数字,而size则是需要增加的数量,通过两个指针与初始数列不断模拟即可解决此题

干巴巴的找数组里面的符合条件的数还是很无聊的,上情景

11. 盛最多水的容器 - 力扣(LeetCode)

盛水的面积在本题中始终是一个矩形,面积长×宽即可算出,让本题的难度大大降低

与常说的短板效应一样,装水的多少取决于相对较短的那边,则两个关键长度分别是数组下标差其内元素较小的那个

数组中的元素对面积的影响明显大于数组下标差,所以优先要找到存在的最大值

左右指针向中间移动,装水的多少取决于短板,那就固定长板就好了,有了长板,那面积真就取决于短板了,这个时候在找到一个尽可能大的短板即可

int maxArea(int* height, int heightSize) {
    int left = 0, right = heightSize - 1;
    int max = fmin(height[0], height[1]);
    while (left < right) {
        max = fmax(max, (right - left) * fmin(height[left], height[right]));
        if (height[left] < height[right]) {
            left++;
        }
        else {
            right--;
        }
    }
    return max;
}

这个题还行吧,但它还是理想化了,实际生活中水的存储不能总是一整块矩形,所以下面这道题应运而生

42. 接雨水 - 力扣(LeetCode)

上一道题进化了,这次我们采用了最真实的物理引擎,不让每一次的雨水都理想化降下来形成一块矩形,自然难度也是肉眼可见的提升

核心的思路还是上一道题中的短板决定储水量固定长板,指针也还是左右两个指针向中间走,可是突然就不会了,如果不是在双指针的题库中见到这道题,博主甚至想不到可以用双指针去解

但既然核心思路相同,那我们就再去尝试求矩形的面积,可这不是不规则图形吗,别着急,换个角度思考,虽然不规则但是不是还是由许多矩形构成的,这就是问题的突破口

把这些连着的柱子强行看成分开的一根根柱子,再把储水的区域从下到上每层每层地分成一个个小矩形,问题的突破口正式出现在眼前
在这里插入图片描述

现在再次观察题目中的样图,每一个矩形和它旁边的柱子拎出来单独看,这是不是和第一题已经差不多了,只不过形成的只有单层的,因为在前面每一个多层已经被我们分成一个个单层了,那这个时候各个矩形的面积就是下标之差,整体面积就可以求解了

int trap(int* height, int heightSize) {
    int sum = 0;
    int tep = 0;
    int left = 0, right = heightSize - 1;
    while (left < right) {
        if (height[left] < height[right]) {
            if (tep < height[left]) {
                tep = height[left];
            }
            else {
                sum += tep - height[left];
            }
            left++;
        }
        else {
            if (tep < height[right]) {
                tep = height[right];
            }
            else {
                sum += tep - height[right];
            }
            right--;
        }
    }
    return sum;
}

事实上这种复杂题目一般都不止一种方法,像这道题还能用动态规划单调栈去做,以后笔者学习更多思想后这道题还会拿出来再说的

总结

总结一下,看完这五道题目,我们能发现能用双指针去解决的题目都有几点共性

  • 要让两个“指针”出现并起作用,首先得有一大片数据, 通常 这些数据的载体是数组,总之需要我们找到载体

  • 其次是要在这些数据中找到一个条件达成,如果没有规定左右区间的长度,又会出现与左右两端的数据进行比较取出极限值的情况

像接雨水这道题,题目中包含的条件非常隐晦,以至于很难想到用双指针去做,但经过分析找到它也存在这种边界上的比较,即短边限制储水量,在见到足够多的题后如果能顿悟到这一点,就会很自然的将双指针的思想运用到题目中了

有句话说的很好,菜就多练,这句话对所有的算法题都很适用,想要将双指针思想活学活用,多做题是唯一的捷径了

  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值