Skip to content

Commit

Permalink
更新题解列表
Browse files Browse the repository at this point in the history
  • Loading branch information
itcharge committed Dec 29, 2023
1 parent 478ff99 commit a684af2
Show file tree
Hide file tree
Showing 16 changed files with 642 additions and 172 deletions.
52 changes: 41 additions & 11 deletions Solutions/0016. 最接近的三数之和.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,50 @@

## 题目大意

给你一个整数数组 `nums` 和 一个目标值 `target`
**描述**:给定一个整数数组 $nums$ 和 一个目标值 $target$

要求:从 `nums` 中选出三个整数,使它们的和与 `target` 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。
**要求**:从 $nums$ 中选出三个整数,使它们的和与 $target$ 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。

**说明**

- $3 \le nums.length \le 1000$。
- $-1000 \le nums[i] \le 1000$。
- $-10^4 \le target \le 10^4$。

**示例**

- 示例 1:

```python
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2)。
```

- 示例 2:

```python
输入:nums = [0,0,0], target = 1
输出:0
```

## 解题思路

### 思路 1:对撞指针

直接暴力枚举三个数的时间复杂度是 $O(n^3)$。很明显的容易超时。考虑使用双指针减少循环内的时间复杂度。具体做法如下:

- 先对数组进行从小到大排序,使用 `ans` 记录最接近的三数之和。
- 遍历数组,对于数组元素 `nums[i]`,使用两个指针 `left``right``left` 指向第 `0` 个元素位置,`right` 指向第 `i - 1` 个元素位置。
- 计算 `nums[i]``nums[left]``nums[right]` 的和与 `target` 的差值,将其与 `ans``target` 的差值作比较。如果差值小,则更新 `ans`
- 如果 `nums[i] + nums[left] + nums[right] < target`,则说明 `left` 小了,应该将 `left` 右移,继续查找。
- 如果 `nums[i] + nums[left] + nums[right] >= target`,则说明 `right` 太大了,应该将 `right` 左移,然后继续判断。
-`left == right` 时,区间搜索完毕,继续遍历 `nums[i + 1]`
- 最后输出 `ans`
- 先对数组进行从小到大排序,使用 $ans$ 记录最接近的三数之和。
- 遍历数组,对于数组元素 $nums[i]$,使用两个指针 $left$、$right$。$left$ 指向第 $0$ 个元素位置,$right$ 指向第 $i - 1$ 个元素位置。
- 计算 $nums[i]$、$nums[left]$、$nums[right]$ 的和与 $target$ 的差值,将其与 $ans$$target$ 的差值作比较。如果差值小,则更新 $ans$
- 如果 $nums[i] + nums[left] + nums[right] < target$,则说明 $left$ 小了,应该将 $left$ 右移,继续查找。
- 如果 $nums[i] + nums[left] + nums[right] \ge target$,则说明 $right$ 太大了,应该将 $right$ 左移,然后继续判断。
-$left == right$ 时,区间搜索完毕,继续遍历 $nums[i + 1]$
- 最后输出 $ans$

这种思路使用了两重循环,其中内层循环当 `left == right` 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$。
这种思路使用了两重循环,其中内层循环当 $left == right$ 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$。

## 代码
### 思路 1:代码

