從陣列中找出N個數,其和為M的所有可能

前端小白ML發表於2019-12-26

問題 (採用二進位制正序表示法)

從一個陣列中找出N個數,其和為M的所有可能。

eg: 從陣列 [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)
// => [[1, 4], [2, 5]]
複製程式碼

實現思路

總體思路: 根據陣列長度構建二進位制資料,再選擇其中滿足條件的資料。

我們用 1 和 0 來表示陣列中的某位元素是否選中。因此,可以用 0110 來表示陣列中第 1 位和 第 2 位 被選中了。

下面列一下長度為 4 的所有二進位制資料表示情況:

  • 0000 表示沒有選擇陣列中的任何元素
  • 0001 表示選擇了陣列中第 3 位元素
  • 0010 表示選擇了陣列中第 2 位元素
  • 0011 表示選擇了陣列中第 2、3位元素
  • 0100 表示選擇了陣列中第 1 位元素
  • 0101 表示選擇了陣列中第 1、3 位元素
  • 0110 表示選擇了陣列中第 1、2 位元素
  • 0111 表示選擇了陣列中第 1、2、3 位元素
  • 1000 表示選擇了陣列中第 0 位元素
  • 1001 表示選擇了陣列中第 0、3 位元素
  • 1010 表示選擇了陣列中第 0、2 位元素
  • 1011 表示選擇了陣列中第 0、2、3 位元素
  • 1100 表示選擇了陣列中第 0、1 位元素
  • 1101 表示選擇了陣列中第 0、1、3 位元素
  • 1110 表示選擇了陣列中第 0、1、2 位元素
  • 1111 表示選擇了陣列中所有位元素

那麼開篇陣列,滿足條件的二進位制(存在兩個1)有 0011、0101、0110、1001、1010、1100 六種可能。而符合對應元素之和為 5 的只有 0110 和 1001.

思路就是構建了所有長度為 4 的二進位制,再找到符合條件的二進位制。

題目的條件有兩個。

  • 被選中的個數是 2.
  • 被選中的和是 5.

遍歷所有的二進位制,判斷選中的個數是否為 2,然後再求對應的元素之和是否為 5.

參考JS 的位運算

第一個問題,如何遍歷所有二進位制資料?

陣列長度為 4,對應16, 即 1 << 4。

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

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

  • 實現方式一:
const n = num => num.toString(2).replace(/0/g, '').length

// console.log(n(3)); // 2    3的二進位制: 0011
複製程式碼

num.toString(2) 將 num 裝換成二進位制

  • 實現方式二:
function search(i) {
   let count = 0
   while(i) {
    //  i & 1  與運算 結果為 二進位制中含有 1 的
     if (i & 1) {
      ++count
     }
    //  位運算 - 右移一位
     i >>= 1;
   }
   return count
 }
 // console.log(search(0b1010)); // 2
複製程式碼

上述演算法的思路其實很簡單,將二進位制逐步右移 1 位,看看末尾為 1 的個數。比如 10 的二進位制是 1010,逐步右移的所有可能是 1010->101->10->1->0,其中有 2 次末尾是 1。因此結果是 2。

第三個問題,如何根據二進位制資料來求和?

比如 0110, 應該求和 arr[1] + arr[2].

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

分析: 0110,我們把 1 一次左移 << (0,1, 2, 3)次,即對應 arr 陣列的下標。然後一次判斷:

1 << 0 => 0001

1 << 1 => 0010

1<< 2 => 0100

1<< 3 => 1000

然後依次 & 運算。

0110 & 0001 => 0

0110 * 0010 => 0010 => 2

0110 * 0100 => 0100 => 3

0110 * 1000 => 0 => 0

所以拿到對應的下標 arr[1] = 2, arr[2] = 3

實現:

var arr = [1,2,3,4]
var s = 0, temp = [], len = arr.length;
for (var i = 0,; 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]]
複製程式碼

時間複雜度 2^n 指數增加?

相關文章