英文:
Find all multiples of 3 & 5 between two limits - Complexity
问题
public static void main(String[] args) {
System.out.println("能被3和5整除的数字:");
nosDivisibleBy3And5(); // 分治法(待考虑的方法)
nosDivisibleBy3And5BruteForce();
}
private static void nosDivisibleBy3And5BruteForce() {
IntStream ar = IntStream.range(1, 10000001); // 开始包含,结束不包含
Integer[] array = ar.boxed().toArray(Integer[]::new);
List<Integer> list = new ArrayList<>();
int count = 0;
long start = System.currentTimeMillis();
/*
* 遍历数组从1到100,如果可以被3或5整除,则计数并打印。
*
*/
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("蛮力法:");
System.out.println("计数的元素数量: " + count);
//Collections.sort(list);
//System.out.println("元素: " + list);
System.out.println("时间: " + (end - start));
}
private static void nosDivisibleBy3And5() {
/*
* 集合包含所有同时被3和5整除的数字。
*
*/
Set<Integer> elementsSet = new HashSet<Integer>();
int fr3,
fr5,
mid,
count;
fr3 = 2; // fr3 表示第一个能被3整除的值的索引。
fr5 = 4; // fr5 表示第一个能被5整除的值的索引。
count = 0;
int end3 = 9999998 , // end3 表示最后一个能被3整除的值的索引。
end5 = 9999999; // end5 表示最后一个能被5整除的值的索引。
/* 从Intstream对象获取所有从1到100的数字 */
IntStream ar = IntStream.range(1, 10000001); // 开始包含,结束不包含
Integer[] array = ar.boxed().toArray(Integer[]::new);
/*
* 使用分治法,mid将数组从1到100分成两部分,第一部分fr3和fr5将起作用,第二部分end3
* 和end5将起作用。
*/
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("我们的方法");
System.out.println("计数的元素数量: " + elementsSet.size());
//System.out.println("元素:" + elementsSet);
System.out.println("时间: " + (end - start));
}
}
英文:
I am trying to find all numbers between 1 and 10000000 (both inclusive). I tried two solutions
- Brute Force Approach: Loop over all the numbers from 1 to 10000000, and find all which are divisible by either 3 or 5 or both.
- Divide & Conquer approach: Having 4 counters (2 from start and 2 from end). 2 counters work on multiples of 3 and two work on multiples of 5. I am putting all multiples in a Set (I do not need Sorted elements, I only need elements , sorting increases my complexity as well).
But, the loop approach is taking smaller time than the 'Divide & Conquer approach' (10 times lesser approximately).
I searched for the solutions online as well. But, I could find loop approach only. Is there something I am missing in my approach which is increasing my execution time? Please point me to that. I started from a List, moved to Sorted Set, then finally settled to use HashSet, but seems to take time.
Here is what I tried.
`
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));
}
}
`
答案1
得分: 2
HashSet在哈希和检查元素是否已存在方面花费了大量时间,比纯粹使用ArrayList的add()
方法要慢。
如果你的问题确实是找出所有可被3或5整除的数字,那么你可以使用具有预定长度的数组:
int from = 1;
int to = 1000000;
int d3 = (to / 3) - (from / 3) + (from % 3 == 0 ? 1 : 0); // 可被3整除的数量
int d5 = (to / 5) - (from / 5) + (from % 5 == 0 ? 1 : 0); // 可被5整除的数量
int d15 = (to / 15) - (from / 15) + (from % 15 == 0 ? 1 : 0); // 可被15整除的数量
int[] array = new int[d3 + d5 - d15]; // 15的倍数被计算了两次
int offset = 0;
for (int i = from; i <= to; i++) {
if (i % 3 == 0 || i % 5 == 0) array[offset++] = i;
}
英文:
HashSet takes a lot of time on hashing and checking if element already exists and is slower than bare ArrayList's add()
If your problem is really finding all the numbers that are divisible by 3 or 5, than you could use array with predetermined length:
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;
}
答案2
得分: 2
以下是翻译好的部分:
这里是另一种解决方案,部分基于优秀的DelfikPro的回答。
它简化了计算数组大小的逻辑,但增加了更多的代码,以防止使用相对较慢的%
余数运算符,并消除了对不进入结果数组的数字的迭代需求。
因此,它应该执行得更快,尽管我还没有进行基准测试来验证是否确实如此。
static int[] multiplesOfThreeAndFive(int from, int to) { // 包括两端
int count = ((to / 3) - ((from - 1) / 3)) // 可被3整除的数量
+ ((to / 5) - ((from - 1) / 5)) // 可被5整除的数量
- ((to / 15) - ((from - 1) / 15)); // 可被15整除的数量,上述计算中被计数两次
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]
英文:
Here is another solution, partially based on the excellent answer by DelfikPro.
It simplifies the logic for calculating the array size, but adds more code to prevent the need for using the relatively slow %
remainder operator, and eliminates the need to iterate over numbers that don't go into the result array.
As such, it should execute faster, though I haven't benchmarked to see if it actually does.
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;
}
Tests
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)));
Outputs
[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
得分: 0
如果目标是返回一个 List<Integer>
,你可以通过扩展 AbstractList
并实现一个 iterator()
来实现 O(1) 的时间和空间复杂度解决方案,该迭代器会跟踪下一个数字的索引,并且根据最大数字进行计算来实现 get(int index)
、size()
、equals()
、hashCode()
等方法以及迭代器的方法。
该列表将是不可变的(可以简单地使用 Collections.unmodifiableList()
进行包装),但会满足列表的契约。
所有操作都可以在不实际存储任何数字的情况下完成。
英文:
If returning a List<Integer>
is the goal, you could have a O(1) time and space solution by extending AbstractList
and implementing an iterator()
that keeps track of the index of the next number, and implement get(int index)
, size()
, equals()
, hashCode()
etc and iterator’s methods all by calculation based on the max number.
The List would be immutable (simply wrap using Collections. unmodifiableList()
), but would fulfil the contract of List.
All done without actually storing any of the numbers.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论