改進,從一個數組中找出 N 個數,其和爲 M 的全部可能

特此說明,本文算法改自於《從一個數組中找出 N 個數,其和爲 M 的全部可能--最 nice 的解法》一文。本文不一樣的是,採用二進制正序表示法,這種實現思路更直觀、更簡單些。javascript

問題

從一個數組中找出 N 個數,其和爲 M 的全部可能。 java

舉個例子,從數組 [1, 2, 3, 4] 中選取 2 個元素,求和爲 5 的全部可能。答案是兩組組合: 1,4 和 2,3。算法

假設封裝函數爲 search數組

function search(arr, count, sum) {
    ...
    return res
}
複製代碼

則有,函數

search([1,2,3,4],2,5)
// => [[2,3],[1,4]]
複製代碼

實現思路

這裏咱們簡單說一下整體思路:根據數組長度構建二進制數據,再選擇其中知足條件的數據。post

咱們用 1 和 0 來表示數組中某位元素是否被選中。所以,能夠用 0110 來表示數組中第 1 位和第 2 位被選中了。ui

下面列一下長度爲 4 的全部二進制數據表示狀況:spa

  • 0000 表示沒有選擇數組中的任何元素
  • 0001 表示選擇了數組中第 3 位元素
  • 0010 表示選擇了數組中第 2 位元素
  • 0011 表示選擇了數組中第 二、3位元素
  • 0100 表示選擇了數組中第 1 位元素
  • 0101 表示選擇了數組中第 一、3 位元素
  • 0110 表示選擇了數組中第 一、2 位元素
  • 0111 表示選擇了數組中第 一、二、3 位元素
  • 1000 表示選擇了數組中第 0 位元素
  • 1001 表示選擇了數組中第 0、3 位元素
  • 1010 表示選擇了數組中第 0、2 位元素
  • 1011 表示選擇了數組中第 0、二、3 位元素
  • 1110 表示選擇了數組中第 0、一、2 位元素
  • 1111 表示選擇了數組中全部位元素

那麼開篇的例子, 4 選 2,知足條件的二進制有 00十一、010一、01十、100一、10十、1100 共 6 種可能。而符合對應元素之和爲 5 的只有 0110 和 1001。code

看到了嗎,思路是咱們構建了全部長度爲 4 的二進制,再找到符合條件的二進制。ip

這裏條件有兩個。

  • 其一是,被選中的個數是 2。
  • 其二是,被選中的和是 5。

咱們的算法思路逐漸清晰起來: 遍歷全部二進制,判斷選中個數是否爲 2,而後再求對應的元素之和,看其是否爲 5。

第一個問題,如何遍歷全部二進制數據呢?

這個難不到咱們,數組長度爲 4,那麼全部二進制數據是 0 - 15。

for (var i = 0; i < 16; i++) {
  ...
}
複製代碼

數組長度 4,對應16,即 1 << 4。

注意 1 << 31 爲-2147483648,可使用Math.pow(2, 31)來代替

第二個問題,如何求取被選中的元素個數呢?即求取二進制字符串中 1 的個數呢?

實現方式有多種,好比其中一種是:

function n(i) {
  var count = 0;
  while( i ) {
   if(i & 1){
    ++count;
   }
   i >>= 1;
  }
  return count;
}
console.log(n(0b1010))
// => 2
複製代碼

上述算法的思路其實很簡單,將二進制逐步右移 1 位,看看末尾爲 1 的個數。好比 10 的二進制是 1010,逐步右移的全部多是 1010->101->10->1->0,其中有 2 次末尾是 1。所以結果是 2。

第三個問題,如何根據二進制數據來求和呢?

好比 0110,咱們應該求和 arr[1] + arr[2]。

問題轉化成了如何判斷數組下標是否在 0110 中呢?

其實也很簡單,好比下標 1 在,而下標 3 不在。咱們把 1 轉化成 0100,0110 & 0100 爲 0100, 大於 0,所以下標 1 在。而 0110 & 0001 爲 0,所以 下標 3 不在。

因此求和咱們能夠以下實現:

var arr = [1,2,3,4]
var s = 0, temp = [];
for (var i = 0, len = arr.length; i < len; i++) {
  if ( 0b0110 & 1 << (len - 1 - i)) {
	s += arr[i]
	temp.push(arr[i])
  }
}
console.log(temp)
// => [2,3]
複製代碼

最終實現

有了以上鋪墊,這裏給出最終實現:

function search(arr, count, sum) {
  var len = arr.length, res = [];
  for (var i = 0; i < Math.pow(2, len); i++) {
	if (n(i) == count) {
	  var s = 0, temp = [];
	  for (var j = 0; j < len; j++) {
		if (i & 1 << (len - 1 -j)) {
		  s += arr[j]
		  temp.push(arr[j])
		}
	  }
	  if (s == sum) {
		res.push(temp)
	  }
	}
  }
  return res;
}

function n(i) {
  var count = 0;
  while( i ) {
   if(i & 1){
    ++count;
   }
   i >>= 1;
  }
  return count;
}

console.log(search([1,2,3,4],2,5))
// => [[2,3],[1,4]]
複製代碼

最後,能夠看出其實不必引入各類概念就能夠把本算法說清楚。

本文完。

相關文章
相關標籤/搜索