```python
class Solution:
Expand All @@ -50,3 +75,8 @@ class Solution:
return res
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n^2)$,其中 $n$ 为数组中元素的个数。
- **空间复杂度**:$O(\log n)$,排序需要 $\log n$ 的栈空间。

54 changes: 43 additions & 11 deletions Solutions/0259. 较小的三数之和.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,54 @@

## 题目大意

给定一个长度为 `n` 的整数数组和一个目标值 `target`
**描述**给定一个长度为 $n$ 的整数数组和一个目标值 $target$

要求:寻找能够使条件 `nums[i] + nums[j] + nums[k] < target` 成立的三元组 (`i`, `j`, `k`) 的个数(`0 <= i < j < k < n`)。
**要求**:寻找能够使条件 $nums[i] + nums[j] + nums[k] < target$ 成立的三元组 ($i$, $j$, $k$) 的个数($0 <= i < j < k < n$)。

注意:最好在 $O(n^2)$ 的时间复杂度内解决问题。
**说明**

- 最好在 $O(n^2)$ 的时间复杂度内解决问题。
- $n == nums.length$。
- $0 \le n \le 3500$。
- $-100 \le nums[i] \le 100$。
- $-100 \le target \le 100$。

**示例**

- 示例 1:

```python
输入: nums = [-2,0,1,3], target = 2
输出: 2
解释: 因为一共有两个三元组满足累加和小于 2:
[-2,0,1]
[-2,0,3]
```

- 示例 2:

```python
输入: nums = [], target = 0
输出: 0
```

## 解题思路

### 思路 1:排序 + 双指针

三元组直接枚举的时间复杂度是 $O(n^3)$,明显不符合题目要求。那么可以考虑使用双指针减少循环内的时间复杂度。具体做法如下:

- 先对数组进行从小到大排序。
- 遍历数组,对于数组元素 `nums[i]`,使用两个指针 `left``right``left` 指向第 `i + 1` 个元素位置,`right` 指向数组的最后一个元素位置。
- 在区间 `[left, right]` 中查找满足 `nums[i] + nums[left] + nums[right] < target`的方案数。
- 计算 `nums[i]``nums[left]``nums[right]` 的和,将其与 `target` 比较。
- 如果 `nums[i] + nums[left] + nums[right] < target`,则说明 `i``left``right` 作为三元组满足题目要求,同时说明区间 `[left, right]` 中的元素作为 `right` 都满足条件,此时将 `left` 右移,继续判断。
- 如果 `nums[i] + nums[left] + nums[right] >= target`,则说明 `right` 太大了,应该缩小 `right`,然后继续判断。
-`left == right` 时,区间搜索完毕,继续遍历 `nums[i + 1]`
- 遍历数组,对于数组元素 $nums[i]$,使用两个指针 $left$、$right$。$left$ 指向第 $i + 1$ 个元素位置,$right$ 指向数组的最后一个元素位置。
- 在区间 $[left, right]$ 中查找满足 $nums[i] + nums[left] + nums[right] < target$的方案数。
- 计算 $nums[i]$、$nums[left]$、$nums[right]$ 的和,将其与 $target$ 比较。
- 如果 $nums[i] + nums[left] + nums[right] < target$,则说明 $i$、$left$、$right$ 作为三元组满足题目要求,同时说明区间 $[left, right]$ 中的元素作为 $right$ 都满足条件,此时将 $left$ 右移,继续判断。
- 如果 $nums[i] + nums[left] + nums[right] \ge target$,则说明 $right$ 太大了,应该缩小 $right$,然后继续判断。
-$left == right$ 时,区间搜索完毕,继续遍历 $nums[i + 1]$

这种思路使用了两重循环,其中内层循环当 `left == right` 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$,符合题目要求。
这种思路使用了两重循环,其中内层循环当 $left == right$ 时循环结束,时间复杂度为 $O(n)$,外层循环时间复杂度也是 $O(n)$。所以算法的整体时间复杂度为 $O(n^2)$,符合题目要求。

## 代码
### 思路 1:代码

```python
class Solution:
Expand All @@ -49,3 +76,8 @@ class Solution:
return res
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n^2)$。
- **空间复杂度**:$O(\log n)$。

49 changes: 41 additions & 8 deletions Solutions/0270. 最接近的二叉搜索树值.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,48 @@

## 题目大意

给定一个不为空的二叉搜索树,以及一个目标值 target。要求在二叉搜索树中找到最接近目标值 target 的数值。
**描述**:给定一个不为空的二叉搜索树的根节点,以及一个目标值 $target$。

**要求**:在二叉搜索树中找到最接近目标值 $target$ 的数值。

**说明**

- 树中节点的数目在范围 $[1, 10^4]$ 内。
- $0 \le Node.val \le 10^9$。
- $-10^9 \le target \le 10^9$。

