哈希常见面试题——C++版

两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。输出结果是唯一确定的

题解:

用两个set分别存储两个数组中的元素,达到去重的效果,然后用其中一个数组去另外一个数组中进行查找

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //将nums1装入set1(去重)
        //将num2装入set2,然后用set2的元素取set1中去寻找

        unordered_set<int> st1;
        unordered_set<int> st2;

        for(auto&e:nums1)
        {
            st1.insert(e);
        }

         for(auto&e:nums2)
        {
            st2.insert(e);
        }

        vector<int> ret;
        for(auto&e:st2)
        {
            if(st1.find(e)!=st1.end())
                ret.push_back(e);
        }

        return ret;

    }
};

时间复杂度:查找效率为O(1),存储元素为O(M+N) -> O(M+N)
空间复杂度O(M+N)

两个数组的交集II

给定两个数组,编写一个函数来计算它们的交集。
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。

题解:

用两个map分别记录数组中出现的元素的次数,然后用一个map中的去另外一个map中进行寻找,找到了后,再根据second判断需要返回多少个值

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int,int> mp1;
        unordered_map<int,int> mp2;

        for(auto&e:nums1)
        {
            mp1[e]++;
        }

         for(auto&e:nums2)
        {
            mp2[e]++;
        }

        vector<int> ret;
        for(auto&e:mp2)
        {
            auto it=mp1.find(e.first);
            if(it!=mp1.end())
            {
                int num=fmin(it->second,e.second);//得到有多少个相同的
                for(int i=0;i<num;i++)
                {
                    ret.push_back(e.first);
                }
            }
        }
        return ret;
    }
};

存在重复元素

用map进行统计次数,如果有出现两次的直接返回

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_map<int,int> mp;

        for(auto&e:nums)
        {
            mp[e]++;
            if(mp[e]==2)
                return true;
        }
        return false;
    }
};

两句话中的不常见单词

给定两个句子 A 和 B 。 (句子是一串由空格分隔的单词。每个单词仅由小写字母组成。)如果一个单词在其中一个句子中只出现一次,在另一个句子中却没有出现,那么这个单词就是不常见的。返回所有不常用单词的列表。您可以按任何顺序返回列表。

题解:

题意转换 -> 两个句子中,只出现一次的单词
用哈希表进行统计 -> 并且将只出现一次的返回即可

技巧:将两个字符串和成一个字符串,并且,将这个字符串的所有单词放入哈希表之中,然后寻找哈希表中只出现一次的单词,再将这个单词返回即可。寻找单词的过程可以转变为寻找空格,即给定两个位置,pos1为单词的起始位置,pos2位空格的位置,pos1和pos2之间就是需要寻找的单词

class Solution {
public:
    vector<string> uncommonFromSentences(string s1, string s2) {
    //题意转换 -> 两个句子中,只出现一次的单词
    //用哈希表进行统计 -> 并且将只出现一次的返回即可

        vector<string> ret;
        unordered_map<string,int> mp;

        if(!s1.empty())//不为空则先加个空格
            s1+=' ';
        s1+=s2;

        if(s1.empty())
            return ret;
        
        //此时s1最少有一个单词
        size_t pos1=0;
        size_t pos2=s1.find(' ');


        while(pos2!=string::npos)
        {
            string temp(s1.begin()+pos1,s1.begin()+pos2);
            mp[temp]++;//添加至mp之中

            pos1=pos2+1;//来到下一个单词的起始处
            pos2=s1.find(' ',pos1);//从pos1处开始寻找下一个空格
        }

        //此时,还剩下一个单词没有添加
        mp[string(s1.begin()+pos1,s1.end())]++;

        for(auto&e:mp)
        {
            if(e.second==1)//只出现一次的
            ret.push_back(e.first);
        }

        return ret;

    }
};

字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

题解:

1.将排好序的字符当作K值,未排序的字符当作V值加入哈希表中
2.哈希表的构建 unordered_map<string,vector>

class Solution {
public:

    void Insert(unordered_map<string,vector<string>>&mp,string &s)
    {
        string copy(s);
        sort(copy.begin(),copy.end());//进行排序
        //排序后的字符串作为K值,未排序的作为V值

        mp[copy].push_back(s);
    }

    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>> mp;
        for(auto&e:strs)
        {
            Insert(mp,e);
        }

        vector<vector<string>> ret;
        for(auto &e:mp)
        {
            ret.push_back(e.second);
        }

        return ret;
    }
};

单词拆分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

题解:

先将单词添加至哈希表中,设dp[i]表示字符串s中0-i的单词是否可以从哈希表中挑选出单词组成
此时需要构建双重循环,外层循环更新i的位置,里层循环for(int j=i;j>=0;j–)表示 j-i的组成的单词是否可以从哈希表中找到对应的单词

