简体   繁体   English

最有效的算法和 BigO 符号

[英]Most efficient algorithm and BigO notation

I am practicing some coding algorithms.我正在练习一些编码算法。 In some interviews they don't only ask you to solve a problem, but to solve it in the most efficient way, and also specify the efficiency of the algorithm (aka Big O notation).在某些面试中,他们不仅要求您解决问题,而且要求您以最有效的方式解决问题,并指定算法的效率(又名 Big O 表示法)。 I've always had issues measuring that efficiency, so I would really appreciate someone explaining how to calculate the efficiency of the algorithmor to point to some resources in order to check it (did not find very useful docs so far).我一直在衡量效率时遇到问题,所以我真的很感激有人解释如何计算算法的效率或指向一些资源以检查它(到目前为止没有找到非常有用的文档)。

For example, take a look to this problem below.例如,看看下面的这个问题。 I have solved it in two different ways.我用两种不同的方式解决了它。 using Java.使用 Java。 The first way is using an imperative approach (which I find it more efficient as we don't need to iterate over the lists many times) and the second approach, with functional programming approach and stream API (that I find much less efficient in this case).第一种方法是使用命令式方法(我发现它更有效,因为我们不需要多次迭代列表)和第二种方法,使用函数式编程方法和流 API(我发现在这方面效率要低得多)案件)。

Could someone tell me with an explanation what is the Big O notation of both approaches, explaining the way to calculate it?有人可以解释一下这两种方法的大 O 表示法是什么,解释计算它的方法吗?

    package exercises;

import org.junit.Assert;
import org.junit.Test;

import java.util.*;
import java.util.stream.Collectors;


/**
 * You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
 *
 * You may assume the two numbers do not contain any leading zero, except the number 0 itself.
 */
public class AddTwoNumbers {

    //Most efficient implemention of the two
    private List<Integer> addTwoNumbersImplementation1(LinkedList<Integer> l1, LinkedList<Integer> l2) {

        Integer carry = 0;
        List<Integer> result = new ArrayList<Integer>();
        while (l1.size() > 0 || l2.size() > 0) {

            Integer l1Last = Optional.ofNullable(l1.pollLast()).orElse(0);
            Integer l2Last = Optional.ofNullable(l2.pollLast()).orElse(0);

            Integer partialResult = l1Last + l2Last + carry;

            if (partialResult >= 10) {
                result.add(Character.getNumericValue(partialResult.toString().charAt(1)));
                carry = 1;
            } else {
                result.add(partialResult);
                carry = 0;
            }
        }

        if(carry == 1) {result.add(1);}

        return result;
    }


    //Least efficient implemention of the two
    private List<Integer> addTwoNumbersImplementation2(LinkedList<Integer> l1, LinkedList<Integer> l2) {
        Integer n1 = Integer.parseInt(new StringBuffer(l1.stream().map(e -> e.toString()).collect(Collectors.joining())).reverse().toString());
        Integer n2 = Integer.parseInt(new StringBuffer(l2.stream().map(e -> e.toString()).collect(Collectors.joining())).reverse().toString());
        Integer result = n1 + n2;
        return new StringBuffer(result.toString()).reverse().toString().chars().mapToObj(Character::getNumericValue).collect(Collectors.toList());
    }


    @Test
    public void test() {
        LinkedList<Integer> list1 = new LinkedList<>();
        list1.addAll(Arrays.asList(2,4,3));
        LinkedList<Integer> list2 = new LinkedList<>();
        list2.addAll(Arrays.asList(5,6,4));
        List<Integer> resultList = new LinkedList<>();
        resultList.addAll(Arrays.asList(7,0,8));
        Assert.assertEquals(resultList, addTwoNumbersImplementation1(list1, list2));
    }

    @Test
    public void test2() {
        LinkedList<Integer> list1 = new LinkedList<>();
        list1.add(0);
        LinkedList<Integer> list2 = new LinkedList<>();
        list2.add(0);
        List<Integer> resultList = new LinkedList<>();
        resultList.add(0);
        Assert.assertEquals(resultList, addTwoNumbersImplementation1(list1, list2));
    }

    @Test
    public void test3() {
        LinkedList<Integer> list1 = new LinkedList<>();
        list1.addAll(Arrays.asList(9,9,9,9,9,9,9));
        LinkedList<Integer> list2 = new LinkedList<>();
        list2.addAll(Arrays.asList(9,9,9,9));
        List<Integer> expected = new LinkedList<>();
        expected.addAll(Arrays.asList(8,9,9,9,0,0,0,1));
        Assert.assertEquals(expected, addTwoNumbersImplementation1(list1, list2));
    }


    @Test
    public void test4() {
        LinkedList<Integer> list1 = new LinkedList<>();
        list1.addAll(Arrays.asList(2,4,3));
        LinkedList<Integer> list2 = new LinkedList<>();
        list2.addAll(Arrays.asList(5,6,4));
        List<Integer> resultList = new LinkedList<>();
        resultList.addAll(Arrays.asList(7,0,8));
        Assert.assertEquals(resultList, addTwoNumbersImplementation2(list1, list2));
    }

    @Test
    public void test5() {
        LinkedList<Integer> list1 = new LinkedList<>();
        list1.add(0);
        LinkedList<Integer> list2 = new LinkedList<>();
        list2.add(0);
        List<Integer> resultList = new LinkedList<>();
        resultList.add(0);
        Assert.assertEquals(resultList, addTwoNumbersImplementation2(list1, list2));
    }

    @Test
    public void test6() {
        LinkedList<Integer> list1 = new LinkedList<>();
        list1.addAll(Arrays.asList(9,9,9,9,9,9,9));
        LinkedList<Integer> list2 = new LinkedList<>();
        list2.addAll(Arrays.asList(9,9,9,9));
        List<Integer> expected = new LinkedList<>();
        expected.addAll(Arrays.asList(8,9,9,9,0,0,0,1));
        Assert.assertEquals(expected, addTwoNumbersImplementation2(list1, list2));
    }
}

For loops, the big O notation time is basically count the iterations.对于循环,大 O 符号时间基本上是计算迭代次数。 If you loop over an array, it's O(n) (n is the size of the list).如果你遍历一个数组,它是 O(n)(n 是列表的大小)。 If you have a loop inside a loop, its O(outer loop count * inner loop count).如果循环内有循环,则其 O(外循环计数 * 内循环计数)。 So the first algorithm is O(n) where n is the size of the array.所以第一个算法是 O(n),其中 n 是数组的大小。

Also- what are you doing with还有——你在做什么

result.add(Character.getNumericValue(partialResult.toString().charAt(1)));
carry = 1;

Here's a much easier way to do it:这是一个更简单的方法:

   result.add(partialResult%10);
   carry = partialResult/10;

This way also works with the exact same code if you're adding 3 or more arrays.如果您要添加 3 个或更多数组,这种方式也适用于完全相同的代码。

Your second one has a major flaw- it won't work for numbers that add up to over 2^32 (or 4 billionish).你的第二个有一个重大缺陷——它不适用于加起来超过 2^32(或 40 亿)的数字。 The first will.第一个会。 Ignoring that, it is an O(n) operation to do the map, then an O(n) operation to join them, then an O(n) operation to reverse it.忽略这一点,做映射是一个 O(n) 操作,然后是一个 O(n) 操作来连接它们,然后是一个 O(n) 操作来反转它。 So it's O(3n), which is the same as O(n).所以它是 O(3n),与 O(n) 相同。

That means complexity wise, they're the same.这意味着复杂性明智,它们是相同的。 Timewise- the first algorithm will blow the second away, because the operation you're doing n times is so much more efficient, especially if you get rid of the needless string usage and use the code I showed you.时间方面-第一个算法将击败第二个算法,因为您执行 n 次的操作效率更高,特别是如果您摆脱了不必要的字符串使用并使用我向您展示的代码。 Complexity is an approximation of time for comparison sake, but its not a direct correlation.为了比较,复杂性是时间的近似值,但不是直接相关。 It can only be used to compare algorithms with different classes (say O(n) vs O(n^2)) and is only valid at large n.它只能用于比较不同类别的算法(比如 O(n) 与 O(n^2)),并且仅在大 n 时有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM