jjzjj

蓝桥杯算法竞赛系列第七章——六道力扣经典带你刷爆双指针

安然无虞 2023-09-24 原文

欢迎回到:遇见蓝桥遇见你,不负代码不负卿! 

目录

一、什么是two pointers

二、 栗子引入

三、力扣经典

栗子一:反转字符串

栗子二:救生艇

栗子三:链表的中间节点

栗子四:环形链表

栗子五:环形链表 II

栗子六:链表的倒数第K个节点

四、蓝桥结语:遇见蓝桥遇见你,不负代码不负卿!


【前言】

蓝桥杯基础部分还有三章就会更新结束,然后笔者就要准备期末考试咯,等到寒假会接着把蓝桥考前冲刺专栏给搞起来,那里都是干货,比这里要干的多!所以我们现在要做的是将基础知识点吃透。记住哦,早成者未必有成,晚达者未必不达!所以,加油吧少年。 

一、什么是two pointers

双指针是算法编程中一种非常重要的思想,但是很少会有教材单独拿出来讲,其中一个原因是它更倾向于一种编程技巧,而长得不太像一个”算法”的模样。双指针的思想十分简洁,但是却提供了非常高的算法效率。

 双指针分为三种

普通的指针:多是两个指针往同一个方向移动;

对撞的指针(多用于有序的情况)两个指针面对面移动(比如一头一尾往中间移动);

快慢的指针:慢指针+快指针。

注意:题目中大都是对撞指针和快慢指针,不过铁汁不要担心,后面有大量的经典题足以让你牢牢掌握two pointers! 

二、 栗子引入

题目:给定一个有序数组(数组是递增的),如数组arr = {1,4,5,7,9};找两个数之和为12,找到一组即可停止。

【方法一】:

很明显,本题采用暴力求解很简单,直接套用两层循环解决了,不过时间复杂度就得是O(N^2),这是非常低效的。 所以不可取!

暴力代码:

for (i = 0; i < n; i++)
{
	for (j = 1; j < n; j++)
	{
		if (arr[i] + arr[j] == k)
		{
			printf("%d %d\n", arr[i], arr[j]);
		}
	}
}

 【方法二】:

本题已经是有序的数组了,所以我们可以采用“对撞的指针”来解决。

思路:

注意哦,本题中 i 和 j 不是无脑++,-- 的哦,有点类似于二分查找的感觉,不信你看。

代码执行: 

#include<stdio.h>

int main()
{
	int arr[] = { 1,4,5,7,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int k = 12;
	int i = 0;//i从头开始
	int j = sz - 1;//j从尾开始

	while (i < j)
	{
		if (arr[i] + arr[j] < k)
		{
			i++;
		}
		else if (arr[i] + arr[j] > k)
		{
			j--;
		}
		else
		{
			printf("%d %d\n", arr[i], arr[j]);
			break;
		}
		
	}
	return 0;
}

这样的解法很简单,稍加改变时间复杂度就控制到O(N)了,所以我们要掌握它!OK,下面上硬菜咯,全都是实打实的经典!

  

三、力扣经典

栗子一:反转字符串

原题链接:力扣

题目描述:

示例:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

 思路(对撞指针):

采用“对撞的指针”,一个从头走、一个从尾走,交换就完事了,so easy!

代码执行:

void reverseString(char* s, int sSize){
    char* left = s;//指向起始地址
    char* right = s + sSize - 1;//指向末位地址
    while(left < right)//当left>=right时就不需要交换了
    {
        char temp = *left;
        *left = *right;
        *right = temp;
        left++;
        right--;
    }
}

栗子二:救生艇

原题链接:力扣

题目描述:

示例1:

输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)

示例2:

输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)

思路(对撞指针):

本题类似于上面那个引入栗子,也是采用“对撞指针”思想,不过本题一开始不是有序的,所以先排序,代码采用C++编写,因为可以用STL,就不需要人为去特意编写一个排序算法了,很是方便。

  

代码执行:

class Solution {
public:
    int numRescueBoats(vector<int>& people, int limit) {
        //先排序数组
        sort(people.begin(),people.end());
        int i = 0;//起始位置
        int j = people.size() - 1;//末尾位置
        int count = 0;
        while(i <= j)
        {
            if(people[i]+people[j] <= limit)//i较特殊,想象特殊在哪里
            {
                i++;
            }
            j--;
            count++;
        }
        return count;
    }
};

OK,前面都是“对撞的指针”,但是在实际运用当中包括笔试面试的时候常常出现的还是“快慢指针法”,不过不用担心,后面有四题哟,来感受它的奇妙叭。

