力扣hot100 -- 哈希

文章详细分析了LeetCode中涉及的算法问题,包括使用暴力法、二分搜索、哈希表解决两数之和,以及利用unordered_map和排序结合处理字母异位词分组和最长连续序列。同时讨论了不同方法的时间复杂度和优化技巧。

目录

🌼两数之和

暴力

二分

哈希

🌼字母异位词分组

unordered_map + 排序

unordered_map + 计数

🌼最长连续序列

unordered_set + 跳过前驱

排序 + dp


🌼两数之和

1. 两数之和 - 力扣(LeetCode)

暴力

O(n^2)  两层循环

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) { // 返回vector数组
        int n = nums.size();
        for (int i = 0; i < n - 1; ++i)
            for (int j = i + 1; j < n; ++j) 
                if (nums[i] + nums[j] == target)    
                    return {i, j};
        return {}; // 返回空数组
    }
};

二分

O(nlogn)

排序 O(nlogn) 

遍历 O(nlogn) = 外层 O(n) + 二分O(logn)

AC  代码

struct node {
    int v, x; // value, index
}a[10010];

bool cmp(node x, node y) {
    return x.v < y.v; // value 从小到大
}

class Solution {
public:

    vector<int> twoSum(vector<int>& nums, int target) {
        int n = nums.size();
        for (int i = 0; i < n; ++i)
            a[i].x = i, a[i].v = nums[i];
        sort(a, a + n, cmp); // v,x 保留原来元素和索引的一一对应关系

        for (int i = 0; i < n - 1; ++i) {
            int l = i + 1, r = n - 1; // i 的下一个位置开始二分
            while (l < r) {
                int m = (l + r + 1) >> 1; // 防止 l 不变,死循环
                if (a[m].v + a[i].v > target)
                    r = m - 1;
                else
                    l = m; 
            }
            if (a[i].v + a[l].v == target)
                return {a[i].x, a[l].x};
        }
        return {}; // 空数组
    }
};

哈希

O(n)

知识

map<string, int>a; //升序
map<string, int, greater<string> >a; //降序

h[key] = val;
//等价于
h.insert(make_pair(key, val));

for(map<string, int>::iterator it = a.begin(); it != a.end(); ++it)
    cout<<it->first<<"\t"<<it->second<<endl;
size/empty/clear //元素个数,判空,清空
begin / end //指向开始 / 结束位置的指针
insert(x) //将元素x插入集合(x为二元组)
erase(x) //删除所有等于x的元素(x为二元组)
erase(it) //删除it指向的元素(it为指向二元组的迭代器)
find(k) //查找键为k的二元组的位置, 若不存在, 返回尾指针 .end()

C++ map和unordered_map详解-腾讯云开发者社区-腾讯云 (tencent.com)

AC  代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) { // 返回vector数组
        int n = nums.size();
        unordered_map<int, int> h; // 键 元素大小;值 元素下标
        for (int i = 0; i < n; ++i) {
            auto it = h.find(target - nums[i]); // 查找这个键 -- 元素大小
            if (it != h.end()) // 找到了该元素
                return {i, it->second}; // 返回下标

            //h.insert(make_pair(nums[i], i)); // 插入键值对
            h[nums[i]] = i; // 插入键值对
        }
        return {}; // 返回空数组
    }
};

🌼字母异位词分组

49. 字母异位词分组 - 力扣(LeetCode)

知识

1)emplace_back 与 push_back

一文弄清楚 push_back 和 emplace_back 的区别-emplace_back和push_back有区别吗 (51cto.com)

2)unordered_map 与 map

map和unordered_map区别及其优缺点 - 己平事 - 博客园 (cnblogs.com)

map、multimap 容器都会自行根据的大小对存储的键值对进行排序 

3)代码中,两个要注意的点

(一)引用传递 比 迭代器 快很多(访问vector<string>时)

// 引用传递
for (string& str : strs) 

// 迭代器
for (vector<string>::iterator it = strs.begin(); it != strs.end(); ++it)

(二)emplace_back 比 push_back 快 20%

unordered_map + 排序

O(n*k*logk)      n -- strs中字符串数量    k 单个字符串最大长度    ≈  5*1e6

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string> > s; // 键 string  值 vector<string>
        vector<vector<string> > ans; // 返回类型

        // 遍历字符串数组 strs
        for (string& str : strs) { // 引用传递 快很多
            string key = str;
            sort(key.begin(), key.end());
            s[key].push_back(str);
        }
        // for (vector<string>::iterator it = strs.begin(); it != strs.end(); ++it) {
        //     string key = *it; // 键
        //     sort(key.begin(), key.end()); // 对键排序
        //     s[key].push_back(*it); // 键 -- 排序后的值
        // }

        for (auto t : s) 
            ans.push_back(t.second); // 插入vector<string>类型数组
        // for (auto it = s.begin(); it != s.end(); ++it)
        //     ans.push_back(it->second);

        return ans;
    }
};

unordered_map + 计数

O(n*k)

排序  -->  计数,变相得到了每个字符串按字典序的排序(隐含的是:int 和 char 之间的

隐式转换

注意:& 比 不用&,快很多,避免将元素的值复制到新的变量中

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        // 键,strs中已排序字符串--temp
        // 值,strs中对应的字母异位词--vector<string>
        unordered_map<string, vector<string>> hash;
        vector<vector<string>> ans;
        for (auto s : strs) {
            string temp(26, '='); // 字符字面量--随便初始化,不为''就行
            for (auto c : s) 
                temp[c - 'a']++; // 每个字符出现次数,记录在字符串 temp 中
            hash[temp].push_back(s); // 键是temp, 值是vector<string>, s 插到值里面
        }
        for (auto s : hash)
            ans.push_back(s.second); // s.second 就是值, 也就是 vector<string>
        return ans;
    }
};

