LeetCode 第25场夜喵双周赛 题解

吐槽: 这次最后一题dp状态集合选错了 居然我傻傻的选了压缩40的! 我太菜了 5555~

a.拥有最多糖果的孩子

a.题目

给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。
对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。

示例 1

输入:candies = [2,3,5,1,3], extraCandies = 3
输出:[true,true,true,false,true]
解释:
孩子 1 有 2 个糖果,如果他得到所有额外的糖果(3个),那么他总共有 5 个糖果,他将成为拥有最多糖果的孩子。
孩子 2 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。
孩子 3 有 5 个糖果,他已经是拥有最多糖果的孩子。
孩子 4 有 1 个糖果,即使他得到所有额外的糖果,他也只有 4 个糖果,无法成为拥有糖果最多的孩子。
孩子 5 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。

示例 2

输入:candies = [4,2,1,1,2], extraCandies = 1
输出:[true,false,false,false,false]
解释:只有 1 个额外糖果,所以不管额外糖果给谁,只有孩子 1 可以成为拥有糖果最多的孩子。

示例 3

输入:candies = [12,1,12], extraCandies = 10
输出:[true,false,true]

提示

  • 2 <= candies.length <= 100
  • 1 <= candies[i] <= 100
  • 1 <= extraCandies <= 50

a.分析

没有什么其他特别要求的 直接贪心就行
想要知道以某种方案分配完糖果后i孩子是否可以是最多糖果的
那么其实只需要贪心的以全部额外糖果分配给i孩子看他是不是最多糖果就行了

证明
如果以全部额外糖果分配给i孩子都不能是最大的话 那么其他分配方式也必然不能使得i孩子糖果数最大
且全部额外糖果分配给i孩子必定是允许的方案之一

优化点在于 可以先预处理出原来所有孩子中糖果数最多的 因为只分配给i孩子 那么其他孩子的数量是不会改变的 那么最大值也不会改变

总的时间复杂度只有预处理的O(n)和遍历的O(n) 因此总的时间复杂度是O(n)

a.参考代码

class Solution {
public:
    vector<bool> kidsWithCandies(vector<int>& c, int e) {
        vector<bool> ans;
        int Max = *max_element(c.begin(),c.end());	//预处理出最大的糖果数
        for(int i=0;i<c.size();i++)
            if(c[i]+e>=Max)ans.push_back(true);	//贪心给糖
            else ans.push_back(false);
        return ans;
    }
};

b.改变一个整数能得到的最大差值

b.题目

给你一个整数 num 。你可以对它进行如下步骤恰好 两次

  • 选择一个数字 x (0 <= x <= 9).
  • 选择另一个数字 y (0 <= y <= 9) 。数字 y 可以等于 x
  • num 中所有出现 x 的数位都用 y 替换。
  • 得到的新的整数 不能 有前导 0 ,得到的新整数也 不能 是 0 。

令两次对 num 的操作得到的结果分别为 ab
请你返回 ab最大差值

示例 1

输入:num = 555
输出:888
解释:第一次选择 x = 5 且 y = 9 ,并把得到的新数字保存在 a 中。
第二次选择 x = 5 且 y = 1 ,并把得到的新数字保存在 b 中。
现在,我们有 a = 999 和 b = 111 ,最大差值为 888

示例 2

输入:num = 9
输出:8
解释:第一次选择 x = 9 且 y = 9 ,并把得到的新数字保存在 a 中。
第二次选择 x = 9 且 y = 1 ,并把得到的新数字保存在 b 中。
现在,我们有 a = 9 和 b = 1 ,最大差值为 8

示例 3

输入:num = 123456
输出:820000

示例 4

输入:num = 10000
输出:80000

示例 5

输入:num = 9288
输出:8700

提示

  • 1 <= num <= 10^8

b.分析

首先看到x1,y1和x2,y2都只能取0-9且最大的数字位数最多只有9位
那么强行暴力枚举出所有可能性的复杂度是O(10^4)用4个for嵌套就行了
对于每次真实的对数字位数进行修改需要O(9)的时间
而判断前导零和是否为0仅需要O(1)的时间
那么总的时间复杂度只有O(10^5)完全是可以暴力过的
比赛时候能快速暴力就先暴力

