L51.【LeetCode题解】438. 找到字符串中所有字母异位词(四种方法)

目录

1.题目

2.分析

暴力解法

方法1:排序(超时)

方法2:哈希表(险过)

★判断两个哈希表是否相同算法(通用方法,必须掌握)

能相等的前提:两个哈希表的大小相等

哈希表有迭代器,可以使用范围for从头到尾遍历

提交结果

优化方法:定长滑动窗口

提交结果

使用哈希数组更快

提交结果

★★★更优化的方法:不定长滑动窗口(比定长的要快!)

提交结果


1.题目

https://leetcode.cn/problems/find-all-anagrams-in-a-string/

给定两个字符串 s 和 p,找到 s 中所有 p 的 

 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

 示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • s 和 p 仅包含小写字母

2.分析

暴力解法

大致思路:使用i遍历字符串s,从i位置截取和p相同长度的子串(前提p.size()<=s.size(),这个要在一开始就要判断),比较这个子串和p是否是异位词

方法1:排序(超时)

可以先对两个串排序,再调用operator==判断是否相等

class Solution {
public:
    vector<int> findAnagrams(string s, string p)
    {
        if (p.size()>s.size())
            return {};
        vector<int> ret;
        sort(p.begin(),p.end());
        for (int i = 0; i <= s.size() - p.size(); i++)
        {
            string tmp=s.substr(i,p.size());
            sort(tmp.begin(),tmp.end());
            if (p==tmp)
                ret.push_back(i);
        }
        return ret;
    }
};

调用sort函数的时间复杂度是O(NlogN),过高容易超时:

方法2:哈希表(险过)

统计s的子串和p串的词频,记录到哈希表中,再判断两个哈希表是否相同,这个时间复杂度比方法1低一点

★判断两个哈希表是否相同算法(通用方法,必须掌握)

虽然可以用哈希数组hash['z'+1]投机取巧(这个解法在本文的最后),但是其他题可能用不了哈希数组,因此掌握通用的方法是很有必要的

算法:

能相等的前提:两个哈希表的大小相等
if (mp1.size() != mp2.size()) 
    return false;
哈希表有迭代器,可以使用范围for从头到尾遍历

STL的map底层实现是红黑树,对于for (const auto& pair : mp1),pair拿到的是mp1的红黑树中一个节点,到mp2中查有没有相同的节点即可,使用find函数

find函数的查找逻辑:

cplusplus网上是这样说的:https://legacy.cplusplus.com/reference/map/map/find/

find取得(get)指向元素的迭代器(iterator to element)

在容器中查找一个键值与k等价的元素,如果找到,则返回指向该元素的迭代器;否则返回指向map::end的迭代器

比较时会出现两种情况

1.找到一个键值与k等价的元素,此时还不能断定两个节点一定相等,需要比较第二个关键字(pair.second)是否相等,如果不等(it->second != pair.second),返回false

2.没找到一个键值与k等价的元素(it == mp2.end()),返回false

class Solution {
public:
    bool check(map<int,int>& mp1,map<int,int>& mp2)
    {
        if (mp1.size() != mp2.size()) 
            return false;

        for (const auto& pair : mp1) 
        {
            auto it = mp2.find(pair.first);
            if (it == mp2.end() || it->second != pair.second) 
                return false;
        }
        return true;
    }

    vector<int> findAnagrams(string s, string p)
    {
        if (p.size()>s.size())
            return {};
        vector<int> ret;
        map<int,int> mp1;
        for (int i=0;i<p.size();i++)
            mp1[p[i]]++;
        for (int i = 0; i <= s.size() - p.size(); i++)
        {
            map<int,int> mp2;
            string tmp=s.substr(i,p.size());
            for (int i=0;i<tmp.size();i++)
                mp2[tmp[i]]++;
            if (check(mp1,mp2))
                ret.push_back(i);
        }
        return ret;
    }
};


提交结果

优化方法:定长滑动窗口

以s = "cbaebabacd", p = "abc"为例分析:

异位词子串首先要长度和p从串相等,对s从头到尾遍历即可,

在移动窗口时,注意左侧元素离开哈希表,右侧元素加入哈希表,当mp2[s[left]]==0时,必须删除这个节点,否则影响哈希表的结构

class Solution {
public:
    bool check(map<int,int>& mp1,map<int,int>& mp2)
    {
        if (mp1.size() != mp2.size()) 
            return false;

        for (const auto& pair : mp1) 
        {
            auto it = mp2.find(pair.first);
            if (it == mp2.end() || it->second != pair.second) 
                return false;
        }
        return true;
    }

