双指针、字符串、哈希表、链表、数组总结

目录

总结

1、交换元素swap

vector也能交换 swap(v[1],v[2]);

2、链表设置哑结点

统一逻辑对头结点操作

3、while(cur -> next != nullptr)代表运行到倒数第二个元素,也就是cur此时为倒数第一个元素
4、在cur初始指向哑结点时,下面执行cur指向index的前一个节点

while(–index) cur = cur -> next;

5、关于链表,什么移动删除元素等,多创建节点指向,别一个节点的next,next的next,而且命名时用题目给的

比如1-》2-》3-》4,就命名为p1 p2 p3等,这样好设计

6、用tab键模拟运行
7、二分的循环最后一次一定是处在left = right状态

最后是一个趋近的过程,left和right的平均值最后趋于不动,这时候,left可能向右走,可能right向左走,这时候一定一定有规律。

8、vector不初始化,不能在下面直接vector[0]进行访问,注意!!!!!!!

要添加元素后,有元素后才能下标访问,也就是必须push_back()后

9、务必进行初始化

数进行循环判断出错了!!!!

10、最大滑窗和最小滑窗模板

最小滑窗

while j < len(nums):
    判断[i, j]是否满足条件
    while 满足条件:
        不断更新结果(注意在while内更新!)
        i += 1 (最大程度的压缩i,使得滑窗尽可能的小)
    j += 1

最大滑窗

while j < len(nums):
    判断[i, j]是否满足条件
    while 不满足条件:
        i += 1 (最保守的压缩i,一旦满足条件了就退出压缩i的过程,使得滑窗尽可能的大)
    不断更新结果(注意在while外更新!)
    j += 1

左窗口一个是满足了就退出,让右窗口接着走,每次走一步
左窗口一个是满足了继续压缩,让右窗口去满足条件,这时候右窗口可能走几步

查看unordered_map的遍历 auto a : mp,a是迭代器(可能是),但是指向second直接用的点

在这里插入图片描述

字符串

1、移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

我觉得可以遇到后,将当前元素与最后一个元素对换,但是判断有点麻烦

双指针法
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置

在这里插入图片描述

2、反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出,,使用 O(1) 的额外空间
输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]

双指针

在这里插入图片描述

3、替换空格

把字符串 s 中的每个空格替换成"%20"。不用额外辅助空间

示例 1: 输入:s = “We are happy.”
输出:“We%20are%20happy.”

换了以后就修改了结构,如何知道位置
先统计空格的个数,计算需要的总长度 ,然后将字符串扩展,快慢指针从扩展的字符串后面开始遍历。
在这里插入图片描述

4、翻转字符串里的单词

这个是翻转单词,空间复杂度要求为O(1)。输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。
输入: “the sky is blue”
输出: “blue is sky the”

不解:我是三个字母,另外是四个字母,那这怎么替换

用下面方法,举个例子,源字符串为:"the sky is blue "(blue后面有个空格)
移除多余空格 : “the sky is blue”
字符串反转:“eulb si yks eht”
单词反转:“blue is sky the”

5、反转链表

6\三数之和为0

三数之和为0
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
在这里插入图片描述

链表

链表——构建

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

1、删除链表中等于给定值 val 的所有节点。

在这里插入图片描述

2、链表——设计链表的各个操作函数

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

3、反转链表

输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

注意这个题是头插法 ,找到头后,从头后面插
在这里插入图片描述
整整真正的做法在这里插入图片描述
在这里插入图片描述

两两交换链表中的节点

在这里插入图片描述
在这里插入图片描述

删除链表的倒数第N个节点

在这里插入图片描述

链表相交

在这里插入图片描述

环形链表II

fast走两步,low走一步
fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇
在这里插入图片描述
为什么一定相遇 ,fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的

公式计算的详细过程
结论是快指针走两步,慢指针走一步,找到它们相遇的地方,然后从这个地方和头结点一起发出一个速度一样的指针 ,然后再相遇的节点就是环形入口
在这里插入图片描述