**示例**

- 示例 1:

![](https://assets.leetcode.com/uploads/2021/03/12/closest1-1-tree.jpg)

```python
输入:root = [4,2,5,1,3], target = 3.714286
输出:4
```

- 示例 2:

```python
输入:root = [1], target = 4.428571
输出:1
```

## 解题思路

题目中最接近目标值 target 的数值指的就是 与 target 相减绝对值最小的数值。
### 思路 1:二分查找算法

题目中最接近目标值 $target$ 的数值指的就是与 $target$ 相减绝对值最小的数值。

而且根据二叉搜索树的性质,我们可以利用二分搜索的方式,查找与 target 相减绝对值最小的数值。具体做法为:
而且根据二叉搜索树的性质,我们可以利用二分搜索的方式,查找与 $target$ 相减绝对值最小的数值。具体做法为:

- 定义一个变量 closest 表示与 target 最接近的数值,初始赋值为根节点的值 root.val。
- 判断当前节点的值域 closet 值哪个更接近 target,如果当前值更接近,则更新 closest。
- 如果 target < 当前节点值,则从当前节点的左子树继续查找。
- 如果 target ≥ 当前节点值,则从当前节点的右子树继续查找。
- 定义一个变量 $closest$ 表示与 $target$ 最接近的数值,初始赋值为根节点的值 $root.val$
- 判断当前节点的值域 $closet$ 值哪个更接近 $target$,如果当前值更接近,则更新 $closest$
- 如果 $target$ < 当前节点值,则从当前节点的左子树继续查找。
- 如果 $target$ ≥ 当前节点值,则从当前节点的右子树继续查找。

## 代码
### 思路 1:代码

```python
class Solution:
Expand All @@ -38,3 +66,8 @@ class Solution:
return closest
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(\log n)$,其中 $n$ 为二叉搜索树的节点个数。
- **空间复杂度**:$O(1)$。

76 changes: 52 additions & 24 deletions Solutions/0360. 有序转化数组.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,60 @@

## 题目大意

给定一个已经排好的整数数组 `nums` 和整数 `a``b``c`
**描述**给定一个已经排好的整数数组 $nums$ 和整数 $a$、$b$、$c$

要求:对于数组中的每一个数 `x`,计算函数值 $f(x) = ax^2 + bx + c$,请将函数值产生的数组返回。
**要求**:对于数组中的每一个数 $x$,计算函数值 $f(x) = ax^2 + bx + c$,请将函数值产生的数组返回。

注意:返回的这个数组必须按照升序排列,并且我们所期望的解法时间复杂度为 $O(n)$。
**说明**

- 返回的这个数组必须按照升序排列,并且我们所期望的解法时间复杂度为 $O(n)$。
- $1 \le nums.length \le 200$。
- $-100 \le nums[i], a, b, c \le 100$。
- $nums$ 按照升序排列。

**示例**

- 示例 1:

```python
输入: nums = [-4,-2,2,4], a = 1, b = 3, c = 5
输出: [3,9,15,33]
```

- 示例 2:

```python
输入: nums = [-4,-2,2,4], a = -1, b = 3, c = 5
输出: [-23,-5,1,7]
```

## 解题思路

### 思路 1: 数学 + 对撞指针

这是一道数学题。需要根据一元二次函数的性质来解决问题。因为返回的数组必须按照升序排列,并且期望的解法时间复杂度为 $O(n)$。这就不能先计算再排序了,而是要在线性时间复杂度内考虑问题。

我们先定义一个函数用来计算 `f(x)`。然后进行分情况讨论。

- 如果 `a == 0`,说明函数是一条直线。则根据 `b` 值的正负来确定数组遍历顺序。
- 如果 `b >= 0`,说明这条直线是一条递增直线。则按照从头到尾的顺序依次计算函数值,并依次存入答案数组。
- 如果 `b < 0`,说明这条直线是一条递减直线。则按照从尾到头的顺序依次计算函数值,并依次存入答案数组。
- 如果 `a > 0`,说明函数是一条开口向上的抛物线,最小值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越大。则可以使用双指针从远到近,由大到小依次填入数组。具体步骤如下:
- 使用双指针 `left``right`,令 `left` 指向数组第一个元素位置,`right` 指向数组最后一个元素位置。再定义 `index = len(nums) - 1` 作为答案数组填入顺序的索引值。
- 比较 `left - diad``right - diad` 的绝对值大小。大的就是目前距离 `diad` 最远的那个。
- 如果 `abs(nums[left] - diad)` 更大,则将其填入答案数组对应位置,并令 `left += 1`
- 如果 `abs(nums[right] - diad)` 更大,则将其填入答案数组对应位置,并令 `right -= 1`
-`index -= 1`
- 直到 `left == right`,最后将 `nums[left]` 填入答案数组对应位置。
- 如果 `a < 0`,说明函数是一条开口向下的抛物线,最大值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越小。则可以使用双指针从远到近,由小到大一次填入数组。具体步骤如下:
- 使用双指针 `left``right`,令 `left` 指向数组第一个元素位置,`right` 指向数组最后一个元素位置。再定义 `index = 0` 作为答案数组填入顺序的索引值。
- 比较 `left - diad``right - diad` 的绝对值大小。大的就是目前距离 `diad` 最远的那个。
- 如果 `abs(nums[left] - diad)` 更大,则将其填入答案数组对应位置,并令 `left += 1`
- 如果 `abs(nums[right] - diad)` 更大,则将其填入答案数组对应位置,并令 `right -= 1`
-`index += 1`
- 直到 `left == right`,最后将 `nums[left]` 填入答案数组对应位置。

## 代码
我们先定义一个函数用来计算 $f(x)$。然后进行分情况讨论。

- 如果 $a == 0$,说明函数是一条直线。则根据 $b$ 值的正负来确定数组遍历顺序。
- 如果 $b \ge 0$,说明这条直线是一条递增直线。则按照从头到尾的顺序依次计算函数值,并依次存入答案数组。
- 如果 $b < 0$,说明这条直线是一条递减直线。则按照从尾到头的顺序依次计算函数值,并依次存入答案数组。
- 如果 $a > 0$,说明函数是一条开口向上的抛物线,最小值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越大。则可以使用双指针从远到近,由大到小依次填入数组。具体步骤如下:
- 使用双指针 $left$、$right$,令 $left$ 指向数组第一个元素位置,$right$ 指向数组最后一个元素位置。再定义 $index = len(nums) - 1$ 作为答案数组填入顺序的索引值。
- 比较 $left - diad$$right - diad$ 的绝对值大小。大的就是目前距离 $diad$ 最远的那个。
- 如果 $abs(nums[left] - diad)$ 更大,则将其填入答案数组对应位置,并令 $left += 1$
- 如果 $abs(nums[right] - diad)$ 更大,则将其填入答案数组对应位置,并令 $right -= 1$
-$index -= 1$
- 直到 $left == right$,最后将 $nums[left]$ 填入答案数组对应位置。
- 如果 $a < 0$,说明函数是一条开口向下的抛物线,最大值横坐标为 $diad = \frac{-b}{2.0 * a}$,离 diad 越远,函数值越小。则可以使用双指针从远到近,由小到大一次填入数组。具体步骤如下:
- 使用双指针 $left$、$right$,令 $left$ 指向数组第一个元素位置,$right$ 指向数组最后一个元素位置。再定义 $index = 0$ 作为答案数组填入顺序的索引值。
- 比较 $left - diad$$right - diad$ 的绝对值大小。大的就是目前距离 $diad$ 最远的那个。
- 如果 $abs(nums[left] - diad)$ 更大,则将其填入答案数组对应位置,并令 $left += 1$
- 如果 $abs(nums[right] - diad)$ 更大,则将其填入答案数组对应位置,并令 $right -= 1$
-$index += 1$
- 直到 $left == right$,最后将 $nums[left]$ 填入答案数组对应位置。

### 思路 1:代码

```python
class Solution:
Expand Down Expand Up @@ -93,3 +116,8 @@ class Solution:
return res
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n)$。
- **空间复杂度**:$O(1)$,不考虑最终返回值的空间占用。

