序章
我们把字符串、数组、正则、排序、递归归为简单算法。接下来系列里,将系列文章里将为大家逐一介绍。
本章节主要介绍数组的这几个问题:电话号码的组合(公式运算)、卡牌分组(归类运算)、种花问题(筛选运算)和格雷编码(二进制运算)。
数组
电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number
步骤一:寻找规律
先分割输入split,然后映射字母。先俩个组合运算,完了和新的继续组成俩个运算。俩个俩个的运算。运用递归。步骤二:伪代码实现
for(i=0;i<arr[0].length;i++)
for(j=0;j<arr[1].length;j++)
result.push(arr[0][i]arr[1][j])
arr.splice(0,2,result)
if(arr.length>1)
递归
else
return result
步骤三:计算子串代码演示
代码思路整理:
- 建立电话号码键盘映射
- 把输入字符串根据单个字符分割为数组,例如'2345'=>[2,3,4,5]
- 保存键盘映射后的字母内容,例如'23'=>['abc','def']
- 创建递归函数:如果俩个数组组合完后,arr的length变为1,直接输出;如果大于1,继续执行递归。
- 调用递归函数:如果输入为1,则直接返回code;如果输入为一个数字,则直接split后形成新的数组输出。
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
let numberMap = ['',1,'abc','def','ghi','jkl','mno','pqrs','tuv','wxyz'],
numInputArr = digits.split(''),
code = [];
numInputArr.forEach(item => {
if(numberMap[item]) {
code.push(numberMap[item])
}
})
let comb = (arr) => {
// 临时变量用来保存前俩个组合的结果
let temp = [];
for(i=0,il=arr[0].length;i<il;i++) {
for(j=0,jl=arr[1].length;j<jl;j++) {
temp.push(`${arr[0][i]}${arr[1][j]}`)
}
}
arr.splice(0,2,temp)
if(arr.length > 1) {
comb(arr);
} else {
return temp
}
return arr[0]
}
if(code.length>1) {
return comb(code)
} else if(code.length===1) {
return code[0].split('')
} else {
return code
}
};
小结:上述做法涉及到的知识点如下所示。
Array.prototype.split
卡牌分组
给定一副牌,每张牌上都写着一个整数。
此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:
每组都有 X 张牌。 组内所有的牌上都写着相同的整数。 仅当你可选的 X >= 2 时返回 true。
示例 1:
输入:[1,2,3,4,4,3,2,1]
输出:true
解释:可行的分组是 [1,1],[2,2],[3,3],[4,4]
示例 2:
输入:[1,1,1,2,2,2,3,3]
输出:false
示例 3:
输入:[1]
输出:false
解释:没有满足要求的分组。
示例 4:
输入:[1,1]
输出:true
解释:可行的分组是 [1,1]
示例 5:
输入:[1,1,2,2,2,2]
输出:true
解释:可行的分组是 [1,1],[2,2],[2,2]
提示:
1 <= deck.length <= 10000 0 <= deck[i] < 10000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards
步骤一:寻找规律
- 给的卡牌毫无规律,首先需要排序
- 寻找最大公约数,没有最大公约数,就不符合题目要求。比如示例2是(3,3,2),没有最大公约数,不符合。示例5是(2,4)最大公约数是2,符合题目要求,可以拆分
- 如何算俩个数之间的最大公约数GCD
a. 穷举法:从1开始往上遍历,找到一个数能被俩个数同时整除,就是最大公约数。此方法比较消耗性能。
b. 辗转相除法:辗转相除法又称为欧几里得算法,该方法依托于一个定理:
gcd(a, b) = gcd(b, a mod b)
其中,a mod b是a除以b所得的余数。也就是说a,b的最大公约数就是b,a mod b的最大公约数。
原理参考链接:https://www.jianshu.com/p/25d0ca88a4a1
步骤二:卡牌组合代码演示
/**
* @param {number[]} deck
* @return {boolean}
*/
var hasGroupsSizeX = function(deck) {
// 卡牌排序
let str = deck.sort().join('');
// 分组(单张或着多张分组):(\d)\1+ 指的是寻找相同数字的牌,或者只有1张牌 \d
let group = str.match(/(\d)\1+|\d/g)
// 求俩个数的最大公约数:辗转相除法
let gcd = (a,b) => {
if(b===0){
return a
} else {
return gcd(b, a%b)
}
}
while(group.length>1){
let a = group.shift().length;
let b = group.shift().length;
let v = gcd(a,b)
if(v === 1) {
return false
} else {
group.unshift('0'.repeat(v) )
}
}
return group.length ? group[0].length > 1 : false
};
代码里用正则去匹配的时候,字符串比较大的时候,总是不能匹配成功。
详情查看:leetcode-cn.com/submissions…代码优化
/**
* @param {number[]} deck
* @return {boolean}
*/
var hasGroupsSizeX = function(deck) {
let group = [];
let tmp = {};
// 计算每张相同卡牌的数量
deck.forEach(item => {
tmp[item] = tmp[item] ? tmp[item] + 1 : 1
})
// 保存数量,例如实例2为(3,3,2)
for (let v of Object.values(tmp)) {
group.push(v)
}
// 求俩个数的最大公约数:辗转相除法
let gcd = (a,b) => {
if(b===0){
return a
} else {
return gcd(b, a%b)
}
}
while(group.length>1){
let a = group.shift();
let b = group.shift();
let v = gcd(a,b)
if(v === 1) {
return false
} else {
group.unshift(v)
}
}
return group.length ? group[0] > 1 : false
};
种花问题
假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。
示例 1:
输入: flowerbed = [1,0,0,0,1], n = 1
输出: True
示例 2:
输入: flowerbed = [1,0,0,0,1], n = 2
输出: False
注意:
数组内已种好的花不会违反种植规则。
输入的数组长度范围为 [1, 20000]。
n 是非负整数,且不会超过输入数组的大小。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/can-place-flowers
解法一
解题思路
因为是线性问题,所以可以用贪心。
因为javascript的数组的特性,如果对应的下标没有值,都返回undefined
所以 !flowerbed[i] 即可以代表为 i位置为0的情况,也可以代表超出边界的情况
代码演示
/**
* @param {number[]} flowerbed
* @param {number} n
* @return {boolean}
*/
var canPlaceFlowers = function (flowerbed, n) {
let count=0
for (let i = 0; i < flowerbed.length; i++) {
if (flowerbed[i] === 0) {
if (!flowerbed[i - 1] && !flowerbed[i + 1]) {
count++
flowerbed[i]=1
}
}
}
return count >= n
};
解法二
解题思路
首先,为了避免首位和尾位的特殊情况,可在首位和尾位都填上一个0。
然后,找到满足左右两边都是0即可的目标值,目标值个数即为可插花朵的最大值。
代码演示
/**
* @param {number[]} flowerbed
* @param {number} n
* @return {boolean}
*/
var canPlaceFlowers = function(flowerbed, n) {
let cnt=0;
flowerbed=[0,...flowerbed,0];
for(i=1,len=flowerbed.length-1;i<len;i++) {
if(flowerbed[i-1]===0 && flowerbed[i]===0 && flowerbed[i+1]===0){
cnt++;
i+=1;
}
}
return cnt >= n
};
格雷编码
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。格雷编码序列必须以 0 开头。
示例 1:
输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2
对于给定的 n,其格雷编码序列并不唯一。
例如,[0,2,3,1] 也是一个有效的格雷编码序列。
00 - 0
10 - 2
11 - 3
01 - 1
示例 2:
输入: 0
输出: [0]
解释: 我们定义格雷编码序列必须以 0 开头。
给定编码总位数为 n 的格雷编码序列,其长度为 2n。当 n = 0 时,长度为 20 = 1。
因此,当 n = 0 时,其格雷编码序列为 [0]。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/gray-code
步骤一: 寻找规律
- 总共是2的n次方个
- 输出的高位部分,一半是0一半是1(二进制,不是0就是1)
- 输出的低位部分,一半是前一个的输出,一半是前一个输出的对称。
/**
* @param {number} n
* @return {number[]}
*/
var grayCode = function(n) {
//递归函数
let make = (n) => {
if(n === 1) {
return ['0', '1'];
} else {
let result = [];
let preResult = make(n-1);
let max = Math.pow(2,n)-1;
for(let i=0,len=preResult.length;i<len;i++) {
result[i]= `0${preResult[i]}`
result[max-i] = `1${preResult[i]}`;
}
// console.log(result)
return result
}
}
if(n===0) {
return ['0'];
} else {
return make(n).map(value => parseInt(value,2));
}
};