降低内部循环的时间复杂度:在第一个循环中查找大于当前元素的元素数量并将其存储在已解决的数组中

Posted

技术标签:

【中文标题】降低内部循环的时间复杂度:在第一个循环中查找大于当前元素的元素数量并将其存储在已解决的数组中【英文标题】:Reduced time complexity of inner loop: Find count of elements greater than current element in the first loop and store that in solved array 【发布时间】:2016-11-03 12:21:24 【问题描述】:

我想降低这个程序的复杂性,并在第一个循环 (array[]) 中找到大于当前/选择元素的元素计数并将计数存储在已解决的数组中 (solved[]) 并循环到大批[]。我已经使用基于通用数组的方法解决了这个问题,当第二个循环很大时,结果证明它具有更大的时间复杂度。 但是如果有人可以在这里用 java 提出一个更好的集合,可以降低这段代码的复杂性,那也将不胜感激。

for (int i = 0; i < input; i++) 
    if (i < input - 1) 
        count=0;
        for (int j = i+1; j < input; j++) 
            System.out.print((array[i])+" ");
            System.out.print("> ");
            System.out.print((array[j]) +""+(array[i] > array[j])+" ");
            if (array[i] > array[j]) 
                count++;
            
        
        solved[i] = count;
    

for (int i = 0; i < input; i++) 
    System.out.print(solved[i] + " ");

我想用更简单的方式实现什么

输入

假设我的

中有 4 个元素

数组[] -->86,77,15,93

输出

已解决[]-->2 1 0 0

2 因为在 86 之后只有两个元素 77,15 小于 86

1 因为在 77 之后只有 15 小于 77

休息 15

【问题讨论】:

其实问题的标题是正文的反面,实际上是在正文中查找元素的数量少于当前元素的数量。 @yeppe Leo算法的时间复杂度为nlogn + (n-1)log(n-1) + ... + 2log2 + 1log1 = O(n^2*logn),即实际上比您的解决方案更高(即使代码肯定更优雅)。您可以检查计算,例如在math.stackexchange.com/questions/121997/… 【参考方案1】:

正如 Miljen Mikic 指出的,正确的方法是使用 RB/AVL 树。这是可以阅读的代码,N个测试用例尽快完成这项工作。接受 Miljen 代码作为解决给定问题陈述的最佳方法。

class QuickReader 
    static BufferedReader quickreader;
    static StringTokenizer quicktoken;

    /** call this method to initialize reader for InputStream */
    static void init(InputStream input) 
        quickreader = new BufferedReader(new InputStreamReader(input));
        quicktoken = new StringTokenizer("");
    

    static String next() throws IOException 
        while (!quicktoken.hasMoreTokens()) 
            quicktoken = new StringTokenizer(quickreader.readLine());
        
        return quicktoken.nextToken();
    

    static int nextInt() throws IOException 
        return Integer.parseInt(next());
    

    static long nextLong() throws IOException 
        return Long.parseLong(next());
    

    static double nextDouble() throws IOException 
        return Double.parseDouble(next());
    


public class ExecuteClass
    static int countInstance = 0;
    static int solved[];
    static int size;

    static class Node 
        public int value, countOfSmaller, sizeOfSubtree;
        public Node left, right;

        public Node(int val, int count, int len, int... arraytoBeused) 
            countInstance++;
            value = val;
            size = len;
            countOfSmaller = count;
            sizeOfSubtree = 1; /** You always add a new node as a leaf */
            solved = arraytoBeused;
            solved[size - countInstance] = count;
        
    

    static Node insert(Node node, int value, int countOfSmaller, int len, int solved[]) 
        if (node == null)
            return new Node(value, countOfSmaller, len, solved);
        if (value > node.value)
            node.right = insert(node.right, value, countOfSmaller + size(node.left) + 1, len, solved);
        else
            node.left = insert(node.left, value, countOfSmaller, len, solved);
        node.sizeOfSubtree = size(node.left) + size(node.right) + 1;
        return node;
    

    static int size(Node n) 
        return n == null ? 0 : n.sizeOfSubtree;
    

    public static void main(String[] args) throws IOException 
        QuickReader.init(System.in);
        int testCase = QuickReader.nextInt();
        for (int i = 1; i <= testCase; i++) 
            int input = QuickReader.nextInt();
            int array[] = new int[input];
            int solved[] = new int[input];
            for (int j = 0; j < input; j++) 
                array[j] = QuickReader.nextInt();
            

            Node root = insert(null, array[array.length - 1], 0, array.length, solved);
            for (int ii = array.length - 2; ii >= 0; ii--)
                insert(root, array[ii], 0, array.length, solved);

            for (int jj = 0; jj < solved.length; jj++) 
                System.out.print(solved[jj] + " ");

            
            System.out.println();
            countInstance = 0;
            solved = null;
            size = 0;
            root = null;
        

    

【讨论】:

【参考方案2】:

