【问题标题】:Why multiply instead of sum为什么要乘而不是求和
【发布时间】:2018-02-16 08:47:58
【问题描述】:

我需要检查两个数组是否包含相同的数字。所以:

[1,1,2] and [1,2,2] = false;
[2,3,1] and [1,2,3] = true;
[1,2,4] and [1,3,2] = false;
[1,1,4] and [1,2,3] = false;

现在不知何故,乘法:

boolean areSimilar(int[] a, int[] b) {
    int s1 = 1, s2 = 1;
    for(int i = 0; i < a.length; i++) {
        s1 *= a[i];
        s2 *= b[i];
    }
    return s1 == s2;
}

但是,如果我将*= 替换为+=,那么只是一些,[1,1,4] and [1,2,3] 将返回 true 而不是 false(显然,因为它会返回 6 等于 6)。

所以我的问题是,我可以确保乘法是防错的吗? 或者是否有可能通过乘法两个数组包含不同的数字但具有相同的乘积?

【问题讨论】:

  • 当然有可能!
  • 考虑反例 [1,2,4] 和 [2,2,2]。产品相同,但数字不同。这是你问的吗?
  • 当考虑到两个数组都包含 0(零)作为它们的元素之一的情况时,所有这些都变得有点明显...... ... ...跨度>
  • 附带事实:如果数组中的所有数字都是素数,它会起作用。
  • 是的,现在我想这是一个愚蠢的问题,但它实际上包含了一个额外的检查。这个想法实际上是检查是否可以通过最多交换一个数组的 1 个数字来获得相似的数组(因此交换第二个数组的索引 1 和 2 并且它们变得相等)。如果那不可能,它们就不相等。因此,额外的检查计算了数组 1 的索引有多少次不等于数组 2 的索引。如果大于 2 并且乘法失败,则不可能使它们相等。但即使是那个解决方案也让我感到困惑

标签: java arrays math


【解决方案1】:

我很确定产品可能是相同的,但其中的数字不同。示例:[9 1 2] [18 1 1]

我会建议一个不同的解决方案。

当您第一次对数组进行排序时,您可以检查每个数字是否相同。

