有序数组能否分割为连续子序列的一种算法实现

1,467 阅读3分钟

近期为面试做准备,刷文一年半前端跳槽面试经验(头条、微信、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…