如何解决初始化开头的指针与开头的指针和结尾的指针背后的直觉 为什么 L = 0, R = length - 1 适用于 2-sum为什么 L = 0, R = 0 不适用于 2-sum为什么 L = 0, R = length - 1 不适用于 2-difference为什么 L = 0, R = 0 适用于 2-difference
给定一个包含 A
个整数和一个整数 N
的未排序数组 B
,查找数组中是否存在差为 B
的一对元素。如果存在任何此类对,则返回 true
,否则返回 false
。对于 [2,3,5,10,50,80]; B=40;
,它应该返回 true
。
作为:
int Solution::solve(vector<int> &A,int B) {
if(A.size()==1) return false;
int i=0,j=0; //note: both initialized at the beginning
sort(begin(A),end(A));
while(i< A.size() && j<A.size()) {
if(A[j]-A[i]==B && i!=j) return true;
if(A[j]-A[i]<B) j++;
else i++;
}
return false;
}
在解决这个问题时,我之前犯过的错误是初始化 i=0
和 j=A.size()-1
。因此,减少 j
和增加 i
both 减少了差异,因此错过了有效差异。如上所述在开始时初始化两者,我能够解决问题。
给定一个整数数组nums
,返回所有的三元组[nums[i],nums[j],nums[k]]
,使得i != j
、i != k
、j != k
和nums[i] + nums[j] + nums[k] == 0
。请注意,解决方案集不得包含重复的三元组。如果 nums = [-1,1,2,-1,-4]
,输出应该是:[[-1,2],[-1,1]]
(任何订单都有效)。
此问题的 solution 表示为:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>> res;
for (unsigned int i=0; i<nums.size(); i++) {
if ((i>0) && (nums[i]==nums[i-1]))
continue;
int l = i+1,r = nums.size()-1; //note: unlike `l`,`r` points to the end
while (l<r) {
int s = nums[i]+nums[l]+nums[r];
if (s>0) r--;
else if (s<0) L++;
else {
res.push_back(vector<int> {nums[i],nums[l],nums[r]});
while (nums[l]==nums[l+1]) L++;
while (nums[r]==nums[r-1]) r--;
L++; r--;
}
}
}
return res;
}
逻辑非常简单:每个nums[i]
(来自外部循环)都是我们搜索的“目标”,在内部while循环中使用两个指针方法,就像第一个代码中的顶。
我没有遵循的是初始化 r=nums.size()-1
和向后工作背后的逻辑 - 如何不被遗漏有效差异(在这种情况下,实际上是“总和”)?
Edit1:两个问题都包含负数和正数,以及零。
Edit2:我了解这两个片段的工作原理。我的问题特别是代码#2中r=nums.size()-1
背后的推理:正如我们在上面的代码#1中看到的那样,从最后开始r
错过了一些有效的对({{ 3}} - 有效对 (10,50) 错过);那么为什么我们不会错过第二个代码中的有效对呢?
解决方法
重新制定问题
两种算法之间的区别归结为加法和减法,而不是 3 对 2 和。
您的 3-sum 变体要求与目标匹配的 3 个数字的总和。当您在外循环中修复一个数字时,内循环会减少为 2-sum,实际上是 2-sum(即加法)。顶部代码中的“2-sum”变体实际上是 2-差(即减法)。
您将 2-sum (A[i] + A[j] == B
s.t. i != j
) 与 2-difference (A[i] - A[j] == B
s.t. i != j
) 进行比较。我将继续使用这些术语,而忘记 3-sum 中的外循环作为红鲱鱼。
2-sum
为什么 L = 0,R = length - 1
适用于 2-sum
对于 2-sum,您可能已经看到从末端开始向中间工作的直觉,但值得明确逻辑。
在循环中的任何迭代中,如果总和为 A[L] + A[R] > B
,那么我们别无选择,只能将右指针递减到较低的索引。增加左指针肯定会增加我们的总和或保持不变,我们将离目标越来越远,可能会关闭找到解决方案对的可能性,其中很可能仍然包括 A[L]
。
另一方面,如果 A[L] + A[R] < B
,那么您必须通过将左指针向前移动到更大的数字来增加总和。有可能 A[R]
仍然是该总和的一部分——我们不能保证它在 A[L] + A[R] > B
之前不是总和的一部分。
关键点在于,在每一步都无需做出决定:要么找到答案,要么可以从进一步考虑中明确排除任一指数中的两个数字之一。 >
为什么 L = 0,R = 0
不适用于 2-sum
这解释了为什么从 0 开始两个数字对 2-sum 没有帮助。您将使用什么规则来增加指针以找到解决方案?没有办法知道哪个指针需要向前移动,哪个应该等待。两次移动都最多增加总和,但都没有减少总和(开始是最小总和,A[0] + A[0]
)。移动错误的一个可能会阻止以后找到解决方案,并且没有办法明确消除任何一个数字。
您又回到了将 left 保持在 0 并将右指针向前移动到导致 A[R] + A[L] > B
的第一个元素,然后运行久经考验的原始双指针逻辑。您不妨从 R
开始 length - 1
。
2-difference
为什么 L = 0,R = length - 1
不适用于 2-difference
现在我们了解了 2-sum 的工作原理,让我们看看 2-difference。为什么同样的方法,从两端开始,向中间工作,行不通?
原因是当你对两个数字相减时,你失去了 2-sum 中最重要的保证,即向前移动左指针将始终增加总和,向后移动右指针将始终减少总和。
在排序数组中的两个数字之间进行减法,A[R] - A[L]
s.t. R > L
,无论您向前移动 L
还是向后移动 R
,总和都会减少,即使在只有正数的数组中也是如此。这意味着在给定的索引处,没有办法知道哪个指针需要移动才能找到正确的对,这与 2-sum 相同的原因破坏了算法,两个指针都从 0 开始。
为什么 L = 0,R = 0
适用于 2-difference
最后,为什么从 0 开始两个指针都适用于 2 差?原因是您回到了 2-sum 保证,即移动一个指针会增加差异,而另一个则减少差异。具体来说,如果 A[R] - A[L] < B
,那么 L++
保证减少差异,而 R++
保证增加它。
我们又回来了:没有选择或神奇的预言来决定移动哪个索引。我们可以系统地消除过大或过小的值,并专注于目标。该逻辑适用于 L = 0,R = length - 1
适用于 2-sum 的相同原因。
顺便说一句,第一个解决方案是次优的 O(n log(n)) 而不是 O(n) 两次传递和 O(n) 空间。您可以使用无序映射来跟踪到目前为止看到的项目,然后对数组中的每个项目执行查找:如果 B - A[i]
为某些 i
在地图中,您找到了您的配对。
考虑一下:
A = {2,3,5,10,50,80}
B = 40
i = 0,j = 5;
当你有类似的东西
while(i<j) {
if(A[j]-A[i]==B && i!=j) return true;
if(A[j]-A[i]>B) j--;
else i++;
}
考虑if(A[j]-A[i]==B && i!=j)
不正确的情况。您的代码做出了一个错误的假设,即如果两个端点的差为 > B
,则应该减少 j
。给定一个已排序的数组,您不知道是先减少 j
然后取差值会得到目标差值,还是增加 i
然后取差值才能得到目标数,因为它可以双向进行。在您的示例中,当 A[5] - A[0] != 10
您可以双向选择时,A[4] - A[0]
(这就是您所做的)或 A[5] - A[1]
.两者都会仍然给你一个比目标差异更大的差异。简而言之,您算法中的假设不正确,因此不是正确的方法。
在第二种方法中,情况并非如此。当未找到三元组 nums[i]+nums[l]+nums[r]
时,您知道数组已排序,如果总和大于 0,则意味着 num[r]
需要自递增 {{1} } 只会进一步增加总和,因为 l
> num[l + 1]
。
您的问题归结为以下几点:
对于升序排列的数组 A
,为什么我们对问题 t
和 A[i] + A[j] == t
执行不同的双指针搜索A[i] - A[j] == t
,其中j > i
?
为什么第一个问题更直观,我们可以将 i
和 j
固定在相反的两端并减少 j
或增加 i
,所以我'将重点解决第二个问题。
对于数组问题,有时最容易绘制出解空间,然后从那里提出算法。首先,我们画出解空间B
,其中B[i][j] = -(A[i] - A[j])
(仅定义为j > i):
B,for A of length N
i ---------------------------->
j B[0][0] B[0][1] ... B[0][N - 1]
| B[1][0] B[1][1] ... B[1][N - 1]
| . . .
| . . .
| . . .
v B[N - 1][0] B[N - 1][1] ... B[N - 1][N - 1]
---
In terms of A:
X -(A[0] - A[1]) -(A[0] - A[2]) ... -(A[0] - A[N - 2]) -(A[0] - A[N - 1])
X X -(A[1] - A[2]) ... -(A[1] - A[N - 2]) -(A[1] - A[N - 1])
. . . . .
. . . . .
. . . . .
X X X ... X -(A[N - 2] - A[N - 1])
X X X ... X X
注意 B[i][j] = A[j] - A[i]
,所以 B 的行是 升序,B 的列是 降序。让我们为 B
计算 A = [2,80]
。
B = [
i------------------------>
j X 1 3 8 48 78
| X X 2 7 47 77
| X X X 5 45 75
| X X X X 40 70
| X X X X X 30
v X X X X X X
]
现在等效的问题是在 B 中搜索 t = 40
。请注意,如果我们从 i = 0
和 j = N = 5
开始,则没有好的/保证的方法可以到达 40
。但是,如果我们从一个位置开始,我们总是可以在 B
中以小步长递增/递减当前元素,我们可以保证我们将尽可能接近 t
。
在这种情况下,我们采取的小步骤包括在矩阵中向右/向下遍历,从左上角开始(可以等效地从右下角向左/向上遍历),这对应于递增 A 中原始问题中的 i
和 j
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。