如果s[j,i]能够找到对应的单词 ,dp[i]=dp[j-1] (j是当前匹配单词的第一个字母,0—i匹配的前提是 0—j-1的字符串也是匹配的)

**注意点:**当dp[i]==true时,就进行剪枝,不再对当前点进行判断,否则容易造成重复判断,出现不匹配的情况

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //将单词添加至哈希表中
        unordered_set<string> set;
        for(auto& s:wordDict)
        {
            set.insert(s);
        }

        vector<bool>dp(s.size()+1,false);//dp[i]表示0-i的字符是否可以由字典里的单词构成
        dp[0]=true;

        for(int i=0;i<s.size();i++)
        {
            for(int j=i;j>=0;j--)
            {
                if(dp[i+1])
                    break;//剪枝,避免重复判断
                    
                string temp(s.begin()+j,s.begin()+i+1);
                if(set.find(temp)!=set.end())//可以找到
                {
                    dp[i+1]=dp[j];
                }
            }
        }

        return dp[s.size()];
    }
};

和为K的子数组

给定一个整数数组和一个整数 **k,**你需要找到该数组中和为 k 的连续的子数组的个数。

题解:

image-20210609112422550

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        //设 1 -i 下标的连续数组和为 sum[i]
        //sum[i] - sum[j] =k -> sum[j] = sum[i] - k  
        //即在其中寻找,是否两个连续数组相减 == k
        //此时需要注意的有如下几点
        //1.当sum[i]==sum[j]时、即k为0时 -> 排除一次自身

        unordered_map<int,int> map;
        int prev=0;
        int count=0;

        map[0]=1;//当sum[i] = k时,算一次
        int val;
        for(auto&e:nums)
        {
            val=prev+e;
            prev+=e;

            auto it=map.find(val-k);
           if(it!=map.end())
            count+=it->second;

            map[val]++;
        }

        return count;
    }
};

任务调度器

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以,以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。

然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的 最短时间 。

题解:

image-20210609112402167

class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        //设有x个桶子,每个桶子执行的时间为(容量) n+1,不同的元素肯定不在同一个桶子里面
        //所以最少需要的桶子的个数为最大任务的长度
        //如果每个桶子都装满了,则剩余的元素随便插入,如果没装满,则所需时间为 (n+1)*(x-1) + 最后一个桶子剩余的元素
        //因为最后一个桶子不需要进行等待

        //进行次数的统计
        unordered_map<char,int> map;

        for(auto&ch:tasks)
        {
            map[ch]++;
        }

        //求出最大元素的个数,即桶子的个数
        priority_queue<int> heap;
        int sum=0;

        for(auto&e:map)//创建一个大堆
        {
            heap.push(e.second);
            sum+=e.second;//记录总体元素的个数
        }

        int max=heap.top();//获得桶子的个数
        heap.pop();
        sum-=max;//出去最大元素,剩余元素的个数
        int time=0;//所需时间

        //判断桶子是否装满
        if(sum>=max*n)//剩余元素的总量大于所有桶子的总量
        {
            time=max*(n+1) + sum-max*n;//每个桶子时间 + 剩余元素个数
            return time;
        }

        //此时,剩余元素装不满所有的桶子

        int tail=1;
        while(!heap.empty()&&heap.top()==max)//取出来的元素个数,和桶子数相等(则一个桶子一个)
        {
            heap.pop();
            tail++;//最后一个桶子的元素
        }

        sum-=(tail-1)*max;//个数小于桶子个数的元素总量

        int num=sum-(max-1)*(n-tail+1);//最后一个桶子可以分配到的元素


        time=(max-1)*(n+1) + tail;//桶子时间 + 最后一个桶子所需要的时间

        if(num>0)
            time+=num;

        return time;

    }
};

最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

进阶:你可以设计并实现时间复杂度为 O(n) 的解决方案吗?

题解:

1.将所有的数据加入哈希表中的同时进行去重

2.查找一个连续数组,我们可以从该段数组的最小值 x 开始查询