其实有O(n*logn)的解决方案,但是应该使用红黑树等自平衡二叉搜索树。

算法的主要思想:

您将从右到左遍历您的数组并在树中插入三元组(value, sizeOfSubtree, countOfSmaller)。变量sizeOfSubtree 将指示以该元素为根的子树的大小,而countOfSmaller 计算原始数组中小于该元素并出现在其右侧的元素的数量。

为什么是二叉搜索树? BST 的一个重要性质是左子树中的所有节点都小于当前节点,而右子树中的所有节点都大于当前节点。

为什么要使用自平衡树?因为这将保证您在插入新元素时O(logn) 的时间复杂度,所以对于数组中的 n 个元素,总共将给出 O(n*logn)

当您插入一个新元素时,您还将通过计算当前在树中且小于该元素的元素来计算 countOfSmaller 的值——这正是我们要寻找的。在插入树时,将新元素与现有节点进行比较,从根开始。 重要提示:如果新元素的值大于根的值,则表示它也大于根的左子树中的所有节点。因此,将countOfSmaller设置为root的左孩子+1的sizeOfSubtree(因为新元素也大于root)并在右子树中递归进行。如果它小于根,它会转到根的左子树。在这两种情况下,都会增加根的sizeOfSubtree 并递归进行。在重新平衡树时,只需为左/右旋转中包含的节点更新 sizeOfSubtree 即可。

示例代码:

public class Test
  
    static class Node 
        public int value, countOfSmaller, sizeOfSubtree;
        public Node left, right;
        public Node(int val, int count) 
            value = val;
            countOfSmaller = count;
            sizeOfSubtree = 1; /** You always add a new node as a leaf */
            System.out.println("For element " + val + " the number of smaller elements to the right is " + count);
        
    
    static Node insert(Node node, int value, int countOfSmaller)
    
        if (node == null)
            return new Node(value, countOfSmaller);

        if (value > node.value)
            node.right = insert(node.right, value, countOfSmaller + size(node.left) + 1);
        else
            node.left = insert(node.left, value, countOfSmaller);

        node.sizeOfSubtree = size(node.left) + size(node.right) + 1;

        /** Here goes the rebalancing part. In case that you plan to use AVL, you will need an additional variable that will keep the height of the subtree.
            In case of red-black tree, you will need an additional variable that will indicate whether the node is red or black */

       return node;
    
    static int size(Node n)
    
        return n == null ? 0 : n.sizeOfSubtree;
    

    public static void main(String[] args)
        
        int[] array = 13, 8, 4, 7, 1, 11;
        Node root = insert(null, array[array.length - 1], 0);
        for(int i = array.length - 2; i >= 0; i--)      
           insert(root, array[i], 0); /** When you introduce rebalancing, this should be root = insert(root, array[i], 0); */
    

【讨论】:

【参考方案3】:

因此,使代码更简单和使代码更快并不一定是一回事。如果您希望代码简单易读,可以尝试排序。也就是说,您可以尝试类似

int[] solved = new int[array.length];
for (int i = 0; i < array.length; i++)
    int[] afterward = Arrays.copyOfRange(array, i, array.length);
    Arrays.sort(afterward);
    solved[i] = Arrays.binarySearch(afterward, array[i]);

它的作用是获取当前索引之后的所有元素(也包括它)的副本,然后对该副本进行排序。任何小于所需元素的元素都将在前面,任何大于所需元素的元素都将在后面。通过查找元素的索引,您可以找到它之前的索引数。

免责声明:如果存在重复项,则无法保证这将起作用。您必须手动检查是否有任何重复值,否则以某种方式确保您不会有任何重复值。

编辑: 该算法运行时间为 O(n2 log n),其中 n 是原始列表的大小。排序需要 O(n log n),并且您执行 n 次。二进制搜索比排序 (O(log n)) 快得多,因此它会从排序中吸收到 O(n log n) 中。它没有完美优化,但代码本身非常简单,这就是这里的目标。

【讨论】:

【参考方案4】:

使用 Java 8 流,您可以像这样重新实现它:

int[] array = new int[]  86,77,15,93 ;
int[] solved =
  IntStream.range(0, array.length)
           .mapToLong((i) -> Arrays.stream(array, i + 1, array.length)
                                   .filter((x) -> x < array[i])
                                   .count())
           .mapToInt((l) -> (int) l)
           .toArray();

【讨论】:

哇,我不知道你现在可以用 Java 进行 Mathematica 编程了。

以上是关于降低内部循环的时间复杂度:在第一个循环中查找大于当前元素的元素数量并将其存储在已解决的数组中的主要内容,如果未能解决你的问题,请参考以下文章

当循环在异步函数内部而不是相反时,为啥 Async/Await 可以正常工作?

在单个链表中查找循环

二分查找

当循环内一个值大于另一个值时停止循环函数

二分查找算法的递归循环实现及其缺陷

二分查找算法的递归循环实现及其缺陷