排列组合与子集

来源:互联网 发布:6300hq和7300hq知乎 编辑:程序博客网 时间:2024/06/05 17:07

        Leetcode上有几道题比较类似,Permutation、Combination和Subsets等。说它们相似,是因为思路类似。要求出某个序列的排列组合和子集,初看起来,要对序列中的每个元素排列、取舍,初看起来,似乎要写一个循环层数正比于元素个数的大循环,而语言并没有提供这种功能。这时就要转变思路,先求部分解或子问题的解。

例如Permutation这题:

vector<vector<int>> permute(vector<int>& nums) {                vector<vector<int> > result;        vector<int> temp;                result.push_back(nums);                /*          每次确定一个字符,第i个字符之前的序列确定后,第i个字符有nums.size()-i种选择。通过将第i个字符与第i+1到第nums.size()-1个字符交换得到不同的排列。          类似于数学归纳法?动态规划?迭代计算出所有排列        */        for(int i = 0; i < nums.size()-1; ++i)         {            /*              必须倒序遍历,从算法逻辑和实现正确性上考虑都必须如此。初始式j = result.size()-1只会执行一次,若采用顺序遍历,              j < result.size()每次都会执行,使result.size()重新计算,而循环每执行一次,result.size()都会增大,导致无穷循环            */            for(int j = result.size()-1; j >= 0; --j)             {                for(int k = i+1; k < nums.size(); ++k)                {                    temp = result[j];                    swap(temp[i], temp[k]);                                        result.push_back(temp);                }            }        }                return result;    }
大体思路就是每次只考虑(一个)子问题,构造出部分解或子问题的解,然后再通过操作部分解或子问题的解来构造更完整的解,迭代直到构造出所有解。也就是说每个解的构造过程不是独立的,较大问题的解需要依赖较小问题的解。
Subsets这题的代码可能能更好地展示这种方法:

/*    求所有子集,就是从原集合中取出若干元素组成新的集合,每个元素要么取要么不取,共有2^n种情况。本题和求排列的问题十分相似,因此思路几乎一致。求n个元素的集合其子集,那就先求前n-1个元素之集合的子集。对于前n-1个元素来说,根本就不知道第n个元素的存在,因此无须考虑,相当于先求子问题的解,类似于动态规划?对于原问题的集合来说,相当于第n个元素不取,先求前n-1个元素的子集,待求得后,再将第n个元素添加进去,即得原问题的解。    思路总结如下:每个元素要么取要么不取,先求第0~i元素的子集,第i+1~n-1个元素不考虑,相当于不取;待求得前i个元素的子集后,再将第i+1个元素添加进去,即得前i+1个元素的子集;而当求前i个元素的子集时,前i-1个元素的子集已经构造完毕,我们只需遍历这些集合,将第i个元素添加进去就行。    与求排列问题不同的是,求排列是先得出部分解,再通过部分解迭代构造出所有解;求子集,是先求子问题的解(虽然子问题的解也是原问题的部分解),再对子问题的解扩展得到更大子问题的解,迭代直到得出原问题的所有解。*/    vector<vector<int>> subsets(vector<int>& nums) {                sort(nums.begin(), nums.end());                vector<vector<int> > result;        vector<int> temp;                result.push_back(temp);                for(int i = 0; i < nums.size(); ++i)        {            int n = result.size();                        for(int j = 0; j < n; ++j)            {                temp = result[j];                temp.push_back(nums[i]);                result.push_back(temp);            }        }                return result;    }

求子集时,我们不是独立地构造每个解,而是在求得的子问题的解中再添加元素得到新的(更大子问题)解。例如,空集{}是集合{1,2,3}的子集,那么在空集中添加元素1,得到集合{1}是原集合的子集,再向集合{1}中添加元素2得到{1,2}就是原问题的更大的子集。

        问题本身都不难,都是常见的问题,但是其中的思路具有通用性和普适性。有点难度的东西不会也就算了,(其实我也学过一些算法,动态规划什么的,但始终不得要领,学的时候似乎没什么问题,用的时候就不会了,再回过头看,当时可能回了,以后遇到又不会了,反反复复如此,说到底,还是手生,练得少。这个其实不光是学算法、编程如此,学一切知识都可能出现这种情况,是一个普遍性问题),简单的东西就要保证会且熟。根据我的经验,简单的知识就可以解决大部分问题了,很多问题并不需要多高深的知识,人们常常在实践中用到了一些复杂的技术却反而忘记了基本的原则,这就本末倒置了。并且,如果熟练掌握了简单的知识,在需要更高深知识的情况下,也可以较容易地进一步学习,这就是打好基础的作用。

        在以后的学习和工作中,希望自己一方面思维能够更开阔、活跃,另一方面,也要更踏实一些。

0 0
原创粉丝点击