算法基础课学习笔记:(三)双指针Two Point
算法介绍
Two Point与其说是一种算法,更像是一种编程技巧,它并不需要复杂且严谨的数学证明,只是在编写程序的过程中顺手就优化了程序而已。
问题引入
我们引入一个问题,给定一组全是正整数且没有重复数字的数列,和一个正整数
K
K
K,若数列中存在两个数处于不同位置的数
a
a
a 和
b
b
b 使得
a
+
b
=
K
a+b=K
a+b=K,则将其视为一种方案,求数列中所有满足条件的方案数量。
比方说,给定数列
[
2
,
4
,
3
,
5
,
1
,
6
]
[2,4,3,5,1,6]
[2,4,3,5,1,6] 和正整数
K
=
8
K=8
K=8,则存在两个满足条件的方案:
2
+
6
=
8
2+6=8
2+6=8和
3
+
5
=
8
3+5=8
3+5=8,最终答案输出2。
我相信大部分人碰到这题的第一想法就是逐个扫描元素,找到每个元素对应的元素是否在序列中吧hh。
来写一下暴力解法的代码:
int main(){
int a[6]={2,4,3,5,1,6};
int k=8;
int ans=0;
for(int i=0;i<6;i++){
for(int j=0;j<6;j++{
if(i!=j&&a[i]+a[j]==k) ans++;
}
}
cout<<ans;
}
分析一下暴力解法的时间复杂度,显然两重循环的时间复杂度是 o ( n 2 ) o(n^2) o(n2)的,对 n n n的数量在 1 0 5 10^5 105 级别是无法承受的。
暴力优化
我们来看一下如何才能优化暴力解法。
首先我们注意到,数组中没有重复数字,这就证明了每个元素
a
a
a 如果要满足条件,对应的元素
b
b
b 是唯一的,我们来将数组排个序。
排序好之后的数组为
[
1
,
2
,
3
,
4
,
5
,
6
]
[1,2,3,4,5,6]
[1,2,3,4,5,6],我们借助两个指针变量
l
e
f
t
left
left 和
r
i
g
h
t
right
right,将
l
e
f
t
left
left 指向数组开头,
r
i
g
h
t
right
right 指向数组末尾,
我们可以将
l
e
f
t
left
left 和
r
i
g
h
t
right
right 指向的元素求和,显然和为
7
<
K
=
8
7<K=8
7<K=8,此时我们将
l
e
f
t
left
left 右移到2,满足了
2
+
6
=
K
=
8
2+6=K=8
2+6=K=8,将方案数加1后同时将
l
e
f
t
left
left 右移和
r
i
g
h
t
right
right 左移,循环操作直到
l
e
f
t
和
r
i
g
h
t
left和right
left和right相遇。
为什么能这么做呢,这是因为数组单调有序,只要移动指针,就能在只扫描数组一遍的情况下,完成方案统计。时间复杂度为
o
(
n
)
o(n)
o(n)。
再加上我们排序的时间复杂度
o
(
n
l
o
g
n
)
o(nlogn)
o(nlogn),总体时间复杂度就为
o
(
n
l
o
g
n
)
o(nlogn)
o(nlogn)。
这种思想就被称作双指针,以一遍扫描数组的形式替代了扫描两个数组。
这里给出代码模板
int main(){
int a[6]={2,4,3,5,1,6};
int k=8;
int ans=0;
sort(a,a+6);
//此时数组内元素为[1,2,3,4,5,6]
int left=0,right=5;
while(left<right){
if(a[left]+a[right]==k){
ans++;
left++,right--;
}
else if(a[left]+a[right]<k){
left++;
}
else if(a[left]+a[right]>k){
right--;
}
}
cout<<ans;
}
思考:如果找三个元素加一起等于
K
K
K的方案,该怎么改动?
总结
双指针运算用于单调有序数组,将扫描两趟才能完成的事情降到扫描一趟就能完成。