📜  Java程序在数组中查找给定索引范围的 GCD

📅  最后修改于: 2022-05-13 01:54:52.049000             🧑  作者: Mango

Java程序在数组中查找给定索引范围的 GCD

给定一个数组 a[0 。 . . n-1]。我们应该能够有效地找到从索引 qs(查询开始)到 qe(查询结束)的 GCD,其中 0 <= qs <= qe <= n-1。例子 :

Input : a[] = {2, 3, 60, 90, 50};
        Index Ranges : {1, 3}, {2, 4}, {0, 2}
Output: GCDs of given ranges are 3, 10, 1

方法1(简单)

一个简单的解决方案是运行从 qs 到 qe 的循环并在给定范围内找到 GCD。在最坏的情况下,此解决方案需要 O(n) 时间。

方法 2(二维阵列)

另一种解决方案是创建一个二维数组,其中条目 [i, j] 将 GCD 存储在范围 arr[i..j] 中。现在可以在 O(1) 时间内计算给定范围的 GCD,但预处理需要 O(n^2) 时间。此外,这种方法需要 O(n^2) 额外空间,这对于大型输入数组可能会变得巨大。

方法 3(段树)

先决条件:Segment Tree Set 1,Segment Tree Set 2
段树可用于适时进行预处理和查询。使用分段树,预处理时间为 O(n),GCD 查询的时间为 O(Logn)。存储段树所需的额外空间是 O(n)。

段树的表示

  • 叶节点是输入数组的元素。
  • 每个内部节点代表其下所有叶子的 GCD。

树的数组表示用于表示段树,即对于索引 i 处的每个节点,

  • 左孩子在索引 2*i+1
  • 右孩子在 2*i+2,父母在 floor((i-1)/2)。

从给定数组构造段树

  • 从段 arr[0 开始。 . . n-1] 并继续分成两半。每次我们将当前段分成两半(如果它还没有变成长度为 1 的段),然后在两半上调用相同的过程,并且对于每个这样的段,我们将 GCD 值存储在一个段树节点中。
  • 所构建的段树的所有级别都将被完全填充,除了最后一层。此外,这棵树将是一棵完整的二叉树(每个节点都有 0 或两个孩子),因为我们总是在每一层将段分成两半。
  • 由于构造的树总是具有 n 个叶子的满二叉树,因此将有 n-1 个内部节点。所以节点总数将为 2*n – 1。
  • 段树的高度将为 &lceillog 2 n&rceil。由于树是使用数组表示的,并且必须保持父索引和子索引之间的关系,因此为段树分配的内存大小将是 2*2 ⌈log 2 n⌉ – 1

查询给定范围的 GCD

/ qs --> query start index, qe --> query end index
int GCD(node, qs, qe)
{
   if range of node is within qs and qe
      return value in node
   else if range of node is completely 
      outside qs and qe
      return INFINITE
   else
      return GCD( GCD(node's left child, qs, qe), 
                  GCD(node's right child, qs, qe) )
}

下面是这个方法的实现:

Java
// Java Program to find GCD of a number in a given Range
// using segment Trees
import java.io.*;
  
public class Main
{
    private static int[] st; // Array to store segment tree
  
    /* Function to construct segment tree from given array.
       This function allocates memory for segment tree and
       calls constructSTUtil() to fill the allocated memory */
    public static int[] constructSegmentTree(int[] arr)
    {
        int height = (int)Math.ceil(Math.log(arr.length)/Math.log(2));
        int size = 2*(int)Math.pow(2, height)-1;
        st = new int[size];
        constructST(arr, 0, arr.length-1, 0);
        return st;
    }
  
    // A recursive function that constructs Segment
    // Tree for array[ss..se]. si is index of current
    // node in segment tree st
    public static int constructST(int[] arr, int ss,
                                  int se, int si)
    {
        if (ss==se)
        {
            st[si] = arr[ss];
            return st[si];
        }
        int mid = ss+(se-ss)/2;
        st[si] = gcd(constructST(arr, ss, mid, si*2+1),
                     constructST(arr, mid+1, se, si*2+2));
        return st[si];
    }
  
    // Function to find gcd of 2 numbers.
    private static int gcd(int a, int b)
    {
        if (a < b)
        {
            // If b greater than a swap a and b
            int temp = b;
            b = a;
            a = temp;
        }
  
        if (b==0)
            return a;
        return gcd(b,a%b);
    }
  
    //Finding The gcd of given Range
    public static int findRangeGcd(int ss, int se, int[] arr)
    {
        int n = arr.length;
  
        if (ss<0 || se > n-1 || ss>se)
            throw new IllegalArgumentException("Invalid arguments");
  
        return findGcd(0, n-1, ss, se, 0);
    }
  
    /*  A recursive function to get gcd of given
    range of array indexes. The following are parameters for
    this function.
  
    st    --> Pointer to segment tree
    si --> Index of current node in the segment tree. Initially
               0 is passed as root is always at index 0
    ss & se  --> Starting and ending indexes of the segment
                 represented by current node, i.e., st[si]
    qs & qe  --> Starting and ending indexes of query range */
    public static int findGcd(int ss, int se, int qs, int qe, int si)
    {
        if (ss>qe || se < qs)
            return 0;
  
        if (qs<=ss && qe>=se)
            return st[si];
  
        int mid = ss+(se-ss)/2;
  
        return gcd(findGcd(ss, mid, qs, qe, si*2+1),
                   findGcd(mid+1, se, qs, qe, si*2+2));
    }
  
    // Driver Code
    public static void main(String[] args)throws IOException
    {
        int[] a = {2, 3, 6, 9, 5};
  
        constructSegmentTree(a);
  
        int l = 1; // Starting index of range.
        int r = 3; //Last index of range.
        System.out.print("GCD of the given range is: ");
        System.out.print(findRangeGcd(l, r, a));
    }
}



输出:
GCD of the given range is: 3

时间复杂度:树构造的时间复杂度为 O(n * log(min(a, b))),其中 n 是模式数,a 和 b 是在合并操作期间计算其 GCD 的节点。总共有2n-1个节点,每个节点的值在建树时只计算一次。查询的时间复杂度为 O(Log n * Log n)。
有关详细信息,请参阅有关数组中给定索引范围的 GCD 的完整文章!