有人可以就下列问题给我一些想法吗?
给定长度为ar[]
的数组n
和一些查询,每个查询的格式为 a, b, c
,找到索引i
最小的数字,使索引位于[c, n]
范围内,a < ar[i] < b
。总共有(n - 1)
,c
从1
转到n - 1
。每个查询的预期复杂性应该是 O(log n)
,并且复杂性的预计算最多为 O(n log n)
是合适的。直观地说,片段树出现在我的脑海中,但我想不出建立它的方法,也不想在每个节点中保留什么。
答案 0 :(得分:4)
好的,我认为我应该实现与user12861讨论的O(nlogn)
/ O(logn)
解决方案。
代码的工作原理是构建n
树,每个c
一个。每棵树与较小的内容共享其大部分内容,最多有logn
个新节点。这样,预处理的总内存和时间成本仅限于O(nlogn)
。
实际的树与间隔树非常相似。节点存储它们跨越的最小值和最大值,以及它们包含的最小索引。然而,我的实现不是自我平衡的,但可以使用您最喜欢的启发式方法添加。
import sys
class Node:
pass
class Interval(Node):
def __init__(self,left,val,i,right):
self.i = i; self.val = val
self.left = left; self.right = right
self.mini = min(left.mini, i, right.mini)
self.min = min(left.min, val, right.min)
self.max = max(left.max, val, right.max)
def add(self,val,i):
# We do naive inserting here. In a real worst case logn solution,
# self-balancing would take place here.
if (val,i) < (self.val,self.i): return Interval(self.left.add(val,i), self.val, self.i, self.right)
if (self.val,self.i) < (val,i): return Interval(self.left, self.val, self.i, self.right.add(val,i))
return self
def query(self,a,b):
# If the entire interval is covered, return mini, this is what keeps us
# at max 2 opened nodes per level in the tree.
if a <= self.min and self.max <= b: return self.mini
# Otherwise compose an answer from the subtrees.
res = sys.maxint
if a <= self.val <= b: res = min(res, self.i)
if self.left.min <= b and self.left.max >= a: res = min(res, self.left.query(a,b))
if self.right.min <= b and self.right.max >= a: res = min(res, self.right.query(a,b))
return res
class Tip(Node):
def __init__(self):
# This is a typical null-pattern idiom. The self.values are set to
# the min/max identities to ensure to interference with the upwards
# value propagation.
self.mini = sys.maxint
self.min = sys.maxint
self.max = -sys.maxint
def add(self,val,i):
return Interval(Tip(), val, i, Tip())
def query(self,a,b):
return sys.maxint
# The input data array
data = [0, 3, 1, 2, 0, 4, 9, 6, 1, 7]
N = len(data)
# Build the array of trees. O(nlogn)
ar = [None]*N + [Tip()]
for i in range(N-1, -1, -1):
ar[i] = ar[i+1].add(data[i],i)
# The query function. O(logn)
def query(a,b,c):
return ar[c].query(a,b)
# Test
print query(2, 7, 4) # = 5
答案 1 :(得分:1)
你经历过this?我想它会帮助你理解结构并想出构建它的方法。可以对比进行比较。
答案 2 :(得分:1)
我做的第一次尝试是从范围构建树。
您首先会找到包含查询所有解决方案的子树的根。这很简单。但是:该子树包含解决方案集之外的一些值。
因此,要找到最小的数字,您将遍历树两次(从树的左侧向下,再从右侧的根向下)。在这些遍历中的每个点,您都会检查是否找到了比先前解决方案更低的解决方案。
完成后,该集合中的最低解决方案将是最低的。
由于检查每个节点的解决方案需要在该子树内的索引列表上进行二进制搜索,因此最终会在O(ln(n)^ 2)上运行,这太慢了。
这方面的改进来自这样一个事实:如果我们一直在寻找数组中的第一个元素,这将很容易。那么你不需要进行二分搜索,只需要获得第一个元素。
要到达那个地方,你最终会建造n棵树。
tree[0] has all values in the initializaing array
tree[1] has all but the first.
tree[2] has all but the first and second.
因此,每个树只包含唯一的最佳解决方案。
所以搜索在O(ln(n))中运行。
在示例代码中,通过不实现自定义数据结构来构建树。
构建节点数组,制作该数组的副本并对其中一个副本进行排序。这在O(n ln(n))中运行。另一个副本用于查找已排序数组中的位置,以便每次构建另一个树时都可以删除元素。
当然可以在常量时间内删除链表中的节点,但由于您只有对该对象的引用,我怀疑java实现必须在链表中搜索要删除的项,所以在这个例子中构建树可能需要O(n ^ 2)。虽然容易修复。
public class ArraySearcher {
public class SegmentNode implements Comparable<SegmentNode> {
int min;
int max;
SegmentNode root;
SegmentNode left;
SegmentNode right;
int lowIndex;
public int compareTo(final SegmentNode obj)
{
return this.min - obj.min;
}
public boolean contains(final int n)
{
if ((n > this.min) && (n < this.max)) {
return true;
} else {
return false;
}
}
/**
* Given the root of a tree, this method will find the center node. The
* center node is defined as the first node in the tree who's left and
* right nodes either contain solutions or are null.
*
* If no node can be found which contains solution, this will return
* null.
*
* @param a
* @param b
* @return
*/
public SegmentNode findCenter(final int a, final int b)
{
SegmentNode currentNode = this;
while (true) {
/*
* first check to see if no solution can be found in this node.
* There is no solution if both nodes are
*/
if ((a < currentNode.min && b < currentNode.min)
|| (a > currentNode.max && b > currentNode.max)) {
return null;
}
/*
* Now check to see if this is the center.
*/
if (((currentNode.left == null) || (a < currentNode.left.max))
&& ((currentNode.right == null) || (b > currentNode.right.min))) {
// this is the center, return it.
return currentNode;
} else if ((currentNode.left == null)
|| (a < currentNode.left.max)) {
// no solution on one side, is it in the left?
currentNode = currentNode.left;
} else {
currentNode = currentNode.right;
}
}
}
public SegmentNode findLeft(final int seekMin)
{
/*
* keep going left until
*/
if ((this.min > seekMin)) {
return this;
} else {
if (this.right == null) {
return null;
}
return this.right.findLeft(seekMin);
}
}
/**
* This method can be called on the center node. it traverses left
*
* @param a
* @param b
* @return
*/
public Integer findLeftmostLowestIndex(final int n)
{
if (null == this.left) {
return null;
}
int lowest = Integer.MAX_VALUE;
SegmentNode current = this.left;
// update the lowest with the right node if it is greater
// than the current node.
while (true) {
// collect the best value from the right and/or left
// if they are greater than N
if ((null != current.left) && (current.left.min > n)) {
lowest = current.left.lowIndex(lowest);
}
if ((null != current.right) && (current.right.min > n)) {
lowest = current.right.lowIndex(lowest);
}
if ((null == current.left) && (null == current.right)
&& (current.max > n)) {
lowest = current.lowIndex(lowest);
}
// quit when we've gone as far left as we can
int seek = current.leftSeek(n);
if (seek == 0) {
break;
} else if (seek < 0) {
current = current.left;
} else {
current = current.right;
}
}
if (lowest == Integer.MAX_VALUE) {
return null;
} else {
return new Integer(lowest);
}
}
public SegmentNode findMatch(final int seekMin, final int seekMax)
{
if ((this.min > seekMin) && (this.max < seekMax)) {
return this;
} else if ((this.min > seekMin) && (this.left != null)) {
return this.left.findMatch(seekMin, seekMax);
} else {
if (this.right == null) {
return null;
}
return this.right.findMatch(seekMin, seekMax);
}
}
public SegmentNode findMatchRight(final int seekMin, final int seekMax)
{
if ((this.min > seekMin) && (this.max < seekMax)) {
return this;
} else if (this.max < seekMax) {
if (this.right == null) {
return null;
}
return this.right.findMatchRight(seekMin, seekMax);
} else {
if (this.left == null) {
return null;
}
return this.left.findMatchRight(seekMin, seekMax);
}
}
/**
* Search for the first number in the tree which is lower than n.
*
* @param n
* @return
*/
public Integer findRightmostLowestIndex(final int n)
{
if (null == this.left) {
return null;
}
int lowest = Integer.MAX_VALUE;
SegmentNode current = this.right;
// update the lowest with the right node if it is greater
// than the current node.
while (true) {
// collect the best value from the right and/or left //this.max
// < b
// if they are greater than N
if ((null != current.left) && (current.left.max < n)) {
lowest = current.left.lowIndex(lowest);
}
if ((null != current.right) && (current.right.max < n)) {
lowest = current.right.lowIndex(lowest);
}
if ((null == current.left) && (null == current.right)
&& (current.min < n)) {
lowest = current.lowIndex(lowest);
}
// quit when we've gone as far left as we can
int seek = current.rightSeek(n);
if (seek == 0) {
break;
} else if (seek < 0) {
current = current.left;
} else {
current = current.right;
}
}
if (lowest == Integer.MAX_VALUE) {
return null;
} else {
return new Integer(lowest);
}
}
/**
*
* @param seekMin
* @param seekMax
* @return
*/
public SegmentNode findSegmentRoot(final int seekMin, final int seekMax)
{
return null;
}
public int leftSeek(final int n)
{
if ((null == this.left) && (null == this.right)) {
return 0;
// } else if ((null != this.left) && (this.left.max > n)) {
} else if ((null != this.left) && ((n < this.left.max))
|| (this.left.contains(n))) {
return -1;
// } else if ((null != this.right) && (this.right.min <= n)) {
} else if ((null != this.right) && ((n >= this.right.min))) {
return +1;
} else {
return 0;
}
}
public int rightSeek(final int n)
{
if ((null == this.left) && (null == this.right)) {
return 0;
} else if ((null != this.left) && (this.left.max >= n)) {
return -1;
} else if ((null != this.right) && (this.right.min < n)) {
return +1;
} else {
return 0;
}
}
@Override
public String toString()
{
StringBuilder value = new StringBuilder();
if (null != this.left) {
value.append("{ { " + this.left.min + ", " + this.left.max
+ "} }, ");
} else {
value.append("{ " + this.min + " }, ");
}
if (null != this.right) {
value.append("{ { " + this.right.min + ", " + this.right.max
+ "} }");
} else {
value.append("{ " + this.max + " }, ");
}
return value.toString();
}
private int lowIndex(final int lowest)
{
if (lowest < this.lowIndex) {
return lowest;
} else {
return this.lowIndex;
}
}
}
public static int bruteForceSearch(final int[] array, final int a,
final int b, final int c)
{
// search from c onward
/**
* search for the first value of the array that falls between a and b
* ignore everything before index of c
*/
for (int i = c; i < array.length; i++) {
if ((a < array[i]) && (array[i] < b)) {
return i;
}
}
return -1;
}
SegmentNode[] trees;
public ArraySearcher(final int[] array)
{
buildTree(array);
}
public void buildTree(final int[] array)
{
ArrayList<SegmentNode> mNodes = new ArrayList<SegmentNode>();
for (int i = 0; i < array.length; i++) {
SegmentNode mCurrentNode = new SegmentNode();
mCurrentNode.lowIndex = i;
mCurrentNode.min = array[i];
mCurrentNode.max = array[i];
mNodes.add(mCurrentNode);
}
ArrayList<SegmentNode> unsortedClone =
new ArrayList<SegmentNode>(mNodes);
// n (ln (n) )
Collections.sort(mNodes);
LinkedList<SegmentNode> nodesList = new LinkedList<SegmentNode>(mNodes);
this.trees = new SegmentNode[nodesList.size()];
for (int i = 0; i < this.trees.length; i++) {
this.trees[i] = merge(nodesList, 0, nodesList.size() - 1);
// we remove the ith one at each iteration
nodesList.remove(unsortedClone.get(i));
}
}
/**
*
* @param nodes
* @param i
* @param j
* @return
*/
public SegmentNode merge(final List<SegmentNode> nodes, final int i,
final int j)
{
if (i > j) {
throw new AssertionError();
}
SegmentNode left;
SegmentNode right;
int count = j - i;
if (count == 1) {
SegmentNode mParent = merge(nodes.get(i), nodes.get(i + 1));
return mParent;
} else if (count == 0) {
return nodes.get(i);
} else {
int mid = (count / 2) + i;
left = merge(nodes, i, mid);
right = merge(nodes, mid + 1, j);
}
return merge(left, right);
}
/**
* Build a parent segment from two other segments.
*
* @param a
* @param b
* @return
*/
public SegmentNode merge(final SegmentNode a, final SegmentNode b)
{
SegmentNode parent = new SegmentNode();
parent.root = parent;
parent.min = a.min;
parent.left = a;
parent.max = b.max;
parent.right = b;
if (a.lowIndex > b.lowIndex) {
parent.lowIndex = b.lowIndex;
b.root = parent;
} else {
parent.lowIndex = a.lowIndex;
a.root = parent;
}
return parent;
}
/**
* The work horse, find all the points with indexes greater than c that lie
* between a and b.
*
* @param a
* @param b
* @param c
* @return
*/
public Integer search(final int a, final int b, final int c)
{
if (c < this.trees.length) {
SegmentNode root = this.trees[c];
if ((a > root.max) || (b < root.min)) {
return null;
}
SegmentNode center = root.findCenter(a, b);
if (null == center) {
return null;
}
// special case to deal with a node with no children.
if ((center.left == null) && (center.right == null)) {
if ((a < center.min) && (b > center.max)) {
return new Integer(center.lowIndex);
} else {
return null;
}
}
Integer right = center.findRightmostLowestIndex(b);
Integer left = center.findLeftmostLowestIndex(a);
if ((null == right) && (null == left)) {
return null;
} else if (null == right) {
return left;
} else if (null == left) {
return right;
} else if (right.compareTo(left) > 0) {
return left;
} else {
return right;
}
} else {
return null;
}
}
}
顺便说一下,我还在使用这样的测试来验证它与强力方法的对比:
static void testBoth(final int[] array, final ArraySearcher searcher,
final int a, final int b, final int c)
{
System.out.println("ArraySearcherTest.testBoth(array, mSearcher, " + a
+ ", " + b + ", " + c + ");");
int expected = ArraySearcher.bruteForceSearch(array, a, b, c);
Integer calcObj = searcher.search(a, b, c);
int calcInt = -1;
if (null != calcObj) {
calcInt = calcObj.intValue();
}
assertEquals(expected, calcInt);
}
@Test
public void randomizedProblemTester()
{
for (int i = 0; i < 100; i++) {
// build arrays from 5 to 20 elements long
int[] array = new int[TestUtils.randomInt(5, 20)];
System.out.print("int[] array = {");
for (int j = 0; j < array.length; j++) {
// with values from 0 to 100
array[j] = TestUtils.randomInt(0, 100);
System.out.print(array[j] + ", ");
}
System.out.println("};");
ArraySearcher mSearcher = new ArraySearcher(array);
for (int j = 0; j < array.length; j++) {
int a = TestUtils.randomInt(0, 100);
int b = TestUtils.randomInt(a, 100);
int c = TestUtils.randomInt(0, 20);
ArraySearcherTest.testBoth(array, mSearcher, a, b, c);
}
}
}
答案 3 :(得分:1)
我已经将梅森布莱恩特的技术修改为有效的方法。问题是FindLowestIndex中的更多错误以及搜索树的更大错误(它可以返回多个结果)。
尽管做了这项工作,它仍然没有真正解决问题。 O(n log n)
时间设置很简单,但使用这种技术我只能获得O((log n)^2)
查询时间。我想知道如果有更多的说明,你是否有原始问题的链接?或者我想知道问题是否真的可以解决。或者O((log n)^2)
可能是问题请求的“约”O(log n)
,无论如何都不到O(n)
。
该技术是将我们的数组存储在典型的段树中,但是除了通常的段信息之外,我们还按顺序存储每个节点下的元素的所有索引。这个额外的存储只需要额外的O(n log n)
时间/空间,如果你正确添加它(n个项目存储在每个log n级别),所以它不会以任何方式影响我们的设置时间。然后我们查询树以找到我们的(a,b)范围所包含的最小节点集。此查询与典型的分段树查询(O(log n)
)大致相同,并且最多可找到大约2 * log n个匹配的分段。在我们查询时,我们在每个匹配段中找到与我们的约束c匹配的最低索引。我们可以使用二进制搜索来查找此索引,因为我们按顺序保存索引,因此每个匹配节点需要O(log n)
时间最坏的情况。当我们恰当地添加它时,总时间为O((log n)^2)
。
让我知道您要澄清哪些步骤。
C#代码:
void Test() {
DateTime start;
TimeSpan at = new TimeSpan(), bt = new TimeSpan();
Random rg = new Random();
for(int i = 0; i < 20; i++) {
// build arrays from 5 to 10000 elements long, random values between 0 and 100
// Break even time for queries is around 10000 elements in array for the values and queries used
int[] array = (from n in Enumerable.Range(1, rg.Next(5, 10000)) select rg.Next(0, 100)).ToArray<int>();
// Setup should be O(n log n) time/space
ArraySearcher Searcher = new ArraySearcher(array);
// Test each array a number of times equal to its length, with random values for a, b, c
for(int j = 0; j < array.Length; j++) {
int a = rg.Next(-1, 101), b = rg.Next(a, 102), c = rg.Next(0, array.Length);
start = DateTime.Now;
int expected = BruteSearch(array, a, b, c);
at += DateTime.Now - start;
// Search should be O(log n) time, but in reality is O((log n)^2) :(
start = DateTime.Now;
int got = Searcher.QuickSearch(a, b, c);
bt += DateTime.Now - start;
System.Diagnostics.Debug.Assert(got == expected);
}
}
MessageBox.Show(at.ToString() + ", " + bt.ToString());
}
int BruteSearch(int[] array, int a, int b, int c) {
for(int i = c; i < array.Length; i++)
if(a < array[i] && array[i] < b)
return i;
return -1;
}
class ArraySearcher {
SegmentNode Root;
List<SegmentNode> Nodes;
public ArraySearcher(int[] array) {
Nodes = array.Select((value, index) => new SegmentNode(value, value, new List<int> { index }, null, null)).ToList<SegmentNode>();
// Sorting will take us O(n log n)
Nodes.Sort();
// Creating a normal segment tree takes O(n log n)
// In addition, in this tree each node stores all the indices below it in order
// There are a total of n of these indices at each tree level, kept in order as we go at no extra time cost
// There are log n levels to the tree
// So O(n log n) extra time and space is spent making our lists, which doesn't hurt our big O notation compared to just sorting
this.Root = SegmentNode.Merge(Nodes, 0, Nodes.Count - 1);
}
public int QuickSearch(int a, int b, int c) {
return this.Root.FindLowestIndex(a, b, c);
}
class SegmentNode : IComparable {
public int Min, Max;
public List<int> Indices;
public SegmentNode Left, Right;
public SegmentNode(int Min, int Max, List<int> Indices, SegmentNode Left, SegmentNode Right) {
this.Min = Min;
this.Max = Max;
this.Indices = Indices;
this.Left = Left;
this.Right = Right;
}
int IComparable.CompareTo(object other) {
return this.Min - ((SegmentNode)other).Min;
}
public static SegmentNode Merge(List<SegmentNode> Nodes, int Start, int End) {
int Count = End - Start;
if(Start == End)
return Nodes[Start];
if(End - Start == 1)
return SegmentNode.Merge(Nodes[Start], Nodes[End]);
return SegmentNode.Merge(SegmentNode.Merge(Nodes, Start, Start + Count/2), SegmentNode.Merge(Nodes, Start + Count/2 + 1, End));
}
public static SegmentNode Merge(SegmentNode Left, SegmentNode Right) {
int LeftCounter = 0, RightCounter = 0;
List<int> NewIndices = new List<int>();
while(LeftCounter < Left.Indices.Count || RightCounter < Right.Indices.Count) {
if(LeftCounter < Left.Indices.Count && (RightCounter == Right.Indices.Count || Left.Indices[LeftCounter] < Right.Indices[RightCounter]))
NewIndices.Add(Left.Indices[LeftCounter++]);
else
NewIndices.Add(Right.Indices[RightCounter++]);
}
return new SegmentNode(Left.Min, Right.Max, NewIndices, Left, Right);
}
public int FindLowestIndex(int SeekMin, int SeekMax, int c) {
// This will find at most O(log n) matching segments, always less than 2 from each level of the tree
// Each matching segment is binary searched in at worst O(log n) time
// Total does indeed add up to O((log n)^2) if you do it right
if(SeekMin < this.Min && SeekMax > this.Max)
return FindLowestIndex(this.Indices, c);
if(SeekMax <= this.Min || SeekMin >= this.Max)
return -1;
int LeftMatch = this.Left.FindLowestIndex(SeekMin, SeekMax, c);
int RightMatch = this.Right.FindLowestIndex(SeekMin, SeekMax, c);
if(LeftMatch == -1)
return RightMatch;
if(RightMatch == -1)
return LeftMatch;
return LeftMatch < RightMatch ? LeftMatch : RightMatch;
}
int FindLowestIndex(List<int> Indices, int c) {
int left = 0, right = Indices.Count - 1, mid = left + (right - left) / 2;
while(left <= right) {
if(Indices[mid] == c)
return c;
if(Indices[mid] > c)
right = mid - 1;
else
left = mid + 1;
mid = left + (right - left) / 2;
}
if(mid >= Indices.Count)
return -1;
// no exact match, so return the next highest.
return Indices[mid];
}
}
}
答案 4 :(得分:0)
也许我错过了什么。这不符合您的要求吗?
for (int i = c; i < n; i++)
{
if (a < ar[i] && ar[i] < b)
return i;
}
答案 5 :(得分:0)
构建索引数组并对其进行排序。也就是说,给定数组[20, 0, 10, 15, 5]
,您将创建一个初始数组[0, 1, 2, 3, 4]
。现在,您对索引数组进行排序,以反映按排序顺序排列的项目。排序后的索引为[1, 4, 2, 3, 0]
。最终得到两个数组:
original = [20, 0, 10, 15, 5]
tag = [1, 4, 2, 3, 0]
您可以通过标记数组二进制搜索原始数组。也就是说,您的比较函数会比较original[tag[x]]
和original[tag[y]]
。这解决了“索引在哪里”的问题。然后,您可以在细分tag[c] ... tag[n]
上使用二进制搜索。
似乎应该有用。你需要一个稳定的排序,以便相同的数字保持它们的相对顺序。
答案 6 :(得分:0)
如果我正确读取此内容,则只有c更改的n-1个查询, 在那种情况下,为什么不向后解决查询? 首先采取最后一个查询,因为它涉及数组检查的最后一个元素,如果元素落在a和b之间,如果是,则将结果存储在数组中ans [n-1] = n-1否则让ans [n-1] = -1,对于从n-2到0的任何下一个查询j
if a[j] is not between a and b
ans[j] = ans[j+1]
else
ans[j] = j
这一切都可以在O(n)中完成。