Google interview question:一个二维数组,有两个方法,一个是update(x,y),更新一个cell的值,一个是query(x1,y1,x2,y2),查询(x1,y1,x2,y2)矩形内所有元素的和。
Senario 1. update调用次数远大于query。
Senario 2. query调用次数远大于update。
Senario 3. 两种方法调用一样多。
对于senario 1,只需要用一个二维数组储存数据,update相应的cell,时间复杂度O(1),query计算范围内所有元素的和,时间复杂度O(N)。
对于senario 2,可以建立一个四维的数组,储存每一个submatrix元素的和,时间复杂度O(N^2),update对应的cell会更新所有包含这个cell的submatrix,时间复杂度为O(N^2),query直接读取相应的submatrix,时间复杂度为O(1)。
对于senario 3,我们需要平衡查询与更新的复杂度。2-D segment tree是一个理想的数据结构来解决RSQ(range sum query), RMQ(range mininum query)这类问题。
在实现2-D segment tree之前,首先需要了解segment tree的原理与实现。介绍segment tree的文章已经有很多。
https://www.youtube.com/watch?v=ZBHKZF5w4YU
这个视频介绍了segment tree,用数组实现,十分简洁易懂。
public class SegmentTree { public static void main(String arcg[]){ int[] input = {-1,0,3,6,2}; int[] segmentTree = new int[nextPowerOf2(input.length)*2-1]; Arrays.fill(segmentTree, Integer.MAX_VALUE); constructSegmentTreeForRangeSumQuery(input, segmentTree, 0, input.length-1, 0); System.out.println(Arrays.toString(segmentTree)); System.out.println(rangeSumQuery(segmentTree, 1, 3, 0, input.length-1, 0)); updateSegmentTreeForRangeSumQuery(segmentTree, 2, 1, 0, input.length-1, 0); System.out.println(Arrays.toString(segmentTree)); System.out.println(rangeSumQuery(segmentTree, 1, 3, 0, input.length-1, 0)); } public static int nextPowerOf2(int n){ n--; n |= n>>1; n |= n>>2; n |= n>>4; n |= n>>8; n |= n>>16; return ++n; } public static void constructSegmentTreeForRangeMinQuery(int[] input, int[] segmentTree, int low, int high, int pos){ if(low == high){ segmentTree[pos] = input[low]; return; } int mid = low+(high-low)/2; constructSegmentTreeForRangeMinQuery(input, segmentTree, low, mid, 2*pos+1); constructSegmentTreeForRangeMinQuery(input, segmentTree, mid+1, high, 2*pos+2); segmentTree[pos] = Math.min(segmentTree[2*pos+1], segmentTree[2*pos+2]); } public static void constructSegmentTreeForRangeSumQuery(int[] input, int[] segmentTree, int low, int high, int pos){ if(low == high){ segmentTree[pos] = input[low]; return; } int mid = low+(high-low)/2; constructSegmentTreeForRangeSumQuery(input, segmentTree, low, mid, 2*pos+1); constructSegmentTreeForRangeSumQuery(input, segmentTree, mid+1, high, 2*pos+2); segmentTree[pos] = segmentTree[2*pos+1]+segmentTree[2*pos+2]; } public static int rangeMinQuery(int[] segmentTree, int qlow, int qhigh, int low, int high, int pos){ if(qlow<=low && qhigh>=high) return segmentTree[pos]; if(qlow > high || qhigh < low) return Integer.MAX_VALUE; int mid = low+(high-low)/2; return Math.min(rangeMinQuery(segmentTree, qlow, qhigh, low, mid, 2*pos+1), rangeMinQuery(segmentTree, qlow, qhigh, mid+1, high, 2*pos+2)); } public static int rangeSumQuery(int[] segmentTree, int qlow, int qhigh, int low, int high, int pos){ if(qlow<=low && qhigh>=high) return segmentTree[pos]; if(qlow > high || qhigh < low) return 0; int mid = low+(high-low)/2; return rangeSumQuery(segmentTree, qlow, qhigh, low, mid, 2*pos+1) + rangeSumQuery(segmentTree, qlow, qhigh, mid+1, high, 2*pos+2); } public static void updateSegmentTreeForRangeMinQuery(int[] segmentTree, int index, int newVal, int low, int high, int pos){ if(index<low || index>high) return; if(low == high){ segmentTree[pos] = newVal; return; } int mid = low+(high-low)/2; updateSegmentTreeForRangeMinQuery(segmentTree, index, newVal, low, mid, 2*pos+1); updateSegmentTreeForRangeMinQuery(segmentTree, index, newVal, mid+1, high, 2*pos+2); segmentTree[pos] = Math.min(segmentTree[2*pos+1], segmentTree[2*pos+2]); } public static void updateSegmentTreeForRangeSumQuery(int[] segmentTree, int index, int newVal, int low, int high, int pos){ if(index<low || index>high) return; if(low == high){ segmentTree[pos] = newVal; return; } int mid = low+(high-low)/2; updateSegmentTreeForRangeSumQuery(segmentTree, index, newVal, low, mid, 2*pos+1); updateSegmentTreeForRangeSumQuery(segmentTree, index, newVal, mid+1, high, 2*pos+2); segmentTree[pos] = segmentTree[2*pos+1] + segmentTree[2*pos+2]; } }
以上是对segment tree的实现,现在需要将其扩展到二维的情况。
2-D segment tree的原理和segment tree一样,只是从二叉树变成了四叉树,因此2-D segment tree也是quadtree,类似的还有octree,广泛运用于图形学中。
同样,2-D segment tree也可以方便的用数组实现。其实现的原理即segment tree的二维推广。
public class SegmentTree2D { public static void main(String arcg[]){ int[][] input = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}}; int[] segmentTree = new int[nextPowerOf4(input.length*input[0].length)*2]; Arrays.fill(segmentTree, Integer.MAX_VALUE); construct2DSegmentTree(input, segmentTree, 0, 0, input.length-1, input[0].length-1, 0); System.out.println(Arrays.toString(segmentTree)); System.out.println(rangeSumQuery(segmentTree, 1, 0, 3, 2, 0, 0, input.length-1, input[0].length-1, 0)); update2DSegmentTree(segmentTree, 2, 1, 8, 0, 0, input.length-1, input[0].length-1, 0); System.out.println(Arrays.toString(segmentTree)); } public static int nextPowerOf4(int n){ int res = 1; while(res<n) res <<= 1; return res; } public static void construct2DSegmentTree(int[][] input, int[] segmentTree, int lowx, int lowy, int highx, int highy, int pos){ if(lowx == highx && lowy == highy){ segmentTree[pos] = input[lowx][lowy]; return; } int midx = lowx+(highx-lowx)/2, midy = lowy+(highy-lowy)/2; construct2DSegmentTree(input, segmentTree, lowx, lowy, midx, midy, 4*pos+1); construct2DSegmentTree(input, segmentTree, lowx, midy+1, midx, highy, 4*pos+2); construct2DSegmentTree(input, segmentTree, midx+1, midy+1, highx, highy, 4*pos+3); construct2DSegmentTree(input, segmentTree, midx+1, lowy, highx, midy, 4*pos+4); segmentTree[pos] = segmentTree[4*pos+1]+segmentTree[4*pos+2]+segmentTree[4*pos+3]+segmentTree[4*pos+4]; } public static int rangeSumQuery(int[] segmentTree, int qlowx, int qlowy, int qhighx, int qhighy, int lowx, int lowy, int highx, int highy, int pos){ if(qlowx<=lowx && qlowy<=lowy && qhighx>=highx && qhighy>=highy) return segmentTree[pos]; if(qlowx>highx || qhighx<lowx || qlowy>highy || qhighy<lowy) return 0; int midx = lowx+(highx-lowx)/2, midy = lowy+(highy-lowy)/2; return rangeSumQuery(segmentTree, qlowx, qlowy, qhighx, qhighy, lowx, lowy, midx, midy, 4*pos+1) + rangeSumQuery(segmentTree, qlowx, qlowy, qhighx, qhighy, lowx, midy+1, midx, highy, 4*pos+2) + rangeSumQuery(segmentTree, qlowx, qlowy, qhighx, qhighy, midx+1, midy+1, highx, highy, 4*pos+3) + rangeSumQuery(segmentTree, qlowx, qlowy, qhighx, qhighy, midx+1, lowy, highx, midy, 4*pos+4); } public static void update2DSegmentTree(int[] segmentTree, int xindex, int yindex, int newVal, int lowx, int lowy, int highx, int highy, int pos){ if(xindex<lowx || xindex>highx || yindex<lowy || yindex>highy) return; if(lowx == highx && lowy == highy) { segmentTree[pos] = newVal; return; } int midx = lowx+(highx-lowx)/2, midy = lowy+(highy-lowy)/2; update2DSegmentTree(segmentTree, xindex, yindex, newVal, lowx, lowy, midx, midy, 4*pos+1); update2DSegmentTree(segmentTree, xindex, yindex, newVal, lowx, midy+1, midx, highy, 4*pos+2); update2DSegmentTree(segmentTree, xindex, yindex, newVal, midx+1, midy+1, highx, highy, 4*pos+3); update2DSegmentTree(segmentTree, xindex, yindex, newVal, midx+1, lowy, highx, midy, 4*pos+4); segmentTree[pos] = segmentTree[4*pos+1]+segmentTree[4*pos+2]+segmentTree[4*pos+3]+segmentTree[4*pos+4]; } }
运用2-D segment tree,对于senario 3,update的时间复杂度为O(log(N)),query的时间复杂度为O(log(N))。