吐槽: 这次最后一题dp状态集合选错了 居然我傻傻的选了压缩40的! 我太菜了 5555~
给你一个数组 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
没有什么其他特别要求的 直接贪心就行
想要知道以某种方案分配完糖果后i孩子是否可以是最多糖果的
那么其实只需要贪心的以全部额外糖果分配给i孩子看他是不是最多糖果就行了
证明:
如果以全部额外糖果分配给i孩子都不能是最大的话 那么其他分配方式也必然不能使得i孩子糖果数最大
且全部额外糖果分配给i孩子必定是允许的方案之一
优化点在于 可以先预处理出原来所有孩子中糖果数最多的 因为只分配给i孩子 那么其他孩子的数量是不会改变的 那么最大值也不会改变
总的时间复杂度只有预处理的O(n)和遍历的O(n) 因此总的时间复杂度是O(n)
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;
}
};
给你一个整数 num
。你可以对它进行如下步骤恰好 两次 :
x (0 <= x <= 9)
.y (0 <= y <= 9)
。数字 y
可以等于 x
。num
中所有出现 x
的数位都用 y
替换。令两次对 num
的操作得到的结果分别为 a
和 b
。
请你返回 a
和 b
的 最大差值 。
示例 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
首先看到x1,y1和x2,y2都只能取0-9且最大的数字位数最多只有9位
那么强行暴力枚举出所有可能性的复杂度是O(10^4)用4个for嵌套就行了
对于每次真实的对数字位数进行修改需要O(9)的时间
而判断前导零和是否为0仅需要O(1)的时间
那么总的时间复杂度只有O(10^5)完全是可以暴力过的
比赛时候能快速暴力就先暴力
暴力
优化: 显然两个数差值最大是一个最大一个最小 那么其实只需要让n1变为最大而n2变为最小 那么这个就直接是答案了
最大的数显然是把最前面的非9的数换成9 而最小的数则是把最前面非1的数换成1
暴力
优化版本的看官可以自己尝试实现
由于比赛需要快速 因此使用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;
}
};
给你两个字符串 s1
和 s2
,它们长度相等,请你检查是否存在一个 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
乍一看好像该题有点复杂 可能需要遍历所有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)
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;
}
};
总共有 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
考虑分析可以知道 每个方案都是由确定的谁戴上确定的帽子
那么所有的方案数的暴力枚举办法就是
很显然 想要得知所有的方案数 别无他法 因为方案中的元素是没有规律的 只有确定了谁戴哪个帽子 才能知道下个人能不能戴
当然 直接暴力枚举所有情况由于状态数太多肯定会超时 于是这里需要进行时间优化
此类方案数问题一般可采用dp优化 因为在某个子步骤中的方案可能一模一样已经出现过了 只不过是前面的选择不同而已
那么对于这种子步骤 我们可以直接得出后面是怎么样的 而不需要进行重复的枚举
此处还有个问题就是 怎么知道子步骤是否重复了呢 这道题显然子步骤是在某个状态下的后面的选择
也就是说 当我面对眼前的情况一模一样的时候 我应该要记起来我之前做过的事情
那么这道题关键点还有在如何保存眼前的情况
很显然 情况就是当前有谁已经戴了帽子了 还剩下哪些帽子 那么这个可以用状态压缩来保存还剩下哪些帽子 而有谁戴帽子 这个让他们顺序戴就好了
因此本题做法为状压dp
所以 我们如果碰到了[i][hat_set]
这种情况 而之前又出现过的 那么就可以直接知道这种情况 后面有多少方案数了
很不幸的是 保存还剩下哪些帽子的集合太大了 1<<40
对于茫茫方案来说 只有比较小的几率可以碰到情况相同 那么这个优化就不明显了
很不幸 本人比赛时候就用的这种 集合保存情况 超时了 555
我们可以看到 这个题目的人数最多只有10位 因此可以把问题条件转化为 已知某个帽子都有谁能戴
然后自然而然类比推出情况为 当前是某个帽子 还剩下谁没有帽子
那么根据情况 就可以选择那个帽子给谁戴或者谁都不给了
集合 都有谁没戴帽子 这个集合大小上限只有 1<<10
因此极大的增加了出现相同情况的概率
最后总结下优化后的步骤
嘿嘿 总结得这么完美 那么直接按照这个思路写记忆化搜索就行了
dp和记忆化搜索本质都是利用了已知信息来剪纸 只不过一个是从自底而上 一个是自顶而下
总的时间复杂度就是枚举方案数的复杂度 为O(nm2^n)
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;
}
};