LeetCode 刷题想法(自用)

1.数组

二分查找

对于二分查找,关于左右边界的问题的一些思考。

  1. 左闭右开
    [left,right)
    由于右区间为开区间,在计算过程中并不能取得,因此存在一些关键的地方需要注意。
    1)right=nums.size();
    2) while(left<right);
    3) right=mid;
  2. 左闭右闭
    由于右区间为闭区间,在计算过程中实际上是能够取得的
    1)right=nums.size()-1;
    2) while(left<=right);
    3) right=mid-1;

双指针

一、快慢指针
对于快慢指针的用法,主要针对的是数组中,原地修改数组,即该元素满足特定的要求,需要删除掉该元素(交换两者元素),即可采取该方法,将后面的元素覆盖掉该元素。此时快指针指向原来的数组,而慢指针即指向新的数组。
1,如LeetCode 27. 移除元素。
2,如LeetCode 283 移动零(将快慢指针进行交换)
二、滑动窗口
滑动窗口其实也可以说是快慢指针的其中一个变体。
当满足题意的数字不断加入滑动窗口,即快指针不断向数组尾部移动。当不满足题意时,则要使得窗口内的数字减少到满足题意,即慢指针向尾部移动,将窗口内的第一个数字去除掉。
1,299 长度最小的子数组。
904,水果成篮(难度:中等)
76,最小覆盖子串。(难度:困难)
滑动窗口基本结构为两个循环。

		for(int right=0;right<xx.size();right++)
		{
			while([题目要求])
			{
				left++;
			}
		}
	第一个for循环主要是控制right指针遍历整个数组,用于扩展滑动窗口的大小
	第二个while循环主要是控制左指针,用于缩小滑动窗口的大小。

滑动窗口的时间复杂度为o(n),虽然用来了两个for循环,但是每个元素都只被操作了两次,一次进入窗口,一次从窗口出去。
三、其他类型
如同二分查找一样,其中一个指针从头开始遍历,另一个指针从尾向头遍历。常用与排序等。
1. LeetCode: 977 有序数组的平方
2. LeetCode: 15 三数之和
3. LeetCode:18 四数之和(与上面是同样的思路,只是在外多套一层循环)

2.链表

一、链表的基础操作

  1. 单链表
    前提:存在一个cur指针,指向当前节点
    1)添加节点操作(添加至当前节点的后)
			ListNode* tmp;
			tmp->val=val;
			tmp->next=cur->next;
			cur->next=tmp;
			cur=tmp;

2)删除节点操作(删除当前指向的节点)

		ListNode* tmp=cur;
		cur=cur->next;
		delete tmp;
  1. 双链表
    双链表和单链表的区别在于,单链表每一个节点只有next指针,而双链表存在一个next指针和一个pre指针,能够实现从后向前遍历。
    前提:存在一个cur指针,指向当前节点
    1)添加节点操作(添加至当前节点后)
		ListNode* tmp;
		tmp->val=val;
		tmp->next=cur->next; 
		tmp->pre=cur;
		cur->next->pre=tmp;
		cur->next=tmp;
		cur=tmp;

2) 删除节点操作(删除当前指向的节点)

		ListNode* tmp=cur;
		cur->next->pre=cur->pre;
		cur->pre->next=cur->next;
		delete tmp;

二、链表的算法题。