3.如果x-1不在set之中,说明这个数没有前驱,即这个数是"当前连续数组的最小值",然后用这个数,依次增大在哈希表中进行查找

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if(nums.size()==0)
            return 0;

        unordered_set<int> set;

        for(auto&e:nums)//将所有数据放入哈希表中
        {
            set.insert(e);
        }

        int max_length=0;
        int length=0;

        for(int i=0;i<nums.size();i++)//找连续的序列
        {
            if(set.find(nums[i]-1)!=set.end())//有前驱
                continue;//跳过

            int num=nums[i]+1;
            length=1;
            while(set.find(num)!=set.end())
            {
                length++;
                num++;
            }
            max_length=fmax(max_length,length);
        }

        return max_length;
    }
};
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
### 回答1: 大数据面试题——spark数据倾斜调优(五) 在Spark中,数据倾斜是一个常见的问题,它会导致任务执行时间过长,甚至导致任务失败。因此,我们需要对数据倾斜进行调优。 以下是一些调优方法: 1. 均匀分布数据 如果数据倾斜是由于数据分布不均匀导致的,可以尝试使用随机数将数据均匀分布到不同的分区中。可以使用repartition或coalesce方法来实现。 2. 使用聚合函数 如果数据倾斜是由于某些键的值过大导致的,可以尝试使用聚合函数,如reduceByKey或aggregateByKey,将键值对合并为一个值。这样可以减少数据传输量,从而减少数据倾斜。 3. 使用随机前缀 如果数据倾斜是由于某些键的值过大导致的,可以尝试使用随机前缀来将键值对分散到不同的分区中。可以使用map方法来实现。 4. 使用自定义分区器 如果数据倾斜是由于默认的哈希分区器导致的,可以尝试使用自定义分区器来将数据均匀分布到不同的分区中。可以实现Partitioner接口来自定义分区器。 5. 使用广播变量 如果数据倾斜是由于某些变量在多个任务中重复计算导致的,可以尝试使用广播变量来共享变量。可以使用broadcast方法来实现。 6. 使用缓存 如果数据倾斜是由于某些数据在多个任务中重复使用导致的,可以尝试使用缓存来避免重复计算。可以使用cache或persist方法来实现。 以上是一些常见的调优方法,但具体的调优方法需要根据具体的情况来选择。 ### 回答2: 在Spark任务中,数据倾斜可能会导致某些任务的执行时间远远超过其他任务,从而导致整个Spark应用程序的执行时间延长。为了解决这个问题,可以采取以下优化措施: 1.数据预处理:可以通过分析数据的相关性以及倾斜数据的分布情况来提前对数据进行处理和转换,以便尽可能地把数据分散到多个partition中。例如,可以采用哈希等方式,将数据平均地分配到多个分区中去。 2.增加分区数量:如果数据存在明显的倾斜态势,那么可以通过增加partition的数量来缓解数据倾斜的影响。可以使用repartition或者coalesce算子来增加分区数量。 3.采用随机算法:随机算法可以有效地减少数据倾斜的影响。例如,在join操作中,可以采用随机抽样的方式来选择少数表的关联键,以达到数据均衡的目的。 4.使用自定义累加器:如果数据倾斜只存在于某些关键数据上,可以采用自定义累加器的方式减少数据倾斜的影响。例如,在计算word count时,可以使用Accumulator来统计单词出现的次数,以达到数据均衡的目的。 5.使用Broadcast变量:如果数据倾斜存在于join表中的话,可以使用Broadcast变量将较小的表广播到每个节点,以减少网络传输的消耗。 综上所述,解决Spark数据倾斜问题需要综合考虑数据处理方式、partition数量、算法选择等方面,根据实际情况来设计和优化Spark应用程序,以达到优化性能、提升运行效率的目的。 ### 回答3: Spark数据倾斜是一个常见的问题,它发生的原因可能是数据分布不均匀或者数据特征相似性较高等。如果不加以处理,数据倾斜会导致运行时间变长,资源浪费,甚至导致任务失败等一系列问题。因此,调优是十分必要的。 一般情况下,Spark数据倾斜调优的方法主要分为以下几种: 1. 手动调节shuffle分区的数量 数据倾斜时,可以通过调整shuffle的分区数量来缓解压力。当数据分布较为均匀时,增加分区数量可以提高并行度,更好地利用资源,减少运行时间。但是原本数据分布不均匀的情况下,增加分区数量只能加重分区内的数据倾斜问题。 2. 增加随机前缀或者后缀 随机前缀或者后缀是一种常用的解决Spark数据倾斜的方法。它通过对相同Key的Value加上随机数的前缀或者后缀,然后再进行处理,将原本的数据压平,以达到均匀分布的效果。 3. 使用Spark SQL的聚合函数 Spark SQL的聚合函数可以更好地解决数据倾斜的问题。如果遇到有大量重复Key的情况,可以使用Spark SQL中的ReduceByKey或者GroupByKey进行聚合,其实现过程中会自动解决数据倾斜的问题。 4. 采用第三方工具 当数据倾斜问题较严重时,可以采用第三方工具,如Spark的Tungsten、HyperLogLog等。这些工具可以对数据进行均衡分布,优化任务,并提高运行效率。 总结起来,在Spark数据倾斜调优中,我们可以通过手动调整shuffle分区数量、增加随机前缀或后缀、使用Spark SQL聚合函数、采用第三方工具等方法来解决问题。但是,具体方法要根据不同场景灵活运用,选择合适的解决方案。同时,对于Spark应用程序的开发和调试,我们也应该加强对Spark内核的理解,减少数据倾斜问题的出现,以提高应用程序的稳定性和运行效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值