49 changes: 42 additions & 7 deletions Solutions/0410. 分割数组的最大值.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,51 @@

## 题目大意

给定一个非负整数数组 nums 和一个整数 m,将数组分成 m 个非空的连续子数组,要求使 m 个子数组各自和的最大值最小,并求出子数组各自和的最大值。
**描述**:给定一个非负整数数组 $nums$ 和一个整数 $k$,将数组分成 $m$ 个非空的连续子数组。

**要求**:使 $m$ 个子数组各自和的最大值最小,并求出子数组各自和的最大值。

**说明**

- $1 \le nums.length \le 1000$。
- $0 \le nums[i] \le 10^6$。
- $1 \le k \le min(50, nums.length)$。

**示例**

- 示例 1:

```python
输入:nums = [7,2,5,10,8], k = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
```

- 示例 2:

```python
输入:nums = [1,2,3,4,5], k = 2
输出:9
```

## 解题思路

先来理解清楚题意。题目的目的是使得 m 个连续子数组各自和的最大值最小。意思是将数组按顺序分成 m 个子数组,然后计算每个子数组的和,然后找出 m 个和中的最大值,要求使这个最大值尽可能小。最后输出这个尽可能小的和最大值。
### 思路 1:二分查找算法

可以用二分查找来找这个子数组和的最大值,我们用 ans 来表示这个值。ans 最小为数组 nums 所有元素的最大值,最大为数组 nums 所有元素的和。即 ans 范围是 [max(nums), sum(nums)]
先来理解清楚题意。题目的目的是使得 $m$ 个连续子数组各自和的最大值最小。意思是将数组按顺序分成 $m$ 个子数组,然后计算每个子数组的和,然后找出 $m$ 个和中的最大值,要求使这个最大值尽可能小。最后输出这个尽可能小的和最大值