    vector<int> findAnagrams(string s, string p)
    {
        if (p.size()>s.size())
            return {};
        vector<int> ret;
        map<int,int> mp1,mp2;
        for (int i=0;i<p.size();i++)
        {
            mp1[p[i]]++;
            mp2[s[i]]++;
        }
            
        for (int left=0,right=p.size()-1; right<s.size();left++,right++)
        {
            if (check(mp1,mp2))
            {
                ret.push_back(left);
            }
            mp2[s[left]]--;
            mp2[s[right+1]]++;
            if (mp2[s[left]]==0)
                mp2.erase(s[left]);
        }
        return ret;
    }
};

注:right+1最大为s.size(),此时mp2[s[right+1]]++;访问到字符串的'\0'没有越界,不影响结果 

提交结果

使用哈希数组更快

将map<int,int>改成数组,其他地方稍作修改即可

class Solution {
public:
    bool check(int* mp1,int* mp2)
    {
        for(int i='a';i<='z';i++)
        if (mp1[i]!=mp2[i])
            return false;
        return true; 
    }

    vector<int> findAnagrams(string s, string p)
    {
        if (p.size()>s.size())
            return {};
        vector<int> ret;
        int mp1['z'+1]={0},mp2['z'+1]={0};
        for (int i=0;i<p.size();i++)
        {
            mp1[p[i]]++;
            mp2[s[i]]++;
        }
            
        for (int left=0,right=p.size()-1; right<s.size();left++,right++)
        {
            if (check(mp1,mp2))
                ret.push_back(left);
            mp2[s[left]]--;
            mp2[s[right+1]]++;
        }
        return ret;
    }
};
提交结果

★★★更优化的方法:不定长滑动窗口(比定长的要快!)

hash_s是字符串s的滑动窗口的哈希数组,hash_p是字符串p的的哈希数组

上面的优化的方法还可以继续优化,引入有效字符的个数:

先让hash_s[s[right]]++,之后判断:

1.当hash_s[s[right]] <= hash_p[s[right]]时计入有效字符的个数count,即count++

2.一旦窗口长度大于len时,及时调整让left++,当hash_s[s[left]] <= hash_p[s[left]]时减小有效字符的个数count,即count--

★更新结果的条件:窗口长度相等,且有效字符的个数要相等,这样就不用像上面方法那样遍历mp1和mp2数组的每个元素

个数从0变成1,有效字符的个数+1,因此count++

个数从1变成0,有效字符的个数-1,因此count--

符合有效字符的个数==p串的长度,将left尾插到返回数组ret

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        if (p.size() > s.size())
            return {};
        vector<int> ret;
        int len = p.size();
        int count = 0;
        int hash_s['z'+1] = {0}, hash_p['z'+1] = {0};

        for (int i = 0; i < p.size(); i++)
            hash_p[p[i]]++;
        
        for (int left = 0, right = 0; right < s.size(); right++) 
        {
            hash_s[s[right]]++;
            
            if (hash_s[s[right]] <= hash_p[s[right]])
                count++;
            
            if (right - left + 1 > len) 
            {
                if (hash_s[s[left]] <= hash_p[s[left]])
                    count--;
                hash_s[s[left]]--;
                left++;
            }
        
            if (count == len)
                ret.push_back(left);
        }
        return ret;
    }
};
提交结果