1、二分查找

这里有两种写法,还是按传统 的写法,结束寻找是left>right,也就是while循环里的判断条件是left<=right,每次判断mid,大了向左走,小了向右走,等于就返回。

在这里插入图片描述

1)搜索插入位置

如果找到返回位置,找不到在合适的位置插入

可以直接从头开始找,暴力,但O(n)
在这里插入图片描述
还是二分,插入位置返回right+1

2)在排序数组中查找元素的第一个和最后一个位置

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
也就是返回目标值的返回,没找到返回[-1,-1]
用两个二分法找左右边界值
在这里插入图片描述
设立的如果mid当前值大了,会让right向左移动
在这里插入图片描述
上面求到的右边界是右边界+1。具体过程是让right不断趋近右边界直到等于右边界或者右边界+1,而mid在判断中,不断赶着left向右移动,直到最后left和right的平均值等于在右边界或者右边界第一个元素,总之是left跟着mid不断趋近右边界的过程,每次用个变量存储left的位置,随着位置的趋近,最后停在右边界右边第一个位置,然后while退出。
同理找到左边界-1
处理时有两种情况,一种是找到,一种是没找到,没找到在数组之外,或者在数组之内,只是没有这个数,没找到前者有一个返回-2,另一种是俩边界相等。
在这里插入图片描述

3)x 的平方根

输入:x = 4
输出:2

也就是从1到x二分找数的平方。
在这里插入图片描述
这样最终有两种结束条件的情况,
在这里插入图片描述

但是上面这样写的话会超时,考虑到除了1,x开平方都是小于x的一半,针对x = 0和x= 1加个判断,让right= x /2;
在这里插入图片描述
但是还是超时

4)有效的完全平方数

在这里插入图片描述

有序数组的平方

一个非递减数组,返回其平方的有序数组
输入:nums = [-7,-3,2,3,11] 输出:[4,9,9,49,121]

负数有可能平方后就大于后面的值了,但是有一点可能把握,两边平方后一定是大的,往里是逐渐变小的。因此可以用双指针一直往里靠近。
在这里插入图片描述

滑动窗口

长度最小的子数组(最小滑动窗口)√

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。

这种题就是看滑动窗口的长度

不能先排序,然后左右指针,大了一点右指针向左移动,小了,左指针向右移动,因为没法启动,怎么让 左指针向右移动呢,怎么让右指针向左移动呢,后者移动后怎么结束呢。

因此应该用滑动窗口,滑动窗口保证大于等于目标值,如果小了右窗口就向右移动也就是再拿个元素,如果大了左窗口就向右移动舍去一个元素。那么这样的话,初始时都指向第一个元素,那么如果第一个元素大了怎么办,岂不是左指针就比右指针大了??? 不能仅仅满足让滑动窗口的值等于目标值,那岂不是不移动了,因此需要逼着 它,满足了或者小于了我就再加个元素

在这里插入图片描述
在这里插入图片描述
因此可以认为右窗口是一个固定的值,让左窗口拉伸伸缩。

水果成篮(最大滑动窗口 需要二刷)

大概意思就是寻找两个数,扩散的范围最大,中间不能有其它的在里面。

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

注意这个题的逻辑表达,如何用map来判断是否满足条件
在这里插入图片描述

最小覆盖子串(最小滑动窗口 需要二刷)

输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
t字符可能有重复的,比如 ABA
包含ABC,并且长度是最小的,由于是在原字符串上找,因此肯定是有左右的东西,所以大概是左右指针或者滑动窗口

这次右窗口的逻辑是直到找到满足条件的情况后才停止,那么左窗口的逻辑就是尝试往右走,符合逻辑就尝试更新最小值,直到不符合逻辑了,这时候就能驱动着右窗口往右走了。

