在两个极限之间找到 3 和 5 的所有倍数 - 复杂性

Posted

技术标签:

【中文标题】在两个极限之间找到 3 和 5 的所有倍数 - 复杂性【英文标题】:Find all multiples of 3 & 5 between two limits - Complexity 【发布时间】:2021-01-24 03:49:31 【问题描述】:

我正在尝试查找 1 到 10000000(包括两者)之间的所有数字。我尝试了两种解决方案

    蛮力法:循环从 1 到 10000000 的所有数字,找出所有能被 3 或 5 整除的数字。 分而治之的方法:有 4 个计数器(2 个从开始,2 个从结束)。 2 个计数器适用于 3 的倍数,两个适用于 5 的倍数。我将所有倍数放在一个 Set 中(我不需要排序元素,我只需要元素,排序也会增加我的复杂性)。

但是,循环方法比“分治法”花费的时间更短(大约少 10 倍)。 我也在网上搜索了解决方案。但是,我只能找到循环方法。我的方法中是否缺少某些东西会增加我的执行时间?请指出这一点。我从 List 开始,移动到 Sorted Set,然后最终确定使用 HashSet,但似乎需要时间。

这是我尝试过的。

`

public static void main(String[] args) 

    System.out.println("Numbers divisible by 3 and 5:");

    nosDivisibleBy3And5();    // divide & conquer approach (approach to consider)

    nosDivisibleBy3And5BruteForce();



private static void nosDivisibleBy3And5BruteForce() 

    IntStream ar = IntStream.range(1, 10000001);  // start inclusive, end exclusive

    Integer[] array = ar.boxed().toArray(Integer[]::new);

    List<Integer> list = new ArrayList<>();

    int count = 0;

    long start = System.currentTimeMillis();

    /* 
     * Traversing array from 1 to 100, 
     * if it is either divisible by 3 or 5 or both, count it , print it. 
     * 
     */
    for(int i = 0; i < array.length ; i ++) 

        if((array[i] % 3 == 0) || (array[i] % 5 == 0)) 

            //System.out.println(array[i]);

            list.add(array[i]);

            count++;
        
    
    long end = System.currentTimeMillis();

    System.out.println("Brute Force Approach:");
    System.out.println("No of elements counted: " + count);

    //Collections.sort(list);

    //System.out.println("Elements: " + list);

    System.out.println("Time: " + (end - start));



private static void nosDivisibleBy3And5() 

    /* 
     * Set has all those numbers which 
     * are divisible by both 3 and 5.
     * 
     */

    Set<Integer> elementsSet = new HashSet<Integer>();

    int fr3,
    fr5,
    mid,
    count;

    fr3 = 2;   // fr3 indicates the index of the first value divisible by 3.
    fr5 = 4;   // fr5 indicates the index of the first value divisible by 5.
    count = 0;

    int end3 = 9999998 , // end3 indicates the index of the last value divisible by 3.
            end5 = 9999999;   // end5 indicates the index of the last value divisible by 5.

    /* Getting all the numbers from 1 to 100 from Intstream object */
    IntStream ar = IntStream.range(1, 10000001);  // start inclusive, end exclusive

    Integer[] array = ar.boxed().toArray(Integer[]::new);

    /* 
     * Using divide and conquer approach , mid divides the array from 1 to 100
     * in two parts, on the first fr3 and fr5 will work, on the second part end3 
     * and end5 will work.
     */
    mid = (fr3 + end3)/2;

    long start = System.currentTimeMillis();

    while(fr3 <= mid && end3 >= mid) 

        elementsSet.add(array[fr3]);

        elementsSet.add(array[fr5]);

        elementsSet.add(array[end3]);

        elementsSet.add(array[end5]);

        fr3 += 3;
        fr5 += 5;
        end3 -= 3;
        end5 -= 5;
    

    long end = System.currentTimeMillis();

    System.out.println("Our approach");
    System.out.println("No of elements counted: " + elementsSet.size());


    //System.out.println("Elements:" + elementsSet);
    System.out.println("Time:  " + (end - start));

`

【问题讨论】:

