题目描述
一张扑克有3个属性, 每种属性有3种值(A、B、C)
- 比如"AAA",第一个属性值A,第二个属性值A,第三个属性值A
- 比如"BCA",第一个属性值B,第二个属性值C,第三个属性值A
给定一个字符串类型的数组cards口,每一个字符串代表一张扑克
从中挑选三张扑克,每种属性达标的条件是:这个属性在三张扑克中全一样,或全不一样
挑选的三张扑克达标的要求是:每种属性都满足上面的条件
比如: “ABC”、 “CBC”、“BBC”
- 第一张第一个属性为"A"、第二张第一个属性为"C"、第三张第一个属性为"B",全不一样
- 第一-张第二个属性为"B"、第二张第二个属性为"B”、第三张第二个属性为"B",全样
- 第一张第三个属性为"C"、第二张第三个属性为"C"、第三张第三个属性为"C",全一样
每种属性都满足在三张扑克中全一样,或全不一样,所以这三张扑克达标
返回在cards口中任意挑选三张扑克,达标的方法数
题目解析
暴力
枚举每一张牌,每种牌有要和不要两种选择
class Solution {
int getWay(std::vector<std::string> & picks){
auto s1 = picks[0], s2 = picks[1], s3 =picks[2];
for (int i = 0; i < 3; i++) {
if ((s1[i] != s2[i] && s1[i] != s3[i] && s2[i] != s3[i]) || (s1[i] == s2[i] && s1[i] == s3[i])) {
continue;
}
return 0;
}
return 1;
}
int process(std::vector<std::string> cards, int idx, std::vector<std::string> & picks){
if(picks.size() == 3){
return getWay(picks);
}
if(idx == cards.size()){
return 0;
}
int ways = process(cards, idx + 1, picks);
picks.emplace_back(cards[idx]);
ways += process(cards, idx + 1, picks);
picks.pop_back();
return ways;
}
public:
int waps(std::vector<std::string> cards){
std::vector<std::string> picks;
return process(cards, 0, picks);
}
};
优化
分析题意
三个扑克一个组合, 从所有扑克中挑选三张
分析
因为牌数可能有非常多,所以我们从牌面入手。
- 一张牌中有三种属性,每种属性只有A、B、C三种可能性,一共有27种牌面。
怎么挑选:
- 如果选择了AAA、AAA,那么剩下的必须是AAA
- 如果选择了AAA、AAB、那么剩下的必须是AAC
因此,一共有两种挑选方法:
- 从自己的那一堆挑选,那么:
- 如果AAA有100张,这100张里随意挑选3张都达标 C 100 3 C_{100}^{3} C1003
- 如果AAB有200张,这200张里随意挑选3张都达标的有 C 200 3 C_{200}^{3} C2003
- 从自己的那一堆挑选一张,剩下的两张从其他牌面中挑选
- 如果AAA有100张,AAB有50张,AAC有20张,那么共有100 * 50 * 20种方法
算法
(1)先收集所有牌面,收集每种牌面有几张
- 一张牌中有三种属性,每种属性只有A、B、C三种可能性,一共有27种牌面。
- 收集每种牌面一共有多少张。可以用map来做,也可以用桶计数来做
- 用桶计数来做,那么桶的大小可以设置为27,然后 使用3进制,将牌面对应成一个数值:A对应0,B对应1,C对应2.那么:
- AAA:000 -->0
- AAB:001 -->1
- AAC:002 -->2
- ABA:010 -->3
- A B B : 011 − − − > 0 ∗ 3 2 + 1 ∗ 3 1 + 1 ∗ 3 0 = 0 + 3 + 1 = 4 ABB:011 ---> 0 * 3^2 + 1 * 3^1 + 1 * 3^0 = 0 + 3 + 1 = 4 ABB:011−−−>0∗32+1∗31+1∗30=0+3+1=4
- A B C : 012 − − − > 0 ∗ 3 2 + 1 ∗ 3 1 + 2 ∗ 3 0 = 0 + 3 + 2 = 5 ABC:012 ---> 0 * 3^2 + 1 * 3^1 + 2 * 3^0 = 0 + 3 + 2 = 5 ABC:012−−−>0∗32+1∗31+2∗30=0+3+2=5
(2)然后开始挑选。一共有两种挑选方法
- 先处理从自己那堆挑选的方法数量:直接根据公式计算
- 然后处理从其他的那堆挑选的方法数量
- 枚举27个牌面,每个牌面要跟不要,但是一旦超过3种,就停止(剪枝)
- 到最后正好三个牌面,如果发现每一位都一样或者不一样,那么就把它们的数量取出来,然后相乘即可
实现
using namespace std;
class Solution {
int getWays(std::vector<int> & counts, std::vector<int> & path){
int v1 = path[0], v2 = path[1], v3 = path[2];
for (int i = 9; i > 0; i /= 3) {
int cur1 = v1 / i;
int cur2 = v2 / i;
int cur3 = v3 / i;
v1 %= i;
v2 %= i;
v3 %= i;
if ((cur1 != cur2 && cur1 != cur3 && cur2 != cur3) || (cur1 == cur2 && cur1 == cur3)) {
continue;
}
return 0;
}
v1 = path[0], v2 = path[1], v3 = path[2];
return counts[v1] * counts[v2] * counts[v3];
}
int process(std::vector<int> & counts, int pre, std::vector<int> & path){
if (path.size() == 3) {
return getWays(counts, path);
}
int ways = 0;
for (int next = pre + 1; next < 27; next++) {
if (counts[next] != 0) {
path.emplace_back(next);
ways += process(counts, next, path);
path.pop_back();
}
}
return ways;
}
public:
int waps(std::vector<std::string> cards){
std::vector<int> counts(27);
for(std::string str : cards){
counts[(str[0] - 'A') * 9 + (str[1] - 'A') * 3 + (str[2] - 'A') * 1]++;
}
int ways = 0;
for (int status = 0; status < 27; ++status) {
int n = counts[status];
if(n > 2){
ways += n == 3 ? 1 : (n * (n - 1) * (n - 2) / 6);
}
}
std::vector<int> path;
for (int i = 0; i < 27; ++i) {
if(counts[i] != 0){
path.emplace_back(i);
ways += process(counts, i, path);
path.pop_back();
}
}
return ways;
}
};