回溯算法-胡牌问题[组合问题的进阶]

原始题目:雀魂启动

题目的大致意思就是,有13张排,加一张,就能胡,用贵州话讲就是一对将,然后加四坎牌。
一坎牌可以是三个一样的碰,也可以是顺子。现在,让你用程序去判定任意一组13排是否能胡,并给出需要哪个牌来胡牌。

这个显然是一个经典的回溯问题,但是和我们常见的回溯问题又有一些区别,比如:组合问题

class Solution {
public:
    vector<vector<int>> arrs;
    map<string,int> maps;
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<int> path;
        sort(nums.begin(),nums.end());
        help(nums, path,0);
        return arrs;
    }
    void help(vector<int>& nums,vector<int>& path,int begin){
        sort(path.begin(),path.end());
        stringstream ss;    
        for (auto k: path){
            ss<<k<<",";
        }
        if(maps[ss.str()] == 0){
            arrs.push_back(path);
            maps[ss.str()]++;
        }
    
        if(path.size()==nums.size()){
            return;
        }
        
        for(int i =begin;i<nums.size();i++){
            if(find(path.begin(),path.end(),nums[i]) == path.end()){
                path.push_back(nums[i]);
                help(nums,path,i);
                path.pop_back();
            }
        }
    }
};

差别在哪呢?胡牌问题里面侧重一个胡字,对于哪个牌能胡,只是一个简单的暴力for循环就行,换句话说:

for(int i =1;i<=9;i++){
	if(胡牌(i)){
		cout<<i<<" ";
	}
}

你就能完成这道题。
但是,这个胡牌,不是一个简单的事情,它也是回溯,但是细节和常规的组合问题不一样

组合问题:我希望找到一条路径,使得这条路径满足某种规则。

胡牌能不能当成寻路问题?
答案是不能简单的当成,但本身也是一个寻路过程,因为直接通过一条路[字符串]径直接判断能否胡牌是很困难的事情,但是组合问题本身只是期待获取这条路径。但是两者本身却都基于回溯进行操作。胡牌问题的难点在于怎么确定胡牌,使用回溯是一个好方法,先确定第一坎牌,然后确定第二坎牌,第三…,突然发现无法完成第n坎牌,回溯,第一次试探时,尝试的是碰!操作,那第二次试探时,则采用顺子操作,当顺子能成功时,则下面又可以碰,或者是顺子

回溯算法-胡牌问题[组合问题的进阶]_第1张图片
组合问题:
回溯算法-胡牌问题[组合问题的进阶]_第2张图片
可以看到,前面的胡牌问题已经变成一个二叉树,而后面的组合问题则是一个多叉树,不管是多插还是二叉,使用回溯都会很简单。而编程二叉树的胡牌问题在使用回溯时,可以直接通过后序遍历确定是否达成条件

//二叉树的后序

bool help(Node* root){
    //op
    bool f1 = help(root.left);
	bool f2 = help(root.right);
	if(f1){
		return f1;
	}
	if(f2){
		return f2;
	}
}

源代码:

#include 
#include 
#include 
using namespace std;
bool isSuccess(int n,map<int,int>& maps,vector<int>& path);

int main(){
    
    map<int,int> maps;
    vector<int> path;
    int* arr = new int[13];
    for(int i =0;i<13;i++){
        cin>>arr[i];
        maps[arr[i]]++;
    }
    for (int i =1;i<=9;i++){
        if (maps[i]<4){
            map<int,int> m2  = maps;
            m2[i]++;
            if(isSuccess(0,m2,path) == true){
                cout<<i<<" ";
            }
        }
    }
    return 0;
}
bool isSuccess(int n,map<int,int>& maps,vector<int>& path){
    if(n == 4){
        for (auto k: maps){
            if(k.second == 2){
                return true;
            }
        }
        return false;
    }
    for(int i =0;i<9;i++){
        int pos = i+1;
        if(maps[pos]>=3){
            maps[pos] -= 3;
            path.push_back(pos);
            bool reback = isSuccess(n + 1,maps,path);
            pos = path[path.size() - 1];
            maps[pos] += 3;
            path.pop_back();
            if(reback){
                return true;
            }
        }
        if(maps[pos]>0&&maps[pos+1]>0&&maps[pos+2]>0){
                maps[pos] -= 1;
                maps[pos+1]-=1;
                maps[pos+2]-= 1;
                path.push_back(pos);
                bool reback = isSuccess(n+1, maps,path);
                pos = path[path.size() - 1];
                path.pop_back();
                maps[pos] += 1;
                maps[pos+1] +=1;
                maps[pos+2] +=1;
                if(reback){
                    return true;
                }
            }
        
    }
    return false;
}

关于碰的操作:for循环,如果发现存在key>3的情况,则碰
关于顺子的操作,for循环,如果发现连续3个value>0的情况则顺子

觉得有人要吐槽:这特么算二叉树我倒立洗头
哎,我也是瞎讲的,从抽象层面上,确实就是一个二叉树,图都出来了

你可能感兴趣的:(算法实验室,算法)