next(needle.size());
getNext(&next[0], needle);
int j = 0;
for (int i = 0; i < haystack.size(); i++) {
while(j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == needle.size() ) {
return (i - needle.size() + 1);
}
}
return -1;
}
};
```
* 时间复杂度: O(n + m)
* 空间复杂度: O(m)
## 总结
我们介绍了什么是KMP,KMP可以解决什么问题,然后分析KMP算法里的next数组,知道了next数组就是前缀表,再分析为什么要是前缀表而不是什么其他表。
接着从给出的模式串中,我们一步一步的推导出了前缀表,得出前缀表无论是统一减一还是不减一得到的next数组仅仅是kmp的实现方式的不同。
其中还分析了KMP算法的时间复杂度,并且和暴力方法做了对比。
然后先用前缀表统一减一得到的next数组,求得文本串s里是否出现过模式串t,并给出了具体分析代码。
又给出了直接用前缀表作为next数组,来做匹配的实现代码。
可以说把KMP的每一个细微的细节都扣了出来,毫无遮掩的展示给大家了!
## 其他语言版本
### Java:
```Java
class Solution {
/**
牺牲空间,换取最直白的暴力法
时间复杂度 O(n * m)
空间 O(n + m)
*/
public int strStr(String haystack, String needle) {
// 获取 haystack 和 needle 的长度
int n = haystack.length(), m = needle.length();
// 将字符串转换为字符数组,方便索引操作
char[] s = haystack.toCharArray(), p = needle.toCharArray();
// 遍历 haystack 字符串
for (int i = 0; i < n - m + 1; i++) {
// 初始化匹配的指针
int a = i, b = 0;
// 循环检查 needle 是否在当前位置开始匹配
while (b < m && s[a] == p[b]) {
// 如果当前字符匹配,则移动指针
a++;
b++;
}
// 如果 b 等于 m,说明 needle 已经完全匹配,返回当前位置 i
if (b == m) return i;
}
// 如果遍历完毕仍未找到匹配的子串,则返回 -1
return -1;
}
}
```
```Java
class Solution {
/**
* 基于窗口滑动的算法
*
* 时间复杂度:O(m*n)
* 空间复杂度:O(1)
* 注:n为haystack的长度,m为needle的长度
*/
public int strStr(String haystack, String needle) {
int m = needle.length();
// 当 needle 是空字符串时我们应当返回 0
if (m == 0) {
return 0;
}
int n = haystack.length();
if (n < m) {
return -1;
}
int i = 0;
int j = 0;
while (i < n - m + 1) {
// 找到首字母相等
while (i < n && haystack.charAt(i) != needle.charAt(j)) {
i++;
}
if (i == n) {// 没有首字母相等的
return -1;
}
// 遍历后续字符,判断是否相等
i++;
j++;
while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
}
if (j == m) {// 找到
return i - j;
} else {// 未找到
i -= j - 1;
j = 0;
}
}
return -1;
}
}
```
```java
// 方法一
class Solution {
public void getNext(int[] next, String s){
int j = -1;
next[0] = j;
for (int i = 1; i < s.length(); i++){
while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
j=next[j];
}
if(s.charAt(i) == s.charAt(j+1)){
j++;
}
next[i] = j;
}
}
public int strStr(String haystack, String needle) {
if(needle.length()==0){
return 0;
}
int[] next = new int[needle.length()];
getNext(next, needle);
int j = -1;
for(int i = 0; i < haystack.length(); i++){
while(j>=0 && haystack.charAt(i) != needle.charAt(j+1)){
j = next[j];
}
if(haystack.charAt(i) == needle.charAt(j+1)){
j++;
}
if(j == needle.length()-1){
return (i-needle.length()+1);
}
}
return -1;
}
}
```
```Java
class Solution {
//前缀表(不减一)Java实现
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.length(); i++) {
while (j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
if (needle.charAt(j) == haystack.charAt(i))
j++;
if (j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
private void getNext(int[] next, String s) {
int j = 0;
next[0] = 0;
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
}
}
```
### Python3:
(版本一)前缀表(减一)
```python
class Solution:
def getNext(self, next, s):
j = -1
next[0] = j
for i in range(1, len(s)):
while j >= 0 and s[i] != s[j+1]:
j = next[j]
if s[i] == s[j+1]:
j += 1
next[i] = j
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = -1
for i in range(len(haystack)):
while j >= 0 and haystack[i] != needle[j+1]:
j = next[j]
if haystack[i] == needle[j+1]:
j += 1
if j == len(needle) - 1:
return i - len(needle) + 1
return -1
```
(版本二)前缀表(不减一)
```python
class Solution:
def getNext(self, next: List[int], s: str) -> None:
j = 0
next[0] = 0
for i in range(1, len(s)):
while j > 0 and s[i] != s[j]:
j = next[j - 1]
if s[i] == s[j]:
j += 1
next[i] = j
def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = 0
for i in range(len(haystack)):
while j > 0 and haystack[i] != needle[j]:
j = next[j - 1]
if haystack[i] == needle[j]:
j += 1
if j == len(needle):
return i - len(needle) + 1
return -1
```
(版本三)暴力法
```python
class Solution(object):
def strStr(self, haystack, needle):
"""
:type haystack: str
:type needle: str
:rtype: int
"""
m, n = len(haystack), len(needle)
for i in range(m):
if haystack[i:i+n] == needle:
return i
return -1
```
(版本四)使用 index
```python
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
try:
return haystack.index(needle)
except ValueError:
return -1
```
(版本五)使用 find
```python
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
return haystack.find(needle)
```
### Go:
```go
// 方法一:前缀表使用减1实现
// getNext 构造前缀表next
// params:
// next 前缀表数组
// s 模式串
func getNext(next []int, s string) {
j := -1 // j表示 最长相等前后缀长度
next[0] = j
for i := 1; i < len(s); i++ {
for j >= 0 && s[i] != s[j+1] {
j = next[j] // 回退前一位
}
if s[i] == s[j+1] {
j++
}
next[i] = j // next[i]是i(包括i)之前的最长相等前后缀长度
}
}
func strStr(haystack string, needle string) int {
if len(needle) == 0 {
return 0
}
next := make([]int, len(needle))
getNext(next, needle)
j := -1 // 模式串的起始位置 next为-1 因此也为-1
for i := 0; i < len(haystack); i++ {
for j >= 0 && haystack[i] != needle[j+1] {
j = next[j] // 寻找下一个匹配点
}
if haystack[i] == needle[j+1] {
j++
}
if j == len(needle)-1 { // j指向了模式串的末尾
return i - len(needle) + 1
}
}
return -1
}
```
```go
// 方法二: 前缀表无减一或者右移
// getNext 构造前缀表next
// params:
// next 前缀表数组
// s 模式串
func getNext(next []int, s string) {
j := 0
next[0] = j
for i := 1; i < len(s); i++ {
for j > 0 && s[i] != s[j] {
j = next[j-1]
}
if s[i] == s[j] {
j++
}
next[i] = j
}
}
func strStr(haystack string, needle string) int {
n := len(needle)
if n == 0 {
return 0
}
j := 0
next := make([]int, n)
getNext(next, needle)
for i := 0; i < len(haystack); i++ {
for j > 0 && haystack[i] != needle[j] {
j = next[j-1] // 回退到j的前一位
}
if haystack[i] == needle[j] {
j++
}
if j == n {
return i - n + 1
}
}
return -1
}
```
### JavaScript:
> 前缀表统一减一
```javascript
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function (haystack, needle) {
if (needle.length === 0)
return 0;
const getNext = (needle) => {
let next = [];
let j = -1;
next.push(j);
for (let i = 1; i < needle.length; ++i) {
while (j >= 0 && needle[i] !== needle[j + 1])
j = next[j];
if (needle[i] === needle[j + 1])
j++;
next.push(j);
}
return next;
}
let next = getNext(needle);
let j = -1;
for (let i = 0; i < haystack.length; ++i) {
while (j >= 0 && haystack[i] !== needle[j + 1])
j = next[j];
if (haystack[i] === needle[j + 1])
j++;
if (j === needle.length - 1)
return (i - needle.length + 1);
}
return -1;
};
```
> 前缀表统一不减一
```javascript
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function (haystack, needle) {
if (needle.length === 0)
return 0;
const getNext = (needle) => {
let next = [];
let j = 0;
next.push(j);
for (let i = 1; i < needle.length; ++i) {
while (j > 0 && needle[i] !== needle[j])
j = next[j - 1];
if (needle[i] === needle[j])
j++;
next.push(j);
}
return next;
}
let next = getNext(needle);
let j = 0;
for (let i = 0; i < haystack.length; ++i) {
while (j > 0 && haystack[i] !== needle[j])
j = next[j - 1];
if (haystack[i] === needle[j])
j++;
if (j === needle.length)
return (i - needle.length + 1);
}
return -1;
};
```
### TypeScript:
> 前缀表统一减一
```typescript
function strStr(haystack: string, needle: string): number {
function getNext(str: string): number[] {
let next: number[] = [];
let j: number = -1;
next[0] = j;
for (let i = 1, length = str.length; i < length; i++) {
while (j >= 0 && str[i] !== str[j + 1]) {
j = next[j];
}
if (str[i] === str[j + 1]) {
j++;
}
next[i] = j;
}
return next;
}
if (needle.length === 0) return 0;
let next: number[] = getNext(needle);
let j: number = -1;
for (let i = 0, length = haystack.length; i < length; i++) {
while (j >= 0 && haystack[i] !== needle[j + 1]) {
j = next[j];
}
if (haystack[i] === needle[j + 1]) {
if (j === needle.length - 2) {
return i - j - 1;
}
j++;
}
}
return -1;
};
```
> 前缀表不减一
```typescript
// 不减一版本
function strStr(haystack: string, needle: string): number {
function getNext(str: string): number[] {
let next: number[] = [];
let j: number = 0;
next[0] = j;
for (let i = 1, length = str.length; i < length; i++) {
while (j > 0 && str[i] !== str[j]) {
j = next[j - 1];
}
if (str[i] === str[j]) {
j++;
}
next[i] = j;
}
return next;
}
if (needle.length === 0) return 0;
let next: number[] = getNext(needle);
let j: number = 0;
for (let i = 0, length = haystack.length; i < length; i++) {
while (j > 0 && haystack[i] !== needle[j]) {
j = next[j - 1];
}
if (haystack[i] === needle[j]) {
if (j === needle.length - 1) {
return i - j;
}
j++;
}
}
return -1;
}
```
### Swift:
> 前缀表统一减一
```swift
func strStr(_ haystack: String, _ needle: String) -> Int {
let s = Array(haystack), p = Array(needle)
guard p.count != 0 else { return 0 }
// 2 pointer
var j = -1
var next = [Int](repeating: -1, count: needle.count)
// KMP
getNext(&next, needle: p)
for i in 0 ..< s.count {
while j >= 0 && s[i] != p[j + 1] {
//不匹配之后寻找之前匹配的位置
j = next[j]
}
if s[i] == p[j + 1] {
//匹配,双指针同时后移
j += 1
}
if j == (p.count - 1) {
//出现匹配字符串
return i - p.count + 1
}
}
return -1
}
//前缀表统一减一
func getNext(_ next: inout [Int], needle: [Character]) {
var j: Int = -1
next[0] = j
// i 从 1 开始
for i in 1 ..< needle.count {
while j >= 0 && needle[i] != needle[j + 1] {
j = next[j]
}
if needle[i] == needle[j + 1] {
j += 1;
}
next[i] = j
}
print(next)
}
```
> 前缀表右移
```swift
func strStr(_ haystack: String, _ needle: String) -> Int {
let s = Array(haystack), p = Array(needle)
guard p.count != 0 else { return 0 }
var j = 0
var next = [Int].init(repeating: 0, count: p.count)
getNext(&next, p)
for i in 0 ..< s.count {
while j > 0 && s[i] != p[j] {
j = next[j]
}
if s[i] == p[j] {
j += 1
}
if j == p.count {
return i - p.count + 1
}
}
return -1
}
// 前缀表后移一位,首位用 -1 填充
func getNext(_ next: inout [Int], _ needle: [Character]) {
guard needle.count > 1 else { return }
var j = 0
next[0] = j
for i in 1 ..< needle.count-1 {
while j > 0 && needle[i] != needle[j] {
j = next[j-1]
}
if needle[i] == needle[j] {
j += 1
}
next[i] = j
}
next.removeLast()
next.insert(-1, at: 0)
}
```
> 前缀表统一不减一
```swift
func strStr(_ haystack: String, _ needle: String) -> Int {
let s = Array(haystack), p = Array(needle)
guard p.count != 0 else { return 0 }
var j = 0
var next = [Int](repeating: 0, count: needle.count)
// KMP
getNext(&next, needle: p)
for i in 0 ..< s.count {
while j > 0 && s[i] != p[j] {
j = next[j-1]
}
if s[i] == p[j] {
j += 1
}
if j == p.count {
return i - p.count + 1
}
}
return -1
}
//前缀表
func getNext(_ next: inout [Int], needle: [Character]) {
var j = 0
next[0] = j
for i in 1 ..< needle.count {
while j>0 && needle[i] != needle[j] {
j = next[j-1]
}
if needle[i] == needle[j] {
j += 1
}
next[i] = j
}
}
```
### PHP:
> 前缀表统一减一
```php
function strStr($haystack, $needle) {
if (strlen($needle) == 0) return 0;
$next= [];
$this->getNext($next,$needle);
$j = -1;
for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始
while($j >= 0 && $haystack[$i] != $needle[$j + 1]) {
$j = $next[$j];
}
if ($haystack[$i] == $needle[$j + 1]) {
$j++;
}
if ($j == (strlen($needle) - 1) ) {
return ($i - strlen($needle) + 1);
}
}
return -1;
}
function getNext(&$next, $s){
$j = -1;
$next[0] = $j;
for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始
while ($j >= 0 && $s[$i] != $s[$j + 1]) {
$j = $next[$j];
}
if ($s[$i] == $s[$j + 1]) {
$j++;
}
$next[$i] = $j;
}
}
```
> 前缀表统一不减一
```php
function strStr($haystack, $needle) {
if (strlen($needle) == 0) return 0;
$next= [];
$this->getNext($next,$needle);
$j = 0;
for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始
while($j > 0 && $haystack[$i] != $needle[$j]) {
$j = $next[$j-1];
}
if ($haystack[$i] == $needle[$j]) {
$j++;
}
if ($j == strlen($needle)) {
return ($i - strlen($needle) + 1);
}
}
return -1;
}
function getNext(&$next, $s){
$j = 0;
$next[0] = $j;
for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始
while ($j > 0 && $s[$i] != $s[$j]) {
$j = $next[$j-1];
}
if ($s[$i] == $s[$j]) {
$j++;
}
$next[$i] = $j;
}
}
```
### Rust:
> 前缀表统一不减一
```Rust
impl Solution {
pub fn get_next(next: &mut Vec, s: &Vec) {
let len = s.len();
let mut j = 0;
for i in 1..len {
while j > 0 && s[i] != s[j] {
j = next[j - 1];
}
if s[i] == s[j] {
j += 1;
}
next[i] = j;
}
}
pub fn str_str(haystack: String, needle: String) -> i32 {
let (haystack_len, needle_len) = (haystack.len(), needle.len());
if haystack_len < needle_len { return -1;}
let (haystack, needle) = (haystack.chars().collect::>(), needle.chars().collect::>());
let mut next: Vec = vec![0; haystack_len];
Self::get_next(&mut next, &needle);
let mut j = 0;
for i in 0..haystack_len {
while j > 0 && haystack[i] != needle[j] {
j = next[j - 1];
}
if haystack[i] == needle[j] {
j += 1;
}
if j == needle_len {
return (i - needle_len + 1) as i32;
}
}
return -1;
}
}
```
> 前缀表统一减一
```rust
impl Solution {
pub fn get_next(next_len: usize, s: &Vec) -> Vec {
let mut next = vec![-1; next_len];
let mut j = -1;
for i in 1..s.len() {
while j >= 0 && s[(j + 1) as usize] != s[i] {
j = next[j as usize];
}
if s[i] == s[(j + 1) as usize] {
j += 1;
}
next[i] = j;
}
next
}
pub fn str_str(haystack: String, needle: String) -> i32 {
if haystack.len() < needle.len() {
return -1;
}
let (haystack_chars, needle_chars) = (
haystack.chars().collect::>(),
needle.chars().collect::>(),
);
let mut j = -1;
let next = Self::get_next(needle.len(), &needle_chars);
for (i, v) in haystack_chars.into_iter().enumerate() {
while j >= 0 && v != needle_chars[(j + 1) as usize] {
j = next[j as usize];
}
if v == needle_chars[(j + 1) as usize] {
j += 1;
}
if j == needle_chars.len() as i32 - 1 {
return (i - needle_chars.len() + 1) as i32;
}
}
-1
}
}
```
>前缀表统一不减一
```csharp
public int StrStr(string haystack, string needle)
{
if (string.IsNullOrEmpty(needle))
return 0;
if (needle.Length > haystack.Length || string.IsNullOrEmpty(haystack))
return -1;
return KMP(haystack, needle);
}
public int KMP(string haystack, string needle)
{
int[] next = GetNext(needle);
int i = 0, j = 0;
while (i < haystack.Length)
{
if (haystack[i] == needle[j])
{
i++;
j++;
}
if (j == needle.Length)
return i-j;
else if (i < haystack.Length && haystack[i] != needle[j])
if (j != 0)
{
j = next[j - 1];
}
else
{
i++;
}
}
return -1;
}
public int[] GetNext(string needle)
{
int[] next = new int[needle.Length];
next[0] = 0;
int i = 1, j = 0;
while (i < needle.Length)
{
if (needle[i] == needle[j])
{
next[i++] = ++j;
}
else
{
if (j == 0)
{
next[i++] = 0;
}
else
{
j = next[j - 1];
}
}
}
return next;
}
```
### C:
> 前缀表统一右移和减一
```c
int *build_next(char* needle, int len) {
int *next = (int *)malloc(len * sizeof(int));
assert(next); // 确保分配成功
// 初始化next数组
next[0] = -1; // next[0] 设置为 -1,表示没有有效前缀匹配
if (len <= 1) { // 如果模式串长度小于等于 1,直接返回
return next;
}
next[1] = 0; // next[1] 设置为 0,表示第一个字符没有公共前后缀
// 构建next数组, i 从模式串的第三个字符开始, j 指向当前匹配的最长前缀长度
int i = 2, j = 0;
while (i < len) {
if (needle[i - 1] == needle[j]) {
j++;
next[i] = j;
i++;
} else if (j > 0) {
// 如果不匹配且 j > 0, 回退到次长匹配前缀的长度
j = next[j];
} else {
next[i] = 0;
i++;
}
}
return next;
}
int strStr(char* haystack, char* needle) {
int needle_len = strlen(needle);
int haystack_len = strlen(haystack);
int *next = build_next(needle, needle_len);
int i = 0, j = 0; // i 指向主串的当前起始位置, j 指向模式串的当前匹配位置
while (i <= haystack_len - needle_len) {
if (haystack[i + j] == needle[j]) {
j++;
if (j == needle_len) {
free(next);
next = NULL
return i;
}
} else {
i += j - next[j]; // 调整主串的起始位置
j = j > 0 ? next[j] : 0;
}
}
free(next);
next = NULL;
return -1;
}
```
================================================
FILE: problems/0031.下一个排列.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
# 31.下一个排列
[力扣题目链接](https://leetcode.cn/problems/next-permutation/)
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
* 输入:nums = [1,2,3]
* 输出:[1,3,2]
示例 2:
* 输入:nums = [3,2,1]
* 输出:[1,2,3]
示例 3:
* 输入:nums = [1,1,5]
* 输出:[1,5,1]
示例 4:
* 输入:nums = [1]
* 输出:[1]
## 思路
一些同学可能手动写排列的顺序,都没有写对,那么写程序的话思路一定是有问题的了,我这里以1234为例子,把全排列都列出来。可以参考一下规律所在:
```
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1
```
如图:
以求1243为例,流程如图:
对应的C++代码如下:
```CPP
class Solution {
public:
void nextPermutation(vector& nums) {
for (int i = nums.size() - 1; i >= 0; i--) {
for (int j = nums.size() - 1; j > i; j--) {
if (nums[j] > nums[i]) {
swap(nums[j], nums[i]);
reverse(nums.begin() + i + 1, nums.end());
return;
}
}
}
// 到这里了说明整个数组都是倒序了,反转一下便可
reverse(nums.begin(), nums.end());
}
};
```
## 其他语言版本
### Java
```java
class Solution {
public void nextPermutation(int[] nums) {
for (int i = nums.length - 1; i >= 0; i--) {
for (int j = nums.length - 1; j > i; j--) {
if (nums[j] > nums[i]) {
// 交换
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
// [i + 1, nums.length) 内元素升序排序
Arrays.sort(nums, i + 1, nums.length);
return;
}
}
}
Arrays.sort(nums); // 不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
}
}
```
> 优化时间复杂度为O(N),空间复杂度为O(1)
```Java
class Solution {
public void nextPermutation(int[] nums) {
// 1.从后向前获取逆序区域的前一位
int index = findIndex(nums);
// 判断数组是否处于最小组合状态
if(index != 0){
// 2.交换逆序区域刚好大于它的最小数字
exchange(nums,index);
}
// 3.把原来的逆序区转为顺序
reverse(nums,index);
}
public static int findIndex(int [] nums){
for(int i = nums.length-1;i>0;i--){
if(nums[i]>nums[i-1]){
return i;
}
}
return 0;
}
public static void exchange(int [] nums, int index){
int head = nums[index-1];
for(int i = nums.length-1;i>0;i--){
if(head < nums[i]){
nums[index-1] = nums[i];
nums[i] = head;
break;
}
}
}
public static void reverse(int [] nums, int index){
for(int i = index,j = nums.length-1;i直接使用sorted()会开辟新的空间并返回一个新的list,故补充一个原地反转函数
```python
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
length = len(nums)
for i in range(length - 2, -1, -1): # 从倒数第二个开始
if nums[i]>=nums[i+1]: continue # 剪枝去重
for j in range(length - 1, i, -1):
if nums[j] > nums[i]:
nums[j], nums[i] = nums[i], nums[j]
self.reverse(nums, i + 1, length - 1)
return
self.reverse(nums, 0, length - 1)
def reverse(self, nums: List[int], left: int, right: int) -> None:
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
"""
265 / 265 个通过测试用例
状态:通过
执行用时: 36 ms
内存消耗: 14.9 MB
"""
```
### Go
```go
//卡尔的解法
func nextPermutation(nums []int) {
for i:=len(nums)-1;i>=0;i--{
for j:=len(nums)-1;j>i;j--{
if nums[j]>nums[i]{
//交换
nums[j],nums[i]=nums[i],nums[j]
reverse(nums,0+i+1,len(nums)-1)
return
}
}
}
reverse(nums,0,len(nums)-1)
}
//对目标切片指定区间的反转方法
func reverse(a []int,begin,end int){
for i,j:=begin,end;i= 0; i--){
for(let j = nums.length - 1; j > i; j--){
if(nums[j] > nums[i]){
[nums[j],nums[i]] = [nums[i],nums[j]]; // 交换
// 深拷贝[i + 1, nums.length)部分到新数组arr
let arr = nums.slice(i+1);
// arr升序排序
arr.sort((a,b) => a - b);
// arr替换nums的[i + 1, nums.length)部分
nums.splice(i+1,nums.length - i, ...arr);
return;
}
}
}
nums.sort((a,b) => a - b); // 不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
};
//另一种
var nextPermutation = function(nums) {
let i = nums.length - 2;
// 从右往左遍历拿到第一个左边小于右边的 i,此时 i 右边的数组是从右往左递增的
while (i >= 0 && nums[i] >= nums[i+1]){
i--;
}
if (i >= 0){
let j = nums.length - 1;
// 从右往左遍历拿到第一个大于nums[i]的数,因为之前nums[i]是第一个小于他右边的数,所以他的右边一定有大于他的数
while (j >= 0 && nums[j] <= nums[i]){
j--;
}
// 交换两个数
[nums[j], nums[i]] = [nums[i], nums[j]];
}
// 对 i 右边的数进行交换
// 因为 i 右边的数原来是从右往左递增的,把一个较小的值交换过来之后,仍然维持单调递增特性
// 此时头尾交换并向中间逼近就能获得 i 右边序列的最小值
let l = i + 1;
let r = nums.length - 1;
while (l < r){
[nums[l], nums[r]] = [nums[r], nums[l]];
l++;
r--;
}
};
```
================================================
FILE: problems/0034.在排序数组中查找元素的第一个和最后一个位置.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
# 34. 在排序数组中查找元素的第一个和最后一个位置
[力扣链接](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 $O(\log n)$ 的算法解决此问题吗?
示例 1:
* 输入:nums = [5,7,7,8,8,10], target = 8
* 输出:[3,4]
示例 2:
* 输入:nums = [5,7,7,8,8,10], target = 6
* 输出:[-1,-1]
示例 3:
* 输入:nums = [], target = 0
* 输出:[-1,-1]
## 思路
这道题目如果基础不是很好,不建议大家看简短的代码,简短的代码隐藏了太多逻辑,结果就是稀里糊涂把题AC了,但是没有想清楚具体细节!
对二分还不了解的同学先做这两题:
* [704.二分查找](https://programmercarl.com/0704.二分查找.html)
* [35.搜索插入位置](https://programmercarl.com/0035.搜索插入位置.html)
下面我来把所有情况都讨论一下。
寻找target在数组里的左右边界,有如下三种情况:
* 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
* 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
* 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
这三种情况都考虑到,说明就想的很清楚了。
接下来,在去寻找左边界,和右边界了。
采用二分法来去寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。
**刚刚接触二分搜索的同学不建议上来就想用一个二分来查找左右边界,很容易把自己绕进去,建议扎扎实实的写两个二分分别找左边界和右边界**
### 寻找右边界
先来寻找右边界,至于二分查找,如果看过[704.二分查找](https://programmercarl.com/0704.二分查找.html)就会知道,二分查找中什么时候用while (left <= right),有什么时候用while (left < right),其实只要清楚**循环不变量**,很容易区分两种写法。
那么这里我采用while (left <= right)的写法,区间定义为[left, right],即左闭右闭的区间(如果这里有点看不懂了,强烈建议把[704.二分查找](https://programmercarl.com/0704.二分查找.html)这篇文章先看了,704题目做了之后再做这道题目就好很多了)
确定好:计算出来的右边界是不包含target的右边界,左边界同理。
可以写出如下代码
```CPP
// 二分查找,寻找target的右边界(不包括target)
// 如果rightBorder为没有被赋值(即target在数组范围的左边,例如数组[3,3],target为2),为了处理情况一
int getRightBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) { // 当left==right,区间[left, right]依然有效
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else { // 当nums[middle] == target的时候,更新left,这样才能得到target的右边界
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
```
### 寻找左边界
```CPP
// 二分查找,寻找target的左边界leftBorder(不包括target)
// 如果leftBorder没有被赋值(即target在数组范围的右边,例如数组[3,3],target为4),为了处理情况一
int getLeftBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,就要在nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
```
### 处理三种情况
左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了
```CPP
class Solution {
public:
vector searchRange(vector& nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
// 情况二
return {-1, -1};
}
private:
int getRightBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
};
```
这份代码在简洁性很有大的优化空间,例如把寻找左右区间函数合并一起。
但拆开更清晰一些,而且把三种情况以及对应的处理逻辑完整的展现出来了。
## 总结
初学者建议大家一块一块的去分拆这道题目,正如本题解描述,想清楚三种情况之后,先专注于寻找右区间,然后专注于寻找左区间,左右根据左右区间做最后判断。
不要上来就想如果一起寻找左右区间,搞着搞着就会顾此失彼,绕进去拔不出来了。
## 其他语言版本
### Java
```java
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
// 情况二
return new int[]{-1, -1};
}
int getRightBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
}
```
```java
// 解法2
// 1、首先,在 nums 数组中二分查找 target;
// 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
// 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution {
public int[] searchRange(int[] nums, int target) {
int index = binarySearch(nums, target); // 二分查找
if (index == -1) { // nums 中不存在 target,直接返回 {-1, -1}
return new int[] {-1, -1}; // 匿名数组
}
// nums 中存在 target,则左右滑动指针,来找到符合题意的区间
int left = index;
int right = index;
// 向左滑动,找左边界
while (left - 1 >= 0 && nums[left - 1] == nums[index]) { // 防止数组越界。逻辑短路,两个条件顺序不能换
left--;
}
// 向右滑动,找右边界
while (right + 1 < nums.length && nums[right + 1] == nums[index]) { // 防止数组越界。
right++;
}
return new int[] {left, right};
}
/**
* 二分查找
* @param nums
* @param target
*/
public int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 不变量:左闭右闭区间
while (left <= right) { // 不变量:左闭右闭区间
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1; // 不变量:左闭右闭区间
}
}
return -1; // 不存在
}
}
```
```java
// 解法三
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = searchLeft(nums,target);
int right = searchRight(nums,target);
return new int[]{left,right};
}
public int searchLeft(int[] nums,int target){
// 寻找元素第一次出现的地方
int left = 0;
int right = nums.length-1;
while(left<=right){
int mid = left+(right-left)/2;
// >= 的都要缩小 因为要找第一个元素
if(nums[mid]>=target){
right = mid - 1;
}else{
left = mid + 1;
}
}
// right = left - 1
// 如果存在答案 right是首选
if(right>=0&&right=0&&left=0&&left=0&&right<=nums.length&&nums[right]==target){
return right;
}
return -1;
}
}
```
### C#
```csharp
public int[] SearchRange(int[] nums, int target) {
var leftBorder = GetLeftBorder(nums, target);
var rightBorder = GetRightBorder(nums, target);
if (leftBorder == -2 || rightBorder == -2) {
return new int[] {-1, -1};
}
if (rightBorder - leftBorder >=2) {
return new int[] {leftBorder + 1, rightBorder - 1};
}
return new int[] {-1, -1};
}
public int GetLeftBorder(int[] nums, int target){
var left = 0;
var right = nums.Length - 1;
var leftBorder = -2;
while (left <= right) {
var mid = (left + right) / 2;
if (target <= nums[mid]) {
right = mid - 1;
leftBorder = right;
}
else {
left = mid + 1;
}
}
return leftBorder;
}
public int GetRightBorder(int[] nums, int target){
var left = 0;
var right = nums.Length - 1;
var rightBorder = -2;
while (left <= right) {
var mid = (left + right) / 2;
if (target >= nums[mid]) {
left = mid + 1;
rightBorder = left;
}
else {
right = mid - 1;
}
}
return rightBorder;
}
```
### Python
```python
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def getRightBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
else: # 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1
rightBoder = left
return rightBoder
def getLeftBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] >= target: # 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1
leftBoder = right
else:
left = middle + 1
return leftBoder
leftBoder = getLeftBorder(nums, target)
rightBoder = getRightBorder(nums, target)
# 情况一
if leftBoder == -2 or rightBoder == -2: return [-1, -1]
# 情况三
if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
# 情况二
return [-1, -1]
```
```python
# 解法2
# 1、首先,在 nums 数组中二分查找 target;
# 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
# 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
index = binarySearch(nums, target)
if index == -1:return [-1, -1] # nums 中不存在 target,直接返回 {-1, -1}
# nums 中存在 target,则左右滑动指针,来找到符合题意的区间
left, right = index, index
# 向左滑动,找左边界
while left -1 >=0 and nums[left - 1] == target: left -=1
# 向右滑动,找右边界
while right+1 < len(nums) and nums[right + 1] == target: right +=1
return [left, right]
```
```python
# 解法3
# 1、首先,在 nums 数组中二分查找得到第一个大于等于 target的下标(左边界)与第一个大于target的下标(右边界);
# 2、如果左边界<= 右边界,则返回 [左边界, 右边界]。否则返回[-1, -1]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int, lower:bool) -> int:
left, right = 0, len(nums)-1
ans = len(nums)
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) //2
# lower为True,执行前半部分,找到第一个大于等于 target的下标 ,否则找到第一个大于target的下标
if nums[middle] > target or (lower and nums[middle] >= target):
right = middle - 1
ans = middle
else:
left = middle + 1
return ans
leftBorder = binarySearch(nums, target, True) # 搜索左边界
rightBorder = binarySearch(nums, target, False) -1 # 搜索右边界
if leftBorder<= rightBorder and rightBorder< len(nums) and nums[leftBorder] == target and nums[rightBorder] == target:
return [leftBorder, rightBorder]
return [-1, -1]
```
```python
# 解法4
# 1、首先,在 nums 数组中二分查找得到第一个大于等于 target的下标leftBorder;
# 2、在 nums 数组中二分查找得到第一个大于等于 target+1的下标, 减1则得到rightBorder;
# 3、如果开始位置在数组的右边或者不存在target,则返回[-1, -1] 。否则返回[leftBorder, rightBorder]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) //2
if nums[middle] >= target:
right = middle - 1
else:
left = middle + 1
return left # 若存在target,则返回第一个等于target的值
leftBorder = binarySearch(nums, target) # 搜索左边界
rightBorder = binarySearch(nums, target+1) -1 # 搜索右边界
if leftBorder == len(nums) or nums[leftBorder]!= target: # 情况一和情况二
return [-1, -1]
return [leftBorder, rightBorder]
```
### Rust
```rust
impl Solution {
pub fn search_range(nums: Vec, target: i32) -> Vec {
let right_border = Solution::get_right_border(&nums, target);
let left_border = Solution::get_left_border(&nums, target);
if right_border == -2 || left_border == -2 {
return vec![-1, -1];
}
if right_border - left_border > 0 {
return vec![left_border, right_border - 1];
}
vec![-1, -1]
}
pub fn get_right_border(nums: &Vec, target: i32) -> i32 {
let mut left = 0;
let mut right = nums.len();
let mut right_border: i32 = -2;
while left < right {
let mid = (left + right) / 2;
if nums[mid] > target {
right = mid;
} else {
left = mid + 1;
right_border = left as i32;
}
}
right_border as i32
}
pub fn get_left_border(nums: &Vec, target: i32) -> i32 {
let mut left = 0;
let mut right = nums.len();
let mut left_border: i32 = -2;
while left < right {
let mid = (left + right) / 2;
if nums[mid] >= target {
right = mid;
left_border = right as i32;
} else {
left = mid + 1;
}
}
left_border as i32
}
}
```
### Go
```go
func searchRange(nums []int, target int) []int {
leftBorder := getLeft(nums, target)
rightBorder := getRight(nums, target)
// 情况一
if leftBorder == -2 || rightBorder == -2 {
return []int{-1, -1}
}
// 情况三
if rightBorder - leftBorder > 1 {
return []int{leftBorder + 1, rightBorder - 1}
}
// 情况二
return []int{-1, -1}
}
func getLeft(nums []int, target int) int {
left, right := 0, len(nums)-1
border := -2 // 记录border没有被赋值的情况;这里不能赋值-1,target = num[0]时,会无法区分情况一和情况二
for left <= right { // []闭区间
mid := left + ((right - left) >> 1)
if nums[mid] >= target { // 找到第一个等于target的位置
right = mid - 1
border = right
} else {
left = mid + 1
}
}
return border
}
func getRight(nums []int, target int) int {
left, right := 0, len(nums) - 1
border := -2
for left <= right {
mid := left + ((right - left) >> 1)
if nums[mid] > target {
right = mid - 1
} else { // 找到第一个大于target的位置
left = mid + 1
border = left
}
}
return border
}
```
### JavaScript
```js
var searchRange = function(nums, target) {
const getLeftBorder = (nums, target) => {
let left = 0, right = nums.length - 1;
let leftBorder = -2;// 记录一下leftBorder没有被赋值的情况
while(left <= right){
let middle = left + ((right - left) >> 1);
if(nums[middle] >= target){ // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
const getRightBorder = (nums, target) => {
let left = 0, right = nums.length - 1;
let rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
let middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
let leftBorder = getLeftBorder(nums, target);
let rightBorder = getRightBorder(nums, target);
// 情况一
if(leftBorder === -2 || rightBorder === -2) return [-1,-1];
// 情况三
if (rightBorder - leftBorder > 1) return [leftBorder + 1, rightBorder - 1];
// 情况二
return [-1, -1];
};
```
### TypeScript
```typescript
function searchRange(nums: number[], target: number): number[] {
const leftBoard: number = getLeftBorder(nums, target);
const rightBoard: number = getRightBorder(nums, target);
// target 在nums区间左侧或右侧
if (leftBoard === (nums.length - 1) || rightBoard === 0) return [-1, -1];
// target 不存在与nums范围内
if (rightBoard - leftBoard <= 1) return [-1, -1];
// target 存在于nums范围内
return [leftBoard + 1, rightBoard - 1];
};
// 查找第一个大于target的元素下标
function getRightBorder(nums: number[], target: number): number {
let left: number = 0,
right: number = nums.length - 1;
// 0表示target在nums区间的左边
let rightBoard: number = 0;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] <= target) {
// 右边界一定在mid右边(不含mid)
left = mid + 1;
rightBoard = left;
} else {
// 右边界在mid左边(含mid)
right = mid - 1;
}
}
return rightBoard;
}
// 查找第一个小于target的元素下标
function getLeftBorder(nums: number[], target: number): number {
let left: number = 0,
right: number = nums.length - 1;
// length-1表示target在nums区间的右边
let leftBoard: number = nums.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] >= target) {
// 左边界一定在mid左边(不含mid)
right = mid - 1;
leftBoard = right;
} else {
// 左边界在mid右边(含mid)
left = mid + 1;
}
}
return leftBoard;
}
```
### Scala
```scala
object Solution {
def searchRange(nums: Array[Int], target: Int): Array[Int] = {
var left = getLeftBorder(nums, target)
var right = getRightBorder(nums, target)
if (left == -2 || right == -2) return Array(-1, -1)
if (right - left > 1) return Array(left + 1, right - 1)
Array(-1, -1)
}
// 寻找左边界
def getLeftBorder(nums: Array[Int], target: Int): Int = {
var leftBorder = -2
var left = 0
var right = nums.length - 1
while (left <= right) {
var mid = left + (right - left) / 2
if (nums(mid) >= target) {
right = mid - 1
leftBorder = right
} else {
left = mid + 1
}
}
leftBorder
}
// 寻找右边界
def getRightBorder(nums: Array[Int], target: Int): Int = {
var rightBorder = -2
var left = 0
var right = nums.length - 1
while (left <= right) {
var mid = left + (right - left) / 2
if (nums(mid) <= target) {
left = mid + 1
rightBorder = left
} else {
right = mid - 1
}
}
rightBorder
}
}
```
### Kotlin
```kotlin
class Solution {
fun searchRange(nums: IntArray, target: Int): IntArray {
var index = binarySearch(nums, target)
// 没找到,返回[-1, -1]
if (index == -1) return intArrayOf(-1, -1)
var left = index
var right = index
// 寻找左边界
while (left - 1 >=0 && nums[left - 1] == target){
left--
}
// 寻找右边界
while (right + 1 target) {
right = middle - 1
}
else {
if (nums[middle] < target) {
left = middle + 1
}
else {
return middle
}
}
}
// 没找到,返回-1
return -1
}
}
```
### C
```c
int searchLeftBorder(int *nums, int numsSize, int target) {
int left = 0, right = numsSize - 1;
// 记录leftBorder没有被赋值的情况
int leftBorder = -1;
// 边界为[left, right]
while (left <= right) {
// 更新middle值,等同于middle = (left + right) / 2
int middle = left + ((right - left) >> 1);
// 若当前middle所指为target,将左边界设为middle,并向左继续寻找左边界
if (nums[middle] == target) {
leftBorder = middle;
right = middle - 1;
} else if (nums[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return leftBorder;
}
int searchRightBorder(int *nums, int numsSize, int target) {
int left = 0, right = numsSize - 1;
// 记录rightBorder没有被赋值的情况
int rightBorder = -1;
while (left <= right) {
int middle = left + ((right - left) >> 1);
// 若当前middle所指为target,将右边界设为middle,并向右继续寻找右边界
if (nums[middle] == target) {
rightBorder = middle;
left = middle + 1;
} else if (nums[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return rightBorder;
}
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int leftBorder = searchLeftBorder(nums, numsSize, target);
int rightBorder = searchRightBorder(nums, numsSize, target);
// 定义返回数组及数组大小
*returnSize = 2;
int *resNums = (int*)malloc(sizeof(int) * 2);
resNums[0] = leftBorder;
resNums[1] = rightBorder;
return resNums;
}
```
================================================
FILE: problems/0035.搜索插入位置.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
# 35.搜索插入位置
[力扣题目链接](https://leetcode.cn/problems/search-insert-position/)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
* 输入: [1,3,5,6], 5
* 输出: 2
示例 2:
* 输入: [1,3,5,6], 2
* 输出: 1
示例 3:
* 输入: [1,3,5,6], 7
* 输出: 4
示例 4:
* 输入: [1,3,5,6], 0
* 输出: 0
## 思路
这道题目不难,但是为什么通过率相对来说并不高呢,我理解是大家对边界处理的判断有所失误导致的。
这道题目,要在数组中插入目标值,无非是这四种情况。

* 目标值在数组所有元素之前
* 目标值等于数组中某一个元素
* 目标值插入数组中的位置
* 目标值在数组所有元素之后
这四种情况确认清楚了,就可以尝试解题了。
接下来我将从暴力的解法和二分法来讲解此题,也借此好好讲一讲二分查找法。
### 暴力解法
暴力解题 不一定时间消耗就非常高,关键看实现的方式,就像是二分查找时间消耗不一定就很低,是一样的。
C++代码
```CPP
class Solution {
public:
int searchInsert(vector& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
// 分别处理如下三种情况
// 目标值在数组所有元素之前
// 目标值等于数组中某一个元素
// 目标值插入数组中的位置
if (nums[i] >= target) { // 一旦发现大于或者等于target的num[i],那么i就是我们要的结果
return i;
}
}
// 目标值在数组所有元素之后的情况
return nums.size(); // 如果target是最大的,或者 nums为空,则返回nums的长度
}
};
```
* 时间复杂度:O(n)
* 空间复杂度:O(1)
效率如下:

### 二分法
既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。

大家注意这道题目的前提是数组是有序数组,这也是使用二分查找的基础条件。
以后大家**只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法。**
同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
大体讲解一下二分法的思路,这里来举一个例子,例如在这个数组中,使用二分法寻找元素为5的位置,并返回其下标。

二分查找涉及的很多的边界条件,逻辑比较简单,就是写不好。
相信很多同学对二分查找法中边界条件处理不好。
例如到底是 `while(left < right)` 还是 `while(left <= right)`,到底是`right = middle`呢,还是要`right = middle - 1`呢?
这里弄不清楚主要是因为**对区间的定义没有想清楚,这就是不变量**。
要在二分查找的过程中,保持不变量,这也就是**循环不变量** (感兴趣的同学可以查一查)。
### 二分法第一种写法
以这道题目来举例,以下的代码中定义 target 是在一个在左闭右闭的区间里,**也就是[left, right] (这个很重要)**。
这就决定了这个二分法的代码如何去写,大家看如下代码:
**大家要仔细看注释,思考为什么要写while(left <= right), 为什么要写right = middle - 1**。
```CPP
class Solution {
public:
int searchInsert(vector& nums, int target) {
int n = nums.size();
int left = 0;
int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
return right + 1;
}
};
```
* 时间复杂度:O(log n)
* 空间复杂度:O(1)
效率如下:

### 二分法第二种写法
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) 。
那么二分法的边界处理方式则截然不同。
不变量是[left, right)的区间,如下代码可以看出是如何在循环中坚持不变量的。
**大家要仔细看注释,思考为什么要写while (left < right), 为什么要写right = middle**。
```CPP
class Solution {
public:
int searchInsert(vector& nums, int target) {
int n = nums.size();
int left = 0;
int right = n; // 定义target在左闭右开的区间里,[left, right) target
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在 [middle+1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值的情况,直接返回下标
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0,0)
// 目标值等于数组中某一个元素 return middle
// 目标值插入数组中的位置 [left, right) ,return right 即可
// 目标值在数组所有元素之后的情况 [left, right),因为是右开区间,所以 return right
return right;
}
};
```
* 时间复杂度:O(log n)
* 空间复杂度:O(1)
## 总结
希望通过这道题目,大家会发现平时写二分法,为什么总写不好,就是因为对区间定义不清楚。
确定要查找的区间到底是左闭右开[left, right),还是左闭又闭[left, right],这就是不变量。
然后在**二分查找的循环中,坚持循环不变量的原则**,很多细节问题,自然会知道如何处理了。
## 其他语言版本
### Java
```java
class Solution {
public int searchInsert(int[] nums, int target) {
int n = nums.length;
// 定义target在左闭右闭的区间,[low, high]
int low = 0;
int high = n - 1;
while (low <= high) { // 当low==high,区间[low, high]依然有效
int mid = low + (high - low) / 2; // 防止溢出
if (nums[mid] > target) {
high = mid - 1; // target 在左区间,所以[low, mid - 1]
} else if (nums[mid] < target) {
low = mid + 1; // target 在右区间,所以[mid + 1, high]
} else {
// 1. 目标值等于数组中某一个元素 return mid;
return mid;
}
}
// 2.目标值在数组所有元素之前 3.目标值插入数组中 4.目标值在数组所有元素之后 return right + 1;
return high + 1;
}
}
```
```java
//第二种二分法:左闭右开
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length;
while (left < right) { //左闭右开 [left, right)
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在 [middle+1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值的情况,直接返回下标
}
}
// 目标值在数组所有元素之前 [0,0)
// 目标值插入数组中的位置 [left, right) ,return right 即可
// 目标值在数组所有元素之后的情况 [left, right),因为是右开区间,所以 return right
return right;
}
```
### C#
```go
public int SearchInsert(int[] nums, int target) {
var left = 0;
var right = nums.Length - 1;
while (left <= right) {
var curr = (left + right) / 2;
if (nums[curr] == target)
{
return curr;
}
if (target > nums[curr]) {
left = curr + 1;
}
else {
right = curr - 1;
}
}
return left;
}
```
### Golang
```go
// 第一种二分法
func searchInsert(nums []int, target int) int {
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
return mid
} else if nums[mid] > target {
right = mid - 1
} else {
left = mid + 1
}
}
return right+1
}
```
### Rust
```rust
impl Solution {
pub fn search_insert(nums: Vec, target: i32) -> i32 {
use std::cmp::Ordering::{Equal, Greater, Less};
let (mut left, mut right) = (0, nums.len() as i32 - 1);
while left <= right {
let mid = (left + right) / 2;
match nums[mid as usize].cmp(&target) {
Less => left = mid + 1,
Equal => return mid,
Greater => right = mid - 1,
}
}
right + 1
}
}
```
### Python
```python
# 第一种二分法: [left, right]左闭右闭区间
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
return middle
return right + 1
```
```python
# 第二种二分法: [left, right)左闭右开区间
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
while (left < right):
middle = (left + right) // 2
if nums[middle] > target:
right = middle
elif nums[middle] < target:
left = middle + 1
else:
return middle
return right
```
### JavaScript
```js
var searchInsert = function (nums, target) {
let l = 0, r = nums.length - 1, ans = nums.length;
while (l <= r) {
const mid = l + Math.floor((r - l) >> 1);
if (target > nums[mid]) {
l = mid + 1;
} else {
ans = mid;
r = mid - 1;
}
}
return ans;
};
```
### TypeScript
```typescript
// 第一种二分法
function searchInsert(nums: number[], target: number): number {
const length: number = nums.length;
let left: number = 0,
right: number = length - 1;
while (left <= right) {
const mid: number = Math.floor((left + right) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] === target) {
return mid;
} else {
right = mid - 1;
}
}
return right + 1;
};
```
### Swift
```swift
// 暴力法
func searchInsert(_ nums: [Int], _ target: Int) -> Int {
for i in 0..= target {
return i
}
}
return nums.count
}
// 二分法
func searchInsert(_ nums: [Int], _ target: Int) -> Int {
var left = 0
var right = nums.count - 1
while left <= right {
let middle = left + ((right - left) >> 1)
if nums[middle] > target {
right = middle - 1
}else if nums[middle] < target {
left = middle + 1
}else if nums[middle] == target {
return middle
}
}
return right + 1
}
```
### Scala
```scala
object Solution {
def searchInsert(nums: Array[Int], target: Int): Int = {
var left = 0
var right = nums.length - 1
while (left <= right) {
var mid = left + (right - left) / 2
if (target == nums(mid)) {
return mid
} else if (target > nums(mid)) {
left = mid + 1
} else {
right = mid - 1
}
}
right + 1
}
}
```
### PHP
```php
// 二分法(1):[左闭右闭]
function searchInsert($nums, $target)
{
$n = count($nums);
$l = 0;
$r = $n - 1;
while ($l <= $r) {
$mid = floor(($l + $r) / 2);
if ($nums[$mid] > $target) {
// 下次搜索在左区间:[$l,$mid-1]
$r = $mid - 1;
} else if ($nums[$mid] < $target) {
// 下次搜索在右区间:[$mid+1,$r]
$l = $mid + 1;
} else {
// 命中返回
return $mid;
}
}
return $r + 1;
}
```
### C
```c
//版本一 [left, right]左闭右闭区间
int searchInsert(int* nums, int numsSize, int target){
//左闭右开区间 [0 , numsSize-1]
int left =0;
int mid =0;
int right = numsSize - 1;
while(left <= right){//左闭右闭区间 所以可以 left == right
mid = left + (right - left) / 2;
if(target < nums[mid]){
//target 在左区间 [left, mid - 1]中,原区间包含mid,右区间边界可以向左内缩
right = mid -1;
}else if( target > nums[mid]){
//target 在右区间 [mid + 1, right]中,原区间包含mid,左区间边界可以向右内缩
left = mid + 1;
}else {
// nums[mid] == target ,顺利找到target,直接返回mid
return mid;
}
}
//数组中未找到target元素
//target在数组所有元素之后,[left, right]是右闭区间,需要返回 right +1
return right + 1;
}
```
```c
//版本二 [left, right]左闭右开区间
int searchInsert(int* nums, int numsSize, int target){
//左闭右开区间 [0 , numsSize)
int left =0;
int mid =0;
int right = numsSize;
while(left < right){//左闭右闭区间 所以 left < right
mid = left + (right - left) / 2;
if(target < nums[mid]){
//target 在左区间 [left, mid)中,原区间没有包含mid,右区间边界不能内缩
right = mid ;
}else if( target > nums[mid]){
// target 在右区间 [mid+1, right)中,原区间包含mid,左区间边界可以向右内缩
left = mid + 1;
}else {
// nums[mid] == target ,顺利找到target,直接返回mid
return mid;
}
}
//数组中未找到target元素
//target在数组所有元素之后,[left, right)是右开区间, return right即可
return right;
}
```
================================================
FILE: problems/0037.解数独.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
> 如果对回溯法理论还不清楚的同学,可以先看这个视频[视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg)
# 37. 解数独
[力扣题目链接](https://leetcode.cn/problems/sudoku-solver/)
编写一个程序,通过填充空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。

一个数独。

答案被标成红色。
提示:
* 给定的数独序列只包含数字 1-9 和字符 '.' 。
* 你可以假设给定的数独只有唯一解。
* 给定数独永远是 9x9 形式的。
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[回溯算法二维递归?解数独不过如此!| LeetCode:37. 解数独](https://www.bilibili.com/video/BV1TW4y1471V/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
棋盘搜索问题可以使用回溯法暴力搜索,只不过这次我们要做的是**二维递归**。
怎么做二维递归呢?
大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://programmercarl.com/0077.组合.html),[131.分割回文串(分割问题)](https://programmercarl.com/0131.分割回文串.html),[78.子集(子集问题)](https://programmercarl.com/0078.子集.html),[46.全排列(排列问题)](https://programmercarl.com/0046.全排列.html),以及[51.N皇后(N皇后问题)](https://programmercarl.com/0051.N皇后.html),其实这些题目都是一维递归。
**如果以上这几道题目没有做过的话,不建议上来就做这道题哈!**
[N皇后问题](https://programmercarl.com/0051.N皇后.html)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。
本题就不一样了,**本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深**。
因为这个树形结构太大了,我抽取一部分,如图所示:

### 回溯三部曲
* 递归函数以及参数
**递归函数的返回值需要是bool类型,为什么呢?**
因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。
代码如下:
```cpp
bool backtracking(vector>& board)
```
* 递归终止条件
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
**不用终止条件会不会死循环?**
递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!
**那么有没有永远填不满的情况呢?**
这个问题我在递归单层搜索逻辑里再来讲!
* 递归单层搜索逻辑

在树形图中可以看出我们需要的是一个二维的递归 (一行一列)
**一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!**
代码如下:(**详细看注释**)
```CPP
bool backtracking(vector>& board) {
for (int i = 0; i < board.size(); i++) { // 遍历行
for (int j = 0; j < board[0].size(); j++) { // 遍历列
if (board[i][j] != '.') continue;
for (char k = '1'; k <= '9'; k++) { // (i, j) 这个位置放k是否合适
if (isValid(i, j, k, board)) {
board[i][j] = k; // 放置k
if (backtracking(board)) return true; // 如果找到合适一组立刻返回
board[i][j] = '.'; // 回溯,撤销k
}
}
return false; // 9个数都试完了,都不行,那么就返回false
}
}
return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
}
```
**注意这里return false的地方,这里放return false 是有讲究的**。
因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
那么会直接返回, **这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!**
### 判断棋盘是否合法
判断棋盘是否合法有如下三个维度:
* 同行是否重复
* 同列是否重复
* 9宫格里是否重复
代码如下:
```CPP
bool isValid(int row, int col, char val, vector>& board) {
for (int i = 0; i < 9; i++) { // 判断行里是否重复
if (board[row][i] == val) {
return false;
}
}
for (int j = 0; j < 9; j++) { // 判断列里是否重复
if (board[j][col] == val) {
return false;
}
}
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复
for (int j = startCol; j < startCol + 3; j++) {
if (board[i][j] == val ) {
return false;
}
}
}
return true;
}
```
最后整体C++代码如下:
```CPP
class Solution {
private:
bool backtracking(vector>& board) {
for (int i = 0; i < board.size(); i++) { // 遍历行
for (int j = 0; j < board[0].size(); j++) { // 遍历列
if (board[i][j] == '.') {
for (char k = '1'; k <= '9'; k++) { // (i, j) 这个位置放k是否合适
if (isValid(i, j, k, board)) {
board[i][j] = k; // 放置k
if (backtracking(board)) return true; // 如果找到合适一组立刻返回
board[i][j] = '.'; // 回溯,撤销k
}
}
return false; // 9个数都试完了,都不行,那么就返回false
}
}
}
return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
}
bool isValid(int row, int col, char val, vector>& board) {
for (int i = 0; i < 9; i++) { // 判断行里是否重复
if (board[row][i] == val) {
return false;
}
}
for (int j = 0; j < 9; j++) { // 判断列里是否重复
if (board[j][col] == val) {
return false;
}
}
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复
for (int j = startCol; j < startCol + 3; j++) {
if (board[i][j] == val ) {
return false;
}
}
}
return true;
}
public:
void solveSudoku(vector>& board) {
backtracking(board);
}
};
```
## 总结
解数独可以说是非常难的题目了,如果还一直停留在单层递归的逻辑中,这道题目可以让大家瞬间崩溃。
所以我在开篇就提到了**二维递归**,这也是我自创词汇,希望可以帮助大家理解解数独的搜索过程。
一波分析之后,再看代码会发现其实也不难,唯一难点就是理解**二维递归**的思维逻辑。
**这样,解数独这么难的问题,也被我们攻克了**。
**恭喜一路上坚持打卡的录友们,回溯算法已经接近尾声了,接下来就是要一波总结了**。
## 其他语言版本
### Java
解法一:
```java
class Solution {
public void solveSudoku(char[][] board) {
solveSudokuHelper(board);
}
private boolean solveSudokuHelper(char[][] board){
//「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,
// 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」
for (int i = 0; i < 9; i++){ // 遍历行
for (int j = 0; j < 9; j++){ // 遍历列
if (board[i][j] != '.'){ // 跳过原始数字
continue;
}
for (char k = '1'; k <= '9'; k++){ // (i, j) 这个位置放k是否合适
if (isValidSudoku(i, j, k, board)){
board[i][j] = k;
if (solveSudokuHelper(board)){ // 如果找到合适一组立刻返回
return true;
}
board[i][j] = '.';
}
}
// 9个数都试完了,都不行,那么就返回false
return false;
// 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
// 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
}
}
// 遍历完没有返回false,说明找到了合适棋盘位置了
return true;
}
/**
* 判断棋盘是否合法有如下三个维度:
* 同行是否重复
* 同列是否重复
* 9宫格里是否重复
*/
private boolean isValidSudoku(int row, int col, char val, char[][] board){
// 同行是否重复
for (int i = 0; i < 9; i++){
if (board[row][i] == val){
return false;
}
}
// 同列是否重复
for (int j = 0; j < 9; j++){
if (board[j][col] == val){
return false;
}
}
// 9宫格里是否重复
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++){
for (int j = startCol; j < startCol + 3; j++){
if (board[i][j] == val){
return false;
}
}
}
return true;
}
}
```
解法二(bitmap标记)
```
class Solution{
int[] rowBit = new int[9];
int[] colBit = new int[9];
int[] square9Bit = new int[9];
public void solveSudoku(char[][] board) {
// 1 10 11
for (int y = 0; y < board.length; y++) {
for (int x = 0; x < board[y].length; x++) {
int numBit = 1 << (board[y][x] - '1');
rowBit[y] ^= numBit;
colBit[x] ^= numBit;
square9Bit[(y / 3) * 3 + x / 3] ^= numBit;
}
}
backtrack(board, 0);
}
public boolean backtrack(char[][] board, int n) {
if (n >= 81) {
return true;
}
// 快速算出行列编号 n/9 n%9
int row = n / 9;
int col = n % 9;
if (board[row][col] != '.') {
return backtrack(board, n + 1);
}
for (char c = '1'; c <= '9'; c++) {
int numBit = 1 << (c - '1');
if (!isValid(numBit, row, col)) continue;
{
board[row][col] = c; // 当前的数字放入到数组之中,
rowBit[row] ^= numBit; // 第一行rowBit[0],第一个元素eg: 1 , 0^1=1,第一个元素:4, 100^1=101,...
colBit[col] ^= numBit;
square9Bit[(row / 3) * 3 + col / 3] ^= numBit;
}
if (backtrack(board, n + 1)) return true;
{
board[row][col] = '.'; // 不满足条件,回退成'.'
rowBit[row] &= ~numBit; // 第一行rowBit[0],第一个元素eg: 1 , 101&=~1==>101&111111110==>100
colBit[col] &= ~numBit;
square9Bit[(row / 3) * 3 + col / 3] &= ~numBit;
}
}
return false;
}
boolean isValid(int numBit, int row, int col) {
// 左右
if ((rowBit[row] & numBit) > 0) return false;
// 上下
if ((colBit[col] & numBit) > 0) return false;
// 9宫格: 快速算出第n个九宫格,编号[0,8] , 编号=(row / 3) * 3 + col / 3
if ((square9Bit[(row / 3) * 3 + col / 3] & numBit) > 0) return false;
return true;
}
}
```
### Python
```python
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
row_used = [set() for _ in range(9)]
col_used = [set() for _ in range(9)]
box_used = [set() for _ in range(9)]
for row in range(9):
for col in range(9):
num = board[row][col]
if num == ".":
continue
row_used[row].add(num)
col_used[col].add(num)
box_used[(row // 3) * 3 + col // 3].add(num)
self.backtracking(0, 0, board, row_used, col_used, box_used)
def backtracking(
self,
row: int,
col: int,
board: List[List[str]],
row_used: List[List[int]],
col_used: List[List[int]],
box_used: List[List[int]],
) -> bool:
if row == 9:
return True
next_row, next_col = (row, col + 1) if col < 8 else (row + 1, 0)
if board[row][col] != ".":
return self.backtracking(
next_row, next_col, board, row_used, col_used, box_used
)
for num in map(str, range(1, 10)):
if (
num not in row_used[row]
and num not in col_used[col]
and num not in box_used[(row // 3) * 3 + col // 3]
):
board[row][col] = num
row_used[row].add(num)
col_used[col].add(num)
box_used[(row // 3) * 3 + col // 3].add(num)
if self.backtracking(
next_row, next_col, board, row_used, col_used, box_used
):
return True
board[row][col] = "."
row_used[row].remove(num)
col_used[col].remove(num)
box_used[(row // 3) * 3 + col // 3].remove(num)
return False
```
### Go
```go
func solveSudoku(board [][]byte) {
var backtracking func(board [][]byte) bool
backtracking = func(board [][]byte) bool {
for i := 0; i < 9; i++ {
for j := 0; j < 9; j++ {
//判断此位置是否适合填数字
if board[i][j] != '.' {
continue
}
//尝试填1-9
for k := '1'; k <= '9'; k++ {
if isvalid(i, j, byte(k), board) == true { //如果满足要求就填
board[i][j] = byte(k)
if backtracking(board) == true {
return true
}
board[i][j] = '.'
}
}
return false
}
}
return true
}
backtracking(board)
}
//判断填入数字是否满足要求
func isvalid(row, col int, k byte, board [][]byte) bool {
for i := 0; i < 9; i++ { //行
if board[row][i] == k {
return false
}
}
for i := 0; i < 9; i++ { //列
if board[i][col] == k {
return false
}
}
//方格
startrow := (row / 3) * 3
startcol := (col / 3) * 3
for i := startrow; i < startrow+3; i++ {
for j := startcol; j < startcol+3; j++ {
if board[i][j] == k {
return false
}
}
}
return true
}
```
### JavaScript
```Javascript
var solveSudoku = function(board) {
function isValid(row, col, val, board) {
let len = board.length
// 行不能重复
for(let i = 0; i < len; i++) {
if(board[row][i] === val) {
return false
}
}
// 列不能重复
for(let i = 0; i < len; i++) {
if(board[i][col] === val) {
return false
}
}
let startRow = Math.floor(row / 3) * 3
let startCol = Math.floor(col / 3) * 3
for(let i = startRow; i < startRow + 3; i++) {
for(let j = startCol; j < startCol + 3; j++) {
if(board[i][j] === val) {
return false
}
}
}
return true
}
function backTracking() {
for(let i = 0; i < board.length; i++) {
for(let j = 0; j < board[0].length; j++) {
if(board[i][j] !== '.') continue
for(let val = 1; val <= 9; val++) {
if(isValid(i, j, `${val}`, board)) {
board[i][j] = `${val}`
if (backTracking()) {
return true
}
board[i][j] = `.`
}
}
return false
}
}
return true
}
backTracking(board)
return board
};
```
### TypeScript
```typescript
/**
Do not return anything, modify board in-place instead.
*/
function isValid(col: number, row: number, val: string, board: string[][]): boolean {
let n: number = board.length;
// 列向检查
for (let rowIndex = 0; rowIndex < n; rowIndex++) {
if (board[rowIndex][col] === val) return false;
}
// 横向检查
for (let colIndex = 0; colIndex < n; colIndex++) {
if (board[row][colIndex] === val) return false;
}
// 九宫格检查
const startX = Math.floor(col / 3) * 3;
const startY = Math.floor(row / 3) * 3;
for (let rowIndex = startY; rowIndex < startY + 3; rowIndex++) {
for (let colIndex = startX; colIndex < startX + 3; colIndex++) {
if (board[rowIndex][colIndex] === val) return false;
}
}
return true;
}
function solveSudoku(board: string[][]): void {
let n: number = 9;
backTracking(n, board);
function backTracking(n: number, board: string[][]): boolean {
for (let row = 0; row < n; row++) {
for (let col = 0; col < n; col++) {
if (board[row][col] === '.') {
for (let i = 1; i <= n; i++) {
if (isValid(col, row, String(i), board)) {
board[row][col] = String(i);
if (backTracking(n, board) === true) return true;
board[row][col] = '.';
}
}
return false;
}
}
}
return true;
}
};
```
### Rust
```Rust
impl Solution {
fn is_valid(row: usize, col: usize, val: char, board: &mut Vec>) -> bool{
for i in 0..9 {
if board[row][i] == val { return false; }
}
for j in 0..9 {
if board[j][col] == val {
return false;
}
}
let start_row = (row / 3) * 3;
let start_col = (col / 3) * 3;
for i in start_row..(start_row + 3) {
for j in start_col..(start_col + 3) {
if board[i][j] == val { return false; }
}
}
return true;
}
fn backtracking(board: &mut Vec>) -> bool{
for i in 0..board.len() {
for j in 0..board[0].len() {
if board[i][j] != '.' { continue; }
for k in '1'..='9' {
if Self::is_valid(i, j, k, board) {
board[i][j] = k;
if Self::backtracking(board) { return true; }
board[i][j] = '.';
}
}
return false;
}
}
return true;
}
pub fn solve_sudoku(board: &mut Vec>) {
Self::backtracking(board);
}
}
```
### C
```C
bool isValid(char** board, int row, int col, int k) {
/* 判断当前行是否有重复元素 */
for (int i = 0; i < 9; i++) {
if (board[i][col] == k) {
return false;
}
}
/* 判断当前列是否有重复元素 */
for (int j = 0; j < 9; j++) {
if (board[row][j] == k) {
return false;
}
}
/* 计算当前9宫格左上角的位置 */
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
/* 判断当前元素所在九宫格是否有重复元素 */
for (int i = startRow; i < startRow + 3; i++) {
for (int j = startCol; j < startCol + 3; j++) {
if (board[i][j] == k) {
return false;
}
}
}
/* 满足条件,返回true */
return true;
}
bool backtracking(char** board, int boardSize, int* boardColSize) {
/* 从上到下、从左到右依次遍历输入数组 */
for (int i = 0; i < boardSize; i++) {
for (int j = 0; j < *boardColSize; j++) {
/* 遇到数字跳过 */
if (board[i][j] != '.') {
continue;
}
/* 依次将数组1到9填入当前位置 */
for (int k = '1'; k <= '9'; k++) {
/* 判断当前位置是否与满足条件,是则进入下一层 */
if (isValid(board, i, j, k)) {
board[i][j] = k;
/* 判断下一层递归之后是否找到一种解法,是则返回true */
if (backtracking(board, boardSize, boardColSize)) {
return true;
}
/* 回溯,将当前位置清零 */
board[i][j] = '.';
}
}
/* 若填入的9个数均不满足条件,返回false,说明此解法无效 */
return false;
}
}
/* 遍历完所有的棋盘,没有返回false,说明找到了解法,返回true */
return true;
}
void solveSudoku(char** board, int boardSize, int* boardColSize) {
bool res = backtracking(board, boardSize, boardColSize);
}
```
### Swift
```swift
func solveSudoku(_ board: inout [[Character]]) {
// 判断对应格子的值是否合法
func isValid(row: Int, col: Int, val: Character) -> Bool {
// 行中是否重复
for i in 0 ..< 9 {
if board[row][i] == val { return false }
}
// 列中是否重复
for j in 0 ..< 9 {
if board[j][col] == val { return false }
}
// 9方格内是否重复
let startRow = row / 3 * 3
let startCol = col / 3 * 3
for i in startRow ..< startRow + 3 {
for j in startCol ..< startCol + 3 {
if board[i][j] == val { return false }
}
}
return true
}
@discardableResult
func backtracking() -> Bool {
for i in 0 ..< board.count { // i:行坐标
for j in 0 ..< board[0].count { // j:列坐标
guard board[i][j] == "." else { continue } // 跳过已填写格子
// 填写格子
for val in 1 ... 9 {
let charVal = Character("\(val)")
guard isValid(row: i, col: j, val: charVal) else { continue } // 跳过不合法的
board[i][j] = charVal // 填写
if backtracking() { return true }
board[i][j] = "." // 回溯:擦除
}
return false // 遍历完数字都不行
}
}
return true // 没有不合法的,填写正确
}
backtracking()
}
```
### Scala
详细写法:
```scala
object Solution {
def solveSudoku(board: Array[Array[Char]]): Unit = {
backtracking(board)
}
def backtracking(board: Array[Array[Char]]): Boolean = {
for (i <- 0 until 9) {
for (j <- 0 until 9) {
if (board(i)(j) == '.') { // 必须是为 . 的数字才放数字
for (k <- '1' to '9') { // 这个位置放k是否合适
if (isVaild(i, j, k, board)) {
board(i)(j) = k
if (backtracking(board)) return true // 找到了立刻返回
board(i)(j) = '.' // 回溯
}
}
return false // 9个数都试完了,都不行就返回false
}
}
}
true // 遍历完所有的都没返回false,说明找到了
}
def isVaild(x: Int, y: Int, value: Char, board: Array[Array[Char]]): Boolean = {
// 行
for (i <- 0 until 9 ) {
if (board(i)(y) == value) {
return false
}
}
// 列
for (j <- 0 until 9) {
if (board(x)(j) == value) {
return false
}
}
// 宫
var row = (x / 3) * 3
var col = (y / 3) * 3
for (i <- row until row + 3) {
for (j <- col until col + 3) {
if (board(i)(j) == value) {
return false
}
}
}
true
}
}
```
遵循Scala至简原则写法:
```scala
object Solution {
def solveSudoku(board: Array[Array[Char]]): Unit = {
backtracking(board)
}
def backtracking(board: Array[Array[Char]]): Boolean = {
// 双重for循环 + 循环守卫
for (i <- 0 until 9; j <- 0 until 9 if board(i)(j) == '.') {
// 必须是为 . 的数字才放数字,使用循环守卫判断该位置是否可以放置当前循环的数字
for (k <- '1' to '9' if isVaild(i, j, k, board)) { // 这个位置放k是否合适
board(i)(j) = k
if (backtracking(board)) return true // 找到了立刻返回
board(i)(j) = '.' // 回溯
}
return false // 9个数都试完了,都不行就返回false
}
true // 遍历完所有的都没返回false,说明找到了
}
def isVaild(x: Int, y: Int, value: Char, board: Array[Array[Char]]): Boolean = {
// 行,循环守卫进行判断
for (i <- 0 until 9 if board(i)(y) == value) return false
// 列,循环守卫进行判断
for (j <- 0 until 9 if board(x)(j) == value) return false
// 宫,循环守卫进行判断
var row = (x / 3) * 3
var col = (y / 3) * 3
for (i <- row until row + 3; j <- col until col + 3 if board(i)(j) == value) return false
true // 最终没有返回false,就说明该位置可以填写true
}
}
```
### C#
```csharp
public class Solution
{
public void SolveSudoku(char[][] board)
{
BackTracking(board);
}
public bool BackTracking(char[][] board)
{
for (int i = 0; i < board.Length; i++)
{
for (int j = 0; j < board[0].Length; j++)
{
if (board[i][j] != '.') continue;
for (char k = '1'; k <= '9'; k++)
{
if (IsValid(board, i, j, k))
{
board[i][j] = k;
if (BackTracking(board)) return true;
board[i][j] = '.';
}
}
return false;
}
}
return true;
}
public bool IsValid(char[][] board, int row, int col, char val)
{
for (int i = 0; i < 9; i++)
{
if (board[i][col] == val) return false;
}
for (int i = 0; i < 9; i++)
{
if (board[row][i] == val) return false;
}
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++)
{
for (int j = startCol; j < startCol + 3; j++)
{
if (board[i][j] == val) return false;
}
}
return true;
}
}
```
================================================
FILE: problems/0039.组合总和.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
# 39. 组合总和
[力扣题目链接](https://leetcode.cn/problems/combination-sum/)
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
* 所有数字(包括 target)都是正整数。
* 解集不能包含重复的组合。
示例 1:
* 输入:candidates = [2,3,6,7], target = 7,
* 所求解集为:
[
[7],
[2,2,3]
]
示例 2:
* 输入:candidates = [2,3,5], target = 8,
* 所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[Leetcode:39. 组合总和讲解](https://www.bilibili.com/video/BV1KT4y1M7HJ),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
题目中的**无限制重复被选取,吓得我赶紧想想 出现0 可咋办**,然后看到下面提示:1 <= candidates[i] <= 200,我就放心了。
本题和[77.组合](https://programmercarl.com/0077.组合.html),[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)的区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
本题搜索的过程抽象成树形结构如下:

注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回!
而在[77.组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html) 中都可以知道要递归K层,因为要取k个元素的组合。
### 回溯三部曲
* 递归函数参数
这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。(这两个变量可以作为函数参数传入)
首先是题目中给出的参数,集合candidates, 和目标值target。
此外我还定义了int型的sum变量来统计单一结果path里的总和,其实这个sum也可以不用,用target做相应的减法就可以了,最后如何target==0就说明找到符合的结果了,但为了代码逻辑清晰,我依然用了sum。
**本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?**
我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[77.组合](https://programmercarl.com/0077.组合.html),[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)。
如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[17.电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)
**注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我在讲解排列的时候会重点介绍**。
代码如下:
```CPP
vector> result;
vector path;
void backtracking(vector& candidates, int target, int sum, int startIndex)
```
* 递归终止条件
在如下树形结构中:

从叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。
sum等于target的时候,需要收集结果,代码如下:
```CPP
if (sum > target) {
return;
}
if (sum == target) {
result.push_back(path);
return;
}
```
* 单层搜索的逻辑
单层for循环依然是从startIndex开始,搜索candidates集合。
**注意本题和[77.组合](https://programmercarl.com/0077.组合.html)、[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)的一个区别是:本题元素为可重复选取的**。
如何重复选取呢,看代码,注释部分:
```CPP
for (int i = startIndex; i < candidates.size(); i++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i); // 关键点:不用i+1了,表示可以重复读取当前的数
sum -= candidates[i]; // 回溯
path.pop_back(); // 回溯
}
```
按照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的模板,不难写出如下C++完整代码:
```CPP
// 版本一
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& candidates, int target, int sum, int startIndex) {
if (sum > target) {
return;
}
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size(); i++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i); // 不用i+1了,表示可以重复读取当前的数
sum -= candidates[i];
path.pop_back();
}
}
public:
vector> combinationSum(vector& candidates, int target) {
result.clear();
path.clear();
backtracking(candidates, target, 0, 0);
return result;
}
};
```
### 剪枝优化
在这个树形结构中:

以及上面的版本一的代码大家可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。
其实如果已经知道下一层的sum会大于target,就没有必要进入下一层递归了。
那么可以在for循环的搜索范围上做做文章了。
**对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历**。
如图:

for循环剪枝代码如下:
```
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
```
整体代码如下:(注意注释的部分)
```CPP
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& candidates, int target, int sum, int startIndex) {
if (sum == target) {
result.push_back(path);
return;
}
// 如果 sum + candidates[i] > target 就终止遍历
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i);
sum -= candidates[i];
path.pop_back();
}
}
public:
vector> combinationSum(vector& candidates, int target) {
result.clear();
path.clear();
sort(candidates.begin(), candidates.end()); // 需要排序
backtracking(candidates, target, 0, 0);
return result;
}
};
```
* 时间复杂度: O(n * 2^n),注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
* 空间复杂度: O(target)
## 总结
本题和我们之前讲过的[77.组合](https://programmercarl.com/0077.组合.html)、[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)有两点不同:
* 组合没有数量要求
* 元素可无限重复选取
针对这两个问题,我都做了详细的分析。
并且给出了对于组合问题,什么时候用startIndex,什么时候不用,并用[17.电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)做了对比。
最后还给出了本题的剪枝优化,这个优化如果是初学者的话并不容易想到。
**在求和问题中,排序之后加剪枝是常见的套路!**
可以看出我写的文章都会大量引用之前的文章,就是要不断作对比,分析其差异,然后给出代码解决的方法,这样才能彻底理解题目的本质与难点。
## 其他语言版本
### Java
```Java
// 剪枝优化
class Solution {
public List> combinationSum(int[] candidates, int target) {
List> res = new ArrayList<>();
Arrays.sort(candidates); // 先进行排序
backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
return res;
}
public void backtracking(List> res, List path, int[] candidates, int target, int sum, int idx) {
// 找到了数字和为 target 的组合
if (sum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = idx; i < candidates.length; i++) {
// 如果 sum + candidates[i] > target 就终止遍历
if (sum + candidates[i] > target) break;
path.add(candidates[i]);
backtracking(res, path, candidates, target, sum + candidates[i], i);
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
}
}
}
```
### Python
回溯(版本一)
```python
class Solution:
def backtracking(self, candidates, target, total, startIndex, path, result):
if total > target:
return
if total == target:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
total += candidates[i]
path.append(candidates[i])
self.backtracking(candidates, target, total, i, path, result) # 不用i+1了,表示可以重复读取当前的数
total -= candidates[i]
path.pop()
def combinationSum(self, candidates, target):
result = []
self.backtracking(candidates, target, 0, 0, [], result)
return result
```
回溯剪枝(版本一)
```python
class Solution:
def backtracking(self, candidates, target, total, startIndex, path, result):
if total == target:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
if total + candidates[i] > target:
break
total += candidates[i]
path.append(candidates[i])
self.backtracking(candidates, target, total, i, path, result)
total -= candidates[i]
path.pop()
def combinationSum(self, candidates, target):
result = []
candidates.sort() # 需要排序
self.backtracking(candidates, target, 0, 0, [], result)
return result
```
回溯(版本二)
```python
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
result =[]
self.backtracking(candidates, target, 0, [], result)
return result
def backtracking(self, candidates, target, startIndex, path, result):
if target == 0:
result.append(path[:])
return
if target < 0:
return
for i in range(startIndex, len(candidates)):
path.append(candidates[i])
self.backtracking(candidates, target - candidates[i], i, path, result)
path.pop()
```
回溯剪枝(版本二)
```python
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
result =[]
candidates.sort()
self.backtracking(candidates, target, 0, [], result)
return result
def backtracking(self, candidates, target, startIndex, path, result):
if target == 0:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
if target - candidates[i] < 0:
break
path.append(candidates[i])
self.backtracking(candidates, target - candidates[i], i, path, result)
path.pop()
```
### Go
主要在于递归中传递下一个数字
```go
var (
res [][]int
path []int
)
func combinationSum(candidates []int, target int) [][]int {
res, path = make([][]int, 0), make([]int, 0, len(candidates))
sort.Ints(candidates) // 排序,为剪枝做准备
dfs(candidates, 0, target)
return res
}
func dfs(candidates []int, start int, target int) {
if target == 0 { // target 不断减小,如果为0说明达到了目标值
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
return
}
for i := start; i < len(candidates); i++ {
if candidates[i] > target { // 剪枝,提前返回
break
}
path = append(path, candidates[i])
dfs(candidates, i, target - candidates[i])
path = path[:len(path) - 1]
}
}
```
### JavaScript
```js
var combinationSum = function(candidates, target) {
const res = [], path = [];
candidates.sort((a,b)=>a-b); // 排序
backtracking(0, 0);
return res;
function backtracking(j, sum) {
if (sum === target) {
res.push(Array.from(path));
return;
}
for(let i = j; i < candidates.length; i++ ) {
const n = candidates[i];
if(n > target - sum) break;
path.push(n);
sum += n;
backtracking(i, sum);
path.pop();
sum -= n;
}
}
};
```
### TypeScript
```typescript
function combinationSum(candidates: number[], target: number): number[][] {
const resArr: number[][] = [];
function backTracking(
candidates: number[], target: number,
startIndex: number, route: number[], curSum: number
): void {
if (curSum > target) return;
if (curSum === target) {
resArr.push(route.slice());
return
}
for (let i = startIndex, length = candidates.length; i < length; i++) {
let tempVal: number = candidates[i];
route.push(tempVal);
backTracking(candidates, target, i, route, curSum + tempVal);
route.pop();
}
}
backTracking(candidates, target, 0, [], 0);
return resArr;
};
```
### Rust
```Rust
impl Solution {
pub fn backtracking(result: &mut Vec>, path: &mut Vec, candidates: &Vec, target: i32, mut sum: i32, start_index: usize) {
if sum == target {
result.push(path.to_vec());
return;
}
for i in start_index..candidates.len() {
if sum + candidates[i] <= target {
sum += candidates[i];
path.push(candidates[i]);
Self::backtracking(result, path, candidates, target, sum, i);
sum -= candidates[i];
path.pop();
}
}
}
pub fn combination_sum(candidates: Vec, target: i32) -> Vec> {
let mut result: Vec> = Vec::new();
let mut path: Vec = Vec::new();
Self::backtracking(&mut result, &mut path, &candidates, target, 0, 0);
result
}
}
```
### C
```c
int* path;
int pathTop;
int** ans;
int ansTop;
//记录每一个和等于target的path数组长度
int* length;
void backTracking(int target, int index, int* candidates, int candidatesSize, int sum) {
//若sum>=target就应该终止遍历
if(sum >= target) {
//若sum等于target,将当前的组合放入ans数组中
if(sum == target) {
int* tempPath = (int*)malloc(sizeof(int) * pathTop);
int j;
for(j = 0; j < pathTop; j++) {
tempPath[j] = path[j];
}
ans[ansTop] = tempPath;
length[ansTop++] = pathTop;
}
return ;
}
int i;
for(i = index; i < candidatesSize; i++) {
//将当前数字大小加入sum
sum+=candidates[i];
path[pathTop++] = candidates[i];
backTracking(target, i, candidates, candidatesSize, sum);
sum-=candidates[i];
pathTop--;
}
}
int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
//初始化变量
path = (int*)malloc(sizeof(int) * 50);
ans = (int**)malloc(sizeof(int*) * 200);
length = (int*)malloc(sizeof(int) * 200);
ansTop = pathTop = 0;
backTracking(target, 0, candidates, candidatesSize, 0);
//设置返回的数组大小
*returnSize = ansTop;
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int i;
for(i = 0; i < ansTop; i++) {
(*returnColumnSizes)[i] = length[i];
}
return ans;
}
```
### Swift
```swift
func combinationSum(_ candidates: [Int], _ target: Int) -> [[Int]] {
var result = [[Int]]()
var path = [Int]()
func backtracking(sum: Int, startIndex: Int) {
// 终止条件
if sum == target {
result.append(path)
return
}
let end = candidates.count
guard startIndex < end else { return }
for i in startIndex ..< end {
let sum = sum + candidates[i] // 使用局部变量隐藏回溯
if sum > target { continue } // 剪枝
path.append(candidates[i]) // 处理
backtracking(sum: sum, startIndex: i) // i不用+1以重复访问
path.removeLast() // 回溯
}
}
backtracking(sum: 0, startIndex: 0)
return result
}
```
### Scala
```scala
object Solution {
import scala.collection.mutable
def combinationSum(candidates: Array[Int], target: Int): List[List[Int]] = {
var result = mutable.ListBuffer[List[Int]]()
var path = mutable.ListBuffer[Int]()
def backtracking(sum: Int, index: Int): Unit = {
if (sum == target) {
result.append(path.toList) // 如果正好等于target,就添加到结果集
return
}
// 应该是从当前索引开始的,而不是从0
// 剪枝优化:添加循环守卫,当sum + c(i) <= target的时候才循环,才可以进入下一次递归
for (i <- index until candidates.size if sum + candidates(i) <= target) {
path.append(candidates(i))
backtracking(sum + candidates(i), i)
path = path.take(path.size - 1)
}
}
backtracking(0, 0)
result.toList
}
}
```
### C#
```csharp
public class Solution
{
public IList> res = new List>();
public IList path = new List();
public IList> CombinationSum(int[] candidates, int target)
{
BackTracking(candidates, target, 0, 0);
return res;
}
public void BackTracking(int[] candidates, int target, int start, int sum)
{
if (sum > target) return;
if (sum == target)
{
res.Add(new List(path));
return;
}
for (int i = start; i < candidates.Length; i++)
{
sum += candidates[i];
path.Add(candidates[i]);
BackTracking(candidates, target, i, sum);
sum -= candidates[i];
path.RemoveAt(path.Count - 1);
}
}
}
// 剪枝优化
public class Solution
{
public IList> res = new List>();
public IList path = new List();
public IList> CombinationSum(int[] candidates, int target)
{
Array.Sort(candidates);
BackTracking(candidates, target, 0, 0);
return res;
}
public void BackTracking(int[] candidates, int target, int start, int sum)
{
if (sum > target) return;
if (sum == target)
{
res.Add(new List(path));
return;
}
for (int i = start; i < candidates.Length && sum + candidates[i] <= target; i++)
{
sum += candidates[i];
path.Add(candidates[i]);
BackTracking(candidates, target, i, sum);
sum -= candidates[i];
path.RemoveAt(path.Count - 1);
}
}
}
```
================================================
FILE: problems/0040.组合总和II.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
# 40.组合总和II
[力扣题目链接](https://leetcode.cn/problems/combination-sum-ii/)
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。解集不能包含重复的组合。
* 示例 1:
* 输入: candidates = [10,1,2,7,6,1,5], target = 8,
* 所求解集为:
```
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
```
* 示例 2:
* 输入: candidates = [2,5,2,1,2], target = 5,
* 所求解集为:
```
[
[1,2,2],
[5]
]
```
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II](https://www.bilibili.com/video/BV12V4y1V73A),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
这道题目和[39.组合总和](https://programmercarl.com/0039.组合总和.html)如下区别:
1. 本题candidates 中的每个数字在每个组合中只能使用一次。
2. 本题数组candidates的元素是有重复的,而[39.组合总和](https://programmercarl.com/0039.组合总和.html)是无重复元素的数组candidates
最后本题和[39.组合总和](https://programmercarl.com/0039.组合总和.html)要求一样,解集不能包含重复的组合。
**本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合**。
一些同学可能想了:我把所有组合求出来,再用set或者map去重,这么做很容易超时!
所以要在搜索的过程中就去掉重复组合。
很多同学在去重的问题上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。
这个去重为什么很难理解呢,**所谓去重,其实就是使用过的元素不能重复选取。** 这么一说好像很简单!
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。**
那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
**所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重**。
为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了)
**强调一下,树层去重的话,需要对数组排序!**
选择过程树形结构如图所示:

可以看到图中,每个节点相对于 [39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)我多加了used数组,这个used数组下面会重点介绍。
### 回溯三部曲
* **递归函数参数**
与[39.组合总和](https://programmercarl.com/0039.组合总和.html)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的。
代码如下:
```CPP
vector> result; // 存放组合集合
vector path; // 符合条件的组合
void backtracking(vector& candidates, int target, int sum, int startIndex, vector& used) {
```
* **递归终止条件**
与[39.组合总和](https://programmercarl.com/0039.组合总和.html)相同,终止条件为 `sum > target` 和 `sum == target`。
代码如下:
```CPP
if (sum > target) { // 这个条件其实可以省略
return;
}
if (sum == target) {
result.push_back(path);
return;
}
```
`sum > target` 这个条件其实可以省略,因为在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。
* **单层搜索的逻辑**
这里与[39.组合总和](https://programmercarl.com/0039.组合总和.html)最大的不同就是要去重了。
前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。
**如果`candidates[i] == candidates[i - 1]` 并且 `used[i - 1] == false`,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]**。
此时for循环里就应该做continue的操作。
这块比较抽象,如图:

我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:
* used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
* used[i - 1] == false,说明同一树层candidates[i - 1]使用过
可能有的录友想,为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。
而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示:

**这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!**
那么单层搜索的逻辑代码如下:
```CPP
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 要对同一树层使用过的元素进行跳过
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1:这里是i+1,每个数字在每个组合中只能使用一次
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
```
**注意sum + candidates[i] <= target为剪枝操作,在[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)有讲解过!**
回溯三部曲分析完了,整体C++代码如下:
```CPP
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& candidates, int target, int sum, int startIndex, vector& used) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 要对同一树层使用过的元素进行跳过
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
public:
vector> combinationSum2(vector& candidates, int target) {
vector used(candidates.size(), false);
path.clear();
result.clear();
// 首先把给candidates排序,让其相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
};
```
* 时间复杂度: O(n * 2^n)
* 空间复杂度: O(n)
### 补充
这里直接用startIndex来去重也是可以的, 就不用used数组了。
```CPP
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& candidates, int target, int sum, int startIndex) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// 要对同一树层使用过的元素进行跳过
if (i > startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i + 1); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
sum -= candidates[i];
path.pop_back();
}
}
public:
vector> combinationSum2(vector& candidates, int target) {
path.clear();
result.clear();
// 首先把给candidates排序,让其相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0);
return result;
}
};
```
## 总结
本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于[39.组合总和](https://programmercarl.com/0039.组合总和.html)难度提升了不少。
**关键是去重的逻辑,代码很简单,网上一搜一大把,但几乎没有能把这块代码含义讲明白的,基本都是给出代码,然后说这就是去重了,究竟怎么个去重法也是模棱两可**。
所以Carl有必要把去重的这块彻彻底底的给大家讲清楚,**就连“树层去重”和“树枝去重”都是我自创的词汇,希望对大家理解有帮助!**
## 其他语言版本
### Java
**使用标记数组**
```Java
class Solution {
LinkedList path = new LinkedList<>();
List> ans = new ArrayList<>();
boolean[] used;
int sum = 0;
public List> combinationSum2(int[] candidates, int target) {
used = new boolean[candidates.length];
// 加标志数组,用来辅助判断同层节点是否已经遍历
Arrays.fill(used, false);
// 为了将重复的数字都放到一起,所以先进行排序
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return ans;
}
private void backTracking(int[] candidates, int target, int startIndex) {
if (sum == target) {
ans.add(new ArrayList(path));
}
for (int i = startIndex; i < candidates.length; i++) {
if (sum + candidates[i] > target) {
break;
}
// 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
continue;
}
used[i] = true;
sum += candidates[i];
path.add(candidates[i]);
// 每个节点仅能选择一次,所以从下一位开始
backTracking(candidates, target, i + 1);
used[i] = false;
sum -= candidates[i];
path.removeLast();
}
}
}
```
**不使用标记数组**
```Java
class Solution {
List> res = new ArrayList<>();
LinkedList path = new LinkedList<>();
int sum = 0;
public List> combinationSum2( int[] candidates, int target ) {
//为了将重复的数字都放到一起,所以先进行排序
Arrays.sort( candidates );
backTracking( candidates, target, 0 );
return res;
}
private void backTracking( int[] candidates, int target, int start ) {
if ( sum == target ) {
res.add( new ArrayList<>( path ) );
return;
}
for ( int i = start; i < candidates.length && sum + candidates[i] <= target; i++ ) {
//正确剔除重复解的办法
//跳过同一树层使用过的元素
if ( i > start && candidates[i] == candidates[i - 1] ) {
continue;
}
sum += candidates[i];
path.add( candidates[i] );
// i+1 代表当前组内元素只选取一次
backTracking( candidates, target, i + 1 );
int temp = path.getLast();
sum -= temp;
path.removeLast();
}
}
}
```
### Python
回溯
```python
class Solution:
def backtracking(self, candidates, target, total, startIndex, path, result):
if total == target:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
if i > startIndex and candidates[i] == candidates[i - 1]:
continue
if total + candidates[i] > target:
break
total += candidates[i]
path.append(candidates[i])
self.backtracking(candidates, target, total, i + 1, path, result)
total -= candidates[i]
path.pop()
def combinationSum2(self, candidates, target):
result = []
candidates.sort()
self.backtracking(candidates, target, 0, 0, [], result)
return result
```
回溯 使用used
```python
class Solution:
def backtracking(self, candidates, target, total, startIndex, used, path, result):
if total == target:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
# 对于相同的数字,只选择第一个未被使用的数字,跳过其他相同数字
if i > startIndex and candidates[i] == candidates[i - 1] and not used[i - 1]:
continue
if total + candidates[i] > target:
break
total += candidates[i]
path.append(candidates[i])
used[i] = True
self.backtracking(candidates, target, total, i + 1, used, path, result)
used[i] = False
total -= candidates[i]
path.pop()
def combinationSum2(self, candidates, target):
used = [False] * len(candidates)
result = []
candidates.sort()
self.backtracking(candidates, target, 0, 0, used, [], result)
return result
```
回溯优化
```python
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
results = []
self.combinationSumHelper(candidates, target, 0, [], results)
return results
def combinationSumHelper(self, candidates, target, index, path, results):
if target == 0:
results.append(path[:])
return
for i in range(index, len(candidates)):
if i > index and candidates[i] == candidates[i - 1]:
continue
if candidates[i] > target:
break
path.append(candidates[i])
self.combinationSumHelper(candidates, target - candidates[i], i + 1, path, results)
path.pop()
```
### Go
主要在于如何在回溯中去重
**使用used数组**
```go
var (
res [][]int
path []int
used []bool
)
func combinationSum2(candidates []int, target int) [][]int {
res, path = make([][]int, 0), make([]int, 0, len(candidates))
used = make([]bool, len(candidates))
sort.Ints(candidates) // 排序,为剪枝做准备
dfs(candidates, 0, target)
return res
}
func dfs(candidates []int, start int, target int) {
if target == 0 { // target 不断减小,如果为0说明达到了目标值
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
return
}
for i := start; i < len(candidates); i++ {
if candidates[i] > target { // 剪枝,提前返回
break
}
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
if i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false {
continue
}
path = append(path, candidates[i])
used[i] = true
dfs(candidates, i+1, target - candidates[i])
used[i] = false
path = path[:len(path) - 1]
}
}
```
**不使用used数组**
```go
var (
res [][]int
path []int
)
func combinationSum2(candidates []int, target int) [][]int {
res, path = make([][]int, 0), make([]int, 0, len(candidates))
sort.Ints(candidates) // 排序,为剪枝做准备
dfs(candidates, 0, target)
return res
}
func dfs(candidates []int, start int, target int) {
if target == 0 { // target 不断减小,如果为0说明达到了目标值
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
return
}
for i := start; i < len(candidates); i++ {
if candidates[i] > target { // 剪枝,提前返回
break
}
// i != start 限制了这不对深度遍历到达的此值去重
if i != start && candidates[i] == candidates[i-1] { // 去重
continue
}
path = append(path, candidates[i])
dfs(candidates, i+1, target - candidates[i])
path = path[:len(path) - 1]
}
}
```
### JavaScript
```js
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum2 = function(candidates, target) {
const res = []; path = [], len = candidates.length;
candidates.sort((a,b)=>a-b);
backtracking(0, 0);
return res;
function backtracking(sum, i) {
if (sum === target) {
res.push(Array.from(path));
return;
}
for(let j = i; j < len; j++) {
const n = candidates[j];
if(j > i && candidates[j] === candidates[j-1]){
//若当前元素和前一个元素相等
//则本次循环结束,防止出现重复组合
continue;
}
//如果当前元素值大于目标值-总和的值
//由于数组已排序,那么该元素之后的元素必定不满足条件
//直接终止当前层的递归
if(n > target - sum) break;
path.push(n);
sum += n;
backtracking(sum, j + 1);
path.pop();
sum -= n;
}
}
};
```
**使用used去重**
```js
var combinationSum2 = function(candidates, target) {
let res = [];
let path = [];
let total = 0;
const len = candidates.length;
candidates.sort((a, b) => a - b);
let used = new Array(len).fill(false);
const backtracking = (startIndex) => {
if (total === target) {
res.push([...path]);
return;
}
for(let i = startIndex; i < len && total < target; i++) {
const cur = candidates[i];
if (cur > target - total || (i > 0 && cur === candidates[i - 1] && !used[i - 1])) continue;
path.push(cur);
total += cur;
used[i] = true;
backtracking(i + 1);
path.pop();
total -= cur;
used[i] = false;
}
}
backtracking(0);
return res;
};
```
### TypeScript
```typescript
function combinationSum2(candidates: number[], target: number): number[][] {
candidates.sort((a, b) => a - b);
const resArr: number[][] = [];
function backTracking(
candidates: number[], target: number,
curSum: number, startIndex: number, route: number[]
) {
if (curSum > target) return;
if (curSum === target) {
resArr.push(route.slice());
return;
}
for (let i = startIndex, length = candidates.length; i < length; i++) {
if (i > startIndex && candidates[i] === candidates[i - 1]) {
continue;
}
let tempVal: number = candidates[i];
route.push(tempVal);
backTracking(candidates, target, curSum + tempVal, i + 1, route);
route.pop();
}
}
backTracking(candidates, target, 0, 0, []);
return resArr;
};
```
### Rust
```Rust
impl Solution {
pub fn backtracking(result: &mut Vec>, path: &mut Vec, candidates: &Vec, target: i32, mut sum: i32, start_index: usize, used: &mut Vec) {
if sum == target {
result.push(path.to_vec());
return;
}
for i in start_index..candidates.len() {
if sum + candidates[i] <= target {
if i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false { continue; }
sum += candidates[i];
path.push(candidates[i]);
used[i] = true;
Self::backtracking(result, path, candidates, target, sum, i + 1, used);
used[i] = false;
sum -= candidates[i];
path.pop();
}
}
}
pub fn combination_sum2(candidates: Vec, target: i32) -> Vec> {
let mut result: Vec> = Vec::new();
let mut path: Vec = Vec::new();
let mut used: Vec = vec![false; candidates.len()];
let mut candidates = candidates;
candidates.sort();
Self::backtracking(&mut result, &mut path, &candidates, target, 0, 0, &mut used);
result
}
}
```
### C
```c
int* path;
int pathTop;
int** ans;
int ansTop;
//记录ans中每一个一维数组的大小
int* length;
int cmp(const void* a1, const void* a2) {
return *((int*)a1) - *((int*)a2);
}
void backTracking(int* candidates, int candidatesSize, int target, int sum, int startIndex) {
if(sum >= target) {
//若sum等于target,复制当前path进入
if(sum == target) {
int* tempPath = (int*)malloc(sizeof(int) * pathTop);
int j;
for(j = 0; j < pathTop; j++) {
tempPath[j] = path[j];
}
length[ansTop] = pathTop;
ans[ansTop++] = tempPath;
}
return ;
}
int i;
for(i = startIndex; i < candidatesSize; i++) {
//对同一层树中使用过的元素跳过
if(i > startIndex && candidates[i] == candidates[i-1])
continue;
path[pathTop++] = candidates[i];
sum += candidates[i];
backTracking(candidates, candidatesSize, target, sum, i + 1);
//回溯
sum -= candidates[i];
pathTop--;
}
}
int** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
path = (int*)malloc(sizeof(int) * 50);
ans = (int**)malloc(sizeof(int*) * 100);
length = (int*)malloc(sizeof(int) * 100);
pathTop = ansTop = 0;
//快速排序candidates,让相同元素挨到一起
qsort(candidates, candidatesSize, sizeof(int), cmp);
backTracking(candidates, candidatesSize, target, 0, 0);
*returnSize = ansTop;
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int i;
for(i = 0; i < ansTop; i++) {
(*returnColumnSizes)[i] = length[i];
}
return ans;
}
```
### Swift
```swift
func combinationSum2(_ candidates: [Int], _ target: Int) -> [[Int]] {
// 为了方便去重复,先对集合排序
let candidates = candidates.sorted()
var result = [[Int]]()
var path = [Int]()
func backtracking(sum: Int, startIndex: Int) {
// 终止条件
if sum == target {
result.append(path)
return
}
let end = candidates.count
guard startIndex < end else { return }
for i in startIndex ..< end {
if i > startIndex, candidates[i] == candidates[i - 1] { continue } // 去重复
let sum = sum + candidates[i] // 使用局部变量隐藏回溯
if sum > target { continue } // 剪枝
path.append(candidates[i]) // 处理
backtracking(sum: sum, startIndex: i + 1) // i+1避免重复访问
path.removeLast() // 回溯
}
}
backtracking(sum: 0, startIndex: 0)
return result
}
```
### Scala
```scala
object Solution {
import scala.collection.mutable
def combinationSum2(candidates: Array[Int], target: Int): List[List[Int]] = {
var res = mutable.ListBuffer[List[Int]]()
var path = mutable.ListBuffer[Int]()
var candidate = candidates.sorted
def backtracking(sum: Int, startIndex: Int): Unit = {
if (sum == target) {
res.append(path.toList)
return
}
for (i <- startIndex until candidate.size if sum + candidate(i) <= target) {
if (!(i > startIndex && candidate(i) == candidate(i - 1))) {
path.append(candidate(i))
backtracking(sum + candidate(i), i + 1)
path = path.take(path.size - 1)
}
}
}
backtracking(0, 0)
res.toList
}
}
```
### C#
```csharp
public class Solution
{
public List> res = new List>();
public List path = new List();
public IList> CombinationSum2(int[] candidates, int target)
{
Array.Sort(candidates);
BackTracking(candidates, target, 0, 0);
return res;
}
public void BackTracking(int[] candidates, int target, int start, int sum)
{
if (sum > target) return;
if (sum == target)
{
res.Add(new List(path));
return;
}
for (int i = start; i < candidates.Length && sum + candidates[i] <= target; i++)
{
if (i > start && candidates[i] == candidates[i - 1]) continue;
sum += candidates[i];
path.Add(candidates[i]);
BackTracking(candidates, target, i + 1, sum);
sum -= candidates[i];
path.RemoveAt(path.Count - 1);
}
}
}
```
================================================
FILE: problems/0042.接雨水.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
# 42. 接雨水
[力扣题目链接](https://leetcode.cn/problems/trapping-rain-water/)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:

* 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
* 输出:6
* 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
* 输入:height = [4,2,0,3,2,5]
* 输出:9
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[单调栈,经典来袭!LeetCode:42.接雨水](https://www.bilibili.com/video/BV1uD4y1u75P/),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
接雨水问题在面试中还是常见题目的,有必要好好讲一讲。
本文深度讲解如下三种方法:
* 暴力解法
* 双指针优化
* 单调栈
### 暴力解法
本题暴力解法也是也是使用双指针。
首先要明确,要按照行来计算,还是按照列来计算。
按照行来计算如图:

按照列来计算如图:

一些同学在实现的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
我个人倾向于按照列来计算,比较容易理解,接下来看一下按照列如何计算。
首先,**如果按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了。**
可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。
这句话可以有点绕,来举一个理解,例如求列4的雨水高度,如图:

列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。
列4 右侧最高的柱子是列7,高度为3(以下用rHeight表示)。
列4 柱子的高度为1(以下用height表示)
那么列4的雨水高度为 列3和列7的高度最小值减列4高度,即: min(lHeight, rHeight) - height。
列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了。
此时求出了列4的雨水体积。
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:
```CPP
for (int i = 0; i < height.size(); i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.size() - 1) continue;
}
```
在for循环中求左右两边最高柱子,代码如下:
```CPP
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i + 1; r < height.size(); r++) {
if (height[r] > rHeight) rHeight = height[r];
}
for (int l = i - 1; l >= 0; l--) {
if (height[l] > lHeight) lHeight = height[l];
}
```
最后,计算该列的雨水高度,代码如下:
```CPP
int h = min(lHeight, rHeight) - height[i];
if (h > 0) sum += h; // 注意只有h大于零的时候,在统计到总和中
```
整体代码如下:
```CPP
class Solution {
public:
int trap(vector& height) {
int sum = 0;
for (int i = 0; i < height.size(); i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.size() - 1) continue;
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i + 1; r < height.size(); r++) {
if (height[r] > rHeight) rHeight = height[r];
}
for (int l = i - 1; l >= 0; l--) {
if (height[l] > lHeight) lHeight = height[l];
}
int h = min(lHeight, rHeight) - height[i];
if (h > 0) sum += h;
}
return sum;
}
};
```
因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)。
力扣后面修改了后台测试数据,所以以上暴力解法超时了。
### 双指针优化
在暴力解法中,我们可以看到只要记录左边柱子的最高高度 和 右边柱子的最高高度,就可以计算当前位置的雨水面积,这就是通过列来计算。
当前列雨水面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度。
为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),这样就避免了重复计算。
当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。
即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
代码如下:
```CPP
class Solution {
public:
int trap(vector& height) {
if (height.size() <= 2) return 0;
vector maxLeft(height.size(), 0);
vector maxRight(height.size(), 0);
int size = maxRight.size();
// 记录每个柱子左边柱子最大高度
maxLeft[0] = height[0];
for (int i = 1; i < size; i++) {
maxLeft[i] = max(height[i], maxLeft[i - 1]);
}
// 记录每个柱子右边柱子最大高度
maxRight[size - 1] = height[size - 1];
for (int i = size - 2; i >= 0; i--) {
maxRight[i] = max(height[i], maxRight[i + 1]);
}
// 求和
int sum = 0;
for (int i = 0; i < size; i++) {
int count = min(maxLeft[i], maxRight[i]) - height[i];
if (count > 0) sum += count;
}
return sum;
}
};
```
### 单调栈解法
关于单调栈的理论基础,单调栈适合解决什么问题,单调栈的工作过程,大家可以先看这题讲解 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。
单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://programmercarl.com/0239.滑动窗口最大值.html)一样,需要我们自己维持顺序,没有现成的容器可以用。
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
而接雨水这道题目,我们正需要寻找一个元素,右边最大元素以及左边最大元素,来计算雨水面积。
#### 准备工作
那么本题使用单调栈有如下几个问题:
1. 首先单调栈是按照行方向来计算雨水,如图:

知道这一点,后面的就可以理解了。
2. 使用单调栈内元素的顺序
从大到小还是从小到大呢?
从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。
因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
如图:

关于单调栈的顺序给大家一个总结: [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 中求一个元素右边第一个更大元素,单调栈就是递增的,[84.柱状图中最大的矩形](https://programmercarl.com/0084.柱状图中最大的矩形.html)求一个元素右边第一个更小元素,单调栈就是递减的。
3. 遇到相同高度的柱子怎么办。
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
例如 5 5 1 3 这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。
**因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度**。
如图所示:

4. 栈里要保存什么数值
使用单调栈,也是通过 长 * 宽 来计算雨水面积的。
长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,
那么栈里有没有必要存一个pair类型的元素,保存柱子的高度和下标呢。
其实不用,栈里就存放下标就行,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了。
所以栈的定义如下:
```
stack st; // 存着下标,计算的时候用下标对应的柱子高度
```
明确了如上几点,我们再来看处理逻辑。
#### 单调栈处理逻辑
以下操作过程其实和 [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 也是一样的,建议先做 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。
以下逻辑主要就是三种情况
* 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
* 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
* 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]
先将下标0的柱子加入到栈中,`st.push(0);`。 栈中存放我们遍历过的元素,所以先将下标0加进来。
然后开始从下标1开始遍历所有的柱子,`for (int i = 1; i < height.size(); i++)`。
如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从小到大的顺序(从栈头到栈底)。
代码如下:
```
if (height[i] < height[st.top()]) st.push(i);
```
如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。
代码如下:
```
if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
st.pop();
st.push(i);
}
```
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:

取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid](就是图中的高度1)。
此时的栈顶元素st.top(),就是凹槽的左边位置,下标为st.top(),对应的高度为height[st.top()](就是图中的高度2)。
当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i](就是图中的高度3)。
此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!**
那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];`
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:`int w = i - st.top() - 1 ;`
当前凹槽雨水的体积就是:`h * w`。
求当前凹槽雨水的体积代码如下:
```CPP
while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,持续跟新栈顶元素
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
```
关键部分讲完了,整体代码如下:
```CPP
class Solution {
public:
int trap(vector& height) {
if (height.size() <= 2) return 0; // 可以不加
stack st; // 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
int sum = 0;
for (int i = 1; i < height.size(); i++) {
if (height[i] < height[st.top()]) { // 情况一
st.push(i);
} if (height[i] == height[st.top()]) { // 情况二
st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
st.push(i);
} else { // 情况三
while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
}
};
```
以上代码冗余了一些,但是思路是清晰的,下面我将代码精简一下,如下:
```CPP
class Solution {
public:
int trap(vector& height) {
stack st;
st.push(0);
int sum = 0;
for (int i = 1; i < height.size(); i++) {
while (!st.empty() && height[i] > height[st.top()]) {
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1;
sum += h * w;
}
}
st.push(i);
}
return sum;
}
};
```
精简之后的代码,大家就看不出去三种情况的处理了,貌似好像只处理的情况三,其实是把情况一和情况二融合了。 这样的代码不太利于理解。
## 其他语言版本
### Java:
暴力解法:
```java
class Solution {
public int trap(int[] height) {
int sum = 0;
for (int i = 0; i < height.length; i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i==0 || i== height.length - 1) continue;
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i+1; r < height.length; r++) {
if (height[r] > rHeight) rHeight = height[r];
}
for (int l = i-1; l >= 0; l--) {
if(height[l] > lHeight) lHeight = height[l];
}
int h = Math.min(lHeight, rHeight) - height[i];
if (h > 0) sum += h;
}
return sum;
}
}
```
双指针:
```java
class Solution {
public int trap(int[] height) {
int length = height.length;
if (length <= 2) return 0;
int[] maxLeft = new int[length];
int[] maxRight = new int[length];
// 记录每个柱子左边柱子最大高度
maxLeft[0] = height[0];
for (int i = 1; i< length; i++) maxLeft[i] = Math.max(height[i], maxLeft[i-1]);
// 记录每个柱子右边柱子最大高度
maxRight[length - 1] = height[length - 1];
for(int i = length - 2; i >= 0; i--) maxRight[i] = Math.max(height[i], maxRight[i+1]);
// 求和
int sum = 0;
for (int i = 0; i < length; i++) {
int count = Math.min(maxLeft[i], maxRight[i]) - height[i];
if (count > 0) sum += count;
}
return sum;
}
}
```
双指针优化
```java
class Solution {
public int trap(int[] height) {
if (height.length <= 2) {
return 0;
}
// 从两边向中间寻找最值
int maxLeft = height[0], maxRight = height[height.length - 1];
int l = 1, r = height.length - 2;
int res = 0;
while (l <= r) {
// 不确定上一轮是左边移动还是右边移动,所以两边都需更新最值
maxLeft = Math.max(maxLeft, height[l]);
maxRight = Math.max(maxRight, height[r]);
// 最值较小的一边所能装的水量已定,所以移动较小的一边。
if (maxLeft < maxRight) {
res += maxLeft - height[l ++];
} else {
res += maxRight - height[r --];
}
}
return res;
}
}
```
单调栈法
```java
class Solution {
public int trap(int[] height){
int size = height.length;
if (size <= 2) return 0;
// in the stack, we push the index of array
// using height[] to access the real height
Stack stack = new Stack();
stack.push(0);
int sum = 0;
for (int index = 1; index < size; index++){
int stackTop = stack.peek();
if (height[index] < height[stackTop]){
stack.push(index);
}else if (height[index] == height[stackTop]){
// 因为相等的相邻墙,左边一个是不可能存放雨水的,所以pop左边的index, push当前的index
stack.pop();
stack.push(index);
}else{
//pop up all lower value
int heightAtIdx = height[index];
while (!stack.isEmpty() && (heightAtIdx > height[stackTop])){
int mid = stack.pop();
if (!stack.isEmpty()){
int left = stack.peek();
int h = Math.min(height[left], height[index]) - height[mid];
int w = index - left - 1;
int hold = h * w;
if (hold > 0) sum += hold;
stackTop = stack.peek();
}
}
stack.push(index);
}
}
return sum;
}
}
```
### Python:
暴力解法:
```Python
class Solution:
def trap(self, height: List[int]) -> int:
res = 0
for i in range(len(height)):
if i == 0 or i == len(height)-1: continue
lHight = height[i-1]
rHight = height[i+1]
for j in range(i-1):
if height[j] > lHight:
lHight = height[j]
for k in range(i+2,len(height)):
if height[k] > rHight:
rHight = height[k]
res1 = min(lHight,rHight) - height[i]
if res1 > 0:
res += res1
return res
```
双指针:
```python
class Solution:
def trap(self, height: List[int]) -> int:
leftheight, rightheight = [0]*len(height), [0]*len(height)
leftheight[0]=height[0]
for i in range(1,len(height)):
leftheight[i]=max(leftheight[i-1],height[i])
rightheight[-1]=height[-1]
for i in range(len(height)-2,-1,-1):
rightheight[i]=max(rightheight[i+1],height[i])
result = 0
for i in range(0,len(height)):
summ = min(leftheight[i],rightheight[i])-height[i]
result += summ
return result
```
单调栈
```Python
class Solution:
def trap(self, height: List[int]) -> int:
# 单调栈
'''
单调栈是按照 行 的方向来计算雨水
从栈顶到栈底的顺序:从小到大
通过三个元素来接水:栈顶,栈顶的下一个元素,以及即将入栈的元素
雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度)
'''
# stack储存index,用于计算对应的柱子高度
stack = [0]
result = 0
for i in range(1, len(height)):
# 情况一
if height[i] < height[stack[-1]]:
stack.append(i)
# 情况二
# 当当前柱子高度和栈顶一致时,左边的一个是不可能存放雨水的,所以保留右侧新柱子
# 需要使用最右边的柱子来计算宽度
elif height[i] == height[stack[-1]]:
stack.pop()
stack.append(i)
# 情况三
else:
# 抛出所有较低的柱子
while stack and height[i] > height[stack[-1]]:
# 栈顶就是中间的柱子:储水槽,就是凹槽的地步
mid_height = height[stack[-1]]
stack.pop()
if stack:
right_height = height[i]
left_height = height[stack[-1]]
# 两侧的较矮一方的高度 - 凹槽底部高度
h = min(right_height, left_height) - mid_height
# 凹槽右侧下标 - 凹槽左侧下标 - 1: 只求中间宽度
w = i - stack[-1] - 1
# 体积:高乘宽
result += h * w
stack.append(i)
return result
# 单调栈压缩版
class Solution:
def trap(self, height: List[int]) -> int:
stack = [0]
result = 0
for i in range(1, len(height)):
while stack and height[i] > height[stack[-1]]:
mid_height = stack.pop()
if stack:
# 雨水高度是 min(凹槽左侧高度, 凹槽右侧高度) - 凹槽底部高度
h = min(height[stack[-1]], height[i]) - height[mid_height]
# 雨水宽度是 凹槽右侧的下标 - 凹槽左侧的下标 - 1
w = i - stack[-1] - 1
# 累计总雨水体积
result += h * w
stack.append(i)
return result
```
### Go:
```go
func trap(height []int) int {
var left, right, leftMax, rightMax, res int
right = len(height) - 1
for left < right {
if height[left] < height[right] {
if height[left] >= leftMax {
leftMax = height[left] // 设置左边最高柱子
} else {
res += leftMax - height[left] // //右边必定有柱子挡水,所以遇到所有值小于等于leftMax的,全部加入水池中
}
left++
} else {
if height[right] > rightMax {
rightMax = height[right] // //设置右边最高柱子
} else {
res += rightMax - height[right] // //左边必定有柱子挡水,所以,遇到所有值小于等于rightMax的,全部加入水池
}
right--
}
}
return res
}
```
双指针解法:
```go
func trap(height []int) int {
sum:=0
n:=len(height)
lh:=make([]int,n)
rh:=make([]int,n)
lh[0]=height[0]
rh[n-1]=height[n-1]
for i:=1;i=0;i--{
rh[i]=max(rh[i+1],height[i])
}
for i:=1;i0{
sum+=h
}
}
return sum
}
func max(a,b int)int{
if a>b{
return a
}
return b
}
func min(a,b int)int{
if a height[st[len(st)-1]] {
top := st[len(st)-1]
st = st[:len(st)-1]
if len(st) != 0 {
tmp := (min(height[i], height[st[len(st)-1]]) - height[top]) * (i - st[len(st)-1] - 1)
res += tmp
}
}
st = append(st, i)
}
}
return res
}
func min(x, y int) int {
if x >= y {
return y
}
return x
}
```
单调栈压缩版:
```go
func trap(height []int) int {
stack := make([]int, 0)
res := 0
// 无需事先将第一个柱子的坐标入栈,因为它会在该for循环的最后入栈
for i := 0; i < len(height); i ++ {
// 满足栈不为空并且当前柱子高度大于栈顶对应的柱子高度的情况时
for len(stack) > 0 && height[stack[len(stack) - 1]] < height[i] {
// 获得凹槽高度
mid := height[stack[len(stack) - 1]]
// 凹槽坐标出栈
stack = stack[: len(stack) - 1]
// 如果栈不为空则此时栈顶元素为左侧柱子坐标
if len(stack) > 0 {
// 求得雨水高度
h := min(height[i], height[stack[len(stack) - 1]]) - mid
// 求得雨水宽度
w := i - stack[len(stack) - 1] - 1
res += h * w
}
}
// 如果栈为空或者当前柱子高度小于等于栈顶对应的柱子高度时入栈
stack = append(stack, i)
}
return res
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
```
### JavaScript:
```javascript
//暴力解法
var trap = function(height) {
const len = height.length;
let sum = 0;
for(let i = 0; i < len; i++){
// 第一个柱子和最后一个柱子不接雨水
if(i == 0 || i == len - 1) continue;
let rHeight = height[i]; // 记录右边柱子的最高高度
let lHeight = height[i]; // 记录左边柱子的最高高度
for(let r = i + 1; r < len; r++){
if(height[r] > rHeight) rHeight = height[r];
}
for(let l = i - 1; l >= 0; l--){
if(height[l] > lHeight) lHeight = height[l];
}
let h = Math.min(lHeight, rHeight) - height[i];
if(h > 0) sum += h;
}
return sum;
};
//双指针
var trap = function(height) {
const len = height.length;
if(len <= 2) return 0;
const maxLeft = new Array(len).fill(0);
const maxRight = new Array(len).fill(0);
// 记录每个柱子左边柱子最大高度
maxLeft[0] = height[0];
for(let i = 1; i < len; i++){
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}
// 记录每个柱子右边柱子最大高度
maxRight[len - 1] = height[len - 1];
for(let i = len - 2; i >= 0; i--){
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}
// 求和
let sum = 0;
for(let i = 0; i < len; i++){
let count = Math.min(maxLeft[i], maxRight[i]) - height[i];
if(count > 0) sum += count;
}
return sum;
};
//单调栈 js数组作为栈
var trap = function(height) {
const len = height.length;
if(len <= 2) return 0; // 可以不加
const st = [];// 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
let sum = 0;
for(let i = 1; i < len; i++){
if(height[i] < height[st[st.length - 1]]){ // 情况一
st.push(i);
}
if (height[i] == height[st[st.length - 1]]) { // 情况二
st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
st.push(i);
} else { // 情况三
while (st.length !== 0 && height[i] > height[st[st.length - 1]]) { // 注意这里是while
let mid = st[st.length - 1];
st.pop();
if (st.length !== 0) {
let h = Math.min(height[st[st.length - 1]], height[i]) - height[mid];
let w = i - st[st.length - 1] - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
};
//单调栈 简洁版本 只处理情况三
var trap = function(height) {
const len = height.length;
if(len <= 2) return 0; // 可以不加
const st = [];// 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
let sum = 0;
for(let i = 1; i < len; i++){ // 只处理的情况三,其实是把情况一和情况二融合了
while (st.length !== 0 && height[i] > height[st[st.length - 1]]) { // 注意这里是while
let mid = st[st.length - 1];
st.pop();
if (st.length !== 0) {
let h = Math.min(height[st[st.length - 1]], height[i]) - height[mid];
let w = i - st[st.length - 1] - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
return sum;
};
```
### TypeScript:
暴力解法:
```typescript
function trap(height: number[]): number {
const length: number = height.length;
let resVal: number = 0;
for (let i = 0; i < length; i++) {
let leftMaxHeight: number = height[i],
rightMaxHeight: number = height[i];
let leftIndex: number = i - 1,
rightIndex: number = i + 1;
while (leftIndex >= 0) {
if (height[leftIndex] > leftMaxHeight)
leftMaxHeight = height[leftIndex];
leftIndex--;
}
while (rightIndex < length) {
if (height[rightIndex] > rightMaxHeight)
rightMaxHeight = height[rightIndex];
rightIndex++;
}
resVal += Math.min(leftMaxHeight, rightMaxHeight) - height[i];
}
return resVal;
};
```
双指针:
```typescript
function trap(height: number[]): number {
const length: number = height.length;
const leftMaxHeightDp: number[] = [],
rightMaxHeightDp: number[] = [];
leftMaxHeightDp[0] = height[0];
rightMaxHeightDp[length - 1] = height[length - 1];
for (let i = 1; i < length; i++) {
leftMaxHeightDp[i] = Math.max(height[i], leftMaxHeightDp[i - 1]);
}
for (let i = length - 2; i >= 0; i--) {
rightMaxHeightDp[i] = Math.max(height[i], rightMaxHeightDp[i + 1]);
}
let resVal: number = 0;
for (let i = 0; i < length; i++) {
resVal += Math.min(leftMaxHeightDp[i], rightMaxHeightDp[i]) - height[i];
}
return resVal;
};
```
单调栈:
```typescript
function trap(height: number[]): number {
const length: number = height.length;
const stack: number[] = [];
stack.push(0);
let resVal: number = 0;
for (let i = 1; i < length; i++) {
let top = stack[stack.length - 1];
if (height[top] > height[i]) {
stack.push(i);
} else if (height[top] === height[i]) {
stack.pop();
stack.push(i);
} else {
while (stack.length > 0 && height[top] < height[i]) {
let mid = stack.pop();
if (stack.length > 0) {
let left = stack[stack.length - 1];
let h = Math.min(height[left], height[i]) - height[mid];
let w = i - left - 1;
resVal += h * w;
top = stack[stack.length - 1];
}
}
stack.push(i);
}
}
return resVal;
};
```
### C:
一种更简便的双指针方法:
之前的双指针方法的原理是固定“底”的位置,往两边找比它高的“壁”,循环若干次求和。
我们逆向思维,把“壁”用两个初始位置在数组首末位置的指针表示,“壁”往中间推,同样可以让每个“底”都能找到最高的“壁”
本质上就是改变了运算方向,从而减少了重复运算
代码如下:
```C
int trap(int* height, int heightSize) {
int ans = 0;
int left = 0, right = heightSize - 1; //初始化两个指针到左右两边
int leftMax = 0, rightMax = 0; //这两个值用来记录左右的“壁”的最高值
while (left < right) { //两个指针重合就结束
leftMax = fmax(leftMax, height[left]);
rightMax = fmax(rightMax, height[right]);
if (leftMax < rightMax) {
ans += leftMax - height[left]; //这里考虑的是下标为left的“底”能装多少水
++left;//指针的移动次序是这个方法的关键
//这里左指针右移是因为左“墙”较矮,左边这一片实际情况下的盛水量是受制于这个矮的左“墙”的
//而较高的右边在实际情况下的限制条件可能不是当前的左“墙”,比如限制条件可能是右“墙”,就能装更高的水,
}
else {
ans += rightMax - height[right]; //同理,考虑下标为right的元素
--right;
}
}
return ans;
}
```
* 时间复杂度 O(n)
* 空间复杂度 O(1)
### Rust:
双指针
```rust
impl Solution {
pub fn trap(height: Vec) -> i32 {
let n = height.len();
let mut max_left = vec![0; height.len()];
let mut max_right = vec![0; height.len()];
max_left.iter_mut().zip(max_right.iter_mut().rev()).enumerate().fold((0, 0), |(lm, rm), (idx, (x, y))| {
let lmax = lm.max(height[idx]);
let rmax = rm.max(height[n - 1 - idx]);
*x = lmax; *y = rmax;
(lmax, rmax)
});
height.iter().enumerate().fold(0, |acc, (idx, x)| {
let h = max_left[idx].min(max_right[idx]);
if h > 0 { h - x + acc } else { acc }
})
}
}
```
单调栈
```rust
impl Solution {
pub fn trap(height: Vec) -> i32 {
let mut stack = vec![];
let mut ans = 0;
for (right_pos, &right_h) in height.iter().enumerate() {
while !stack.is_empty() && height[*stack.last().unwrap()] <= right_h {
let mid_pos = stack.pop().unwrap();
if !stack.is_empty() {
let left_pos = *stack.last().unwrap();
let left_h = height[left_pos];
let top = std::cmp::min(left_h, right_h);
if top > height[mid_pos] {
ans += (top - height[mid_pos]) * (right_pos - left_pos - 1) as i32;
}
}
}
stack.push(right_pos);
}
ans
}
}
```
Rust
双指针
```rust
impl Solution {
pub fn trap(height: Vec) -> i32 {
let n = height.len();
let mut max_left = vec![0; height.len()];
let mut max_right = vec![0; height.len()];
max_left.iter_mut().zip(max_right.iter_mut().rev()).enumerate().fold((0, 0), |(lm, rm), (idx, (x, y))| {
let lmax = lm.max(height[idx]);
let rmax = rm.max(height[n - 1 - idx]);
*x = lmax; *y = rmax;
(lmax, rmax)
});
height.iter().enumerate().fold(0, |acc, (idx, x)| {
let h = max_left[idx].min(max_right[idx]);
if h > 0 { h - x + acc } else { acc }
})
}
}
```
单调栈
```rust
impl Solution {
pub fn trap(height: Vec) -> i32 {
let mut stack = vec![];
let mut ans = 0;
for (right_pos, &right_h) in height.iter().enumerate() {
while !stack.is_empty() && height[*stack.last().unwrap()] <= right_h {
let mid_pos = stack.pop().unwrap();
if !stack.is_empty() {
let left_pos = *stack.last().unwrap();
let left_h = height[left_pos];
let top = std::cmp::min(left_h, right_h);
if top > height[mid_pos] {
ans += (top - height[mid_pos]) * (right_pos - left_pos - 1) as i32;
}
}
}
stack.push(right_pos);
}
ans
}
}
```
================================================
FILE: problems/0045.跳跃游戏II.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
> 相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不少,做好心理准备!
# 45.跳跃游戏 II
[力扣题目链接](https://leetcode.cn/problems/jump-game-ii/)
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
- 输入: [2,3,1,1,4]
- 输出: 2
- 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[贪心算法,最少跳几步还得看覆盖范围 | LeetCode: 45.跳跃游戏 II](https://www.bilibili.com/video/BV1Y24y1r7XZ),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
本题相对于[55.跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)还是难了不少。
但思路是相似的,还是要看最大覆盖范围。
本题要计算最少步数,那么就要想清楚什么时候步数才一定要加一呢?
贪心的思路,局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最少步数。
思路虽然是这样,但在写代码的时候还不能真的能跳多远就跳多远,那样就不知道下一步最远能跳到哪里了。
**所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!**
**这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖**。
如果移动下标达到了当前这一步的最大覆盖最远距离了,还没有到终点的话,那么就必须再走一步来增加覆盖范围,直到覆盖范围覆盖了终点。
如图:

**图中覆盖范围的意义在于,只要红色的区域,最多两步一定可以到!(不用管具体怎么跳,反正一定可以跳到)**
### 方法一
从图中可以看出来,就是移动下标达到了当前覆盖的最远距离下标时,步数就要加一,来增加覆盖距离。最后的步数就是最少步数。
这里还是有个特殊情况需要考虑,当移动下标达到了当前覆盖的最远距离下标时
- 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。
- 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。
C++代码如下:(详细注释)
```CPP
// 版本一
class Solution {
public:
int jump(vector& nums) {
if (nums.size() == 1) return 0;
int curDistance = 0; // 当前覆盖最远距离下标
int ans = 0; // 记录走的最大步数
int nextDistance = 0; // 下一步覆盖最远距离下标
for (int i = 0; i < nums.size(); i++) {
nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖最远距离下标
if (i == curDistance) { // 遇到当前覆盖最远距离下标
ans++; // 需要走下一步
curDistance = nextDistance; // 更新当前覆盖最远距离下标(相当于加油了)
if (nextDistance >= nums.size() - 1) break; // 当前覆盖最远距到达集合终点,不用做ans++操作了,直接结束
}
}
return ans;
}
};
```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
### 方法二
依然是贪心,思路和方法一差不多,代码可以简洁一些。
**针对于方法一的特殊情况,可以统一处理**,即:移动下标只要遇到当前覆盖最远距离的下标,直接步数加一,不考虑是不是终点的情况。
想要达到这样的效果,只要让移动下标,最大只能移动到 nums.size - 2 的地方就可以了。
因为当移动下标指向 nums.size - 2 时:
- 如果移动下标等于当前覆盖最大距离下标, 需要再走一步(即 ans++),因为最后一步一定是可以到的终点。(题目假设总是可以到达数组的最后一个位置),如图:

- 如果移动下标不等于当前覆盖最大距离下标,说明当前覆盖最远距离就可以直接达到终点了,不需要再走一步。如图:

代码如下:
```CPP
// 版本二
class Solution {
public:
int jump(vector& nums) {
int curDistance = 0; // 当前覆盖的最远距离下标
int ans = 0; // 记录走的最大步数
int nextDistance = 0; // 下一步覆盖的最远距离下标
for (int i = 0; i < nums.size() - 1; i++) { // 注意这里是小于nums.size() - 1,这是关键所在
nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖的最远距离下标
if (i == curDistance) { // 遇到当前覆盖的最远距离下标
curDistance = nextDistance; // 更新当前覆盖的最远距离下标
ans++;
}
}
return ans;
}
};
```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
可以看出版本二的代码相对于版本一简化了不少!
**其精髓在于控制移动下标 i 只移动到 nums.size() - 2 的位置**,所以移动下标只要遇到当前覆盖最远距离的下标,直接步数加一,不用考虑别的了。
## 总结
相信大家可以发现,这道题目相当于[55.跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)难了不止一点。
但代码又十分简单,贪心就是这么巧妙。
理解本题的关键在于:**以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点**,这个范围内最少步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。
## 其他语言版本
### Java
```Java
// 版本一
class Solution {
public int jump(int[] nums) {
if (nums == null || nums.length == 0 || nums.length == 1) {
return 0;
}
//记录跳跃的次数
int count=0;
//当前的覆盖最大区域
int curDistance = 0;
//最大的覆盖区域
int maxDistance = 0;
for (int i = 0; i < nums.length; i++) {
//在可覆盖区域内更新最大的覆盖区域
maxDistance = Math.max(maxDistance,i+nums[i]);
//说明当前一步,再跳一步就到达了末尾
if (maxDistance>=nums.length-1){
count++;
break;
}
//走到当前覆盖的最大区域时,更新下一步可达的最大区域
if (i==curDistance){
curDistance = maxDistance;
count++;
}
}
return count;
}
}
```
```java
// 版本二
class Solution {
public int jump(int[] nums) {
int result = 0;
// 当前覆盖的最远距离下标
int end = 0;
// 下一步覆盖的最远距离下标
int temp = 0;
for (int i = 0; i <= end && end < nums.length - 1; ++i) {
temp = Math.max(temp, i + nums[i]);
// 可达位置的改变次数就是跳跃次数
if (i == end) {
end = temp;
result++;
}
}
return result;
}
}
```
### Python
贪心(版本一)
```python
class Solution:
def jump(self, nums):
if len(nums) == 1:
return 0
cur_distance = 0 # 当前覆盖最远距离下标
ans = 0 # 记录走的最大步数
next_distance = 0 # 下一步覆盖最远距离下标
for i in range(len(nums)):
next_distance = max(nums[i] + i, next_distance) # 更新下一步覆盖最远距离下标
if i == cur_distance: # 遇到当前覆盖最远距离下标
ans += 1 # 需要走下一步
cur_distance = next_distance # 更新当前覆盖最远距离下标(相当于加油了)
if next_distance >= len(nums) - 1: # 当前覆盖最远距离达到数组末尾,不用再做ans++操作,直接结束
break
return ans
```
贪心(版本二)
```python
class Solution:
def jump(self, nums):
cur_distance = 0 # 当前覆盖的最远距离下标
ans = 0 # 记录走的最大步数
next_distance = 0 # 下一步覆盖的最远距离下标
for i in range(len(nums) - 1): # 注意这里是小于len(nums) - 1,这是关键所在
next_distance = max(nums[i] + i, next_distance) # 更新下一步覆盖的最远距离下标
if i == cur_distance: # 遇到当前覆盖的最远距离下标
cur_distance = next_distance # 更新当前覆盖的最远距离下标
ans += 1
return ans
```
贪心(版本三) 类似‘55-跳跃游戏’写法
```python
class Solution:
def jump(self, nums) -> int:
if len(nums)==1: # 如果数组只有一个元素,不需要跳跃,步数为0
return 0
i = 0 # 当前位置
count = 0 # 步数计数器
cover = 0 # 当前能够覆盖的最远距离
while i <= cover: # 当前位置小于等于当前能够覆盖的最远距离时循环
for i in range(i, cover+1): # 遍历从当前位置到当前能够覆盖的最远距离之间的所有位置
cover = max(nums[i]+i, cover) # 更新当前能够覆盖的最远距离
if cover >= len(nums)-1: # 如果当前能够覆盖的最远距离达到或超过数组的最后一个位置,直接返回步数+1
return count+1
count += 1 # 每一轮遍历结束后,步数+1
```
动态规划
```python
class Solution:
def jump(self, nums: List[int]) -> int:
result = [10**4+1] * len(nums) # 初始化结果数组,初始值为一个较大的数
result[0] = 0 # 起始位置的步数为0
for i in range(len(nums)): # 遍历数组
for j in range(nums[i] + 1): # 在当前位置能够跳跃的范围内遍历
if i + j < len(nums): # 确保下一跳的位置不超过数组范围
result[i + j] = min(result[i + j], result[i] + 1) # 更新到达下一跳位置的最少步数
return result[-1] # 返回到达最后一个位置的最少步数
```
### Go
```go
/**
* @date: 2024 Jan 06
* @time: 13:44
* @author: Chris
**/
// 贪心算法优化版
// 记录步骤规则:每超过上一次可达最大范围,需要跳跃一次,次数+1
// 记录位置:i == lastDistance + 1
func jump(nums []int) int {
// 根据题目规则,初始位置为nums[0]
lastDistance := 0 // 上一次覆盖范围
curDistance := 0 // 当前覆盖范围(可达最大范围)
minStep := 0 // 记录最少跳跃次数
for i := 0; i < len(nums); i++ {
if i == lastDistance+1 { // 在上一次可达范围+1的位置,记录步骤
minStep++ // 跳跃次数+1
lastDistance = curDistance // 记录时才可以更新
}
curDistance = max(nums[i]+i, curDistance) // 更新当前可达的最大范围
}
return minStep
}
```
```go
// 贪心版本一
func jump(nums []int) int {
n := len(nums)
if n == 1 {
return 0
}
cur, next := 0, 0
step := 0
for i := 0; i < n; i++ {
next = max(nums[i]+i, next)
if i == cur {
if cur != n-1 {
step++
cur = next
if cur >= n-1 {
return step
}
} else {
return step
}
}
}
return step
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
```
```go
// 贪心版本二
func jump(nums []int) int {
n := len(nums)
if n == 1 {
return 0
}
cur, next := 0, 0
step := 0
for i := 0; i < n-1; i++ {
next = max(nums[i]+i, next)
if i == cur {
cur = next
step++
}
}
return step
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
```
### JavaScript
```Javascript
var jump = function(nums) {
let curIndex = 0
let nextIndex = 0
let steps = 0
for(let i = 0; i < nums.length - 1; i++) {
nextIndex = Math.max(nums[i] + i, nextIndex)
if(i === curIndex) {
curIndex = nextIndex
steps++
}
}
return steps
};
```
### TypeScript
```typescript
function jump(nums: number[]): number {
const length: number = nums.length;
let curFarthestIndex: number = 0,
nextFarthestIndex: number = 0;
let curIndex: number = 0;
let stepNum: number = 0;
while (curIndex < length - 1) {
nextFarthestIndex = Math.max(nextFarthestIndex, curIndex + nums[curIndex]);
if (curIndex === curFarthestIndex) {
curFarthestIndex = nextFarthestIndex;
stepNum++;
}
curIndex++;
}
return stepNum;
}
```
### Scala
```scala
object Solution {
def jump(nums: Array[Int]): Int = {
if (nums.length == 0) return 0
var result = 0 // 记录走的最大步数
var curDistance = 0 // 当前覆盖最远距离下标
var nextDistance = 0 // 下一步覆盖最远距离下标
for (i <- nums.indices) {
nextDistance = math.max(nums(i) + i, nextDistance) // 更新下一步覆盖最远距离下标
if (i == curDistance) {
if (curDistance != nums.length - 1) {
result += 1
curDistance = nextDistance
if (nextDistance >= nums.length - 1) return result
} else {
return result
}
}
}
result
}
}
```
### Rust
```Rust
//版本一
impl Solution {
pub fn jump(nums: Vec) -> i32 {
if nums.len() == 1 {
return 0;
}
let mut cur_distance = 0;
let mut ans = 0;
let mut next_distance = 0;
for (i, &n) in nums.iter().enumerate().take(nums.len() - 1) {
next_distance = (n as usize + i).max(next_distance);
if i == cur_distance {
if cur_distance < nums.len() - 1 {
ans += 1;
cur_distance = next_distance;
if next_distance >= nums.len() - 1 {
break;
};
} else {
break;
}
}
}
ans
}
}
```
```Rust
//版本二
impl Solution {
pub fn jump(nums: Vec) -> i32 {
if nums.len() == 1 {
return 0;
}
let mut cur_distance = 0;
let mut ans = 0;
let mut next_distance = 0;
for (i, &n) in nums.iter().enumerate().take(nums.len() - 1) {
next_distance = (n as usize + i).max(next_distance);
if i == cur_distance {
cur_distance = next_distance;
ans += 1;
}
}
ans
}
}
```
### C
```c
#define max(a, b) ((a) > (b) ? (a) : (b))
int jump(int* nums, int numsSize) {
if(numsSize == 1){
return 0;
}
int count = 0;
// 记录当前能走的最远距离
int curDistance = 0;
// 记录下一步能走的最远距离
int nextDistance = 0;
for(int i = 0; i < numsSize; i++){
nextDistance = max(i + nums[i], nextDistance);
// 下标到了当前的最大距离
if(i == nextDistance){
count++;
curDistance = nextDistance;
}
}
return count;
}
```
### C#
```csharp
// 版本二
public class Solution
{
public int Jump(int[] nums)
{
int cur = 0, next = 0, step = 0;
for (int i = 0; i < nums.Length - 1; i++)
{
next = Math.Max(next, i + nums[i]);
if (i == cur)
{
cur = next;
step++;
}
}
return step;
}
}
```
================================================
FILE: problems/0046.全排列.md
================================================
* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html)
# 46.全排列
[力扣题目链接](https://leetcode.cn/problems/permutations/)
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
* 输入: [1,2,3]
* 输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[组合与排列的区别,回溯算法求解的时候,有何不同?| LeetCode:46.全排列](https://www.bilibili.com/video/BV19v4y1S79W/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
此时我们已经学习了[77.组合问题](https://programmercarl.com/0077.组合.html)、 [131.分割回文串](https://programmercarl.com/0131.分割回文串.html)和[78.子集问题](https://programmercarl.com/0078.子集.html),接下来看一看排列问题。
相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。
所以正如我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)所讲的为什么回溯法是暴力搜索,效率这么低,还要用它?
**因为一些问题能暴力搜出来就已经很不错了!**
我以[1,2,3]为例,抽象成树形结构如下:

### 回溯三部曲
* 递归函数参数
**首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方**。
可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。
但排列问题需要一个used数组,标记已经选择的元素,如图橘黄色部分所示:

代码如下:
```cpp
vector> result;
vector path;
void backtracking (vector& nums, vector& used)
```
* 递归终止条件

可以看出叶子节点,就是收割结果的地方。
那么什么时候,算是到达叶子节点呢?
当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。
代码如下:
```cpp
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
```
* 单层搜索的逻辑
这里和[77.组合问题](https://programmercarl.com/0077.组合.html)、[131.切割问题](https://programmercarl.com/0131.分割回文串.html)和[78.子集问题](https://programmercarl.com/0078.子集.html)最大的不同就是for循环里不用startIndex了。
因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。
**而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次**。
代码如下:
```cpp
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
```
整体C++代码如下:
```CPP
class Solution {
public:
vector> result;
vector path;
void backtracking (vector& nums, vector& used) {
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
vector> permute(vector& nums) {
result.clear();
path.clear();
vector used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
```
* 时间复杂度: O(n!)
* 空间复杂度: O(n)
## 总结
大家此时可以感受出排列问题的不同:
* 每层都是从0开始搜索而不是startIndex
* 需要used数组记录path里都放了哪些元素了
排列问题是回溯算法解决的经典题目,大家可以好好体会体会。
## 其他语言版本
### Java
```java
class Solution {
List> result = new ArrayList<>();// 存放符合条件结果的集合
LinkedList path = new LinkedList<>();// 用来存放符合条件结果
boolean[] used;
public List> permute(int[] nums) {
if (nums.length == 0){
return result;
}
used = new boolean[nums.length];
permuteHelper(nums);
return result;
}
private void permuteHelper(int[] nums){
if (path.size() == nums.length){
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++){
if (used[i]){
continue;
}
used[i] = true;
path.add(nums[i]);
permuteHelper(nums);
path.removeLast();
used[i] = false;
}
}
}
```
```java
// 解法2:通过判断path中是否存在数字,排除已经选择的数字
class Solution {
List> result = new ArrayList<>();
LinkedList path = new LinkedList<>();
public List> permute(int[] nums) {
if (nums.length == 0) return result;
backtrack(nums, path);
return result;
}
public void backtrack(int[] nums, LinkedList path) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i =0; i < nums.length; i++) {
// 如果path中已有,则跳过
if (path.contains(nums[i])) {
continue;
}
path.add(nums[i]);
backtrack(nums, path);
path.removeLast();
}
}
}
```
### Python
回溯 使用used
```python
class Solution:
def permute(self, nums):
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False
```
### Go
```Go
var (
res [][]int
path []int
st []bool // state的缩写
)
func permute(nums []int) [][]int {
res, path = make([][]int, 0), make([]int, 0, len(nums))
st = make([]bool, len(nums))
dfs(nums, 0)
return res
}
func dfs(nums []int, cur int) {
if cur == len(nums) {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
}
for i := 0; i < len(nums); i++ {
if !st[i] {
path = append(path, nums[i])
st[i] = true
dfs(nums, cur + 1)
st[i] = false
path = path[:len(path)-1]
}
}
}
```
### JavaScript
```js
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
const res = [], path = [];
backtracking(nums, nums.length, []);
return res;
function backtracking(n, k, used) {
if(path.length === k) {
res.push(Array.from(path));
return;
}
for (let i = 0; i < k; i++ ) {
if(used[i]) continue;
path.push(n[i]);
used[i] = true; // 同支
backtracking(n, k, used);
path.pop();
used[i] = false;
}
}
};
```
### TypeScript
```typescript
function permute(nums: number[]): number[][] {
const resArr: number[][] = [];
const helperSet: Set = new Set();
backTracking(nums, []);
return resArr;
function backTracking(nums: number[], route: number[]): void {
if (route.length === nums.length) {
resArr.push([...route]);
return;
}
let tempVal: number;
for (let i = 0, length = nums.length; i < length; i++) {
tempVal = nums[i];
if (!helperSet.has(tempVal)) {
route.push(tempVal);
helperSet.add(tempVal);
backTracking(nums, route);
route.pop();
helperSet.delete(tempVal);
}
}
}
};
```
### Rust
```Rust
impl Solution {
fn backtracking(result: &mut Vec>, path: &mut Vec, nums: &Vec, used: &mut Vec) {
let len = nums.len();
if path.len() == len {
result.push(path.clone());
return;
}
for i in 0..len {
if used[i] == true { continue; }
used[i] = true;
path.push(nums[i]);
Self::backtracking(result, path, nums, used);
path.pop();
used[i] = false;
}
}
pub fn permute(nums: Vec) -> Vec