🌼最长连续序列

128. 最长连续序列 - 力扣(LeetCode)

知识 

size/empty/clear //元素个数  判空  清空
begin/end //开始和结束位置
inset(x) //元素x插入集合
erase(x) //删除所有值为x的元素
erase(it) //删除it迭代器指向的元素
find(x) //查找x在集合的位置,不存在则返回end
count(x) //统计x元素的个数
lower_bound(x) //返回大于等于x的最小元素位置
upper_bound(x) //返回大于x的最小元素位置

思路

set 用于去重

unordered_set,不需要有序,所以用 unordered

存在前驱则跳过

unordered_set + 跳过前驱

O(n)

外层循环 for,因为“存在前驱,则跳过”,数组中每个数,只会进入 内层 while 循环 1 次

所以外层 O(n),内层 O(1)

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {

        int ans = 0; // 初始0 有可能空数组

        unordered_set<int> s; // 去重
        for (const int& num : nums) // 引用传递
            s.insert(num);

        for (auto& num : s) { // 遍历哈希表

            if (s.count(num - 1)) continue; // 存在前驱 -- 跳过当前

            int currentNum = num, currentLen = 1; // 当前长度 1
            while (s.count(currentNum + 1)) {
                currentLen++; // 长度 +1
                currentNum++; // 值 +1
            }
            ans = max(ans, currentLen);
        }
        return ans;
    }
};

排序 + dp

很好奇,为什么 O(nlogn) 的复杂度,要比上面的 O(n) 快得多........

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if (!nums.size()) return 0; // 避免空数组

        sort(nums.begin(), nums.end()); // 先排序

        vector<int> nums2;
        nums2.push_back(nums[0]);
        for (int i = 1; i < nums.size(); ++i) 
            if (nums[i] != nums[i - 1]) // 再去重
                nums2.push_back(nums[i]); 
        
        int n = nums2.size(), ans = 1;
        if (n == 0) return 0; // 避免空数组
        vector<int> dp(n, 1); // 初始化
        for (int i = 1; i < n; ++i) {
            if (nums2[i] == nums2[i - 1] + 1)
                dp[i] = dp[i - 1] + 1;
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

/*
含义:dp[i] 以第 i 个数结尾的最大长度(下标 0 开始)
递推式:if (nums[i] == nums[i - 1] + 1) dp[i] = dp[i - 1] + 1;
初始化:dp[0...n-1] = 1
遍历顺序:nums[]  0...n-1
打表检查
*/

### LeetCode Hot 100 编程问题及解决方案概述 LeetCode Hot 100 是一份精选的编程题目列表,广泛被用于算法学习和面试准备。这些问题涵盖了多种数据结构和算法类型,包括但不限于数组、链表、字符串、树、图、动态规划等。 #### 常见题型与解法 - **两数之和 (Two Sum)** 给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那两个整数,并返回它们的数组下标。 解决方案可以使用哈希表来实现 $O(n)$ 的时间复杂度: ```cpp vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int, int> map; for (int i = 0; i < nums.size(); ++i) { int complement = target - nums[i]; if (map.find(complement) != map.end()) { return {map[complement], i}; } map[nums[i]] = i; } return {}; } ``` - **最长有效括号 (Longest Valid Parentheses)** 给定一个只包含字符 `'('` 和 `')'` 的字符串,计算其中有效括号子串的最大长度。 可以使用栈或动态规划的方法来解决此问题。例如,使用栈的方式可以遍历字符串并维护栈中的索引,从而找到最长的有效括号序列 [^1]。 - **搜索旋转排序数组 (Search in Rotated Sorted Array)** 假设按照升序排序的数组在某个关键点上进行了旋转(例如 `[0,1,2,4,5,6,7]` 变成了 `[4,5,6,0,1,2]`),给定一个目标值,请判断是否存在该值。 使用二分查找策略,通过比较中间元素和左右边界的值来调整搜索范围。 - **组合总和 (Combination Sum)** 给定一个无重复元素的数组 `candidates` 和一个目标数 `target`,找出所有满足条件的唯一组合:这些数字的加和等于目标值且每个数字可以无限制重复使用。 此问题通常使用回溯法解决,递归地尝试所有可能的组合路径。 - **接雨水 (Trapping Rain Water)** 给定一个非负整数数组 `height`,表示一个高程图,计算它可以存储多少单位的雨水。 可以使用双指针方法或者预处理左右最大高度数组来优化计算,确保线性时间复杂度。 #### 数据结构与算法的应用 - **栈** 栈常用于处理括号匹配、表达式求值等问题。例如,“字符串解码”可以通过栈来解析嵌套的编码结构 [^2]。 - **排序与双指针** 在“两数之和”的变体中,可以利用排序后使用双指针技巧快速定位符合条件的元素对 [^3]。 - **动态规划** 许多 LeetCode Hot 100 题目涉及动态规划,如“最大子数组和”、“不同路径”、“编辑距离”等。动态规划的核心是定义状态转移方程,并逐步填充 DP 表。 - **滑动窗口** 对于连续子数组相关的问题,如“最小覆盖子串”、“无重复字符的最长子串”,滑动窗口是一种高效的解决方案。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

千帐灯无此声

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

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

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

打赏作者

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

抵扣说明:

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

余额充值