分治 分治算法,比较经典的算法思想,且看它的描述:把问题分割多 N 多个子问题,然后 求解子问题的答案,利用递归把子问题的结果*合并处理 返回.
从上面可以得出以下结论:
一般把大问题进行分割,然后变成一对一求解 
一般配合递归,返回”子问题求解的结果,然后进行合并 “ 
 
递归 递归就是,不断的调用自身函数,直到执行某一个条件,递归终止,返回某个值 特点如下:
不断调用函数自身,通常对 传入的值进行切分 
有一个终止函数调用的条件,通常对传入函数的值进行判断 
对函数栈返回的结果进行组合 
 
下面来几个简单的例子
n 的阶乘 计算公式如下: n!=n(n-1)*(n-2)—-*(1)
如: n=6
n!=6*5*4*3*2*1
 观察可知,把 6 不断的分解成1(递进),直到n=1,返回的结果相乘(归),代码实现如下
1 2 3 4 5 6 7 function  factorial (n ){    if (n===1 ){        return  1 ;    }    return  n*factorial (n-1 ) }
 
递归的实现,符合计算机的逻辑,非常的巧妙,一开始很不习惯递归, 看思考多了,却发现,大多数问题都可以用递归的思想去思考,当然,递归又可以用for循环改写
外观序列 n        结果         描述
1.  ==>   1           1
2.  ==>   11          1 个 1
3.  ==>   21          2 个 1
4.  ==>   1211        1 个 2  1 个 1
5.  ==>   111221      1 个 1  1 个 2  2 个 1
 
递归解法
观察上面,我们可以看出,我们都是依据上一项来描述当前项的, 所以,我们可以用递归的思维去思考问题。
对于n=2 时,字符串 ‘21’,它产生的下一项, 我们可以通过代码这样去描述 count =1 ,遍历’21’,如果前一面与后一项相等, 我们就把count+1,如果不相等,我们就加入 count+str[i],重置 count=1
下面的代码就是体现了这一过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const  countAndSay = function  (n ) {   if  (n === 1 ) {     return  "1" ;   }   let  base = countAndSay (n - 1 );   let  result = "" ;   let  count = 1 ;   for  (let  i = 0 ; i < base.length ; i++) {     if  (base[i] === base[i + 1 ]) {       count = count + 1 ;     } else  {       result += count + base[i];       count = 1 ;     }   }   return  result; };
 
归并排序 最经典的运用分治算法思想进行排序,对一组 N 个无序数组排序,利用分治思想来解决此问题:
分割大问题成小问题 这或许是最难的一步,对于排序,我们一般的想法就是冒泡(两个 for 循环搞定),或者选择排序 (每一轮循环,取最大值 或最小值,然后放在相应位置),插入排序 (每一轮前后比较,小在前,大在后).切换分治的思想来考虑排序 我们可以先对两个数排序,产生排序后,我们再与第三个数进行排序,与此类推,最后成为一个排序 好了数组.可以看出,最后的排序结果,依赖于前一轮的排序结果 ,也就是子问题的结果,通过递归进行组合成大问题的结果 ,
如何查找子问题 注意几个字眼,分 ,思考的方向,取两个数,求出问题结果,然后再拿这两个数与下一个数进行某种运算得出结果,一层一层往上嵌套 . 如果 符合这种特征,说明适用分治算法解决此题
排序分治实现 了解思想是一回事,实现又是一回事,由于分治算法,一般配合的递归,比较难看懂代码,但都有 一般的实现套路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function  mergeSort (array ) {      if  (array.length  > 1 ) {     const  { length } = array;          const  middle = Math .floor (length / 2 );      const  left = mergeSort (array.slice (0 , middle));     const  right = mergeSort (array.slice (middle));          array = merge (left, right);    }      return  array; }function  merge (left, right ) {   let  i = 0 ;   let  j = 0 ;   const  result = [];      while  (i < left.length  && j < right.length ) {     result.push (left[i] < right[j] ? left[i++] : right[j++]);    }      return  result.concat (i < left.length  ? left.slice (i) : right.slice (j));  }
 
实现套路: 
一般有个主函数和一个合并结果的辅助函数 
一般把元素分割成两部分,然后递归的调用主函数,把分割的两部分 left 和 right ,然后把 left, right 传入 辅助函数中,返回结果 
主函数 一般有两个返回, 一个是 递归条件的结果,一个是合并结果的返回 
 
代码模板如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function  mainFunction (arr ) {   if  (arr.length  === 1 ) {     return  arr[0 ];   }   if  (arr.length  > 1 ) {     const  middle = Math .floor (length / 2 );           const  left = mergeSort (array.slice (0 , middle));     const  right = mergeSort (array.slice (middle));     result = merge (left, right);    }   return  result; }function  merge (left, right ) {}
 
运算字符串组合数 题目描述如下:
给定一个含有数字和运算符的字符串,为表达式添加括号, 改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含+,-以及*。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 示例 1:输入: "2-1 -1 " 输出: [0, 2] 解释: ((2-1 )-1 ) = 0 (2-(1-1 )) = 2 示例 2:输入: "2*3-4 *5" 输出: [-34 , -14 , -10 , -10 , 10] 解释: (2*(3-(4*5))) = -34  ((2*3)-(4*5)) = -14  ((2*(3-4 ))*5) = -10  (2*((3-4 )*5)) = -10  (((2*3)-4 )*5) = 10
 