栗子三:链表的中间节点

原题链接:力扣

题目描述:

示例1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

 示例2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

思路(快慢指针):

本题采用“快慢指针”解决,慢指针一次走一步,快指针一次走两步,不过需要注意的是本题受到节点总数是奇偶的影响,当节点总数是奇数时,fast->next == NULL,slow就指向了中间结点;当节点总数是偶数时,fast == NULL,slow就指向了中间结点,所以循环条件有两个,但是循环条件的先后顺序也是有讲究的,不信你看代码。

代码执行: 

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* middleNode(struct ListNode* head){
    //两个指针的起始位置都是从头开始
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    //注意循环条件不能写成fast->next && fast这种形式,原因在于fast走两步后可能就指向空了
    //再执行循环条件fast->next会导致空指针异常,越界了,但是fast写在前面就不会出现上述情况,因为&&存在短路求值
    while(fast && fast->next)
    {
        slow = slow->next;//slow每次走一步
        fast = fast->next->next;//fast每次走两步
    }
    return slow;//此时slow就指向中间节点
}

栗子四:环形链表

原题链接:力扣

题目描述:

示例1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

 示例2:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

 思路(快慢指针):

很简单,利用快慢指针来做就行了,如果链表有环,那么slow和fast一定会相遇。

代码执行:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

bool hasCycle(struct ListNode *head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    这里添加fast->next目的是防止空指针异常,因为循环体内fast一次走两步
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            return true;
        }
    }
    return false;
}

栗子五:环形链表 II

原题链接:力扣

题目描述:

示例1:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

 示例2:

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

 思路(快慢指针):

本题比较难理解,所以笔者特意画了出来,好好康康哦。

