zoukankan      html  css  js  c++  java
  • 组合类算法问题

      最近一位同学做完求职笔试的时候,问我知不知道怎么划分子数组,我一懵,问输出结果是如何的,同学说,假如原始数字是3,那么输出结果就是

    [1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]

      我想起之前做过类似的题目,但具体的思路没有想起来,于是上力扣上面搜了一下划分子数组,没有搜索到我曾做过的名为划分子数组的题目,但后来一想,这一题的输出结果其实和全排列是很像的,于是我找到全排列的题目,回忆起了解题方法是回溯,其实我之前笔试时也遇到过全排列的原题,当时以为稳了,但实际上在敲代码过程中被卡住了,最大问题就是我对回溯类的问题解题过程不清晰,接下来就从全排列开始,对组合类的算法问题做一个总结。

      全排列问题我提交的代码为:

     1 /**
     2  * @param {number[]} nums
     3  * @return {number[][]}
     4  */
     5 
     6 var permute = function(nums) {
     7     let res = [];
     8     let swap = function(nums, i, j) {
     9         let temp = nums[i];
    10         nums[i] = nums[j];
    11         nums[j] = temp;
    12     }
    13     let find = function(nums, begin, end, res) {
    14 //         结束条件,当开始下标和结束下标相同,说明已经达到其要求的数组元素个数,将数组结果添加至结果集
    15         if(begin == end) {
    16             res.push([...nums]);    //注意这里要先把参数类数组转换为数组
    17             return ;
    18         }
    19         for(let start = begin; start <= end; start++) {
    20 //             由于在全排列里面,数组下标和元素是严格对立的,所以通过交换数字来达到 对于两个数组,同一下标上有不同元素就当成是全排列的两个结果
    21             swap(nums, start, begin);
    22 //             开始回溯寻找路径
    23             find(nums, begin + 1, end, res);
    24 //             回溯后再替换为原先的数组元素
    25             swap(nums, start, begin);
    26         }
    27     }
    28     find(nums, 0, nums.length - 1, res);
    29     return res;
    30 };

      一般解决回溯问题的时候,都会画决策树,画完后寻找大致的代码思路,这时需要思考个问题:

    1.路径:怎么样走才算找到题意里的东西

    2.选择列表:要做抉择的变量在什么可选择范围内

    3.结束条件:到达决策树底层,需要return出去的if语句

      关于结束条件,比较容易找,一般都是对长度做限制或者起始终止做限制,那么选择列表和路径要怎么理解呢,怎么把这个思想应用到代码中去呢?这些问题都隐藏在回溯算法的核心框架里了:

    for 遍历选择操作时所需的变量 in 选择列表:
        //先尝试做选择
        把已经做了的选择从选择列表中移除
        在路径里面加入刚才选择的元素
        backTrack(更新的路径,更新的选择列表)    //回溯
    
        //撤销刚才的选择,便于寻找另外的解
        在刚才的路径里撤销所做选择
        把该选择再次加入选择列表

      组合问题也是用回溯去做,就比如,我现在给出了1-4这4个数字,要求你用高中的组合知识得出它的从1个数到4个数的所有组合结果,你给出的答案必然是(说不定顺序都和我一样)

    1 2 3 4 1,2 1,3 1,4 2,3 2,4 3,4 1,2,3 1,2,4 1,3,4 2,3,4 1,2,3,4

      力扣上的组合题,规定的是特定的组合数组长度,所以是上面所列举的组合数学问题的一个子集,我提交的代码为:

     1 /**
     2  * @param {number} n
     3  * @param {number} k
     4  * @return {number[][]}
     5  */
     6 var combine = function(n, k) {
     7     let arr = [],
     8         res = [];
     9     let findCombination = function(n, k, begin, arr) {
    10         if(arr.length == k) {
    11             res.push([...arr]);
    12             return ;
    13         }
    14         for(let i = begin; i <= n - (k - arr.length) + 1; i++) {
    15             arr.push(i);
    16             findCombination(n, k, i + 1, arr);
    17             arr.pop();
    18         }
    19     }
    20     findCombination(n, k, 1, arr);
    21     return res;
    22 };

      没给注释,因为总体上就是套着回溯的代码框架来的。

      你能看出组合题跟全排列的代码差别么?思考一下

      ----------------------------------------------------------------

      答案揭晓!

      其实有两点不同,在于二者回溯的终止条件不同和二者在不在意数组元素所在位置(全排列的话,[1,2,3]和[3,2,1]算作不同的结果,而组合的话,[1,2,3]和[3,2,1]由于含有的元素个数和值相同,所以看作同一个解,只取其一就可)。假如是全排列的话,最终的结果肯定跟原数组的长度是一致的,举个例子:假如让你说出[1,2,3]的全排列结果,你肯定会说,[1,3,2],[2,1,3]......对吧!所以排列后的数组长度跟原数组一样,都是3。那假如现在说让你说出[1,2,3]任意两个数的组合结果,那么你回溯到数组长度=2时,就要return了。

      有了这一题的基础,就能够做出我同学的那道笔试了,聪明的你一定想到了,就是把 k 放到循环里面去遍历就可以了,所以最终的代码应该为:

     1 function subArray(n) {
     2     let arr = [],
     3         res = [];
     4     let findCombination = function(n, k, begin, arr) {
     5         if(arr.length == k) {
     6             res.push([...arr]);
     7             return ;
     8         }
     9         for(let i = begin; i <= n - (k - arr.length) + 1; i++) {
    10             arr.push(i);
    11             findCombination(n, k, i + 1, arr);
    12             arr.pop();
    13         }
    14     }
    15     for(let i = 1; i <= n; i++){
    16         findCombination(n, i, 1, arr);
    17     }
    18     console.log(res)
    19 }
    20 subArray(4);

      之后可能再更新一些回溯方面的题目。

     

    单纯是把博客作为学习的记录,有些博客是看了别人的文章之后再自己总结的,所以不保证完完整整的原创性,如果引用了链接或者作者觉得侵权的话可以联系我删除哦~
  • 相关阅读:
    runtime iOS 运行时机制
    iOS 文件操作
    responseCode 状态吗查询
    iOS常用宏定义
    Block里用self造成循环引用
    iOS Block全面分析
    OC与Swift混编
    iOS打包app发给测试人员测试
    Swift UITextField
    sqilite学习
  • 原文地址:https://www.cnblogs.com/hey-Sarah/p/13377140.html
Copyright © 2011-2022 走看看