但关键是怎么存储,
可以用一个哈希表表示 t中所有的字符以及它们的个数
用一个哈希表动态维护窗口中所有的字符以及它们的个数
如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是「可行」的
在这里插入图片描述
在这里插入图片描述

螺旋矩阵II

在这里插入图片描述
这个循环,[),[),[),[)四个左闭右开就全连上了,一个完整的赋值循环
循环多少次,n/2,
如果是奇数怎么办,比如3,那么3/2=1,就只循环一次,单独设置最后一个元素

在这里插入图片描述
在这里插入图片描述
![在这里插入图片描述](https://img-
blog.csdnimg.cn/2706c551ede642aba33c5b348939c396.png)

哈希表

哈希表基础

哈希表都是用来快速判断一个元素是否出现集合里。

有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词
就是仅仅顺序不一样,字母组成一样
先遍历S,加入哈希表。再遍历T,从哈希表中减去。

赎金信

判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。但是第二个字符串的每个字符只能用一次。
输入:ransomNote = “aa”, magazine = “aab”
输出:true

先遍历后者加入哈希表。再遍历第一个,减哈希表。

字母异位词分组

在这里插入图片描述
定义一个unordered_map,字符串为键,值为一个vector,每次对str排序,然后将值加入map
在这里插入图片描述

找到字符串中所有字母异位词(重要 滑动窗口加哈希表)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

数组

两个数组的交集

第一个数组中有4 9 ,第二个数组里也有 9 4,就是 看都有的元素
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

第一个加入哈希表,第二个加入哈希表
遍历第一个哈希表判断第二个哈希表有没有

三数之和

1、首先对数组进行排序,这是双指针法的前提条件。

2、遍历数组,对于每一个数 nums[i],我们将其作为第一个数,然后在剩下的数中使用双指针法寻找两个数,使得三个数之和等于0。

3、双指针法的具体实现如下:定义两个指针,左指针 l = i + 1,右指针 r = n - 1,其中 n 为数组的长度。每次移动左右指针,直到找到符合条件的三个数。如果三数之和小于0,则左指针向右移动一位,如果三数之和大于0,则右指针向左移动一位,否则说明找到了一个符合条件的三元组。

在遍历过程中,需要注意去重,避免输出重复的三元组。

去重有两处
i的遍历,当前i已经找到了,下一个i还一样,就跳过了

for (int i = 0; i < nums.size(); i++) {
    if (i > 0 && nums[i] == nums[i-1]) continue; // 跳过相同的第一个元素
    // ...
}

找到目标值了,随便移动一个指针打破结构,但是移动的这个指针需要找到下一个不一样的元素

if (nums[left] + nums[right] + nums[i] == 0)
            {
                result.push_back({ nums[i],nums[left],nums[right] });
                do { left++; } while (left < right && nums[left] == nums[left-1]);
            }

在这里插入图片描述

四数之和

在这里插入图片描述

翻转字符串

左右指针 swap函数

反转字符串 II

i每次走2k个长度,这个是一步
2k个长度,前k个反转,如果不到k了,有多少个反转多少个
在这里插入图片描述

处理空格

字符串前面有空格,字符串里面的单词里也可能有大于1个的空格,字符串后面也可能有空格,将其处理成正常的字符
" the sky is blue "
“the sky is blue”
这个在for和while的处理上体现了个人的代码风格
在这里插入图片描述
下面这种方法前闭后开,闭是指fast指向一个正常字符,开是会略过空格
为保证前闭,刚进入for要到闭那里
resize要放在外面写,否则会出现并没有resize的情况
在这里插入图片描述

反转字符串中的单词

使用上面的处理函数
在这里插入图片描述

剑指Offer 05.替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1: 输入:s = “We are happy.”
输出:“We%20are%20happy.”

先for一遍计算空格个数,快慢指针,快指针指向当前数组的最后一个,然后和慢指针配合

剑指Offer58-II.左旋转字符串

将前面几个字符挪到后面去
输入: s = “abcdefg”, k = 2
输出: “cdefgab” 这个是将ab挪到后面
不能申请额外空间

