算法:扑克牌问题

题目描述

一张扑克有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 ABB011>032+131+130=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 ABC012>032+131+230=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;
    }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值