当前位置:  首页>> 技术小册>> 数据结构与算法(中)

难度:
Hard

题意:

  1. 给定一个数组A
  2. 求A的所有连续子数组中,和大于K,最短的一个,长度是多少

思路:

  • 如果A里面都是正整数,那就是一个妥妥的移动区间,可惜有负数
  • 换种思路,A[i…j] = sum[1..j] - sum[1..i-1],我们可以从前往后累加,当累加到j时,我们只需要把前面sum[1..1]到sum[1..j-1]的累加值中寻找一个最大的i,使得sum[1..j] - sum[1..i-1] >= K,复杂度是o(n^2),是不可接受的
  • 注意到,当我们累加到j,如果存在一个i,使得sum[1..i] <= sum[1..i + 1],那么,假设sum[1..j] - sum[1..i] >= K,那么sum[1..j] - sum[1..i + 1] 肯定也是 >= K,所以当sum[1..i + 1]存在时,sum[1..i] 没有存在的意义。因此,我们只需要维护一个单调递增队列sum[1..a1],sum[1..a2],…sum[1..am],其中sum[1..a1] < sum[1..a2] < …. ,a1 < a2 < …
  • 有了单调递增队列,当遍历到j,我们只需要二分这个队列,就可以找到一个最大的i,使的sum[1..j]-sum[1..i] >= K,插入一个数到单调队列,只需要从队列尾开始比较,把比要插入的数大的都出列,维持单调递增特性
  • 复杂度是o(nlogn)

代码:

  1. class Solution {
  2. private class Node {
  3. int sum;
  4. int pos;
  5. public Node(int sum, int pos) {
  6. this.sum = sum;
  7. this.pos = pos;
  8. }
  9. }
  10. private int find(Node[] incrQueue, int n, int value) {
  11. int left = -1;
  12. int right = n;
  13. while (right - left > 1) {
  14. int mid = (left + right) / 2;
  15. if (incrQueue[mid].sum <= value) {
  16. left = mid;
  17. } else {
  18. right = mid;
  19. }
  20. }
  21. return left;
  22. }
  23. public int shortestSubarray(int[] A, int K) {
  24. Node[] incrQueue = new Node[A.length + 1];
  25. int n = 0;
  26. int ret = 0x3fffffff;
  27. incrQueue[n++] = new Node(0, -1);
  28. int sum = 0;
  29. for (int i = 0;i < A.length;i++) {
  30. sum += A[i];
  31. int pos = find(incrQueue, n, sum - K);
  32. if (pos != -1) {
  33. ret = ret < (i - incrQueue[pos].pos) ? ret : (i - incrQueue[pos].pos);
  34. }
  35. while (n != 0 && incrQueue[n - 1].sum > sum) {
  36. n--;
  37. }
  38. incrQueue[n++] = new Node(sum, i);
  39. }
  40. if (ret == 0x3fffffff) {
  41. return -1;
  42. } else {
  43. return ret;
  44. }
  45. }
  46. }

该分类下的相关小册推荐: