Skip to content

Commit

Permalink
feat: #1014
Browse files Browse the repository at this point in the history
  • Loading branch information
lucifer committed Jan 4, 2020
1 parent 3ab443a commit 16a3cc5
Showing 1 changed file with 52 additions and 111 deletions.
163 changes: 52 additions & 111 deletions problems/1014.best-sightseeing-pair.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,126 +23,67 @@ https://leetcode-cn.com/problems/best-sightseeing-pair/description/

## 思路

这是一个很少见的直接考察`排序`的题目。 其他题目一般都是暗含`排序`,这道题则简单粗暴,直接让你排序。
并且这道题目的难度是`Medium`, 笔者感觉有点不可思议。

我们先来看题目的限制条件,这其实在选择算法的过程中是重要的。 看到这道题的时候,大脑就闪现出了各种排序算法,
这也算是一个复习`排序算法`的机会吧。

题目的限制条件是有两个,第一是元素个数不超过 10k,这个不算大。 另外一个是数组中的每一项范围都是`-50k``50k`(包含左右区间)。
看到这里,基本我就排除了时间复杂度为 O(n^2)的算法。

> 我没有试时间复杂度 O(n^2) 的解法,大家可以试一下,看是不是会 TLE。
剩下的就是基于比较的`nlogn`算法,以及基于特定条件的 O(n)算法。

由于平时很少用到`计数排序`等 O(n)的排序算法,一方面是空间复杂度不是常量,另一方面是其要求数据范围不是很大才行,不然会浪费很多空间。
但是这道题我感觉可以试一下。 在这里,我用了两种方法,一种是`计数排序`,一种是`快速排序`来解决。 大家也可以尝试用别的解法来解决。

### 解法一 - 计数排序

时间复杂度 O(n)空间复杂度 O(m) m 为数组中值的取值范围,在这道题就是`50000 * 2 + 1`

我们只需要准备一个数组取值范围的数字,然后遍历一遍,将每一个元素放到这个数组对应位置就好了,
放的规则是`索引为数字的值,value为出现的次数`

这样一次遍历,我们统计出了所有的数字出现的位置和次数。 我们再来一次遍历,将其输出到即可。

![sort-an-array-1](../assets/problems/912.sort-an-array-1.png)

### 解法二 - 快速排序

快速排序和归并排序都是分支思想来进行排序的算法, 并且二者都非常流行。 快速排序的核心点在于选择轴元素。

每次我们将数组分成两部分,一部分是比 pivot(轴元素)大的,另一部分是不比 pivot 大的。 我们不断重复这个过程,
直到问题的规模缩小的寻常(即只有一个元素的情况)。

快排的核心点在于如何选择轴元素,一般而言,选择轴元素有三种策略:
最简单的思路就是两两组合,找出最大的,妥妥超时,我们来看下代码:

```python
class Solution:
def maxScoreSightseeingPair(self, A: List[int]) -> int:
n = len(A)
res = 0
for i in range(n - 1):
for j in range(i + 1, n):
res = max(res, A[i] + A[j] + i - j)
return res
```

- 数组最左边的元素
- 数组最右边的元素
- 数组中间的元素(我采用的是这种,大家可以尝试下别的)
- 数组随机一项元素
我们思考如何优化。 其实我们可以遍历一遍数组,对于数组的每一项`A[j] - j` 我们都去前面找`最大`的 A[i] + i (这样才能保证结果最大)。

![sort-an-array-2](../assets/problems/912.sort-an-array-2.png)
我们考虑使用动态规划来解决, 我们使用 dp[i] 来表示 数组 A 前 i 项的`A[i] + i`的最大值。

(图片来自: https://www.geeksforgeeks.org/quick-sort/)
```python
class Solution:
def maxScoreSightseeingPair(self, A: List[int]) -> int:
n = len(A)
dp = [float('-inf')] * (n + 1)
res = 0
for i in range(n):
dp[i + 1] = max(dp[i], A[i] + i)
res = max(res, dp[i] + A[i] - i)
return res
```

> 图片中的轴元素是最后面的元素,而提供的解法是中间元素,这点需要注意,但是这并不影响理解
如上其实我们发现,dp[i + 1] 只和 dp[i] 有关,这是一个空间优化的信号。我们其实可以使用一个变量来记录,而不必要使用一个数组,代码见下方

## 关键点解析

- 排序算法
- 注意题目的限制条件从而选择合适的算法

## 代码

计数排序:

代码支持: JavaScript

```js
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
const counts = Array(50000 * 2 + 1).fill(0);
const res = [];
for (const num of nums) counts[50000 + num] += 1;
for (let i in counts) {
while (counts[i]--) {
res.push(i - 50000);
}
}
return res;
};
```python
class Solution:
def maxScoreSightseeingPair(self, A: List[int]) -> int:
n = len(A)
pre = A[0] + 0
res = 0
for i in range(1, n):
res = max(res, pre + A[i] - i)
pre = max(pre, A[i] + i)
return res
```

快速排序:

代码支持: JavaScript

```js
function swap(nums, a, b) {
const temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}

function helper(nums, start, end) {
if (start >= end) return;
const pivotIndex = start + ((end - start) >>> 1);
const pivot = nums[pivotIndex];
let i = start;
let j = end;
while (i <= j) {
while (nums[i] < pivot) i++;
while (nums[j] > pivot) j--;
if (i <= j) {
swap(nums, i, j);
i++;
j--;
}
}
helper(nums, start, j);
helper(nums, i, end);
}

/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
helper(nums, 0, nums.length - 1);
return nums;
};
## 小技巧

Python 的代码如果不使用 max,而是使用 if else 效率目测会更高,大家可以试一下。

```python
class Solution:
def maxScoreSightseeingPair(self, A: List[int]) -> int:
n = len(A)
pre = A[0] + 0
res = 0
for i in range(1, n):
# res = max(res, pre + A[i] - i)
# pre = max(pre, A[i] + i)
res = res if res > pre + A[i] - i else pre + A[i] - i
pre = pre if pre > A[i] + i else A[i] + i
return res
```

## 扩展

- 你是否可以用其他方式排序算法解决

## 参考

- [QuickSort - geeksforgeeks](https://www.geeksforgeeks.org/quick-sort/)

0 comments on commit 16a3cc5

Please sign in to comment.