所以就确定了二分查找的两个指针位置。left 指向 max(nums),right 指向 sum(nums)。然后取中间值 mid,计算当子数组和的最大值为 mid 时,所需要分割的子数组最少个数
可以用二分查找来找这个子数组和的最大值,我们用 $ans$ 来表示这个值。$ans$ 最小为数组 $nums$ 所有元素的最大值,最大为数组 $nums$ 所有元素的和。即 $ans$ 范围是 $[max(nums), sum(nums)]$

- 如果需要分割的子数组最少个数大于 m 个,则说明子数组和的最大值取小了,不满足条件,应该继续调大,将 left 右移,从右区间继续查找。
- 如果需要分割的子数组最少个数小于或等于 m 个,则说明子数组和的最大值满足条件,并且还可以继续调小,将 right 左移,从左区间继续查找,看是否有更小的数组和满足条件。
所以就确定了二分查找的两个指针位置。$left$ 指向 $max(nums)$,$right$ 指向 $sum(nums)$。然后取中间值 $mid$,计算当子数组和的最大值为 mid 时,所需要分割的子数组最少个数。

- 如果需要分割的子数组最少个数大于 $m$ 个,则说明子数组和的最大值取小了,不满足条件,应该继续调大,将 $left$ 右移,从右区间继续查找。
- 如果需要分割的子数组最少个数小于或等于 $m$ 个,则说明子数组和的最大值满足条件,并且还可以继续调小,将 $right$ 左移,从左区间继续查找,看是否有更小的数组和满足条件。
- 最终,返回符合条件的最小值即可。

## 代码
### 思路 1:代码

```python
class Solution:
Expand All @@ -50,3 +80,8 @@ class Solution:
return left
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n \times \log (\sum nums))$,其中 $n$ 为数组中的元素个数。
- **空间复杂度**:$O(1)$。

Loading

0 comments on commit a684af2

Please sign in to comment.