leetcode
leetcode 1551 ~ 1600
最接近目标值的子序列和

最接近目标值的子序列和

难度:

标签:

题目描述

代码结果

运行时间: 545 ms, 内存: 147.6 MB


/* 
 * 思路:
 * 1. 使用流来生成所有可能的子序列和。
 * 2. 将数组分成两部分,分别处理。
 * 3. 使用流和二分查找合并结果,找到绝对差最小的值。
 */
import java.util.*;
import java.util.stream.*;

public class Solution {
    public int minAbsDifference(int[] nums, int goal) {
        int n = nums.length;
        List<Integer> leftSums = generateSums(Arrays.copyOfRange(nums, 0, n / 2));
        List<Integer> rightSums = generateSums(Arrays.copyOfRange(nums, n / 2, n));
        Collections.sort(rightSums);

        return leftSums.stream()
            .mapToInt(sum -> {
                int remaining = goal - sum;
                int idx = Collections.binarySearch(rightSums, remaining);
                if (idx >= 0) {
                    return 0;
                } else {
                    idx = -idx - 1;
                    int diff1 = idx < rightSums.size() ? Math.abs(remaining - rightSums.get(idx)) : Integer.MAX_VALUE;
                    int diff2 = idx > 0 ? Math.abs(remaining - rightSums.get(idx - 1)) : Integer.MAX_VALUE;
                    return Math.min(diff1, diff2);
                }
            })
            .min()
            .orElse(Integer.MAX_VALUE);
    }

    private List<Integer> generateSums(int[] nums) {
        return IntStream.range(0, 1 << nums.length)
            .mapToObj(mask -> IntStream.range(0, nums.length)
                .filter(i -> (mask & (1 << i)) != 0)
                .map(i -> nums[i])
                .sum())
            .collect(Collectors.toList());
    }
}

解释

方法:

此题解采用了分治与双指针的策略。首先,它将输入数组分为两部分,分别计算每部分可能的子序列和,存储在两个列表中。对于计算子序列和的过程,使用了一个集合来避免重复和。每次从当前子序列和集合中取一个元素,并与当前数字相加,形成新的子序列和,更新到集合中。然后对两个结果列表排序,并使用双指针技术来找到两个列表中元素之和最接近目标值的组合。指针的移动根据当前和与目标值的比较结果进行:如果和小于目标值,则移动较小列表的指针;如果和大于目标值,则移动较大列表的指针。这种方法可以有效地缩小搜索范围,直到找到最小的绝对差。

时间复杂度:

O(2^(n/2) log(2^(n/2)))

空间复杂度:

O(2^(n/2))

代码细节讲解

🦆
在生成子序列和的过程中,使用集合而不是列表来存储和的原因是什么?这对性能有何影响?
在生成子序列和的过程中使用集合而不是列表的主要原因是为了避免重复的子序列和。由于集合的元素是唯一的,这样可以自动去除重复的和,从而减少了处理的数据量。使用集合插入和查询操作平均时间复杂度为O(1),相比于列表在检查或插入新元素时可能需要O(n)的复杂度,性能更优。因此,使用集合可以提高算法的效率,尤其是在处理大数据量时更为显著。
🦆
题解中提到,如果和小于目标值,则移动较小列表的指针;如果和大于目标值,则移动较大列表的指针。这种指针移动策略是如何确保能快速找到最接近目标值的和的?
这种指针移动策略基于两个已排序列表的特性。当和小于目标值时,增加较小列表的指针可以使总和增大,这是因为较小列表的下一个元素比当前元素大;反之,当和大于目标值时,减少较大列表的指针可以使总和减小,因为较大列表的前一个元素比当前元素小。这种策略利用了排序和双指针的特点,可以有效地调整和的大小,同时避免了不必要的组合,因此能更快地找到接近目标值的和。
🦆
双指针技术在这种场景中为什么有效?它与单纯的遍历所有可能的子序列和有何优势?
双指针技术在这种场景中有效,因为它可以在两个已排序的列表中以线性时间复杂度找到最接近目标的和。相较于遍历所有可能的子序列和的组合(时间复杂度可能达到O(n^2)),双指针技术通过每次只移动一个指针,并基于当前和与目标值的关系来决定移动哪一个指针,大幅减少了需要考虑的组合数,从而提高了效率。这种方法既减少了计算量,也降低了时间复杂度,使得算法在处理大量数据时更为高效。
🦆
题解最后的循环处理中,当指针越界(例如i>=len(res1)或j<0)时,如何处理?这种边界处理对算法的正确性和完整性有何影响?
当指针越界时,算法通过检查是否还有其他可能的组合来继续调整和。例如,如果i指针越界(i>=len(res1)),则只能移动j指针来寻找可能的更小和;相反,如果j指针越界(j<0),则只能移动i指针来寻找可能的更大和。这种边界处理确保了算法在到达任一列表末端时仍然能尝试所有有效的组合,保证了算法的正确性和完整性,避免了因忽略边界情况而导致的错误结果。

相关问题