这道题说实话,不太那么容易看出分治算法的实现,目前我也无法用语言表达 一图胜千言,按操作符号分割字符串
 
实现如下 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function  diffWaysToCompute (str ) {   if  (Number (str)) {     return  [Number (str)];   }   let  len = str.length ;   const  result = [];   for  (let  i = 0 ; i < len; i++) {     if  (str[i] === "+"  || str[i] === "-"  || str[i] === "*" ) {       let  left = diffWaysToCompute (str.substring (0 , i));       let  right = diffWaysToCompute (str.slice (i + 1 ));              for  (let  lv of  left) {         for  (let  rv of  right) {           switch  (str[i]) {             case  "+" :               result.push (lv + rv);               break ;             case  "-" :               result.push (lv - rv);               break ;             case  "*" :               result.push (lv * rv);           }         }       }     }   }   return  result; }
 
求数组字符串中的最长公共子串 对数组字符串进行分割,然后两两比较,递归合并结果,跟归并排序差不多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const  searchCommonStrTwo = function  (strArr ) {   if  (strArr.length  === 0 ) {     return  "" ;   }   let  prefix;   if  (strArr.length  === 1 ) {     return  strArr[0 ];   }   if  (strArr.length  > 1 ) {     const  length = strArr.length ;     const  mod = Math .floor (length / 2 );     let  left = searchCommonStrTwo (strArr.slice (0 , mod));     let  right = searchCommonStrTwo (strArr.slice (mod));     prefix = searchTowStr (left, right);   }   return  prefix;    };function  searchTowStr (left, right ) {   let  prefix = left;   while  (right.indexOf (prefix) !== 0 ) {     prefix = prefix.substring (0 , prefix.length  - 1 );     if  (!prefix) return  "" ;   }   return  prefix; }console .log (searchCommonStrTwo (["axx" , "xx3" , "xx1" ]));
 
反转单链表 题目描述: 链表为:1->2->3->4。反转后为 4->3->2->1
此题也可用分治递归的思想,来解决,我们可以这样想,且看思想:
3->4 ===> 4->3 2 4->3 ===> 4->3->2 1 4 3 2 ====>4->3->2->1
过程如下:先交换 3 和 4 得到结果 结果 4->3
第二轮 2 和 4->3 这个结果组合 ,得到 4->3->2 ,可以观察到是加在了结果链表的最后一个节点
第三轮 1 和 4->3->2 这个结果组合,得到 4->3->2-1 ,此时递归结束,得到结果
实现代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const  testLink = {   val : 1 ,   next : {     val : 2 ,     next : {       val : 3 ,       next : null ,     },   }, };function  revertLink (testLink ) {      if  (!testLink) {     return  testLink;   } else  {     let  result = revertLink (testLink.next );     if  (result === null ) {       return  testLink;      }          let  header = result;     while  (header.next  !== null ) {       header = header.next ;     }     header.next  = testLink;     testLink.next  = null ;     return  result;   } }console .log (revertLink (testLink));
 
此道题可以发现,一般递归都伴随着分治的思想
给定一个数字数组,和一个数,去除数组中和这个数相同的一项 如: nums=[1,2,2,4,5] value=2 返回 [1,4,5] 数组中的顺序无关要紧
也可以用分治的思想来实现
数组中的每个数与 value 比较 得到的结果合并就是我们的过滤后的数组
步骤如下: 1 和 2 比较 ,如果 相等 就返回 [] 如果不相等得到集合结果 [1], 2 与 2 比较, 得到 [] ,与上一个比较结果 合并,得到 [1] 2 与 2 比较, 得到 [] ,与上一个比较结果 合并,得到 [1] 4 与 2 比较, 得到 [4] ,与上一个比较结果 合并,得到 [1,4] 5 与 2 比较, 得到 [5] ,与上一个比较结果 合并,得到 [1,4,5]
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const  removeElement = function  (nums, val ) {   let  result = [];   if  (nums.length  === 1  && nums[0 ] !== val) {     return  nums;   } else  if  (nums.length  === 1  && nums[0 ] === val) {     return  [];   } else  {     const  value = nums.slice (0 , 1 );     result = removeHelp (nums.slice (1 ), val);     if  (value[0 ] !== val) {       return  result.concat (value);     }     return  result;   } };
 
移除链表中的重复元素 如: 1->1->2 1->2
算法思路如下: 链表末尾元素 : 1 和 2 比较 1->2
1 和 1->2 比较 1=1 ,返回 1->2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const  sortLink = {   val : 1 ,   next : {     val : 1 ,     next : {       val : 2 ,       next : {         val : 3 ,         next : {           val : 3 ,           next : null ,         },       },     },   }, };function  deleteDuplicates (head ) {   if  (!head) {     return  null ;   } else  {     let  result = deleteDuplicates (head.next );     if  (result === null ) {       return  head;     }     if  (result.val  !== head.val ) {       head.next  = result;       return  head;     } else  {       return  result;     }   } }
 
小结 分解->解决->合并
分解:分解原问题为结构相同的子问题(即寻找子问题) 解决:当分解到容易求解的边界后,进行递归求解 合并:将子问题的解合并成原问题的解