当前位置:   article > 正文

C语言 | Leetcode C语言题解之第327题区间和的个数

C语言 | Leetcode C语言题解之第327题区间和的个数

题目:

题解:

  1. #define FATHER_NODE(i) (0 == (i) ? -1 : ((i) - 1 >> 1))
  2. #define LEFT_NODE(i) (((i) << 1) + 1)
  3. #define RIGHT_NODE(i) (((i) << 1) + 2)
  4. /* 优先队列(小根堆)。 */
  5. typedef struct
  6. {
  7. int *arr;
  8. int arrSize;
  9. }
  10. PriorityQueue;
  11. /* 二进制树(01字典树)。 */
  12. typedef struct
  13. {
  14. int *arr;
  15. int highestBit;
  16. }
  17. BinaryTree;
  18. /* 几个自定义函数的声明,具体实现见下。 */
  19. static void queuePush(PriorityQueue *queue, long long *prefix, int x);
  20. static void queuePop(PriorityQueue *queue, long long *prefix);
  21. static int treeHighestBit(int mostVal);
  22. static void treePush(BinaryTree *tree, int x);
  23. static void treePop(BinaryTree *tree, int x);
  24. static int treeSmaller(BinaryTree *tree, int x);
  25. /* 主函数:
  26. treeSize: 二进制树的数组空间大小,等于里面最大值的3倍,具体证明略,大致就是等比数列求和的结果。
  27. prefix[]: 其中prefix[x]表示[0, x]范围内子数组的前缀和。这里必须以long long类型存储。
  28. window[]: 里面存储prefix[]数组的下标,即prefix[window[x]]才真正表示一个前缀和。
  29. buff1[],buff2[]: 优先队列与二进制树各自使用的数组空间。其中buff2[]必须初始化为全0。
  30. queue: 优先队列(小根堆),为了不打乱prefix[]数组中的数值顺序,而且window[]数组中实际存放的是下标,所以借用堆排序。
  31. tree: 二进制树(01字典树)。 */
  32. int countRangeSum(int *nums, int numsSize, int lower, int upper)
  33. {
  34. const int treeSize = numsSize * 3;
  35. int x = 0, y = 0, z = 0, t = 0, result = 0;
  36. long long sum = 0;
  37. long long prefix[numsSize];
  38. int window[numsSize], buff1[numsSize], buff2[treeSize];
  39. PriorityQueue queue;
  40. BinaryTree tree;
  41. /* 初始化。 */
  42. queue.arr = buff1;
  43. queue.arrSize = 0;
  44. memset(buff2, 0, sizeof(buff2));
  45. tree.arr = buff2;
  46. tree.highestBit = treeHighestBit(numsSize - 1);
  47. /* 计算前缀和,并将前缀和的下标放到一个小根堆里面,小根堆里面以对应的前缀和为优先级。 */
  48. for(x = 0; numsSize > x; x++)
  49. {
  50. sum += nums[x];
  51. prefix[x] = sum;
  52. queuePush(&queue, prefix, x);
  53. /* 如果[0, x]的子数组和本来就在[lower, upper]之间,计数累计。 */
  54. if((long long)lower <= sum && (long long)upper >= sum)
  55. {
  56. result++;
  57. }
  58. }
  59. /* 将前缀和数组的下标,以对应的prefix值从小到大的顺序,放到window数组中。 */
  60. while(0 < queue.arrSize)
  61. {
  62. window[t] = queue.arr[0];
  63. t++;
  64. queuePop(&queue, prefix);
  65. }
  66. /* 开始以滑动窗口的形式移动窗口的左右指针。 */
  67. for(x = 0; numsSize > x; x++)
  68. {
  69. /* 将所有prefix[window[x]] - prefix[window[y]] >= lower的下标y都加入。 */
  70. while(numsSize > y && prefix[window[x]] - lower >= prefix[window[y]])
  71. {
  72. treePush(&tree, window[y]);
  73. y++;
  74. }
  75. /* 将所有prefix[window[x]] - prefix[window[z]] > upper的下标z都移除。 */
  76. while(numsSize > z && prefix[window[x]] - upper > prefix[window[z]])
  77. {
  78. treePop(&tree, window[z]);
  79. z++;
  80. }
  81. /* 将窗口内(树内)剩余的下标值中,小于window[x]的数量加到结果中。 */
  82. t = treeSmaller(&tree, window[x]);
  83. result += t;
  84. }
  85. return result;
  86. }
  87. /* 小根堆的push操作。由于堆中存储的是prefix[]数组的下标,所以入参需带上prefix。 */
  88. static void queuePush(PriorityQueue *queue, long long *prefix, int x)
  89. {
  90. int son = queue->arrSize, father = FATHER_NODE(son);
  91. /* 新加入数值之后,数量加一。 */
  92. queue->arrSize++;
  93. /* 根据对应prefix[]值的大小关系,调整父子节点的位置。 */
  94. while(-1 != father && prefix[x] < prefix[queue->arr[father]])
  95. {
  96. queue->arr[son] = queue->arr[father];
  97. son = father;
  98. father = FATHER_NODE(son);
  99. }
  100. /* 放到实际满足父子节点大小关系的位置。 */
  101. queue->arr[son] = x;
  102. return;
  103. }
  104. /* 小根堆的pop操作。由于堆中存储的是prefix[]数组的下标,所以入参需带上prefix。 */
  105. static void queuePop(PriorityQueue *queue, long long *prefix)
  106. {
  107. int father = 0, left = LEFT_NODE(father), right = RIGHT_NODE(father), son = 0;
  108. /* 堆顶pop之后,数量减一。默认将堆尾的数值补充上来填补空位。 */
  109. queue->arrSize--;
  110. /* 根据对应prefix[]值的大小关系,调整父子节点的位置。 */
  111. while((queue->arrSize > left && prefix[queue->arr[queue->arrSize]] > prefix[queue->arr[left]])
  112. || (queue->arrSize > right && prefix[queue->arr[queue->arrSize]] > prefix[queue->arr[right]]))
  113. {
  114. son = (queue->arrSize > right && prefix[queue->arr[left]] > prefix[queue->arr[right]]) ? right : left;
  115. queue->arr[father] = queue->arr[son];
  116. father = son;
  117. left = LEFT_NODE(father);
  118. right = RIGHT_NODE(father);
  119. }
  120. /* 放到实际满足父子节点大小关系的位置。 */
  121. queue->arr[father] = queue->arr[queue->arrSize];
  122. return;
  123. }
  124. /* 计算二进制树中,可能出现的最大的数值,所占据的最高二进制比特。
  125. 比如,最大值的二进制是101100(b),那么返回的最高比特是100000(b)。特殊的,最大是0的时候返回1(b)。 */
  126. static int treeHighestBit(int mostVal)
  127. {
  128. int x = 1;
  129. if(0 < mostVal)
  130. {
  131. while(0 < mostVal)
  132. {
  133. x++;
  134. mostVal = mostVal >> 1;
  135. }
  136. x = 1 << x - 2;
  137. }
  138. return x;
  139. }
  140. /* 往二进制树中push一个数值。 */
  141. static void treePush(BinaryTree *tree, int x)
  142. {
  143. int i = 0, bit = tree->highestBit;
  144. /* 从最高位到最低位的顺序,该位为1就给右分支加1,为0就给左分支加1。 */
  145. while(0 < bit)
  146. {
  147. i = (bit & x) ? RIGHT_NODE(i) : LEFT_NODE(i);
  148. tree->arr[i]++;
  149. bit = bit >> 1;
  150. }
  151. return;
  152. }
  153. /* 从二进制树中pop一个数值。 */
  154. static void treePop(BinaryTree *tree, int x)
  155. {
  156. int i = 0, bit = tree->highestBit;
  157. /* 从最高位到最低位的顺序,该位为1就给右分支减1,为0就给左分支减1。 */
  158. while(0 < bit)
  159. {
  160. i = (bit & x) ? RIGHT_NODE(i) : LEFT_NODE(i);
  161. tree->arr[i]--;
  162. bit = bit >> 1;
  163. }
  164. return;
  165. }
  166. /* 在二进制树中查找比输入数值小的数值数量。 */
  167. static int treeSmaller(BinaryTree *tree, int x)
  168. {
  169. int i = 0, bit = tree->highestBit, result = 0;
  170. /* 从高位到低位的顺序查看。 */
  171. while(0 < bit)
  172. {
  173. /* 该位为1,则肯定比高位相同,该位为0的数值更大,即把左分支的数量加到结果中。 */
  174. if(bit & x)
  175. {
  176. result += tree->arr[LEFT_NODE(i)];
  177. i = RIGHT_NODE(i);
  178. }
  179. /* 该位为0,就往左分支走,不做任何其它处理。 */
  180. else
  181. {
  182. i = LEFT_NODE(i);
  183. }
  184. bit = bit >> 1;
  185. }
  186. return result;
  187. }
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号