单链表

  1. LeetCode:203 移除链表操作(需要注意的是删除头结点和非头结点的不同)
  2. LeetCode:206 翻转链表操作(通过使用双指针的方法,通过将该当前节点的next指针指向pre节点,实现翻转链表)
  3. LeetCode:24 两两交换链表中的节点(最好画图实现,通过两个临时指针,用于记住其中一个交换的节点和交换后的下一个节点)
  4. LeetCode:19 删除链表的倒数第N个节点(快慢指针)
    该题解题思路:
    思路一:暴力解法:
    通过两次循环,第一次循环得到链表的长度,第二次循环找到需要删除的节点,将该节点 算出即可。
    思路二:O(n)的解法:
    设置快慢指针,一次循环。快指针先走n+1步后,快慢指针同时走,当快指针走到链表尾部时,即慢指针指向了待删除节点的前一个节点。此时删除该节点即可。
    LeetCode:160 相交链表
    该题解题思路:
    思路一:通过两个指针,分别指向A链表和B链表,A指针循环遍历A链表+C链表后再遍历B链表,B指针遍历B链表+C链表+A链表,当两个指针指向同一个节点时,表明该点为共同的节点。
    证明:记A链表的长度为a,B链表的长度为b,C链表(共同位置)的长度为c
    则A指针走过的长度为a+b+c,B指针走过的长度为a+b+c,两者走过相同长度的路,则接下来的一个节点即为共同。
  5. LeetCode:142 环形链表II
    思路一:快慢指针
    该题目的解题思路总共分为两步。
    第一步:需要确定该链表是否存在圆环:通过两个指针,一个快指针,每次走两步,慢指针,每次走一步,若两者相遇,即为存在环形。
    第二步:需要找出该链表的环形入口节点。此时我们已经确定了相遇的节点,使用indexA指针指向该相遇节点,使用indexB指针指向链表的头结点,两者沿着链表同时运动,当两者相遇时,即为该环形链表的入口节点。

3. 哈希表

一、哈希表的基本知识

在C++ 中,哈希表主要是指map和set两种形式。
1. set(集合)

集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::set红黑树有序O(log n)O(log n)
std::multiset红黑树有序O(log n)O(log n)
std::unordered_set哈希表无序O(1)O(1)

如上表所示,一般情况下,如果不是要求需要有排序的形式存在,采用unordered_set的情况比较多。

  1. map
映射底层实现是否有序是否可以重复是否能够更改查询效率增删效率
std::map红黑树key有序O(log n)O(log n)
std::multimap红黑树key有序O(log n)O(log n)
std::unordered_map哈希表key无序O(1)O(1)

二、哈希表的基本操作

哈希表的遍历操作](https://blog.csdn.net/qq_21539375/article/details/122003559)

三、哈希表的算法题

LeetCode :242 有效的字母异位词
LeetCode:49 字母异位词分组(对每一个字符串进行排序,由于异位词排序有着相同的顺序,因此,可以直接在map中,将排序的字符串当做key值,而未排序的当做value值存入即可。)
LeetCode:438 找到字符串中所有字母异位词
解题思路:
看到找到字符串中的满足题意的位置,脑子里第一个冒出来的就是滑动窗口。
因此,通过建立两个map,一个为滑动窗口window,另一个为目标字符串p的字母及个数的map即为need,为了方便比对。
整体思想,
1. 扩展窗口:右指针遍历字符串s,若遇到need中存在的字母,即将该字母加入window中,同时比对window中和need中该字母的个数,如果个数相同,则表明该字母已经是符合条件的,则有效字符valid++;
2. 缩小窗口:当右指针-左指针+1的大小已经比p大时,应该进行缩小窗口的操作。此时通过left指针所指的字母,与need进行比对,若是need中的字母,则需进一步比较窗口内的该字母的个数是否已经满足need中字母的个数,换句话说该字母是否已经存入有效数字了。如果是,则需valid–,再将window中该字母的个数减一。
LeetCode:349 两个数组的交集(采用unordered_set存入两个数组中相同的元素,最后输出即可。)
LeetCode:350 两个数组的交集II(输出的数字可重复)
LeetCode:1 两数之和(使用哈希表:注意,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。)
LeetCode:454 四数相加II
LeetCode: 383 赎金信

4. 字符串

一、较为简单的算法题

  1. LeetCode 344:翻转字符串
  2. LeetCode 541:翻转字符串II
  3. LeetCode 151:翻转字符串中的单词
    法一:
    注意事项:
    1. 空格的问题,单词之间的空格可能是多个,也可能是单个;句首和句末可能有多余的空格。
    2. 翻转整个句子的同时,保证每个单词不变。
    算法思路:
    3. 针对第一个注意事项,必须要有一个删除空格的方法。采用的是双指针的方法,如LeetCode 27 题一样。仅需注意的是在每两个单词之间,我们需要添加一个空格,通过if(slow!=0) s[slow++]=’ '即可。注意最后需要返回新的string大小。
    4. 针对第二个注意事项,仅需首先翻转整个字符串,再翻转每个单词即可
    法二:
    在法一中,我们采用了先将整个字符串整体翻转,再局部翻转的方法。在法二中,我们不采用翻转这个C++的内置函数,而是使用substr()这个截取函数。
    算法思路:
    由于需要将整个字符串倒置,因此就直接从字符串的尾部开始,直接截取尾部的的单词,然后作为第一个单词append到res字符串中。
 string reverseWords(string s) {
    
        string res;
        for(int j=s.size()-1;j>=0;--j)
        {
            if(s[j]==' ')
                continue;
            int i=j;
            while(i>=0&&s[i]!=' ') i--;
            res.append(s.substr(i + 1, j - i));
            res.append(" ");
            j=i;            
            
        }
        
        if (!res.empty()) res.pop_back();
        return res;

    }
  1. LeetCode 剑指offer58-II 左旋转字符串。
    做该题同样有两种思路,与上述第三题的思路一一对应。
    法一:
    采用reverse()的方法,整体翻转+局部翻转的方法。首先将前n的单词翻转,再将后n个单词翻转,再整体翻转
    法二:
    采用substr()的方法,即在原来的字符串上在附上一个完整的字符串,从第n位开始截取s长度的字符串即可。
    5. LeetCode 剑指offer 05 替换空格

二、KMP算法

1. next数组的理解与代码

1)理解
①首先提出一个问题,next数组里到底是什么?
next数组里存的是最长前后缀相等的长度。
②问题又来了,最长前缀和最长后缀是啥,如何计算。
*** 注意:最长前缀是指从字符串的起始字母到最后一个字母(不包含最后一个字母)
*** 同样的:最长后缀是指从字符串的最后一个字母到字符串的起始字母(不包含起始字母)
举个栗子:
如字符串 “ababcab”
其前缀分别为:a,ab,aba,abab,ababc,ababca
其后缀分别为:b,ab,cab,bcab,abcab,babcab
则其最长相等的前后缀为:ab,则其长度为2,
此时该位置的next[6]数组中存入的数字就是2.

