原型:
给定一个数组,求出数组内元素任意顺序重新排列的所有组合。
初级版:
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
-
示例 1: 输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
-
示例 2: 输入:nums = [0,1] 输出:[[0,1],[1,0]]
-
示例 3: 输入:nums = [1] 输出:[[1]]
分析:
- 数组长度为 n,每一位数字,按照排列组合,有 n 的阶乘种可能:
n * (n-1) * (n-2) * ... * 3 * 2 * 1 = n!,n 个数字,所以 时间复杂度 为 O(n * n !) - 逐个数字设定可能出现的数字,设定到最后一位,即表示本次排列组合完成
- 再回退到上一步,重复下一种可能的排列组合。 这就是回溯法:一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即回溯并且再次尝试。
- 我们可以将已经确定的数字和后面的数字交换,这样不用额外的存储空间,即可缩小需要排列数字的范围
代码如下:
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
let ans = [];
backtrack(ans, nums, 0, nums.length);
return ans;
};
var backtrack = function (ans, curArr, idx, leng) {
if (idx == leng) {
ans.push(curArr);
return;
}
for (let i = idx; i < leng; i++) {
[curArr[i], curArr[idx]] = [...swap(curArr[i], curArr[idx])];
backtrack(ans, curArr.concat([]), idx + 1, leng);
[curArr[i], curArr[idx]] = [...swap(curArr[i], curArr[idx])];
}
}
var swap = function(a, b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
return [a, b];
}
进阶版
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
-
示例 1: 输入:nums = [1,1,2] 输出:[[1,1,2], [1,2,1], [2,1,1]]
-
示例 2: 输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
分析:
- 方案一:可以先按照无重复的方式,列出来所有的组合,然后自己去重。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function(nums) {
let ans = new Set();
backtrack(ans, nums, 0);
let arr = [...ans];
for (let i = 0; i < arr.length; i++) {
arr[i] = arr[i].split('+');
for (let j = 0; j < arr[i].length; j++) {
arr[i][j] = parseInt(arr[i][j]);
}
}
return arr;
};
var backtrack = function (result, curArray, curIdx) {
let leng = curArray.length;
if (curIdx === leng) {
result.add(curArray.join('+'));
}
for (let i = curIdx; i < leng; i++) {
[curArray[i], curArray[curIdx]] = [...swap(curArray[i], curArray[curIdx])];
backtrack(result, curArray.concat([]), curIdx + 1);
[curArray[i], curArray[curIdx]] = [...swap(curArray[i], curArray[curIdx])];
}
}
var swap = function (a, b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
return [a, b];
}
这种方式执行效率比较低,用时264ms
- 方法二: 我们发现,如果遇到重复的数字,那么组合后的结果是一样的,那如果遇到重复的,我们只取第一次重复值进行排列,既可以避免重复。为了实现这种思路,需要先解决两个问题:
- 首先需要当前元素,后面的元素是升序排列的,这样只要是重复的,就会挨在一起。
- 我们需要把组合的数组切成两份,一份是已经组合好的部分,另一份是未组合的部分,对未组合的部分进行排序
let sortArr = curArray.slice(curIdx)
sortArr.sort((a, b) => a - b);
curArray = curArray.slice(0, curIdx).concat(sortArr);
- 相邻数字的判断就比较容易了
if ( (i < leng - 1 && curArray[i] === curArray[i + 1] ) ) {
continue;
}
代码如下:
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function(nums) {
let ans = [];
backtrack(ans, nums, 0);
return ans;
};
var backtrack = function (result, curArray, curIdx) {
let leng = curArray.length;
if (curIdx === leng) {
result.push(curArray);
return;
}
let sortArr = curArray.slice(curIdx);
sortArr.sort((a, b) => a - b);
curArray = curArray.slice(0, curIdx).concat(sortArr);
for (let i = curIdx; i < leng; i++) {
if ( (i < leng - 1 && curArray[i] === curArray[i + 1] ) ) {
continue;
}
[curArray[i], curArray[curIdx]] = [...swap(curArray[i], curArray[curIdx])];
backtrack(result, curArray.concat([]), curIdx + 1);
[curArray[i], curArray[curIdx]] = [...swap(curArray[i], curArray[curIdx])];
}
}
var swap = function (a, b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
return [a, b];
}
这种方法的执行效率比方法一好,用时 100ms
高级版
给定正整数 N ,我们按任何顺序(包括原始顺序)将数字重新排序,注意其前导数字不能为零。 如果我们可以通过上述方式得到 2 的幂,返回 true;否则,返回 false。
-
示例 1: 输入:1 输出:true
-
示例 2: 输入:10 输出:false
-
示例 3: 输入:16 输出:true
-
示例 4: 输入:24 输出:false
-
示例 5: 输入:46 输出:true
分析:
- 数字需要拆分成数组
- 数组进行全排列
- 对所有全排列结果处理,首位为 0 的不处理
- 将全排列结果转换成数字,并判断是否为 2 的幂数
如何判断 2 的幂数很简单,只要一直除以 2 最后等于 1,且过程中可以取 2 模 即可
var isSqrt = function (num) {
let left = 0, right = num;
while (num != 1) {
if (num % 2 != 0) {
return false;
}
num /= 2;
}
return true;
}
还有一种高效的方法: 一个数 n 是 2 的幂,当且仅当 n 是正整数,并且 n 的二进制表示中仅包含 1 个 1。 因此我们可以考虑使用位运算,将 n 的二进制表示中最低位的那个 1 提取出来,再判断剩余的数值是否为 0 即可。下面介绍两种常见的与「二进制表示中最低位」相关的位运算技巧。
- 「临近按位与」
return n > 0 && (n & (n - 1)) === 0;
其中 & 表示按位与运算。该位运算技巧可以直接将 n 二进制表示的最低位 1 移除
- 「取反按位与」
return n > 0 && (n & -n) == n;
由于负数是按照补码规则在计算机中存储的,-n 的二进制表示为 n 的二进制表示的每一位取反再加上 1,再与 n 按位与运算,还等于 n 。
将数字拆分成数组也很简单,一直取余数即可 ```JavaScript let arr = []; let value = 0 while (n) { value = n % 10; arr.push(value); n = Math.floor(n / 10); } ```
代码如下:
/**
* @param {number} n
* @return {boolean}
*/
var reorderedPowerOf2 = function(n) {
let arr = [];
let value = 0
while (n) {
value = n % 10;
arr.push(value);
n = Math.floor(n / 10);
}
let allPermute = permuteUnique(arr);
let ans = allPermute.some((item) => {
item = item.join('');
if (item[0] != '0') {
item = Number(item);
if (isSqrt(item)) {
return true;
}
}
})
return ans || false;
};
var permuteUnique = function (nums) {
let ans = [];
backtrack(ans, nums, 0);
return ans;
}
var backtrack = function (result, curArray, curIdx) {
let leng = curArray.length;
if (curIdx === leng) {
result.push(curArray);
return;
}
let sortArr = curArray.slice(curIdx);
sortArr.sort((a, b) => a - b );
curArray = curArray.slice(0, curIdx).concat(sortArr);
for (let i = curIdx; i < leng; i++) {
if (i < leng - 1 && curArray[i] === curArray[i + 1]) { continue; }
[curArray[i], curArray[curIdx]] = [...swap(curArray[i], curArray[curIdx])];
backtrack(result, curArray.concat([]), curIdx + 1);
[curArray[i], curArray[curIdx]] = [...swap(curArray[i], curArray[curIdx])];
}
}
var swap = function (a, b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
return [a, b];
}
var isSqrt = function (n) {
return n > 0 && (n & (n - 1)) === 0;
}
都看到这里了,就点个赞吧