【讨论】:

    【解决方案2】:

    不,从这个意义上说,乘法不是“故障证明”。

    明显的反例是两个数组都包含 0(零)作为其元素之一。无论其他元素如何,乘积将始终为 0。即使没有一个元素为零,也很容易找到更多反例。

    只有当且仅当数组中的数字是相同数字的prime factors 时,使用乘法才是“安全的”。素数分解是唯一的,元素相乘的顺序无关紧要。

    编辑:

    在这里,我提出了一个解决实际问题的方法,即检查两个数组是否包含相同的元素,不考虑顺序。

    正如在 cmets 中所讨论的,事实证明,虽然它理论上(渐近地)较慢,但 answer by dnswlt 中提出的基于排序的方法在实践中似乎要高效得多,如果您不关心克隆数组所隐含的内存消耗。

    我在这里最初提出的解决方案的优点是内存消耗更少,渐近运行时间更短,并且它在概念上适用于其他类型的元素(特别是对于包含不可比较的元素的数组,因此无法排序)。

    方法是为每个数组构造一个Map。这个Map 将数组的每个元素映射到它在数组中出现的次数

    import java.util.Map;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    public class SameNumbersInArray
    {
        public static void main(String[] args)
        {
            System.out.println(areSimilar(
                new int[] { 1, 1, 2 },
                new int[] { 1, 2, 2 }));
    
            System.out.println(areSimilar(
                new int[] { 2, 3, 1 },
                new int[] { 1, 2, 3 }));
    
            System.out.println(areSimilar(
                new int[] { 1, 2, 4 },
                new int[] { 1, 3, 2 }));
    
            System.out.println(areSimilar(
                new int[] { 1, 1, 4 },
                new int[] { 1, 2, 3 }));
    
            System.out.println(areSimilar(
                new int[] { 1, 1, 1, 4 },
                new int[] { 1, 1, 4 }));
    
            System.out.println(areSimilar(
                new int[] { 1, 1, 1, 4 },
                new int[] { 1, 1, 4, 1 }));
    
        }
    
        private static boolean areSimilar(int[] a, int[] b) {
    
            Map<Integer, Long> frequenciesA = IntStream.of(a).boxed()
                .collect(Collectors.groupingBy(
                    Function.identity(), Collectors.counting()));
            Map<Integer, Long> frequenciesB = IntStream.of(b).boxed()
                .collect(Collectors.groupingBy(
                    Function.identity(), Collectors.counting()));
            return frequenciesA.equals(frequenciesB);
        }
    
    }
    

    测试用例的输出是

    false
    true
    false
    false
    false
    true
    

    但是,对于int[] 数组,基于排序的方法在实践中更有效(可能是由于基于计数的方法进行了昂贵的装箱转换)。

    【讨论】:

    • 感谢您的解释! 0 确实很有意义 XD 所以我想我必须找到另一个解决方案
    • 感谢您的完整解决方案!看起来挺好的!但是有一个问题,您不认为对两个数组进行排序并检查它们是否等于 (Arrays.equals(arr1, arr2);) 实际上更容易吗?还是该解决方案有一些缺点?
    • 排序和比较是一种选择,有两个理论上的缺点: 1. 对数组进行排序是 O(nlogn),而我提出的解决方案是 O(n)。但在实践中(基于快速测试),排序和比较(如answer by dnswlt 中提出的,+1!)似乎总是更有效。 2.如果不想修改给定的数组,则必须创建数组的副本,对于大数组可能会消耗大量内存。
    • 基于计数的方法的一个(也是理论)优势:如果您计算一个数组的频率,并最终得到例如如果发现3 出现了 10 次,那么您可以 进行优化:如果在处理第二个数组时,您发现3 出现了 11 次,您已经可以返回false。但是,实现不会那么简单,这在实践中是否带来优势在很大程度上取决于所比较的数组的内容。
    • IMO 排序和比较也会产生更清晰的代码,因为它很容易理解你在做什么。
    【解决方案3】:

    正如其他人指出的那样,显然加法或乘法都不够。一个典型的基于排序的解决方案如下所示:

    boolean areSimilar(int[] a, int[] b) {
        if (a.length != b.length) {
            return false;
        }
        int[] aSorted = a.clone();
        Arrays.sort(aSorted);
        int[] bSorted = b.clone();
        Arrays.sort(bSorted);
        return Arrays.equals(aSorted, bSorted);
    }
    

    请注意,我使用clone() 来确保您的原始数组保持不变。如果没关系,可以直接对ab进行排序。

    【讨论】:

    • 查看this answer 的类似问题。你不需要自己比较元素,使用Arrays.equals(a1, a2)
    • 为提示干杯,改编了我的答案。
    【解决方案4】:

    不,乘法不能防错。

    考虑数组 {1,1,6} 和 {1,2,3}。它们的乘积相同 (6),但位数不同。

    【讨论】:

    • 我的刺:它是“故障证明”当且仅当数组中的数字是(相同)数字的素数分解。
    • 但同样的道理,添加也不起作用,例如{1,1,8}{1,2,7}.
    • 猜这是一个愚蠢的问题。实际的想法是我需要检查是否可以通过最多交换一个数字来生成类似的数组。所以有一个额外的检查来检查一个和另一个数组的索引有多少次不包含相同的值。如果它小于或等于 2 并且乘法失败,则不可能通过交换 1 个数字来使它们相等
    【解决方案5】:

    你不能用加法乘法来做到这一点。

    您正在尝试将 N 个 32 位数字映射到一个 32 位数字。那就是数据压缩。您不能将 32*N 位信息放入 32 位中,就像您不能将 N 升水放入一升瓶中一样(当然,除非 N 为 1)。

    有时,您可以利用数据的某些属性,以便您需要少于32*N 位的信息:例如,如果您知道您的数字在 0 到 15 之间,则每个数字只需要 4 位。但一般情况下,你不能。

    很简单,总会有“冲突”,两个不同的数组映射到同一个值。

    可以做的是说这些数字肯定相同。你可以用乘法或加法来做到这一点:如果两个数组的乘积或和不同,它们肯定不一样;如果是,则必须进一步检查数字是否相同。

    这是hashCode 方法的基础:当正确实现时,hashCode 允许您确定两个实例是否相等;如果你想知道它们是否相等,你必须使用equals 方法。

    【讨论】:

    • 感谢您的全面回答,安迪。很有意义。猜猜 Malte Hartwig 的回答也很有帮助(首先对数组进行排序,然后检查它们是否相等,如果没有一个数组包含一组不同的数字)。
    • @ErikvandeVen 这是一个“更深层次的检查”:首先进行简单的加法或乘法检查(甚至可以在此之前检查它们的长度!),这样您就可以避免总是需要进行排序。
    【解决方案6】:

    乘法不是防错的。 这只是一个快速检查。有点像查找比较是否值得做的速记。校验和(此处为校验乘法)类型的校验。

    [0, x, y, z, ...] == [..a,b, 0, c ...] 总是

    【讨论】:

      【解决方案7】:

      为什么不结合 sum 和 product? (下面等效的java代码)

      斯卡拉:

      val r = scala.util.Random 
      // 10 digits produce to few collisions to control by hand:
      // def arr = Vector (r.nextInt (10), r.nextInt (10), r.nextInt (10)) 
      def arr = Vector (r.nextInt (3), r.nextInt (3), r.nextInt (3)) 
      // first idea: a*b*c + a+b+c, but  3,0,0 and 2,1,0 produce 
      // product 0 and sum 3  
      // def checksum (v: Vector[Int]) : Int = v.product +  v.sum
      // so we just add 1 to every digit 
      def checksum (v: Vector[Int]) : Int = (v(0)+1)*(v(1)+1)*(v(2)+1) +  v(0)+v(1)+v(2) 
      def cmp (a:Vector[Int], b:Vector[Int]) : Boolean = { checksum(a) == checksum(b)} 
      (1 to 30).foreach (i => {val a=arr; val b=arr; val m = cmp (a, b); println (s"match: $a $b $m") })
      

      测试数据:

      match: Vector(0, 1, 2) Vector(0, 1, 0) false
      match: Vector(1, 1, 0) Vector(2, 1, 1) false
      match: Vector(2, 1, 0) Vector(2, 0, 2) false
      match: Vector(0, 0, 2) Vector(0, 1, 1) false
      match: Vector(2, 1, 1) Vector(2, 1, 1) true
      match: Vector(0, 1, 1) Vector(1, 0, 2) false
      match: Vector(2, 0, 2) Vector(2, 0, 0) false
      match: Vector(0, 2, 2) Vector(2, 1, 0) false
      match: Vector(2, 2, 2) Vector(0, 1, 2) false
      match: Vector(2, 2, 0) Vector(1, 1, 0) false
      match: Vector(0, 2, 2) Vector(0, 0, 2) false
      match: Vector(0, 2, 1) Vector(2, 1, 1) false
      match: Vector(0, 2, 1) Vector(1, 0, 0) false
      match: Vector(2, 2, 2) Vector(2, 1, 1) false
      match: Vector(0, 1, 1) Vector(1, 0, 2) false
      match: Vector(1, 0, 0) Vector(1, 0, 2) false
      match: Vector(2, 1, 1) Vector(0, 1, 2) false
      match: Vector(2, 0, 0) Vector(2, 1, 0) false
      match: Vector(1, 2, 1) Vector(2, 1, 2) false
      match: Vector(2, 2, 1) Vector(2, 1, 2) true
      match: Vector(0, 0, 0) Vector(0, 2, 2) false
      match: Vector(2, 0, 1) Vector(0, 1, 1) false
      match: Vector(2, 0, 1) Vector(1, 2, 1) false
      match: Vector(0, 1, 1) Vector(1, 1, 0) true
      match: Vector(0, 2, 0) Vector(1, 0, 0) false
      match: Vector(1, 2, 1) Vector(0, 1, 0) false
      match: Vector(2, 0, 2) Vector(0, 1, 0) false
      match: Vector(0, 1, 0) Vector(1, 2, 2) false
      match: Vector(1, 1, 0) Vector(0, 0, 0) false
      match: Vector(0, 1, 1) Vector(0, 2, 1) false
      

      仍然需要逐个控制,我将编写一个检查函数,在比较之前对向量进行排序。我想,虽然代码是 scala,但很容易理解并转化为 java 等价物?

      def cmp2 (a:Vector[Int], b:Vector[Int]) : Boolean = { a.sorted == b.sorted} 
      (1 to 3000).foreach (i => {val a=arr; val b=arr; if (cmp (a, b) != cmp2 (a, b)) println (s"match: $a $b ${cmp(a,b)} ${cmp2(a,b)}") })
      // silence
      

      当然,你也可以做数学证明:

      (a+1)*(b+1)*(c+1)+a+b+c ?= (d+a+1)*(e+b+1)*(c+1)+(d+a)+b+c | d!=0
      // or
      (a+1)*(b+1)*(c+1)+a+b+c ?= (d+a+1)*(e+b+1)*(c+1)+(d+a)+b+c | d!=0, e!=0
      

      像往常一样,留给读者作为练习:)

      Java:

      Random r = new java.util.Random();
      
      int [] arr (int max) {
        int[] v = new int[3];
        v[0] = r.nextInt (max);
        v[1] = r.nextInt (max);
        v[2] = r.nextInt (max);
        return v;
      }
      
      int checksum (int[] v) { return (v[0] +1) * (v[1] +1) * (v[2] +1) + v[0] + v[1] + v[2]; }
      
      boolean cmp (int[] a, int[] b) { return checksum(a) == checksum(b); } 
      
      for (int i = 0; i < 30; ++i) {
        int [] a = arr (3); 
        int [] b = arr (3); 
        boolean m = cmp (a, b); 
        System.out.printf ("match: %6s %6s %b\n",  Arrays.toString (a), Arrays.toString (b), m);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-12-07
        • 2017-03-08
        • 2017-01-16
        • 2023-03-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-04
        相关资源
        最近更新 更多