【问题标题】:Find the smallest sum of all indexes of unique number pairs summing to a given number求给定数之和的唯一数对的所有索引的最小总和
【发布时间】:2023-03-25 10:57:01
【问题描述】:

我想遍历一个数组,然后将每个值彼此相加(除了自身+自身),并且如果循环通过的两个值的总和等于我函数中的第二个参数,并且这对值之前没有遇到过,然后记住它们的索引,最后返回所有记住的索引的完整总和。


换句话说,问题陈述是:给定一个整数数组 A 和第二个值 s,它是所需的总和,从数组中找到所有值对A 在索引 ij 上,使得 i 和 A[i] + A[ j] = s,并返回这些对的所有索引的总和,具有以下限制:

  • 不要重复使用值对,即如果两个索引对 ijkl满足上述条件,如果 A[i] == A[k]A[j] == A[l]A[i ] == A[l]A[j] == A[k],然后忽略索引和较高的对。

示例

例如,functionName([1, 4, 2, 3, 0, 5], 7) 应该返回 11,因为值 4、2、3 和 5 可以相互配对以等于 7,而 11 来自将它们的索引相加得到 11 其中:

4 + 3 = 7 
5 + 2 = 7 

4 [index: 1]
2 [index: 2]
3 [index: 3]
5 [index: 5]

1 + 2 + 3 + 5 = 11

示例 #2

functionName([1, 3, 2, 4], 4) 只会等于 1,因为只有前两个元素可以配对等于 4,并且第一个元素的索引为 0,第二个元素的索引为 1

1 + 3 = 4

1 [index: 0]
3 [index: 1]

0 + 1 = 1

这是我目前所拥有的:

function functionName(arr, arg) {
    var newArr = [];
    for(var i = 0; i < arr.length; i++){
        for(var j = i + 1; j < arr.length; j++) {
            if((arr[i] + arr[j]) === arg ) {
                newArr.push(i , j);
            }
        }
    }

    if(newArr.length === 0) {
        return console.log(0);
    }
    return console.log(newArr.reduce(function(a,b){return a + b}));
}

functionName([1, 4, 2, 3, 0, 5], 7);

我遇到的问题是这一切都有效,但我有一个问题,一旦它找到一个等于第二个参数的对,那么它不应该再次使用相同的值对,但我的确实如此,例如:

如果数组是[1,1,1],第二个参数是2,循环将通过并找到答案,但它会在找到总和后继续搜索,我只希望它使用[1, 1]一次,所以如果它在索引[0, 1] 找到这样的一对,那么它不应该包含任何其他包含值1 的对。


我在想,如果使用过滤器找到超过 2 个相同的值,我可以删除其余相同的值,如果数组中有 2 个相同的值,我就不必担心循环两次找到 1 + 1,但这是最好的方法吗?

我还是新手,但期待你的 cmets

PS 我打算使用纯 JavaScript 而不是库来做这个

链接到一个 JS fiddle,这可能会让事情更容易看到我有什么。 https://jsfiddle.net/ToreanJoel/xmumv3qt/

【问题讨论】:

  • 我对您遇到的问题有点困惑。在 [1,1,1] 示例中,函数是否应该返回 1,即 0 + 1,然后前 2 个 1 不再可用?如果是这样,您可以在推送到 newArr 后立即设置 arr[i] 和 arr[j] = null
  • 是的,你有它,你可以看看我发送的小提琴,但我会测试你的观点,看看它是否有效
  • 为了更好地处理它......如果使用functionName([1, 2, 3, 1, 2, 3, 1], 4),你会期待什么
  • @Torean 澄清一下,functionName([1, 1, 1], 2) 的预期答案是什么?
  • @Jaromanda X 我希望这样做如下: [1,2,3,1,2,3,1] 1 + 2 1 + 3 1 + 1 等然后寻找等于 4 的那些将是 [1,2,3,2]。不要以为在使用一对之后就不需要再次使用它并且循环不需要去。所以最终答案需要是 7 1 [index: 0] 2 [index: 1] 3 [index: 2] 2 [index: 4] 0+1+2+4 = 7 希望这会有所帮助

