近期为面试做准备,刷文一年半前端跳槽面试经验(头条、微信、shopee)时,看到一道不错的算法题(leetcode),原题如下:
输入一个按升序排序的整数数组(可能包含重复数字),你需要将它们分割成几个子序列,其中每个子序列至少包含三个连续整数。返回你能否做出这样的分割
示例 1: 输入: [1,2,3,3,4,5] 输出: true 解释: 你可以分割出这样两个连续子序列 : 1, 2, 3 3, 4, 5示例 2: 输入: [1,2,3,3,4,4,5,5] 输出: true 解释: 你可以分割出这样两个连续子序列 : 1, 2, 3, 4, 5 3, 4, 5
示例 3: 输入: [1,2,3,4,4,5] 输出: false示例 4: 输入: [1,2,4,5,6,7] 输出: false
示例 5: 输入: [1,2,3,3,4,4,5,5,5,5,6,6,7,7] 输出: true
题注
- 连续的数组[1,2,3,4,5]返回为true.(分割为一个)
- 给出的数组可以不连续,如示例4
- 分割即每个数组元素只能在其中一个子序列里出现
想到了一个不错的实现方式,代码如下:
var isPossible = function(nums, min=3) {
const len = nums.length;
if (len < min) return false;
let list = [1];
let copy = 0;
let p = nums[0];
let ret = nums.slice(1).every((num,i, arr) => {
let ret = true;
if (num - p === 1) {
let current = 0;
while (num === arr[++i] && i < len-1) ++current;
if (current < copy) {
ret = list.splice(0, copy - current).every(num => num>=min);
}
list[0] = (list[0] || 0) + 1;
copy = 0;
} else if (num === p) {
++copy;
list[copy] = (list[copy] || 0) + 1
} else {
if (list.every(num => num >= min)) {
// less 1 than origin nums
ret = isPossible(nums.slice(i+1))
} else {
ret = false;
}
}
// console.log(num, JSON.stringify(list), copy);
p = num;
return ret;
})
if (!ret) return false;
return list.every(num => num >= min);
};
// test
const testData = [
[1,2,3,3,4,5,6,6,7,9], // false
[1,2,2,3,3,4,5,6,6,7,7,8], // true
[1,2,3,4,5,6,7,8,9], // true
[1,2,3,3,4,4,4,5,5,6,6,7,8], // true
[1,2,3,3,4,4,5,5,5,5,6,6,7,7,8,8,9], // true
[1,2,4,5,6,7,8], // false
[1,2,3,5,6,7], // true
[1,2,3,4,5,5,6] // false
]
testData.forEach(nums => {
console.log(isPossible(nums),JSON.stringify(nums));
console.log('--------')
})
算法实现解读
该算法的核心思想,从子序列的生成/结束规则着手:
- 初始生成一个子序列,然后遍历数组剩下的元素
nums.slice(1) - 如果数组元素一直是递增的,则加入至该序列
++list[0] - 如果数组元素是重复的,则需要新增一个子序列
list[++copy] = 1 - 如果当前的数组重复元素比之前的多,则需新增子序列
- 如果当前的数组重复元素比之前的少,初始的子序列需要结束相应数量
list.splice(0, num),并判断删除的是否是有效的every del list length >= min - 如果数组元素是不连续的,判断前面已生成的子序列,后续的重复上面的规则
- 子序列退出时,立即判断是否满足,不满足即可提前终止判断.
discuss: github.com/pagemarks/c…