一、题目描述
给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。
返回仅包含 1 的最长(连续)子数组的长度。
示例 1:
输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:
[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:
1 <= A.length <= 20000
0 <= K <= A.length
A[i] 为 0 或 1
二、思路分析
这道题目中,需要将K个0翻转到1,求最长子序列,这个描述大家都看的懂,不过转化成算法应该就比较懵了。这里我们可以换一种思路,不要关注1的数量,而是先明确最终数组中最终0的数量为K,算是确定的,在这个基础上求最长子序列的问题,也就是求包含K个0的最长子序列的长度。
到这一步该题也就转化为了常规的双指针算法,首先定义两个指针(start,end)都从数组第一位开始移动,每次移动判断两个指针之间的数组包含0的个数是否大于K,如果大于K那么start指针需要右移。此外如果当次循环start指针没有右移,意味着可能出现最长子路径,最长子路径等于两个指针之间的距离与之前记录的路径长度中较大的那一个。
来人,上代码~
var longestOnes = function (A, K) {
let start = 0
let end = 0
let max = 0
for (let i = 0; i < A.length; i++) {
let currentArray = A.filter((v, inx) => inx >= start && inx <= end)
console.log(currentArray)
let len = currentArray.filter(v => v === 0).length
if (len > K) {
start++
} else {
max = currentArray.length > max ? currentArray.length : max
max = max > A.length ? A.length : max
}
end++
}
return max
}
IDE中执行结果和预期的一致,美滋滋的点了提交发现超时了不通过,果然以上代码仍需优化~
三、优化
首先分析一波算法复杂度,空间复杂度为O(n),因为在循环中创建了对象;时间复杂度O(N^2/2),应该都有优化的空间
首先考虑如何把循环中获取额外数组这一步考虑优化掉,时间复杂度就能变为O(N)。
我们在循环中获取currentArray主要是两个作用,第一是拿到两个指针之间0的个数,这一点我们可以在循环之外引用一个变量count,每次end指针右移时判断移动到的这一位是0还是1,如果为0那么要判断当前子序列中包含的0是否超过K,超过就需要将start指针右移,一直移动直到子序列中包含0的数量等于K。
第二是获取两个指针之间的距离(也就是当前子序列的长度),这个比较好优化,直接可以用end-start+1得到,注意这里加一是因为当两个指针指向同一个元素时,此时的子序列长度就是1。
另外我们发现end指针实际与for循环的索引一致,所以将循环的索引直接命名为end。
此时空间复杂度为O(1),变量为常数个,时间复杂度为O(n)
上代码~
var longestOnes = function (A, K) {
let start = 0
let max = 0
let count = 0
for (let end = 0; end < A.length; end++) {
// console.log(A, start, end, count, A.filter((v, i) => i >= start && i <= end))
if (!A[end]) {
count++
if (count > K) {
do {
start++
} while (A[start - 1])
count--
}
}
max = Math.max(max, end - start + 1)
}
return max
}
四、踩坑记录
本题的测试用例一共48个,其中包含大量数据的校验(最长的数组长度为12500),就因为我在循环中加了一句log,导致提示我超出输入限制,还一直在考虑优化。。。各位同学小心
END
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情