四数之和第二层循环剪枝为何不能直接return

leetcode18四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

思路与三数之和类似,使用指针解决。我写代码遇到与三数之和不同点是第二层i循环剪枝操作

# 第二层剪枝
for i in range(k+1, n-2):
    if nums[k]+nums[i] > target and nums[k]+nums[i] > 0:
        break

与第一层k循环剪枝操作不同 

# 第一层剪枝
for k in range(n-3):
    if nums[k] > target and nums[k] > 0:
        return ans

第一层剪枝可以直接返回,原因是

正数nums[k]一旦大于target,那么它后面的nums[i],nums[left],nums[right] 都会是正数,四数相加一定大于target,因此没有进入循环的必要。

然而第二层循环不可以直接返回,原因是

虽然nums[k]+nums[i]大于target且为正数,下一轮nums[k]大于这一轮nums[k],但是我们不能确定nums[i]的大小,因为i也在进行循环。比如nums=[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],一轮中nums[k]=-2,nums[i]=0,nums[left]=0,nums[right]=2,target=0,我们找到了四元组[-1, 0, 0, 1]。i继续循环,直到nums[i]=3,此时满足nums[k]+nums[i]=1大于target且为正数,但是我们不能确定下一轮nums[k]+nums[i]与本轮nums[k]+nums[i]的大小。例如下一轮次nums[k]=-1,nums[i]=0,相加等于-1,并没有大于上一轮结束时nums[k]+nums[i]。

出现这种情况的主要原因是在一轮k循环中nums[k]是固定的的,但nums[i]是变化的,它可以在一轮k循环中变得很大,但是下一轮k循环又会重新变小。因此直接return会丢失可能的四元组,应该使用break。

完整代码如下:

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        n = len(nums)
        if n < 4:
            return []
        ans = []
        nums.sort()
        for k in range(n-3):
            if nums[k] > target and nums[k] > 0:
                return ans
            if k > 0 and nums[k] == nums[k-1]:
                continue
            for i in range(k+1, n-2):
                if i > k+1 and nums[i] == nums[i-1]:
                    continue
                if nums[k]+nums[i] > target and nums[k]+nums[i] > 0:
                    break
                left = i + 1
                right = n - 1
                while left < right:
                    if nums[k]+nums[i]+nums[left]+nums[right] == target:
                        ans.append([nums[k], nums[i], nums[left], nums[right]])
                        while left < right and nums[right] == nums[right-1]:
                            right -= 1
                        while left < right and nums[left] == nums[left+1]:
                            left += 1
                        left += 1
                        right -= 1
                    elif nums[k]+nums[i]+nums[left]+nums[right] > target:
                        right -= 1
                    else:
                        left += 1
        return ans

为了方便理解,我先解释一下FIFO分支限界算法的基本思路。FIFO分支限界算法是一种广度优先搜索算法,它的基本思路是:将搜索树的节点放入一个队列中,每次从队列中取出队首元素扩展子节点,并将子节点加入队列末尾,然后按照某种规则排序队列,使得优先级高的节点优先扩展。 接下来,我们按照题目要求,使用FIFO分支限界算法求解子集和问题。首先,读入输入数据: ```python n = int(input()) # 集合大小 M = int(input()) # 目标和 w = list(map(int, input().split())) # 集合中的数 ``` 接着,定义一个节点类,用于表示搜索树的节点。每个节点包含以下属性: - `path`:表示节点对应的可行解。 - `bound`:表示节点的上界,用于排序和剪枝。 - `level`:表示节点在搜索树中的深度。 ```python class Node: def __init__(self, path, bound, level): self.path = path self.bound = bound self.level = level ``` 接下来,定义一个计算上界的函数`bound()`。该函数用于计算当前节点的上界,用于排序和剪枝。具体计算方法如下: 1. 计算当前节点已选数的和`cur_sum`。 2. 计算剩余数的和`rem_sum`,即从当前节点所在层往下所有数的和。 3. 如果`cur_sum`已经超过了目标和`M`,则返回负无穷;如果`cur_sum`加上`rem_sum`仍然小于`M`,则返回当前节点的和作为上界;否则,返回当前节点的和加上剩余数的平均值作为上界。 ```python def bound(path, w, M): cur_sum = sum(w[i-1] for i in path) rem_sum = sum(w[i-1] for i in range(path[-1]+1, len(w)+1)) if cur_sum > M: return float('-inf') elif cur_sum == M: return cur_sum else: return cur_sum + (rem_sum / len(w[path[-1]:])) ``` 接下来,定义搜索函数`subset_sum()`,该函数使用FIFO分支限界算法搜索所有满足条件的子集。具体步骤如下: 1. 将根节点加入队列,并初始化最优解和最优路径。 2. 循环执行以下操作,直到队列为空: 1. 取出队首元素。 2. 如果当前节点的上界小于当前已知最优解,则剪枝直接跳过。 3. 如果当前节点的和等于目标和,将当前节点的可行解加入结果集,并更新最优解和最优路径。 4. 否则,生成当前节点的所有子节点,并计算它们的上界,将子节点加入队列,并按照上界从小到大排序。 3. 返回结果集和最优路径。 ```python def subset_sum(w, M): # 初始化根节点 root = Node([], bound([], w, M), 0) queue = [root] # 初始化队列 result = [] # 初始化结果集 best_path = [] # 初始化最优路径 best_bound = float('-inf') # 初始化最优上界 while queue: node = queue.pop(0) # 剪枝 if node.bound <= best_bound: continue # 找到一个可行解 if sum(w[i-1] for i in node.path) == M: result.append(node.path) if not best_path or len(node.path) < len(best_path): best_path = node.path best_bound = len(best_path) # 扩展子节点 for i in range(node.level+1, len(w)+1): child_path = node.path + [i] child_bound = bound(child_path, w, M) if child_bound > best_bound: queue.append(Node(child_path, child_bound, node.level+1)) queue.sort(key=lambda x: x.bound) if result: print(result) else: print("no solution!") ``` 最后,调用`subset_sum()`函数进行搜索,并输出结果: ```python subset_sum(w, M) ``` 完整代码如下:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值