比如ABCD,翻转前两个,再反转后面的,再来个大反转
ABCD -> BACD->BADC->CDAB

KMP(用状态集好难理解)

KMP暴力匹配

// 暴力匹配(伪码)
int search(String pat, String txt) {
    int M = pat.length;
    int N = txt.length;
    for (int i = 0; i <= N - M; i++) {  //从头开始对每个字符都进行匹配
        int j;
        for (j = 0; j < M; j++) {   //在里面开始对每个字符进行匹配
            if (pat[j] != txt[i+j])
                break;
        }
        // pat 全都匹配了
        if (j == M) return i;
    }
    // txt 中不存在 pat 子串
    return -1;
}

在这里插入图片描述

在这里插入图片描述
KMP就是一个动态规划,dp数组里面存着数据
因此给我个上面的apt,我就能计算出dp数组来,计算出dp来,如果根据这些信息移动pat的指针,那么就需要确定有限状态机来确定了,到达某个状态,遇到什么字符,确定什么行为。明确两个变量,一个是当前的匹配状态,另一个是遇到的字符,通过确定有限状态机来确定它的状态转移行为 。

在这里插入图片描述
在这里插入图片描述

确定有限自动机用二维dp数组来表示

dp[j][c] = next
0 <= j < M,代表当前的状态
0 <= c < 256,代表遇到的字符(ASCII 码)
0 <= next <= M,代表下一个状态

dp[4]['A'] = 3 表示:
当前是状态 4,如果遇到字符 A,
pat 应该转移到状态 3

所以说在创建为dp后,就能很快写出代码

public int search(String txt) {
    int M = pat.length();
    int N = txt.length();
    // pat 的初始态为 0
    int j = 0;
    for (int i = 0; i < N; i++) {
        // 当前是状态 j,遇到字符 txt[i],
        // pat 应该转移到哪个状态?
        j = dp[j][txt.charAt(i)];
        // 如果达到终止态,返回匹配开头的索引
        if (j == M) return i - M + 1;
    }
    // 没到达终止态,匹配失败
    return -1;
}

关键的是在于如何构建状态转移图

for 0 <= j < M: # 状态
    for 0 <= c < 256: # 字符
        dp[j][c] = next

状态推进:如果遇到的字符 c 和 pat[j] 匹配的话,next = j + 1
状态重启:如果字符 c 和 pat[j] 不匹配的话,状态就要回退(或者原地不动)
而所谓的状态重启就是到和它有相同前缀的位置。
看看有相同前缀位置的地方遇到和我一样的字符能否推进,如果能推进就转移过去,如果不能推进就回退到状态
下面 状态 X 总是落后状态 j 一个状态,与 j 具有最长的相同前缀

在这里插入图片描述

X = dp[X][慢指针的下一个 c]
 X最多前进一步,它是看当前遍历到的,和慢指针是否一样,一样就一起往前走,只不过往前走用dp里的数字代替了
 dp[快指针][下一个元素] += 1
 dp[快指针][其他元素元素] = dp[x][快指针下一个元素] 一般是0 因为不匹配了

实现 strStr() (KMP)

在一个串中查找是否出现过另一个串
示例 1: 输入: haystack = “hello”, needle = “ll” 输出: 2
示例 2: 输入: haystack = “aaaaa”, needle = “bba” 输出: -1

KMP暴力匹配

// 暴力匹配(伪码)
int search(String pat, String txt) {
    int M = pat.length;
    int N = txt.length;
    for (int i = 0; i <= N - M; i++) {  //从头开始对每个字符都进行匹配
        int j;
        for (j = 0; j < M; j++) {   //在里面开始对每个字符进行匹配
            if (pat[j] != txt[i+j])
                break;
        }
        // pat 全都匹配了
        if (j == M) return i;
    }
    // txt 中不存在 pat 子串
    return -1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪睡的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值