标签: javascript arrays algorithm


【解决方案1】:

这比最初看起来要复杂。事实上,在循环中创建循环会导致算法在数组大小方面具有quadratic time complexity。换句话说,对于大量的数字,需要很长时间才能完成。

另一种处理这个问题的方法是注意你实际上只需要使用数组中的每个唯一值一次(或两次,如果 s 是偶数并且你有两个 s/ 2 值数组中的某处)。否则,您将拥有非唯一对。这是因为如果你需要成对的数字 xy 使得 x + y = s,如果你知道 x,则确定y -- 必须等于s - x

所以你实际上可以解决linear time complexity 中的问题(公平地说,如果A 中的所有值都是唯一的,有时是n*log(n),因为我们必须对它们进行一次排序)。

算法步骤如下:

  1. 制作一个映射,其键是数组 A 中的值,值是这些值出现在 A 中的索引的排序列表。
  2. 按升序遍历 A 中的所有唯一值(您在解决第 1 步时收集了这些值)。对于每个这样的值:
    1. 假设它是搜索到的值对中的较低值。
    2. 计算较大的值(等于s - lower
    3. 检查 A 中是否也存在较高的值(由于步骤 1 中创建的地图,您可以在恒定时间内执行此操作)。
    4. 如果是,则将较低值和较高值的最低索引添加到结果中。
  3. 返回结果。

这是完整的代码:

function findSumOfUniquePairs(numbers, sum) {

  // First, make a map from values to lists of indexes with this value:
  var indexesByValue = {},
      values = [];
  numbers.forEach(function (value, index) {
    var indexes = indexesByValue[value];
    if (!indexes) {
      indexes = indexesByValue[value] = [];
      values.push(value);
    }
    indexes.push(index);
  });

  values.sort();

  var result = 0;

  for (var i = 0, maxI = values.length; i < maxI; ++i) {
    var lowerValue = values[i],
        higherValue = sum - lowerValue;
    if (lowerValue > higherValue) {
      // We don't have to check symmetrical situations, so let's quit early:
      break;
    }
    var lowerValueIndexes = indexesByValue[lowerValue];
    if (lowerValue === higherValue) {
      if (lowerValueIndexes.length >= 2) {
        result += lowerValueIndexes[0] + lowerValueIndexes[1];
      }
    } else {
      var higherValueIndexes = indexesByValue[higherValue];
      if (higherValueIndexes) {
        result += lowerValueIndexes[0] + higherValueIndexes[0];
      }
    }
  }

  return result;
}

document.write(findSumOfUniquePairs([1, 4, 2, 3, 0, 5], 7) + '<br>'); // 11;
document.write(findSumOfUniquePairs([1, 3, 2, 4], 4) + '<br>'); // 1
document.write(findSumOfUniquePairs([1, 1, 1], 2) + '<br>'); // 1
document.write(findSumOfUniquePairs([1, 1, 1, 1], 2) + '<br>'); // 1
document.write(findSumOfUniquePairs([1, 2, 3, 1, 2, 3, 1], 4) + '<br>'); // 7
document.write(findSumOfUniquePairs([5, 5, 1, 1, 1], 6) + '<br>'); // 2
document.write(findSumOfUniquePairs([0, 5, 0, 5, 1, 1, 1], 6) + '<br>'); // 5

【讨论】:

    【解决方案2】:

    这行得通,但它弄乱了初始数组。

    function functionName(arr, arg) {
        var newArr = [];
        for(var i = 0; i < arr.length; i++){
            for(var j = i + 1; j < arr.length; j++) {
                if((arr[i] + arr[j]) === arg ) {
                    newArr.push(i , j);
                    arr[i] = null;
                    arr[j] = null;
                }
            }
        }
    
        if(newArr.length === 0) {
            return console.log(0);
        }
        return console.log(newArr.reduce(function(a,b){return a + b}));
    }
    

    【讨论】:

    • 首先复制arrvar newArray = arr.slice();。然后使用newArray :D
    • 这不起作用。它几乎没有通过所有测试,包括functionName([1, 3, 2, 4], 4)functionName([1, 1, 1, 1], 2)
    【解决方案3】:

    如果找到总和,则使用重启的循环解决方案。找到的summands存储在usedNumbers中,然后进行排序并用于获取索引以对索引求和。

    排序和最后一个index 提供了Array.prototype.indexOf 的正确起始位置。

    编辑:

    那么 [1,1,1,1], 2 ... 应该是 6 还是 1? – Jaromanda X 21

    @JaromandaX 应该是 1,在找到具有值的对之后,它不应该再次寻找具有相同值的对 - Torean

    这个版本可以满足要求。

    function f(array, sum) {
        var arrayCopy = array.slice(0),
            usedNumbers = [],
            index = 0,
            indexA = 0,
            indexB, 
            a, b;
    
        while (indexA < arrayCopy.length) {
            indexB = indexA + 1;
            while (indexB < arrayCopy.length) {
                a = arrayCopy[indexA];
                b = arrayCopy[indexB];
                if (a + b === sum) {
                    usedNumbers.push(a, b);
                    arrayCopy = arrayCopy.filter(function (i) { return a !== i && b !== i; });
                    indexA--; // correction to keep the index
                    break;
                }
                indexB++;
            }
            indexA++;
        }
        return usedNumbers.sort().reduce(function (r, a, i) {
            index = array.indexOf(a, i === 0 || a !== usedNumbers[i - 1] ? 0 : index + 1);
            return r + index;
        }, 0);
    }
    
    document.write(f([1, 4, 2, 3, 0, 5], 7) + '<br>'); // 11
    document.write(f([1, 1, 1], 2) + '<br>'); // 1
    document.write(f([5, 5, 1, 1, 1], 6) + '<br>'); // 2
    document.write(f([0, 5, 0, 5, 1, 1, 1], 6) + '<br>'); // 5
    document.write(f([1, 1, 1, 1], 2) + '<br>'); // 1

    【讨论】:

    • 这是我所需要的,我将通过只是为了了解你做了什么,但这是正确的,谢谢@Nina
    • 此代码错误:f([1, 1, 1, 1], 2) 产生 6,但它应该产生 1,根据问题下 @Torean 的 cmets。此外,您的测试 3 和 4 打破了相同的假设——它们应该分别返回 2 和 5。
    • 这会产生正确的结果,但是当找到每个匹配项时,它可能会多次重写整个数组(使用splice),从而使恶意输入具有指数级复杂性。恶意输入示例: f([5, 1, 1, 1, ..., 1], 6) 其中 1 重复了 1000000 次——在我放弃并杀死它之前在我的机器上运行了一分钟。
    【解决方案4】:

    下面的解决方案非常紧凑。它仅通过相关元素避免不必要的检查和循环。您可以在此处检查工作的 codepen: http://codepen.io/PiotrBerebecki/pen/RRGaBZ.

    function pairwise(arr, arg) {
      var sum = 0;
      for (var i=0; i<arr.length-1; i++) {
        for (var j=i+1; j<arr.length; j++) {
          if (arr[i] <= arg && arr[j] <= arg && arr[i] + arr[j] == arg) {
            sum += i+j; 
            arr[i] = arr[j] = NaN;
          }   
        }
      }
      return sum;
    }
    
    console.log(  pairwise([1, 1, 0, 2], 2)  ) // should return 6
    

    幕后:

    1. 从索引 (i) = 0 的元素开始循环。
    2. 仅为数组中后面的元素添加第二个循环。他们的索引j 总是高于i,因为我们将i 加1。
    3. 如果两个元素(数字)都小于或等于arg,检查它们的总和是否等于arg。如果任何一个数字大于arg,这将避免检查总和。
    4. 如果已找到该对,则将其值更改为 NaN 以避免进一步检查和重复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-15
      • 1970-01-01
      相关资源
      最近更新 更多