<think>好的,我现在需要回答用户关于STC单片机是否有其他方法将变量存储到Flash中的问题。首先,我需要回忆之前关于STC单片机使用`code`和`_at_`的知识,然后思考其他可能的方法。 用户之前已经询问过使用`code`关键字和`_at_`指定地址的注意事项,现在想知道是否有其他方法。我需要确保回答全面,涵盖不同的方法,同时避免重复之前提到的内容。 首先,我应该考虑STC单片机常用的编程方法,例如使用特定的关键字或者编译器扩展。例如,除了`code`和`_at_`,是否还有其他关键字或宏可以用来声明Flash变量?比如,有些编译器可能支持`__flash`或类似的修饰符。 其次,可能需要通过指针直接访问Flash地址。用户可以通过定义指针变量,指向Flash的特定地址,然后通过该指针读取数据。这种方法需要了解Flash的地址布局,并且可能需要使用`code`指针来确保正确访问。 另外,STC单片机支持IAP(在应用中编程)和ISP(在系统编程)功能,允许程序在运行时修改Flash内容。这可能是一种方法,但通常用于动态写入,而不是静态存储变量。不过,用户可能想知道是否可以利用IAP功能来存储数据,比如将变量存储在特定的Flash扇区中,然后在程序运行时读取或更新这些数据。需要注意的是,这种方法涉及到擦除和写入Flash扇区的操作,需要谨慎处理,避免损坏程序代码或其他数据。 还有,链接器脚本(linker script)或分散加载文件(scatter file)可以用来指定变量的存储位置。通过修改链接脚本,可以将某些变量或数据段分配到Flash的特定区域。这种方法需要熟悉编译器和链接器的配置,可能对初学者来说比较复杂,但提供了更灵活的控制。 此外,某些开发环境或框架可能提供了宏或特定的库函数来简化Flash存储。例如,STC官方提供的头文件或库可能有相关的函数或宏定义,帮助用户将数据存储在Flash中。 还需要考虑数据初始化和持久化的问题。存储在Flash中的变量通常在程序烧录时写入,运行时不可修改(除非使用IAP)。因此,用户需要明确这些变量的只读特性,或者在需要修改时使用IAP操作。 另外,可能需要注意不同型号的STC单片机在Flash存储方面的差异。例如,Flash的容量、分页大小、擦写次数等特性可能影响存储策略的选择。需要查阅具体型号的数据手册以获取准确的信息。 总结起来,可能的其他方法包括: 1. 使用特定编译器扩展或关键字(如`__flash`)。 2. 通过指针直接访问Flash地址。 3. 利用IAP/ISP功能动态管理Flash数据。 4. 修改链接器脚本分配变量到Flash区域。 5. 使用官方库或框架提供的接口。 在回答时,需要详细说明每种方法的实现步骤、优缺点以及注意事项,帮助用户根据具体需求选择合适的方法。同时,要提醒用户注意Flash的只读特性(除非使用IAP),以及操作Flash时可能的风险,如数据丢失或程序崩溃,特别是在进行擦写操作时需谨慎处理中断和时序。</think>在 STC 单片机中,除了使用 `code` 和 `_at_` 关键字外,还可以通过以下方法将变量存储到 Flash 中。这些方法各有特点,需根据具体需求选择: --- ### **1. 使用 `__flash` 或 `const` 关键字(编译器相关)** - **适用场景**:某些编译器(如 SDCC 或 Keil C51 扩展)支持 `__flash` 或 `const` 修饰符,直接将变量分配到 Flash 中。 - **示例**: ```c // SDCC 编译器示例 __flash const uint8_t lookup_table[] = {0x01, 0x02, 0x03}; // Keil C51 扩展(需启用扩展语法) const uint8_t code lookup_table[] = {0x01, 0x02, 0x03}; ``` - **注意**: - 不同编译器语法可能不同,需查阅文档。 - `const` 在某些编译器中可能默认分配到 Flash,但并非绝对(需验证)。 --- ### **2. 通过指针直接访问 Flash 地址** - **适用场景**:直接定义指针指向 Flash 的物理地址,通过指针读取数据。 - **示例**: ```c // 定义一个指向 Flash 地址 0x2000 的指针 code uint8_t *flash_ptr = (code uint8_t *)0x2000; // 读取数据 uint8_t value = *flash_ptr; ``` - **注意**: - 必须声明指针为 `code` 类型(C51 编译器要求)。 - 确保地址范围合法且未被程序代码占用。 --- ### **3. 使用 IAP(在应用中编程)功能动态管理 Flash** - **适用场景**:运行时动态读写 Flash(如保存配置参数、日志等)。 - **实现步骤**: 1. **启用 IAP 功能**:调用 STC 官方提供的 IAP 函数(需包含 `iap.h`)。 2. **擦除扇区**:Flash 擦除以扇区为单位(如 512 字节)。 3. **写入数据**:按字节或字写入(需按扇区对齐)。 - **示例**: ```c #include "stc_iap.h" void save_to_flash(uint16_t addr, uint8_t *data, uint16_t len) { IAP_DisableInterrupts(); // 关闭中断 IAP_EraseSector(addr); // 擦除目标扇区 IAP_WriteBytes(addr, data, len); // 写入数据 IAP_EnableInterrupts(); // 恢复中断 } // 读取数据 uint8_t read_from_flash(uint16_t addr) { return *(code uint8_t *)addr; // 通过指针读取 } ``` - **注意**: - IAP 操作期间必须关闭中断,防止打断时序。 - Flash 擦写次数有限(约 10 万次),需避免频繁操作。 - 确保操作地址不覆盖程序自身代码(参考芯片手册的 Flash 分区)。 --- ### **4. 修改链接脚本(L51 文件)分配 Flash 区域** - **适用场景**:精确控制变量在 Flash 中的位置,避免地址冲突。 - **实现方法**: 1. 创建或修改链接脚本(如 `my_project.l51`)。 2. 指定代码段和数据段的地址范围,预留 Flash 空间。 - **示例链接脚本片段**: ```l51 ?PR?MAIN?MAIN (0x0000) // 主程序代码段 ?CO?LOOKUP_TABLE (0x2000) // 自定义数据段起始地址 ``` - **C 代码配合**: ```c #pragma codeseg ?CO?LOOKUP_TABLE // 指定后续代码/数据段 const uint8_t lookup_table[] = {0x01, 0x02, 0x03}; // 分配到预留地址 #pragma codeseg ?PR?MAIN?MAIN // 恢复默认代码段 ``` - **注意**: - 需熟悉编译器和链接器的段(Segment)管理机制。 - 避免与中断向量表或其他关键代码段重叠。 --- ### **5. 使用 EEPROM 模拟(部分 STC 型号支持)** - **适用场景**:需要频繁擦写的非易失数据存储。 - **实现原理**: - STC 某些型号(如 STC15 系列)支持通过 IAP 模拟 EEPROM,将 Flash 划分为多个扇区循环使用。 - 通过磨损均衡算法延长 Flash 寿命。 - **示例**: ```c #include "stc_eeprom.h" uint8_t read_eeprom(uint16_t addr) { return EEPROM_Read(addr); } void write_eeprom(uint16_t addr, uint8_t data) { EEPROM_Write(addr, data); } ``` - **注意**: - 需使用 STC 官方提供的 EEPROM 库(如 `STC15_EEPROM.h`)。 - 写入前需擦除扇区,单字节写入需缓存整个扇区数据。 --- ### **6. 使用 Flash 的 XDATA 扩展访问(增强型型号)** - **适用场景**:STC8 或 STC15 等支持 XDATA 扩展的型号,通过 MOVX 指令访问 Flash。 - **实现方法**: - 配置特殊功能寄存器(如 `P_SW2`)启用扩展 RAM/Flash 访问。 - 通过 `xdata` 或 `far` 关键字声明变量。 - **示例**: ```c // STC8H 系列示例 sfr P_SW2 = 0xBA; // 扩展寄存器 #define ENABLE_XRAM() (P_SW2 |= 0x80) // 启用 XDATA 扩展 xdata uint8_t flash_buffer[256] _at_(0x8000); // 定义在扩展地址 ``` - **注意**: - 需在访问前启用扩展模式。 - 不同型号的扩展地址范围不同,需查阅手册。 --- ### **总结:方法对比与选择建议** | **方法** | **优点** | **缺点** | **适用场景** | |-----------------------|----------------------------------|----------------------------------|---------------------------| | `code` 或 `__flash` | 简单、编译器自动管理 | 仅限只读数据 | 常量表、字库等静态数据 | | `_at_` 指定地址 | 精确定位地址 | 需手动规划地址,易冲突 | 固定位置的配置参数 | | IAP 动态操作 | 支持运行时读写 | 操作复杂,需处理中断和扇区对齐 | 需频繁更新的非易失数据 | | 链接脚本分配 | 灵活控制整个 Flash 布局 | 需掌握链接器配置 | 复杂项目地址规划 | | EEPROM 模拟 | 简化频繁擦写逻辑 | 依赖特定库和型号支持 | 日志、用户配置保存 | | XDATA 扩展访问 | 兼容大容量 Flash | 仅限特定增强型型号 | 需要大缓冲区存储的场合 | --- ### **注意事项** 1. **数据持久性**: Flash 数据在断电后保留,但写入次数有限,需避免频繁擦写。 2. **代码安全**: 动态修改 Flash 时,确保不会覆盖自身程序代码(尤其是中断向量表)。 3. **编译验证**: 使用 HEX 文件查看工具(如 STC-ISP)确认变量地址是否按预期分配。 4. **型号差异**: 不同 STC 单片机的 Flash 大小和 IAP 函数接口可能不同,需参考具体数据手册。 通过上述方法,可以灵活地将变量存储到 STC 单片机的 Flash 中,满足不同项目的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

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

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

打赏作者

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

抵扣说明:

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

余额充值