i0123456
pabcabbb
next0001200

③在理解了next数组的含义,其次我们必须了解,为什么要使用next数组。
在不使用KMP算法,而是使用暴力解法,即两个循环遍历时,当遇到s[i]!=p[j]时,需要将i回溯到i+1的位置,此时j则从0开始遍历。在这个阶段中将会有较多的重复。
而使用KMP算法将能够借助next数组中存在的最长相等前后缀,直接移动到前后缀相同的即可。
借助其他博主的图片一用。
原博
在这里插入图片描述
当两者匹配到该位置时不匹配,如果使用next数组,在b不匹配后,我们需要回溯一步,只需要获得next[j-1]的值,按照上表所示,为2,此时,我们将j移动2即可,即p[2]=c。
在这里插入图片描述
通过使用next数组,我们就可以减少i的回溯,从而提高效率,这就是next数组使用的含义。
再次提醒,next数组中存放的最长相等前后缀的长度。
2)代码
处理next数组总体来说有3个步骤
① 初始化next数组;
②处理前后缀不相同的情况,关键在于j=next[j]
③处理前后缀相同的情况

void getNext(int* next, const string& s)//next为存的最长前后缀的长度-1,(即跳转的索引)
    {
        int j=-1;
        next[0]=j; //初始化
        for(int i=1;i<s.size();i++)
        {
            while(j>=0&&s[i]!=s[j+1])//前后缀相等的情况
                j=next[j];//向前回退,找到前后缀相同的情况,再次比较。
            if(s[i]==s[j+1])
                j++; //前后缀不等的情况
            next[i]=j;
        }
    }

由于目前广泛使用的next数组中,都是在上述计算的数值上-1后的值。因此原计算next数值变为

i0123456
pabcabbb
next-1-1-101-1-1

上述代码也是计算该类型的值。

2. 使用next数组进行匹配

int KMP(string s,string p){
	int next[p.size()];
	getNext(next,p);
	int j=-1;
	for(int i=0;i<s.size();i++)
	{
		while(j>=0&&s[i]!=p[j+1])
			j=next[j];//前往最长前后缀表中寻找到其前后缀所在的位置,牢记,j是从-1开始的,最后找到的j需要+1,才是实际的位置。
		if(s[i]==p[j+1])
			j++;
		if(j==p.size()-1)
			return (i-p.size()+1);
	}
	return -1;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值