代码执行:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next)//先找第一次相遇点
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            break;
        }
    }
    //链表无环时的情况,如果有环,一定不会有NULL
    if(fast == NULL || fast->next == NULL)
    {
        return NULL;
    }
    //相遇后即有环,此时使用两个指针,一个从头开始走
    //一个从相遇点开始走,两者再次相遇处即为入口点
    slow = head;
    while(slow != fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

栗子六:链表的倒数第K个节点

原题链接:力扣

题目描述:

 示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

思路(快慢指针):

先让fast走k步,然后fast和slow再一起走。比较简单,这道题上次写到过,所以直接将JAVA代码上传咯。

代码执行:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        //单链表为空时
        if(head == null) {
            return null;
        }
        //判断k的有效性,为了一遍遍历链表解决问题,所有没有加上k > size()这个条件
        if(k <= 0) {
            return null;
        }
        //利用快慢指针,fast先走k-1步,注意要在这里防止k>size()
        ListNode fast = head;
        ListNode slow = head;
        while(k -1 > 0) {
            if(fast.next != null) {
                fast = fast.next;
                k--;
            }else {
                return null;
            }
        } 
        //此时fast和slow一起走
        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

  

四、蓝桥结语:遇见蓝桥遇见你,不负代码不负卿!

蓝桥基础部分大概还剩下三章的内容,笔者正在加班加点整理,由于近期快期末考试了,所以可能会影响进度,但是会挤时间更新的哦,铁汁们的支持就是我最大的动力,求求三连鸭。

有关蓝桥杯算法竞赛系列第七章——六道力扣经典带你刷爆双指针的更多相关文章

  1. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  2. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  3. 蓝桥杯备赛(二) - 2

    目录前言: 一、ASC分析代码实现二、 卡片分析代码实现三、 直线分析代码实现四、货物摆放分析代码实现小结:前言:  在刷题的过程中,发现蓝桥杯的题目和力扣的差别很大。让人有一种不一样的感觉,蓝桥杯题目偏向对于实际问题用编程去的解决,而力扣给人感觉很锻炼自己的编程思维,逻辑能力。两者结合去刷,相信会有不一样的收获。 一、ASC  已知大写字母A的ASCII码为65,请问大写字母L的ASCII码是多少?分析  这道题目看上去很简单,我们需确定自己计算的准确,所以我建议用编程去解决。代码实现publicclassTest8{publicstaticvoidmain(String[]args){Sy

  4. ruby - 在 Ruby 中实现 Luhn 算法 - 2

    我一直在尝试用Ruby实现Luhn算法。我一直在执行以下步骤:该公式根据其包含的校验位验证数字,该校验位通常附加到部分帐号以生成完整帐号。此帐号必须通过以下测试:从最右边的校验位开始向左移动,每第二个数字的值加倍。将乘积的数字(例如,10=1+0=1、14=1+4=5)与原始数字的未加倍数字相加。如果总模10等于0(如果总和以零结尾),则根据Luhn公式该数字有效;否则无效。http://en.wikipedia.org/wiki/Luhn_algorithm这是我想出的:defvalidCreditCard(cardNumber)sum=0nums=cardNumber.to_s.s

  5. Ruby 斐波那契算法 - 2

    下面是我写的一个计算斐波那契数列中的值的方法:deffib(n)ifn==0return0endifn==1return1endifn>=2returnfib(n-1)+(fib(n-2))endend它工作到n=14,但在那之后我收到一条消息说程序响应时间太长(我正在使用repl.it)。有人知道为什么会这样吗? 最佳答案 Naivefibonacci进行了大量的重复计算-在fib(14)fib(4)中计算了很多次。您可以将内存添加到您的算法中以使其更快:deffib(n,memo={})ifn==0||n==1returnnen

  6. ruby-on-rails - Rails add_index 算法 : :concurrently still causes database lock up during migration - 2

    为了防止在迁移到生产站点期间出现数据库事务错误,我们遵循了https://github.com/LendingHome/zero_downtime_migrations中列出的建议。(具体由https://robots.thoughtbot.com/how-to-create-postgres-indexes-concurrently-in概述),但在特别大的表上创建索引期间,即使是索引创建的“并发”方法也会锁定表并导致该表上的任何ActiveRecord创建或更新导致各自的事务失败有PG::InFailedSqlTransaction异常。下面是我们运行Rails4.2(使用Acti

  7. ruby - 趋势算法 - 2

    我正在开发一个类似微论坛的项目,其中一个特殊用户发布一条快速(接近推文大小)的主题消息,订阅者可以用他们自己的类似大小的消息来响应。直截了当,没有任何形式的“挖掘”或投票,只是每个主题消息的响应按时间顺序排列。但预计会有很高的流量。我们想根据它们引起的响应嗡嗡声来标记主题消息,使用0到10的等级。在谷歌上搜索了一段时间的趋势算法和开源社区应用示例,到目前为止已经收集到两个有趣的引用资料,但我还没有完全理解它们:Understandingalgorithmsformeasuringtrends,关于使用基线趋势算法比较维基百科页面浏览量的讨论,在SO上。TheBritneySpearsP

  8. Ruby - 不支持的密码算法 (AES-256-GCM) - 2

    我收到错误:unsupportedcipheralgorithm(AES-256-GCM)(RuntimeError)但我似乎具备所有要求:ruby版本:$ruby--versionruby2.1.2p95OpenSSL会列出gcm:$opensslenc-help2>&1|grepgcm-aes-128-ecb-aes-128-gcm-aes-128-ofb-aes-192-ecb-aes-192-gcm-aes-192-ofb-aes-256-ecb-aes-256-gcm-aes-256-ofbRuby解释器:$irb2.1.2:001>require'openssl';puts

  9. java实现Dijkstra算法 - 2

    文章目录一.Dijkstra算法想解决的问题二.Dijkstra算法理论三.java代码实现一.Dijkstra算法想解决的问题解决的问题:求解单源最短路径,即各个节点到达源点的最短路径或权值考察其他所有节点到源点的最短路径和长度局限性:无法解决权值为负数的情况二.Dijkstra算法理论参数:S记录当前已经处理过的源点到最短节点U记录还未处理的节点dist[]记录各个节点到起始节点的最短权值path[]记录各个节点的上一级节点(用来联系该节点到起始节点的路径)Dijkstra算法步骤:(1)初始化:顶点集S:节点A到自已的最短路径长度为0。只包含源点,即S={A}顶点集U:包含除A外的其他顶

  10. 蓝桥杯C/C++VIP试题每日一练之报时助手 - 2

    ?作者主页:静Yu?简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者?社区地址:前端知识交流社区?博主的个人博客:静Yu的个人博客?博主的个人笔记本:前端面试题个人笔记本只记录前端领域的面试题目,项目总结,面试技巧等等。接下来会更新蓝桥杯官方系统基础练习的VIP试题,依然包括解题思路,源代码等等。问题描述:给定当前的时间,请用英文的读法将它读出来。时间用时h和分m表示,在英文的读法中,读一个时间的方法是:  如果m为0,则将时读出来,然后加上“o’clock”,如3:00读作“threeo’clock”。  如果m不为0,则将时读出来,然后将分读出来,如5

随机推荐