在测量任何代码的执行时间之前请read this。您首先需要更准确的测量。 for(long i = 0; i @Sweeper,感谢您的链接。虽然只有增加的输入大小才能决定复杂性,但在这里我可以看到控制台挂在我的方法和循环方法中,因为我正在增加输入大小。计算时差只是为了检查运行时间,与我的意思没有复杂性的联系。这只是我关心的控制台挂起,因为我可以看到我在那里花时间。 您要计数还是找到它们? @Ashish 对于 n = 0 到 666665,数字是 [3,5,6,9,10,12,15] + 15*n,然后在 n = 666666 时停止在 10。您所需要做的就是将它们添加到列表中。复杂度是 O(n),假设添加到列表是 O(1)。如果没有,分配一个大小为 4666667 的数组,并将它们添加到数组中。时间复杂度是 O(n),因为索引到数组中肯定是 O(1)。 【参考方案1】:

HashSet 花费大量时间进行散列和检查元素是否已经存在并且比裸 ArrayList 的 add()

如果您的问题真的是找到所有可被 3 或 5 整除的数字,那么您可以使用预定长度的数组:

int from = 1;
int to = 1000000;
int d3 = (to / 3) - (from / 3) + (from % 3 == 0 ? 1 : 0); // how many divisible by 3
int d5 = (to / 5) - (from / 5) + (from % 5 == 0 ? 1 : 0); // how many divisible by 5
int d15 = (to / 15) - (from / 15) + (from % 15 == 0 ? 1 : 0); // how many divisible by 15

int[] array = new int[d3 + d5 - d15]; // counted 15's twice

int offset = 0;
for (int i = from; i <= to; i++) 
  if (i % 3 == 0 || i % 5 == 0) array[offset++] = i;

【讨论】:

我同意你的第一句话。似乎 Hashset 花费了很多时间。我想我应该只打印它。会看到我可以使用的数据结构来存储元素。您发布的代码部分是循环方法,所以根据您的说法,我应该遵循循环方法? 不错的答案,虽然数组长度计算可以简化一点,见my answer。【参考方案2】:

如果返回 List&lt;Integer&gt; 是目标,您可以通过扩展 AbstractList 并实现跟踪下一个数字的索引的 iterator() 来获得 O(1) 时间和空间解决方案,并实现 @ 987654328@、size()equals()hashCode()等和迭代器的方法都是根据最大数计算的。

List 将是不可变的(只需使用 Collections. unmodifiableList() 进行包装),但会履行 List 的合同。

在没有实际存储任何数字的情况下全部完成。

【讨论】:

现在,使用 Java 8,它应该返回 Stream 而不是 Iterator。这实际上意味着它应该实现Spliterator,或者更确切地说是Spliterator.OfIntSpliterator 可以轻松实现trySplit(),允许并行处理。 Spliterator 将具有除 CONCURRENT 之外的所有特征。 @Andreas 你会 extend AbstractList 并覆盖 iterator() (以及一些其他方法,如 size() 等),它会免费为您提供 stream() (和其他相关的 Java 8 方法),因为它们在Collection 中实现,最终都使用impl的Iterator。 你为什么要继承AbstractList 来创建一个List 实现,它违反了大多数方法的List 的约定?这是一个非常糟糕的解决方案。或者您是否打算以某种方式实现get(int index),这不是可选方法? @Andreas 我不认为我需要说它......当然这将是一个不可变的列表(只需使用Collections.unmodifiableList()),如果它当然必须实现get(int index)(我什至打算发布一个 impl,但决定不做所有 OP 的工作),我将在迭代器中使用它,然后只需要存储索引(一个整洁的 impl 恕我直言)。这样的列表履行List 的合同。这一直是我的意图。【参考方案3】:

这是另一个解决方案,部分基于优秀的answer by DelfikPro。

它简化了计算数组大小的逻辑,但添加了更多代码以防止使用相对较慢的% 余数运算符,并消除了对不进入结果数组的数字进行迭代的需要。

因此,它应该执行得更快,尽管我还没有进行基准测试以查看它是否确实如此。

static int[] multiplesOfThreeAndFive(int from, int to)  // both inclusive
    int count = ((to /  3) - ((from - 1) /  3))  // how many divisible by 3
              + ((to /  5) - ((from - 1) /  5))  // how many divisible by 5
              - ((to / 15) - ((from - 1) / 15)); // how many divisible by 15,  counted twice above
    
    int[] result = new int[count];
    int[] multiples =  0, 3, 5, 6, 9, 10, 12 ;
    
    int startIndex = Arrays.binarySearch(multiples, from % 15);
    if (startIndex < 0)
        startIndex = -startIndex - 1;
    for (int r = 0, offset = from / 15 * 15; r < count; offset += 15, startIndex = 0)
        for (int i = startIndex; r < count && i < multiples.length; i++, r++)
            result[r] = offset + multiples[i];
    return result;

测试

System.out.println(Arrays.toString(multiplesOfThreeAndFive(1, 100)));
System.out.println(Arrays.toString(multiplesOfThreeAndFive(0, 100)));
System.out.println(Arrays.toString(multiplesOfThreeAndFive(29, 100)));
System.out.println(Arrays.toString(multiplesOfThreeAndFive(30, 100)));
System.out.println(Arrays.toString(multiplesOfThreeAndFive(31, 99)));
System.out.println(Arrays.toString(multiplesOfThreeAndFive(31, 101)));

输出

[3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100]
[0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99]
[30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100]
[30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100]
[33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99]
[33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100]

【讨论】:

以上是关于在两个极限之间找到 3 和 5 的所有倍数 - 复杂性的主要内容,如果未能解决你的问题,请参考以下文章

Python:在x和y之间生成随机数,它是5的倍数[重复]

C语言,1到1000所有3的倍数且含有2和5

Leetcode 448.找到所有数组中消失的数字

用c语言编写,输出1-100之间即是3的倍数又是2的倍数的所有数据,每行输出五个

最小公倍数 [Javascript 挑战]

计算两个不同数字的倍数之差