代码随想录算法day5 - 哈希表1

题目1 242. 有效的字母异位词

给定两个字符串 *s**t* ,编写一个函数来判断 *t* 是否是 *s* 的字母异位词。

字母异位词 是通过重新排列不同单词或短语的字母而形成的单词或短语,通常只使用所有原始字母一次。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母

进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

思路

哈希表方法

这道题是经典的用空间换时间,只需要用一个额外的数组来统计两个字符串的长度就行了,时间复杂度从O(nm)变为O(n+m)

代码

class Solution {
public:
    bool isAnagram(string s, string t) {
        if(s.size() != t.size())
        {
            return false;
        }
        int hash[26];
        for(int i = 0; i < 26; i++)
            hash[i] = -1;
        vector<int> result;
        for(auto c : s)
        {
            if(hash[c - 'a'] == -1)
            {
                result.push_back(1);
                hash[c - 'a'] = result.size() - 1;
            }
            else
            {
                result[hash[c - 'a']]++;
            }
        }
        for(auto c : t)
        {
            //如果没有初始化这个字母则说明字符串s中没有这个字符
            if(hash[c - 'a'] == -1)
            {
                return false;
            }
            result[hash[c - 'a']]--;
        }
        for(auto &num : result)
        {
            if(num != 0)
                return false;
        }
        return true;
    }
};

暴力解法1(超时)

我试了嵌套循环,第35个测试用例直接超时,如果能在每次遍历t时缩小遍历范围也许能过。用暴力解法的时间复杂度为O(nm)其中n为s的长度,m为t的长度。

代码

class Solution {
public:
    bool isAnagram(string s, string t) {
        if(s.size() != t.size())
        {
            return false;
        }
        int tLength = t.size();
        for(auto &c1 : s)
        {
            int length = 0;
            for(auto &c2 : t)
            {
                if(c1 == c2)
                {
                    c2 = 'a' - 1;
                    break;
                }
                length++;
            }
            if(length == tLength)
                return false;
        }
        return true;
    }
};

暴力解法2

这种方法通过调用algorithm头文件里的sort对字符串s和t进行排序,之后再进行一一比较得出结果。其复杂度为:

O(nlogn+nlogn+n)=O(nlogn)

class Solution {
public:
    bool isAnagram(string s, string t) {
        if(s.size() != s.size())
        {
            return false;
        }
        sort(s.begin(), s.end());
        sort(t.begin(), t.end());
        return s == t;
    }
};

题目2 349. 两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的

交集

。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

思路

这道题也是用空间换时间,暴力解法就嵌套循环遍历找交集就行了时间复杂度为O(nm),n为nums1的长度,m为nums2的长度。使用额外数组空间来记录可以将时间复杂度减少为O(n+m)

当然使用随想录的模板类unordered_set的做法也是类似的思路。

代码

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int> result;
        result.reserve(1001);
        int hash[1001];
        memset(hash,0,sizeof(hash));
        for(auto &num : nums1)
        {
            hash[num]++;
        }
        for(auto &num : nums2)
        {
            //如果有交集则hash中的数值置0,且将该数放入result中
            if(hash[num] > 0)
            {
                hash[num] = 0;
                result.push_back(num);
            }
        }
        return result;
    }
};

题目3 202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

提示:

  • 1 <= n <= 231 - 1

思路

这道题的解题关键在题干里的无限循环,既然无限循环的话就表明可以用set模板类来记录不重复的计算结果,并通过结果数量来判断是否循环。

代码

class Solution {
public:
    bool isHappy(int n) {
        int index   = 0,
            lst[32] = {0};
        set<int> uniqueNum;
        int num;
        while(1)
        {
            index = 0;
            num = uniqueNum.size();
            uniqueNum.insert(n);
            //如果出现无限循环则不是快乐数
            if(num == uniqueNum.size())
            {
                return false;
            }
            while(n != 0)
            {
                lst[index] = n % 10;
                n /= 10;
                index++;
            }
            for(int i = 0; i < index; i++)
            {
                n += lst[i] * lst[i];
            }
            if(n == 1)
            {
                return true;
            }
        }
    }
};

题目4 1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2<=nums.length<=104
  • 109<=nums[i]<=109
  • 109<=target<=109
  • 只会存在一个有效答案

思路

unordered_set

这道题的思路比较唯一就是通过target-nums[i]获取另一个元素的值,保存到hash表中并继续进行数组遍历找到目标值。比较需要注意的就是hash模板类的选择,这道题如果用unordered_map的效率是最高的,因为能够同时保存目标元素值与当前元素的下标,避免了另一轮的循环。我使用的是unordered_set模板类,代价就是额外需要循环判断下标。(当然如果能将元素值与下标进行编码和解码并保存到unordered_set中,效率会更高,因为节省了空间。)我也尝试了一下编解码和unoredered_set模板类,但在处理result-nums[i] < 0的情况时出了点问题。

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_set<int> result;
        vector<int> ret(2, INT_MAX);
        for(int i = 0; i < nums.size(); i++)
        {
            if(find(result.begin(), result.end(), nums[i]) != result.end())
            {
                ret[1] = i;
                ret[0] = target - nums[i];
                break;
            }
            else
            {
                result.insert(target - nums[i]);
            }
        }
        for(int i = 0; i < nums.size(); i++)
        {
            if(nums[i] == ret[0])
            {
                ret[0] = i;
                break;
            }
        }
        return ret;
    }
};

编解码set

这道题使用编解码的方式做有点吃亏,因为题目提供的容器内部的元素为int型,int是4字节也就是32位,而vector长度为2到104也就是至少要用214=16384=14来存储下标(用整型提交卡在最后几个样例了);而元素大小的范围是[109,109],也就是要用231=2,147,483,648=31来存储元素。那么14+31=45>32也就表明我们用int型来同时存储下标和元素是不满足的,那么接下来有两种做法,一种是使用自定义的6字节整型数,另一种是直接用8字节64位的long long,我选用了long long来做。(自定义整型型后面有空再写)

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        set<long long> result;
        vector<int> ret(2, 0);
        for(long long i = 0; i < nums.size(); i++) {
            long long encodedValue;
            if(target - nums[i] >= 0)
                encodedValue = (i << 32) | ((target - nums[i]) & 0xFFFFFFFF);
            else
                encodedValue = (i << 32) | (-(target - nums[i]) & 0xFFFFFFFF);

            auto iter = find_if(result.begin(), result.end(), [&](int encoded){
                return (encoded & 0xFFFFFFFF) == nums[i] || -(encoded & 0xFFFFFFFF) == nums[i];
            });
            if(iter != result.end()) {
                if(nums[i] + nums[*iter >> 32] == target) {
                    ret[1] = i;
                    ret[0] = *iter >> 32;
                    return ret;
                } else {
                    result.insert(encodedValue);
                }
            } else {
                result.insert(encodedValue);
            }
        }
        return ret;
    }
};
//后面这段代码只能用在正整数部分
/* 
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_set<long long> result;
        vector<int> ret(2, INT_MAX);
        for(long long i = 0; i < nums.size(); i++)
        {
            unordered_set<long long>::iterator iter;
            iter = find_if(result.begin(), result.end(), [&](long long encoded){
                return (encoded & 0xFFFF) == nums[i];
            });
            if(iter != result.end())
            {
                ret[1] = i;
                ret[0] = *iter >> 32;
                return ret;
            }
            else
            {
                long long encodedValue = (i << 32) | ((target - nums[i]) & 0xFFFF);
                result.insert(encodedValue);
            }
        }
        return ret;
    }
};
*/
posted @   酱油黑龙  阅读(725)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示