Leetcode刷题笔记4

1658. 将 x 减到 0 的最小操作数

1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10

输出:5

解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

如果想直接找到这些元素是比较困难的,所以:

正难则反

找到两个区域,a在左边,b在右边是的a + b = x。

\[ \] \[ \]

a b

创建一个sum,sum是整个数组的和

这时可以考虑中间的这个区域,使得sum - x,使中间这块连续的区域最长

转化:找出最长的子数组的长度,所有元素的和正好等于sum - x

len target

n - len

解法一:暴力枚举

target = sum - x

\< target \] R(\>=target) \[ \] \]

L

sum1 来标记L和R所指的区域的和

让R依次向后移动找那个最佳的位置

如果R找到一个元素使得前面的和 >= target,那么那个元素之前的元素之和肯定 < target

暴力解法的话,就让L右移一步,然后R回到L这里

但是R还有必要向左移动吗?

其实没有必要,本身前面的区域就小于target了,这时删除了第一个元素肯定还是小于target

所以R没有必要回去,所以让R要么原地不动,要么向后移动

解法二:滑动窗口

1.left = 0,right = 0

2.进窗口 -> sum1 += nums[right]

3.判断 -> 当这段区域的和 > target 就出窗口

出窗口 -> sum1 -= nums[left]

更新结果 -> 如果这段区域的和 = target 再更新结果

最后再用n减一下,得出最后结果

时间复杂度:O(N)

空间复杂度:O(1)

代码:C++

cpp 复制代码
class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int sum = 0; // 整个数组的和

        for(int a : nums) sum += a; // 把里面每个数都拿出来然后+=
        int target = sum - x;

        // 细节问题
        if(target < 0) return -1;

        int ret = -1; // 如果没有找到结果,返回-1,所以直接赋值-1
        for(int left=0, right=0, tmp=0; right < nums.size(); right++)
        {
            // 进窗口
            tmp += nums[right];

            while(tmp > target) // 判断
        {
            tmp -= nums[left++]; // 出窗口
        }
        if(tmp == target) // 更新结果
        {
            ret = max(ret, right - left + 1);
        }
        }
        if(ret == -1) return ret;
        else return nums.size() - ret;
    }
};

904. 水果成篮

904. 水果成篮 - 力扣(LeetCode)

等于是连续摘

转化:找出一个最长的子数组长度,子数组中不超过两种类型的水果

fruits = [1,2,3,2,2]

解法一:暴力枚举 + 哈希表

1,2,3,2,2

比如固定1,那就只能找到1,2

如果固定2,可以采摘到2,3,2,2

...

找出所有可能性,然后选择最长的

可以借助哈希表储存水果类型,统计水果一共出现多少种

解法二:滑动窗口

比如:

R

f = [1, 2, 1, 2, 3, 2, 3, 3]

L

hash<int,int> 不仅要存入水果的种类,还要存入水果的数量

  1. left = 0,right = 0

  2. 进窗口 -> hash[f[Right]]++

  3. 判断 -> hash.size > 2

出窗口 -> hash[f[Left]]--,要判断一下,如果对应的位置的元素变成0,要从哈希表中删除

更新结果

代码:C++

cpp 复制代码
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        unordered_map<int, int> hash; // 统计窗口内出现了多少种水果

        int ret = 0;
        for(int left = 0, right = 0; right < fruits.size(); right++)
        {
            hash[fruits[right]]++; // 进窗口

            while(hash.size() > 2) // 判断
            {
                hash[fruits[left]]--; // 出窗口
                if(hash[fruits[left]] == 0)
                {
                    hash.erase(fruits[left]); // 变成0就从哈希表中删除
                }
                left++;
            }
            ret = max(ret, right - left + 1); // 结果应该是left和right所指区间的长度
        }
        return ret;
    }
};

时间复杂度比较耗时,所以可以对哈希表做一个优化

cpp 复制代码
// 优化后:
// 因为
//1 <= fruits.length <= 10^5
//0 <= fruits[i] < fruits.length
//所以可以使用一个数组来代表哈希表

int hash[100001] = {0};
//
for (int left = 0, right = 0, kinds = 0; right < fruits.size(); right++)
if(hash[fruits[right]] == 0) kinds++; // 维护水果的种类
while (kinds > 2)
hash.erase(fruits[left]); -> if(hash[fruits[left]] == 0) kinds--;

// 如果数据范围是有限的,可以使用数组来替代哈希表,比直接使用容器效率的提升很大

438. 找到字符串中所有字母异位词

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

1. 暴力解法

  1. 如何快速判断两个字符串是否是"异位词"

s1 = "aabca"

s2 = "abaca"

用排序然后再一一比较的方法太耗时了

所以可以利用哈希表

遍历s1的字符,把字符依次丢到hash1

遍历s2的字符,把字符依次丢到hash2

然后比较两个哈希表中对应位置字符出现的个数是否相等,相等就是异位词

2.解决问题

比如:

s = "cbaebabacd"

以c为起点,找一个长度为m的子串,

把子串里面每个字符出现的个数丢到另一个哈希表hash2里面

然后遍历完这个子串之后,比较一下这两个哈希表是否相等就可以

然后以b为起点...

p = "abc",长度为m,hash1

hash1统计p里面的个数

