LC 1139. the kth subarray
这题目我是在 https://www.lintcode.com/contest/85/ 看到的,其实上一道题目也是相同的问题:
- https://www.lintcode.com/problem/the-kth-subarray/description
- https://www.lintcode.com/problem/beautiful-subarrays/description
思路都是遍历+二分搜索,但其实都是可以转换成为双指针遍历。第二题 "beautiful-subarrays" 如果使用遍历+二分还没有什么问题, 但是第一题因为外层还有一个二分,所以内层如果继续使用遍历+二分就会出现超时(我也不确定,因为我用python版本内层是遍历依然超时)。
两道题目的思路是相近的,先说说第二题 "beautiful-subarrays". 我最开始的思路是
- 创建odd前缀数组,然后遍历这个前缀数组,假设当前点的值是 k
- 那么分别找到第一个 (k-numOdds+1) 的位置p1, 以及第一个 (k-numOdds) 的位置p2
- 那么对于当前值,有(p1-p2)个点,以这些点为起始到当前点,odd是满足要求的
def BeautifulSubarrays(self, nums, numOdds): # write your code here tmp = [] odd = 0 for x in nums: if x % 2 == 1: odd += 1 tmp.append(odd) def find(x, end): if x == 0: return -1 s, e = 0, end while s <= e: m = (s + e) // 2 if tmp[m] >= x: e = m - 1 else: s = m + 1 p = s return p ans = 0 # print(tmp) for i in range(len(tmp)): k = tmp[i] if k < numOdds: continue exp1 = k - numOdds + 1 p1 = find(exp1, i) if p1 > i: continue exp2 = k - numOdds p2 = find(exp2, i) if p2 > i: continue # print(i, k, p2, p1) res = p1 - p2 ans += res return ans
不过其实细想就会发现,没有必要每次都从头开始二分,因为二分数组都是非递减的。一旦我们保留合适的二分起始位置,那么就可以比较容易地变为双指针遍历。
def BeautifulSubarrays(self, nums, numOdds): # write your code here odd = 0 i, j = 0, -1 while i < len(nums): odd += nums[i] % 2 if odd == numOdds: break i += 1 ans = 0 while i < len(nums): k = j + 1 while nums[k] % 2 == 0: k += 1 kk = i + 1 while kk < len(nums) and nums[kk] % 2 == 0: kk += 1 print(i, kk, j, k) ans += (kk - i) * (k - j) # 从[j..k-1] [i, kk-1] 这些搭配都是满足odd == numOdds i = kk j = k return ans
然后再说回第一题,这题最外层是一个二分,判断值 `value` 在所有子数组和的rank是多少,也就是说有多少值是 `<=value` 的。 我们也可以对数组先计算前缀树,然后遍历所有点x,判断以这些点为结尾,最开始点是在什么位置y它们之间的和是 `<=value` 的, 那么就有(x-y+1)个子数组的和 `<=value`.
如果使用遍历+二分的话,那么代码如下
def test(v): res = 0 for i in range(len(tmp)): s, e = i+1, len(tmp) - 1 while s <= e: m = (s + e) // 2 if (tmp[m] - tmp[i]) > v: e = m - 1 else: s = m + 1 # print('>>>', v, i, e) sz = (e - i) res += sz return res
如果延续上面的思路,其实我们可以变为双指针遍历的,代码如下
def test(v): res = 0 j = 0 for i in range(len(tmp)): while j < len(tmp) and (tmp[j] - tmp[i]) <= v: j += 1 j -= 1 sz = (j - i) res += sz return res
最后不知道为什么python代码在lintcode上运行很慢,一直TLE(10s)左右,但是我抄写成为java之后1s就完成了。
// https://www.lintcode.com/problem/the-kth-subarray/description public class Solution { /** * @param a: an array * @param k: the kth * @return: return the kth subarray */ public long find_rank(long[] tmp, long value) { long res = 0; int j = 0; for (int i = 0; i < tmp.length; i++) { while ((j < tmp.length) && ((tmp[j] - tmp[i]) <= value)) { j += 1; } j -= 1; res += (j - i); } return res; } public long thekthSubarray(int[] a, long k) { // wrrite your code here long[] tmp = new long[a.length + 1]; long amin = 0, asum = 0; for (int i = 0; i < a.length; i++) { tmp[i + 1] = tmp[i] + a[i]; amin = Math.min(amin, a[i]); asum += a[i]; } long s = amin, e = asum; while (s <= e) { long m = (e - s) / 2 + s; long rank = find_rank(tmp, m); if (rank >= k) { e = m - 1; } else { s = m + 1; } } return s; } }