暴力

  • 枚举所有x1和y1对数字修改的可能
    • 没有前导零 修改后的数记为n1
      • 枚举所有x2和y2对数字修改的可能
        • 没有前导0 修改后的数记为n2 求出n1和n2的差 并去更新答案ans

优化: 显然两个数差值最大是一个最大一个最小 那么其实只需要让n1变为最大而n2变为最小 那么这个就直接是答案了
最大的数显然是把最前面的非9的数换成9 而最小的数则是把最前面非1的数换成1

b.参考代码

暴力
优化版本的看官可以自己尝试实现
由于比赛需要快速 因此使用to_string函数

//修改数字
inline string work(string s,int x,int y)
{
    string ret;
    for(int i=0;i<s.size();i++)
        if(s[i]-'0'==x)ret.push_back(y+'0');
        else ret.push_back(s[i]);
    return ret;
}
//判断前导零
inline bool isok(string s)
{
    if(s[0]=='0')return false;
    return true;
}
class Solution {
public:
    int maxDiff(int num) {
        string n = to_string(num);
        int ans=0;
        for(int x1=0;x1<=9;x1++)
            for(int y1=0;y1<=9;y1++){
                string n1=work(n,x1,y1);	//暴力枚举出n1
                if(!isok(n1))continue;
                for(int x2=0;x2<=9;x2++)
                    for(int y2=0;y2<=9;y2++){
                        string n2=work(n,x2,y2);	//暴力枚举出n2
                        if(!isok(n2))continue;
                        int a = stoi(n1);
                        int b = stoi(n2);
                        ans=max(ans,abs(a-b));	//求差并更新答案
                    }
            }
        return ans;
    }
};

c.检查一个字符串是否可以打破另一个字符串

c.题目

给你两个字符串 s1s2 ,它们长度相等,请你检查是否存在一个 s1 的排列可以打破 s2 的一个排列,或者是否存在一个 s2 的排列可以打破 s1 的一个排列。
字符串 x 可以打破字符串 y (两者长度都为 n )需满足对于所有 i(在 0 到 n - 1 之间)都有 x[i] >= y[i](字典序意义下的顺序)。

示例 1

输入:s1 = “abc”, s2 = “xya”
输出:true
解释:“ayx” 是 s2=“xya” 的一个排列,“abc” 是字符串 s1=“abc” 的一个排列,且 “ayx” 可以打破 “abc” 。

示例 2

输入:s1 = “abe”, s2 = “acd”
输出:false
解释:s1=“abe” 的所有排列包括:“abe”,“aeb”,“bae”,“bea”,“eab” 和 “eba” ,s2=“acd” 的所有排列包括:“acd”,“adc”,“cad”,“cda”,“dac” 和 “dca”。然而没有任何 s1 的排列可以打破 s2 的排列。也没有 s2 的排列能打破 s1 的排列。

示例 3

输入:s1 = “leetcodee”, s2 = “interview”
输出:true

提示

  • s1.length == n
  • s2.length == n
  • 1 <= n <= 10^5
  • 所有字符串都只包含小写英文字母。

c.分析

乍一看好像该题有点复杂 可能需要遍历所有s1和s2的排列组合然后去进行比较
但是再仔细思考就会发现 如果s1可以打破s2 那么只需要s1和s2上字符的位置是一一对应的 只要对应上之后 对他们同时进行移动位置进行组合
我们会发现移动完之后的两个字符串还是可以打破的 因此这题中只有字符和字符对应和串的位置无关
分析出了和位置无关之后 我们就要想如何构造一种字符和字符的对应模式呢
此处可以选择贪心构造极限情况
为什么需要构造极限情况 是因为如果你构造出来的s1和s2无打破关系 但是不能排除其他构造方式可以 那么这个构造就是无效的

先说结论
我们把s1和s2都按照字符大小来从小到大排序,如果排序后的s1和s2存在打破关系 那么就存在 否则肯定不存在

证明
排序后的s1和s2显然是小对小 大对大
假设现在s1无法打破s2 比如 "xyz" "azz" 此例子是y无法打破z 那么想要y可能打破其他字符 那么必然要将y的配对往前挪找到了a
但是此时由于y是往前挪找配对 它的前面x必然比他小 那么x就需要取代y原来的匹配z 那么就更不可能可以打破了
于是得证了排序后是构造了极限情况

排序的复杂度为O(nlogn) 判断是否可以打破只需要两次遍历O(n) 所以总的时间复杂度是O(nlogn)

c.参考代码

class Solution {
public:
    bool checkIfCanBreak(string s1, string s2) {
        sort(s1.begin(),s1.end());	//排序
        sort(s2.begin(),s2.end());
        bool flag = true;
        for(int i=0;i<s1.size();i++)	//s1打破s2
            if(s1[i]<s2[i]){
                flag=false;
                break;
            }
        if(flag)return true;
        for(int i=0;i<s2.size();i++)	//s2打破s1
            if(s1[i]>s2[i]){
                flag=true;
                break;
            }
        if(!flag)return  true;
        return false;
    }
};

d.每个人戴不同帽子的方案数

d.题目

总共有 n 个人和 40 种不同的帽子,帽子编号从 1 到 40 。
给你一个整数列表的列表 hats ,其中 hats[i] 是第 i 个人所有喜欢帽子的列表。
请你给每个人安排一顶他喜欢的帽子,确保每个人戴的帽子跟别人都不一样,并返回方案数。
由于答案可能很大,请返回它对 10^9 + 7 取余后的结果。

示例 1

输入:hats = [[3,4],[4,5],[5]]
输出:1
解释:给定条件下只有一种方法选择帽子。
第一个人选择帽子 3,第二个人选择帽子 4,最后一个人选择帽子 5。

示例 2

输入:hats = [[3,5,1],[3,5]]
输出:4
解释:总共有 4 种安排帽子的方法:
(3,5),(5,3),(1,3) 和 (1,5)

示例 3

输入:hats = [[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
输出:24
解释:每个人都可以从编号为 1 到 4 的帽子中选。
(1,2,3,4) 4 个帽子的排列方案数为 24 。

示例 4

输入:hats = [[1,2,3],[2,3,5,6],[1,3,7,9],[1,8,9],[2,5,7]]
输出:111

提示

  • n == hats.length
  • 1 <= n <= 10
  • 1 <= hats[i].length <= 40
  • 1 <= hats[i][j] <= 40
  • hats[i] 包含一个数字互不相同的整数列表。

d.分析

考虑分析可以知道 每个方案都是由确定的谁戴上确定的帽子
那么所有的方案数的暴力枚举办法就是

  • 先对某个确定的人i让他戴上某个帽子j
  • 然后从剩下的帽子集合删掉j
  • 再看下其他人i是否有其他帽子戴
    • 如果没有 那么这个方案就是不成立的
    • 如果有 那么让该人成为i 重复第一点
  • 直到所有人都有帽子 那么这个就是一个可行的方案
  • 回到第一步 帽子集合重置 让i换个帽子戴 戴上j+1
  • 如此就可遍历出全部方案数

很显然 想要得知所有的方案数 别无他法 因为方案中的元素是没有规律的 只有确定了谁戴哪个帽子 才能知道下个人能不能戴
当然 直接暴力枚举所有情况由于状态数太多肯定会超时 于是这里需要进行时间优化

此类方案数问题一般可采用dp优化 因为在某个子步骤中的方案可能一模一样已经出现过了 只不过是前面的选择不同而已
那么对于这种子步骤 我们可以直接得出后面是怎么样的 而不需要进行重复的枚举

此处还有个问题就是 怎么知道子步骤是否重复了呢 这道题显然子步骤是在某个状态下的后面的选择
也就是说 当我面对眼前的情况一模一样的时候 我应该要记起来我之前做过的事情
那么这道题关键点还有在如何保存眼前的情况
很显然 情况就是当前有谁已经戴了帽子了 还剩下哪些帽子 那么这个可以用状态压缩来保存还剩下哪些帽子 而有谁戴帽子 这个让他们顺序戴就好了

因此本题做法为状压dp

所以 我们如果碰到了[i][hat_set]这种情况 而之前又出现过的 那么就可以直接知道这种情况 后面有多少方案数了

很不幸的是 保存还剩下哪些帽子的集合太大了 1<<40 对于茫茫方案来说 只有比较小的几率可以碰到情况相同 那么这个优化就不明显了
很不幸 本人比赛时候就用的这种 集合保存情况 超时了 555

我们可以看到 这个题目的人数最多只有10位 因此可以把问题条件转化为 已知某个帽子都有谁能戴
然后自然而然类比推出情况为 当前是某个帽子 还剩下谁没有帽子
那么根据情况 就可以选择那个帽子给谁戴或者谁都不给了
集合 都有谁没戴帽子 这个集合大小上限只有 1<<10 因此极大的增加了出现相同情况的概率

最后总结下优化后的步骤

  • 当前帽子i 当前谁没帽子set (初始化当然就是帽子0开始以及谁都没帽子)
  • 对当前帽子i j可以戴
    • j有帽子了 不管他
    • j没有帽子 这是一种方案 给他 在set上标记j有帽子了 然后去看看下一个帽子i+1的选择
  • 当前帽子谁都不戴 这是一种方案 set不变 直接去看看下一个帽子i+1的选择
  • 每个人都有帽子了 那么这就是一个方案
  • 帽子选完了 但是不是每个人都有 这不是一个合适的方案
  • 对每个帽子的某个情况下的方案数加以记录

嘿嘿 总结得这么完美 那么直接按照这个思路写记忆化搜索就行了
dp和记忆化搜索本质都是利用了已知信息来剪纸 只不过一个是从自底而上 一个是自顶而下

总的时间复杂度就是枚举方案数的复杂度 为O(nm2^n)

d.参考代码

TLE的悲惨人选帽子

const int mod = 1e9+7;
//加速加法  超时了并没有什么卵用
inline int add(int a,int b)
{
    if(a+b>=mod)return a+b-mod;
    else return a+b;
}
vector<vector<int>> h;
int n;
class Solution {
public:
    unordered_map<long long,int> mem[12];	//存下了哪个人的哪种帽子没选的情况的方案数
    int numberWays(vector<vector<int>>& hats) {
        h = hats;
        n = hats.size();
        return ms(0,0);
    }
    int ms(int i,long long Set)
    {
        if(i==n)return 1;	//人都选完了 是一种情况
        if(mem[i].count(Set))return mem[i][Set];	//已经有相同情况
        int sum=0;
        for(int j=0;j<h[i].size();j++)	//去尝试所有有可能的帽子
        {
            int hat = 1<<h[i][j];
            if(hat&Set)continue;	//帽子没了
            else sum = add(sum,ms(i+1,Set|hat));	//他选了这个帽子 在set中去掉 并找下一个人
        }
        mem[i][Set]=sum;
        return sum;
    }
};

AC的帽子选人

class Solution {
public:
    const int mod = 1e9+7;
    int mem[45][1<<12];	//选到哪个帽子而且谁没有帽子的情况的方案数
    vector<int> persons[45];	//第i个帽子都有谁能戴
    int numberWays(vector<vector<int>>& hats) {
        memset(mem,-1,sizeof(mem));
        for(int i=0;i<hats.size();i++)
            for(int j=0;j<hats[i].size();j++)
                persons[hats[i][j]].push_back(i);	//预处理谁能戴帽子
        int Set = 2047;	//初始情况 用位压缩 第i位为1表示i有帽子 0表示i没帽子
        for(int i=0;i<hats.size();i++)
            Set^=1<<i;	//初始集合0~n-1号人没有帽子
        return ms(1,Set);
    }
    int ms(int i,int Set)
    {
        if(Set==2047)return 1;	//都有帽子了
        if(i==41)return 0;	//选完了 没合适的方案
        if(mem[i][Set]!=-1)return mem[i][Set];
        int sum = 0;
        for(int j=0;j<persons[i].size();j++){
            int person = 1<<persons[i][j];
            if(person&Set)continue;	//这个人有帽子了
            sum = (sum+ms(i+1,Set|person))%mod;	//把当前的帽子给他 并标记他有帽子了 进行下一个帽子的选人
        }
        sum = (sum+ms(i+1,Set))%mod;	//这个帽子谁都不给 直接下一个帽子选人
        return mem[i][Set]=sum;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值