hash2统计滑动窗口里面每个字符出现的个数

如何优化呢

每次枚举的时候,只需要把第一个字符删除,然后添加下一个字符就可以了

所以没有必要重新从头开始统计信息

s = "c b a e b a b a c d"

L R

s = "c b a e b a b a c d"

L R

解法:滑动窗口 + 哈希表

  1. left = 0, right = 0

  2. 进窗口 -> hash2[in]++

  3. 判断 -> right - left + 1 > m,就移动

出窗口 -> hash2[out]--

更新结果 -> check(hash2, hash1)

优化:可以对更新结果的判断条件(check)进行优化

利用变量count来统计窗口中"有效字符"的个数

s = "ccbaebabacd",hash2

p = "abc",hash1: a -> 1, b -> 1, c -> 1

进窗口:进入后 -> 判断此时hash2[in]是否小于等于hash1[in]

如果小于等于,就是添加的有效字符,count++

如果是其余情况,就是无效字符,count不变

出窗口:出去前 -> hash2[out] <= hash1[out] -> count--

其余情况count不变

更新结果:count == m

代码:C++

cpp 复制代码
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> ret;
        int hash1[26] = { 0 }; // 统计字符串p中每个字符出现的个数

        for(auto ch : p) hash1[ch - 'a']++; // ch - 'a'对应位置的下标

        int hash2[26] = { 0 }; // 统计窗口里面每一个字符出现的个数
        int m = p.size();
        for(int left=0, right=0, count=0; right < s.size(); right++)
        {
            char in = s[right];
            //hash2[in - 'a']++; // 进窗口
            // 维护一下count,可以在hash2前面添加++
            if(++hash2[in - 'a'] <= hash1[in - 'a']) count++; // 进窗口+维护

            if(right - left + 1 > m) // 判断
            {
                char out = s[left++]; // 元素搞出去之后,left向右移动一步
                if(hash2[out - 'a']-- <= hash1[out - 'a']) count--; // 出窗口 + 出去前维护count
                //hash2[out - 'a']--; // 出窗口
            }
            // 更新结果
            if(count == m) ret.push_back(left);
        }
        return ret;
    }
};

30. 串联所有单词的子串

30. 串联所有单词的子串 - 力扣(LeetCode)

s = "barfoothefoobarman", words = ["foo","bar"] words里面的字符串长度都为3

s = "[bar][foo][the][foo][bar][man]"

b a c a b d

跟438. 找出所有字母的异位词 很像

滑动窗口 + 哈希表

不同之处:

  1. 哈希表

hash<string, int> -> string对应字符,int对应字符串出现的次数

  1. left与right指针的移动

移动的步长,是给定字符串里面每个单词的长度(len)

  1. 滑动窗口执行的次数

len

代码:C++

cpp 复制代码
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string, int> hash1; // 保存words 里面所有单词的频次

        for(auto& s: words) hash1[s]++; // 遍历字符串数组

        vector<int> ret;
        int len = words[0].size(), m = words.size();
        for(int i = 0; i<len; i++) // 执行len次
        {
            // 这个哈希表维护滑动窗口内面单词的频次
            unordered_map<string, int> hash2;
            for(int left=i, right=i, count=0; right + len <= s.size(); right+=len) // 因为是要把后面的字符串放到滑动窗口里面,如果right太靠后,后面根本就没有长度为len的字符串
            {
                // 进窗口 + 维护count
                string in = s.substr(right,len);
                hash2[in]++;
                // 第一个哈希表不一定有这个单词,这样的话就会重新创建一个in,丢到哈希表
                // 所以会有时间消耗
                // 如果in出现在第一个哈希表里面的时候才接下来再判断
                // 因为此时判断的时候这个in一定是在里面了,所以就不用重新再创建一个in在哈希表里面了
                if(hash1.count(in) && hash2[in] <= hash1[in]) count++; // 可以提前判断一下,节省时间
                // 判断
                if(right - left + 1 > len * m) // len * m 窗口里面所有字符串的长度
                {
                   // 出窗口 + 维护count
                   string out = s.substr(left, len);
                   if(hash1.count(out) && hash2[out] <= hash1[out]) count--; // 有效单词
                   hash2[out]--;
                   // 出窗口后,left向后移动len步
                   left += len;
                }

                // 更新结果,当有效字符的个数正好等于字典里面单词的个数时
                if(count == m) ret.push_back(left);
            }
        }
        return ret;
    }
};
相关推荐
我爱挣钱我也要早睡!3 小时前
Java 复习笔记
java·开发语言·笔记
luckys.one6 小时前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
~|Bernard|7 小时前
在 PyCharm 里怎么“点鼠标”完成指令同样的运行操作
算法·conda
战术摸鱼大师7 小时前
电机控制(四)-级联PID控制器与参数整定(MATLAB&Simulink)
算法·matlab·运动控制·电机控制
Christo37 小时前
TFS-2018《On the convergence of the sparse possibilistic c-means algorithm》
人工智能·算法·机器学习·数据挖掘
汇能感知8 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun8 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
好家伙VCC8 小时前
数学建模模型 全网最全 数学建模常见算法汇总 含代码分析讲解
大数据·嵌入式硬件·算法·数学建模
茯苓gao9 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾9 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang