指针算法
When dealing with strings and arrays in the context of algorithm challenges, our first instinct usually revolves around built-in methods.
当在算法挑战的背景下处理字符串和数组时,我们的第一个本能通常是围绕内置方法。
Let’s take a look at this seemingly easy problem:
让我们看一下这个看似简单的问题:
/* Description:
Given a sorted (ascending) array of integers,
write a function that returns a sorted (ascending) array
which contains the square of each number.
*/// Examples:
square([0, 1, 2, 3, 4, 5])
// => [0, 1, 4, 9, 16, 25])square([-7, -3, 2, 3, 11])
// => [4, 9, 9, 49, 121]
Like many others, my immediate reaction was to make use of sort()
method after mapping out (map()
) the squared version of each integer, like so:
与许多其他方法一样,我的直接React是在映射( map()
)每个整数的平方后,利用sort()
方法,如下所示:
function square(arr) {
arr = arr.map(num => num * num)
return arr.sort((a, b) => a - b)
}
While my solution above achieves the desired result, its somewhat brute-force approach leads to a not-so-performant O(n log(n))
time complexity.
虽然我上面的解决方案获得了预期的结果,但其某种程度上的蛮力方法导致了O(n log(n))
时间复杂度不高的情况。
So how can we improve the runtime complexity?
那么如何改善运行时的复杂性呢?
This is where a popular and effective strategy, Two-Pointer Technique, comes into play.
这是一种流行且有效的策略,即“两点技术” 。
When iterating over an array or string, we can set two pointers to search and/or compare two elements. There are three common ways to set the pointers:
当遍历数组或字符串时,我们可以设置两个指针来搜索和/或比较两个元素。 共有三种设置指针的方法:
- Start both pointers at the beginning of the iteration 在迭代开始时启动两个指针
- Start both pointers at the end of the iteration在迭代结束时启动两个指针
- Start one pointer at the beginning, the other at the end, both moving toward each other and meeting in the middle.在起点处开始一个指针,在终点处开始另一个指针,两者都彼此相对并在中间相遇。
Here’s how it works in our square()
example:
在我们的square()
示例中,它的工作方式如下:
步骤0: (Step 0:)
Initiate an empty array that will store our results.
启动一个空数组,它将存储我们的结果。
第1步: (Step 1:)
Create two pointers, i
and j
, where i
keeps track of the negative integers, while j
keeps track of the positives.
创建两个指针i
和j
,其中i
跟踪负整数,而j
跟踪正整数。
第2步: (Step 2:)
Iterate over the array. Keep moving j
forward until the element of the array (arr[j]
) is a positive integer.
遍历数组。 继续向前移动j
,直到数组的元素( arr[j]
)为正整数。
第三步: (Step 3:)
Inside the iteration, compare the squared elements between index i and index j, push/append the smaller element to the resulting array.
在迭代内部,比较索引i和索引j之间的平方元素,将较小的元素推入/追加到结果数组中。
第4步: (Step 4:)
After the iteration in Step 3, our resulting array will have a sorted set of integers. What remains is the element(s) at index i and index j.
在第3步中进行迭代之后,我们得到的数组将具有一组排序的整数。 剩下的是索引i和索引j处的元素。
We can subsequently push/append the remaining elements(s) to the resulting array.
随后,我们可以将其余元素推入/追加到结果数组中。
步骤5: (Step 5:)
Return the resulting array.
返回结果数组。
Here’s the two-pointer technique approach (courtesy of Women Who Code San Diego):
这是两点技术方法(由圣地亚哥妇女编码提供):
function squareTwoPointer(arr) {
let result = []
// create 2 pointers: i keeps track of negatives, j keeps track of positives
let j = 0
let i; while (j < arr.length && arr[j] < 0) {
j++
i = j - 1
} while (j < arr.length && i >= 0) {
if ((arr[i] * arr[i]) < (arr[j] * arr[j])) {
result.push((arr[i] * arr[i]))
i--
} else {
result.push((arr[j] * arr[j]))
j++
} } while (i >= 0) {
result.push((arr[i] * arr[i]))
i--
} while (j < arr.length) {
result.push((arr[j] * arr[j]))
j++
} return result
}
The time complexity of this optimized solution is O(n)
because we only perform one iteration at a time and sort the elements in place.
此优化解决方案的时间复杂度为O(n)
因为我们一次只执行一次迭代并对元素进行排序。
As with almost all algorithm challenges, there are multiple ways to approach this problem. The two-pointer strategy appears to be a good starting point for optimization.
与几乎所有算法挑战一样,有多种方法可以解决此问题。 两点策略似乎是优化的良好起点。
If you haven’t applied two-pointer techniques in your problem-solving process, I hope this example boosts your confidence in coming up with more performant algorithm solutions.
如果您在解决问题的过程中没有应用两点技巧,那么我希望这个例子可以增强您对提出更多高性能算法解决方案的信心。
Onward and upward!
步步高升!
指针算法