Merge branch 'master' into master

This commit is contained in:
程序员Carl 2022-12-01 11:37:33 +08:00 committed by GitHub
commit eeeb64dc32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2097 additions and 1114 deletions

View File

@ -134,12 +134,13 @@ public int[] twoSum(int[] nums, int target) {
}
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i];
int temp = target - nums[i]; // 遍历当前元素并在map中寻找是否有匹配的key
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
break;
}
map.put(nums[i], i);
map.put(nums[i], i); // 如果没找到匹配对就把访问过的元素和下标加入到map中
}
return res;
}
@ -152,16 +153,17 @@ class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, value in enumerate(nums):
if target - value in records:
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index
records[value] = index # 遍历当前元素并在map中寻找是否有匹配的key
return []
```
Go
```go
// 暴力解法
func twoSum(nums []int, target int) []int {
for k1, _ := range nums {
for k2 := k1 + 1; k2 < len(nums); k2++ {
@ -216,11 +218,11 @@ Javascript
```javascript
var twoSum = function (nums, target) {
let hash = {};
for (let i = 0; i < nums.length; i++) {
for (let i = 0; i < nums.length; i++) { // 遍历当前元素并在map中寻找是否有匹配的key
if (hash[target - nums[i]] !== undefined) {
return [i, hash[target - nums[i]]];
}
hash[nums[i]] = i;
hash[nums[i]] = i; // 如果没找到匹配对就把访问过的元素和下标加入到map中
}
return [];
};

View File

@ -253,13 +253,15 @@ class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) {
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
@ -273,7 +275,7 @@ class Solution {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
@ -294,12 +296,15 @@ class Solution:
ans = []
n = len(nums)
nums.sort()
# 找出a + b + c = 0
# a = nums[i], b = nums[left], c = nums[right]
for i in range(n):
left = i + 1
right = n - 1
if nums[i] > 0:
# 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if nums[i] > 0:
break
if i >= 1 and nums[i] == nums[i - 1]:
if i >= 1 and nums[i] == nums[i - 1]: # 去重a
continue
while left < right:
total = nums[i] + nums[left] + nums[right]
@ -309,13 +314,14 @@ class Solution:
left += 1
else:
ans.append([nums[i], nums[left], nums[right]])
# 去重逻辑应该放在找到一个三元组之后对b 和 c去重
while left != right and nums[left] == nums[left + 1]: left += 1
while left != right and nums[right] == nums[right - 1]: right -= 1
left += 1
right -= 1
return ans
```
Python (v2):
Python (v3):
```python
class Solution:
@ -344,32 +350,36 @@ class Solution:
Go
```Go
func threeSum(nums []int)[][]int{
func threeSum(nums []int) [][]int {
sort.Ints(nums)
res:=[][]int{}
for i:=0;i<len(nums)-2;i++{
n1:=nums[i]
if n1>0{
res := [][]int{}
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for i := 0; i < len(nums)-2; i++ {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
n1 := nums[i]
if n1 > 0 {
break
}
if i>0&&n1==nums[i-1]{
// 去重a
if i > 0 && n1 == nums[i-1] {
continue
}
l,r:=i+1,len(nums)-1
for l<r{
n2,n3:=nums[l],nums[r]
if n1+n2+n3==0{
res=append(res,[]int{n1,n2,n3})
for l<r&&nums[l]==n2{
l, r := i+1, len(nums)-1
for l < r {
n2, n3 := nums[l], nums[r]
if n1+n2+n3 == 0 {
res = append(res, []int{n1, n2, n3})
// 去重逻辑应该放在找到一个三元组之后对b 和 c去重
for l < r && nums[l] == n2 {
l++
}
for l<r&&nums[r]==n3{
for l < r && nums[r] == n3 {
r--
}
}else if n1+n2+n3<0{
} else if n1+n2+n3 < 0 {
l++
}else {
} else {
r--
}
}

View File

@ -136,25 +136,26 @@ class Solution {
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// nums[i] > target 直接返回, 剪枝操作
if (nums[i] > 0 && nums[i] > target) {
return result;
}
if (i > 0 && nums[i - 1] == nums[i]) {
if (i > 0 && nums[i - 1] == nums[i]) { // 对nums[i]去重
continue;
}
for (int j = i + 1; j < nums.length; j++) {
if (j > i + 1 && nums[j - 1] == nums[j]) {
if (j > i + 1 && nums[j - 1] == nums[j]) { // 对nums[j]去重
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target int会溢出
long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
@ -162,7 +163,7 @@ class Solution {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
@ -186,10 +187,10 @@ class Solution:
nums.sort()
n = len(nums)
res = []
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]: continue
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]: continue # 对nums[i]去重
for k in range(i+1, n):
if k > i + 1 and nums[k] == nums[k-1]: continue
if k > i + 1 and nums[k] == nums[k-1]: continue # 对nums[k]去重
p = k + 1
q = n - 1
@ -198,6 +199,7 @@ class Solution:
elif nums[i] + nums[k] + nums[p] + nums[q] < target: p += 1
else:
res.append([nums[i], nums[k], nums[p], nums[q]])
# 对nums[p]和nums[q]去重
while p < q and nums[p] == nums[p + 1]: p += 1
while p < q and nums[q] == nums[q - 1]: q -= 1
p += 1
@ -258,12 +260,12 @@ func fourSum(nums []int, target int) [][]int {
// if n1 > target { // 不能这样写,因为可能是负数
// break
// }
if i > 0 && n1 == nums[i-1] {
if i > 0 && n1 == nums[i-1] { // 对nums[i]去重
continue
}
for j := i + 1; j < len(nums)-2; j++ {
n2 := nums[j]
if j > i+1 && n2 == nums[j-1] {
if j > i+1 && n2 == nums[j-1] { // 对nums[j]去重
continue
}
l := j + 1
@ -320,6 +322,8 @@ var fourSum = function(nums, target) {
if(sum < target) { l++; continue}
if(sum > target) { r--; continue}
res.push([nums[i], nums[j], nums[l], nums[r]]);
// 对nums[left]和nums[right]去重
while(l < r && nums[l] === nums[++l]);
while(l < r && nums[r] === nums[--r]);
}

View File

@ -76,7 +76,7 @@ cd a/b/c/../../
**一些同学,在面试中看到这种题目上来就开始写代码,然后就越写越乱。**
建议写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。
建议写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。
先来分析一下 这里有三种不匹配的情况,

View File

@ -196,25 +196,6 @@ class Solution {
Python
```python 3
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
if nums is None or len(nums)==0:
return 0
l=0
r=len(nums)-1
while l<r:
while(l<r and nums[l]!=val):
l+=1
while(l<r and nums[r]==val):
r-=1
nums[l], nums[r]=nums[r], nums[l]
print(nums)
if nums[l]==val:
return l
else:
return l+1
```
``` python 3
class Solution:

View File

@ -94,7 +94,7 @@ next数组就是一个前缀表prefix table
**前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。**
为了清楚了解前缀表的来历,我们来举一个例子:
为了清楚了解前缀表的来历,我们来举一个例子:
要在文本串aabaabaafa 中查找是否出现过一个模式串aabaaf。
@ -110,9 +110,9 @@ next数组就是一个前缀表prefix table
![KMP详解1](https://code-thinking.cdn.bcebos.com/gifs/KMP%E7%B2%BE%E8%AE%B21.gif)
动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说
动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说
可以看出文本串中第六个字符b 和 模式串的第六个字符f不匹配了。如果暴力匹配发现不匹配,此时就要从头匹配了。
可以看出文本串中第六个字符b 和 模式串的第六个字符f不匹配了。如果暴力匹配发现不匹配此时就要从头匹配了。
但如果使用前缀表就不会从头匹配而是从上次已经匹配的内容开始匹配找到了模式串中第三个字符b继续开始匹配。
@ -157,7 +157,7 @@ next数组就是一个前缀表prefix table
以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要!
**下标5之前这部分的字符串也就是字符串aabaa的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面新匹配就可以了。**
**下标5之前这部分的字符串也就是字符串aabaa的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面新匹配就可以了。**
所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。
@ -199,7 +199,7 @@ next数组就是一个前缀表prefix table
所以要看前一位的 前缀表的数值。
前一个字符的前缀表的数值是2把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。
前一个字符的前缀表的数值是2把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。
最后就在文本串中找到了和模式串匹配的子串了。
@ -211,7 +211,7 @@ next数组就可以是前缀表但是很多实现都是把前缀表统一减
为什么这么做呢,其实也是很多文章视频没有解释清楚的地方。
其实**这并不涉及到KMP的原理而是具体实现next数组可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1。**
其实**这并不涉及到KMP的原理而是具体实现next数组可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1。**
后面我会提供两种不同的实现代码,大家就明白了。
@ -231,7 +231,7 @@ next数组就可以是前缀表但是很多实现都是把前缀表统一减
其中n为文本串长度m为模式串长度因为在匹配的过程中根据前缀表不断调整匹配的位置可以看出匹配的过程是O(n)之前还要单独生成next数组时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
暴力的解法显而易见是O(n × m),所以**KMP在字符串匹配中极大的提高的搜索的效率。**
暴力的解法显而易见是O(n × m),所以**KMP在字符串匹配中极大地提高了搜索的效率。**
为了和力扣题目28.实现strStr保持一致方便大家理解以下文章统称haystack为文本串, needle为模式串。
@ -251,7 +251,7 @@ void getNext(int* next, const string& s)
2. 处理前后缀不相同的情况
3. 处理前后缀相同的情况
接下来我们详解详解一下。
接下来我们详解一下。
1. 初始化:
@ -613,12 +613,12 @@ 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)){
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)){
if(s.charAt(i) == s.charAt(j+1)){
j++;
}
next[i] = j;
@ -632,14 +632,14 @@ class Solution {
int[] next = new int[needle.length()];
getNext(next, needle);
int j = -1;
for(int i = 0; i<haystack.length();i++){
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)){
if(haystack.charAt(i) == needle.charAt(j+1)){
j++;
}
if(j==needle.length()-1){
if(j == needle.length()-1){
return (i-needle.length()+1);
}
}
@ -694,9 +694,9 @@ class Solution(object):
:type needle: str
:rtype: int
"""
m,n=len(haystack),len(needle)
m, n = len(haystack), len(needle)
for i in range(m):
if haystack[i:i+n]==needle:
if haystack[i:i+n] == needle:
return i
return -1
```
@ -704,31 +704,31 @@ class Solution(object):
// 方法一
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
a=len(needle)
b=len(haystack)
if a==0:
a = len(needle)
b = len(haystack)
if a == 0:
return 0
next=self.getnext(a,needle)
next = self.getnext(a,needle)
p=-1
for j in range(b):
while p>=0 and needle[p+1]!=haystack[j]:
p=next[p]
if needle[p+1]==haystack[j]:
p+=1
if p==a-1:
while p >= 0 and needle[p+1] != haystack[j]:
p = next[p]
if needle[p+1] == haystack[j]:
p += 1
if p == a-1:
return j-a+1
return -1
def getnext(self,a,needle):
next=['' for i in range(a)]
k=-1
next[0]=k
for i in range(1,len(needle)):
while (k>-1 and needle[k+1]!=needle[i]):
k=next[k]
if needle[k+1]==needle[i]:
k+=1
next[i]=k
next = ['' for i in range(a)]
k = -1
next[0] = k
for i in range(1, len(needle)):
while (k > -1 and needle[k+1] != needle[i]):
k = next[k]
if needle[k+1] == needle[i]:
k += 1
next[i] = k
return next
```
@ -736,34 +736,34 @@ class Solution:
// 方法二
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
a=len(needle)
b=len(haystack)
if a==0:
a = len(needle)
b = len(haystack)
if a == 0:
return 0
i=j=0
next=self.getnext(a,needle)
while(i<b and j<a):
if j==-1 or needle[j]==haystack[i]:
i+=1
j+=1
i = j = 0
next = self.getnext(a, needle)
while(i < b and j < a):
if j == -1 or needle[j] == haystack[i]:
i += 1
j += 1
else:
j=next[j]
if j==a:
j = next[j]
if j == a:
return i-j
else:
return -1
def getnext(self,a,needle):
next=['' for i in range(a)]
j,k=0,-1
next[0]=k
while(j<a-1):
if k==-1 or needle[k]==needle[j]:
k+=1
j+=1
next[j]=k
def getnext(self, a, needle):
next = ['' for i in range(a)]
j, k = 0, -1
next[0] = k
while(j < a-1):
if k == -1 or needle[k] == needle[j]:
k += 1
j += 1
next[j] = k
else:
k=next[k]
k = next[k]
return next
```
@ -777,17 +777,17 @@ Go
// next 前缀表数组
// s 模式串
func getNext(next []int, s string) {
j := -1 // j表示 最长相等前后缀长度
j := -1 // j表示 最长相等前后缀长度
next[0] = j
for i := 1; i < len(s); i++ {
for j >= 0 && s[i] != s[j+1] {
j = next[j] // 回退前一位
j = next[j] // 回退前一位
}
if s[i] == s[j+1] {
j++
}
next[i] = j // next[i]是i包括i之前的最长相等前后缀长度
next[i] = j // next[i]是i包括i之前的最长相等前后缀长度
}
}
func strStr(haystack string, needle string) int {
@ -796,15 +796,15 @@ func strStr(haystack string, needle string) int {
}
next := make([]int, len(needle))
getNext(next, needle)
j := -1 // 模式串的起始位置 next为-1 因此也为-1
j := -1 // 模式串的起始位置 next为-1 因此也为-1
for i := 0; i < len(haystack); i++ {
for j >= 0 && haystack[i] != needle[j+1] {
j = next[j] // 寻找下一个匹配点
j = next[j] // 寻找下一个匹配点
}
if haystack[i] == needle[j+1] {
j++
}
if j == len(needle)-1 { // j指向了模式串的末尾
if j == len(needle)-1 { // j指向了模式串的末尾
return i - len(needle) + 1
}
}
@ -842,7 +842,7 @@ func strStr(haystack string, needle string) int {
getNext(next, needle)
for i := 0; i < len(haystack); i++ {
for j > 0 && haystack[i] != needle[j] {
j = next[j-1] // 回退到j的前一位
j = next[j-1] // 回退到j的前一位
}
if haystack[i] == needle[j] {
j++

View File

@ -271,6 +271,64 @@ class Solution {
}
```
```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<nums.length&&nums[right]==target){
return right;
}
if(left>=0&&left<nums.length&&nums[left]==target){
return left;
}
return -1;
}
public int searchRight(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){
left = mid + 1;
}else{
right = mid - 1;
}
}
// left = right + 1
// 要找最后一次出现 如果有答案 优先找left
if(left>=0&&left<nums.length&&nums[left]==target){
return left;
}
if(right>=0&&right<=nums.length&&nums[right]==target){
return right;
}
return -1;
}
}
```
### Python
@ -685,3 +743,4 @@ class Solution {
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>

View File

@ -119,6 +119,19 @@ class Solution:
return False
```
```python
## for循环
class Solution:
def canJump(self, nums: List[int]) -> bool:
cover = 0
if len(nums) == 1: return True
for i in range(len(nums)):
if i <= cover:
cover = max(i + nums[i], cover)
if cover >= len(nums) - 1: return True
return False
```
### Go
```Go
func canJUmp(nums []int) bool {

View File

@ -272,24 +272,28 @@ class Solution:
row = len(obstacleGrid)
col = len(obstacleGrid[0])
dp = [[0 for _ in range(col)] for _ in range(row)]
dp[0][0] = 1 if obstacleGrid[0][0] != 1 else 0
if dp[0][0] == 0: return 0 # 如果第一个格子就是障碍return 0
dp[0][0] = 0 if obstacleGrid[0][0] == 1 else 1
if dp[0][0] == 0:
return 0 # 如果第一个格子就是障碍return 0
# 第一行
for i in range(1, col):
if obstacleGrid[0][i] != 1:
dp[0][i] = dp[0][i-1]
if obstacleGrid[0][i] == 1:
# 遇到障碍物时直接退出循环后面默认都是0
break
dp[0][i] = 1
# 第一列
for i in range(1, row):
if obstacleGrid[i][0] != 1:
dp[i][0] = dp[i-1][0]
print(dp)
if obstacleGrid[i][0] == 1:
# 遇到障碍物时直接退出循环后面默认都是0
break
dp[i][0] = 1
# print(dp)
for i in range(1, row):
for j in range(1, col):
if obstacleGrid[i][j] != 1:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
if obstacleGrid[i][j] == 0:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[-1][-1]
```

View File

@ -21,7 +21,7 @@
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。
那么如比较呢?
那么如比较呢?
比较的是两个子树的里侧和外侧的元素是否相等。如图所示:
@ -80,7 +80,7 @@ else if (left == NULL && right == NULL) return true;
else if (left->val != right->val) return false; // 注意这里我没有使用else
```
注意上面最后一种情况我没有使用else而是elseif 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。
注意上面最后一种情况我没有使用else而是else if 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。
3. 确定单层递归的逻辑
@ -244,7 +244,7 @@ public:
这次我们又深度剖析了一道二叉树的“简单题”大家会发现真正的把题目搞清楚其实并不简单leetcode上accept了和真正掌握了还是有距离的。
我们介绍了递归法和迭代法,递归依然通过递归三部曲来解决了这道题目,如果只看精简的代码根本看不出来递归三部曲是如解题的。
我们介绍了递归法和迭代法,递归依然通过递归三部曲来解决了这道题目,如果只看精简的代码根本看不出来递归三部曲是如解题的。
在迭代法中我们使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。
@ -259,7 +259,7 @@ public:
# 其他语言版本
## Java
Java
```Java
/**
@ -364,7 +364,7 @@ public:
```
## Python
Python
递归法:
```python
@ -464,8 +464,7 @@ class Solution:
return True
```
## Go
Go
```go
/**
* Definition for a binary tree node.
@ -488,10 +487,12 @@ func defs(left *TreeNode, right *TreeNode) bool {
}
return defs(left.Left, right.Right) && defs(right.Left, left.Right);
}
func isSymmetric(root *TreeNode) bool {
return defs(root.Left, root.Right);
}
// 迭代
func isSymmetric(root *TreeNode) bool {
var queue []*TreeNode;
@ -515,59 +516,60 @@ func isSymmetric(root *TreeNode) bool {
```
## JavaScript
JavaScript
递归判断是否为对称二叉树:
```javascript
var isSymmetric = function(root) {
//使用递归遍历左右子树 递归三部曲
// 使用递归遍历左右子树 递归三部曲
// 1. 确定递归的参数 root.left root.right和返回值true false
const compareNode=function(left,right){
//2. 确定终止条件 空的情况
if(left===null&&right!==null||left!==null&&right===null){
const compareNode = function(left, right) {
// 2. 确定终止条件 空的情况
if(left === null && right !== null || left !== null && right === null) {
return false;
}else if(left===null&&right===null){
} else if(left === null && right === null) {
return true;
}else if(left.val!==right.val){
} else if(left.val !== right.val) {
return false;
}
//3. 确定单层递归逻辑
let outSide=compareNode(left.left,right.right);
let inSide=compareNode(left.right,right.left);
return outSide&&inSide;
// 3. 确定单层递归逻辑
let outSide = compareNode(left.left, right.right);
let inSide = compareNode(left.right, right.left);
return outSide && inSide;
}
if(root===null){
if(root === null) {
return true;
}
return compareNode(root.left,root.right);
return compareNode(root.left, root.right);
};
```
队列实现迭代判断是否为对称二叉树:
```javascript
var isSymmetric = function(root) {
//迭代方法判断是否是对称二叉树
//首先判断root是否为空
if(root===null){
// 迭代方法判断是否是对称二叉树
// 首先判断root是否为空
if(root === null) {
return true;
}
let queue=[];
let queue = [];
queue.push(root.left);
queue.push(root.right);
while(queue.length){
let leftNode=queue.shift();//左节点
let rightNode=queue.shift();//右节点
if(leftNode===null&&rightNode===null){
while(queue.length) {
let leftNode = queue.shift(); //左节点
let rightNode = queue.shift(); //右节点
if(leftNode === null && rightNode === null) {
continue;
}
if(leftNode===null||rightNode===null||leftNode.val!==rightNode.val){
if(leftNode === null || rightNode === null || leftNode.val !== rightNode.val) {
return false;
}
queue.push(leftNode.left);//左节点左孩子入队
queue.push(rightNode.right);//右节点右孩子入队
queue.push(leftNode.right);//左节点右孩子入队
queue.push(rightNode.left);//右节点左孩子入队
queue.push(leftNode.left); //左节点左孩子入队
queue.push(rightNode.right); //右节点右孩子入队
queue.push(leftNode.right); //左节点右孩子入队
queue.push(rightNode.left); //右节点左孩子入队
}
return true;
};
```
@ -575,33 +577,34 @@ var isSymmetric = function(root) {
栈实现迭代判断是否为对称二叉树:
```javascript
var isSymmetric = function(root) {
//迭代方法判断是否是对称二叉树
//首先判断root是否为空
if(root===null){
// 迭代方法判断是否是对称二叉树
// 首先判断root是否为空
if(root === null) {
return true;
}
let stack=[];
let stack = [];
stack.push(root.left);
stack.push(root.right);
while(stack.length){
let rightNode=stack.pop();//左节点
let leftNode=stack.pop();//右节点
if(leftNode===null&&rightNode===null){
while(stack.length) {
let rightNode = stack.pop(); //左节点
let leftNode=stack.pop(); //右节点
if(leftNode === null && rightNode === null) {
continue;
}
if(leftNode===null||rightNode===null||leftNode.val!==rightNode.val){
if(leftNode === null || rightNode === null || leftNode.val !== rightNode.val) {
return false;
}
stack.push(leftNode.left);//左节点左孩子入队
stack.push(rightNode.right);//右节点右孩子入队
stack.push(leftNode.right);//左节点右孩子入队
stack.push(rightNode.left);//右节点左孩子入队
stack.push(leftNode.left); //左节点左孩子入队
stack.push(rightNode.right); //右节点右孩子入队
stack.push(leftNode.right); //左节点右孩子入队
stack.push(rightNode.left); //右节点左孩子入队
}
return true;
};
```
## TypeScript
TypeScript
> 递归法
@ -670,7 +673,7 @@ function isSymmetric(root: TreeNode | null): boolean {
};
```
## Swift:
Swift:
> 递归
```swift
@ -752,25 +755,140 @@ func isSymmetric3(_ root: TreeNode?) -> Bool {
}
```
## Scala
Scala
递归:
> 递归:
```scala
object Solution {
object Solution {
def isSymmetric(root: TreeNode): Boolean = {
if (root == null) return true // 如果等于空直接返回true
def compare(left: TreeNode, right: TreeNode): Boolean = {
if (left == null && right == null) return true // 如果左右都为空则为true
if (left == null && right != null) return false // 如果左空右不空不对称返回false
if (left != null && right == null) return false // 如果左不空右空不对称返回false
if (left == null && right == null) true // 如果左右都为空则为true
else if (left == null && right != null) false // 如果左空右不空不对称返回false
else if (left != null && right == null) false // 如果左不空右空不对称返回false
// 如果左右的值相等,并且往下递归
left.value == right.value && compare(left.left, right.right) && compare(left.right, right.left)
else left.value == right.value && compare(left.left, right.right) && compare(left.right, right.left)
}
// 分别比较左子树和右子树
compare(root.left, root.right)
}
}
```
> 迭代 - 使用栈
```scala
object Solution {
import scala.collection.mutable
def isSymmetric(root: TreeNode): Boolean = {
if (root == null) return true
val cache = mutable.Stack[(TreeNode, TreeNode)]((root.left, root.right))
while (cache.nonEmpty) {
cache.pop() match {
case (null, null) =>
case (_, null) => return false
case (null, _) => return false
case (left, right) =>
if (left.value != right.value) return false
cache.push((left.left, right.right))
cache.push((left.right, right.left))
}
}
true
}
}
```
> 迭代 - 使用队列
```scala
object Solution {
import scala.collection.mutable
def isSymmetric(root: TreeNode): Boolean = {
if (root == null) return true
val cache = mutable.Queue[(TreeNode, TreeNode)]((root.left, root.right))
while (cache.nonEmpty) {
cache.dequeue() match {
case (null, null) =>
case (_, null) => return false
case (null, _) => return false
case (left, right) =>
if (left.value != right.value) return false
cache.enqueue((left.left, right.right))
cache.enqueue((left.right, right.left))
}
}
true
}
}
```
## Rust
递归:
```rust
impl Solution {
pub fn is_symmetric(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
Self::recur(
&root.as_ref().unwrap().borrow().left,
&root.as_ref().unwrap().borrow().right,
)
}
pub fn recur(
left: &Option<Rc<RefCell<TreeNode>>>,
right: &Option<Rc<RefCell<TreeNode>>>,
) -> bool {
match (left, right) {
(None, None) => true,
(Some(n1), Some(n2)) => {
return n1.borrow().val == n2.borrow().val
&& Self::recur(&n1.borrow().left, &n2.borrow().right)
&& Self::recur(&n1.borrow().right, &n2.borrow().left)
}
_ => false,
}
}
}
```
迭代:
```rust
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
impl Solution {
pub fn is_symmetric(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
let mut queue = VecDeque::new();
if let Some(node) = root {
queue.push_back(node.borrow().left.clone());
queue.push_back(node.borrow().right.clone());
}
while !queue.is_empty() {
let (n1, n2) = (queue.pop_front().unwrap(), queue.pop_front().unwrap());
match (n1.clone(), n2.clone()) {
(None, None) => continue,
(Some(n1), Some(n2)) => {
if n1.borrow().val != n2.borrow().val {
return false;
}
}
_ => return false,
};
queue.push_back(n1.as_ref().unwrap().borrow().left.clone());
queue.push_back(n2.as_ref().unwrap().borrow().right.clone());
queue.push_back(n1.unwrap().borrow().right.clone());
queue.push_back(n2.unwrap().borrow().left.clone());
}
true
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">

File diff suppressed because it is too large Load Diff

View File

@ -200,32 +200,6 @@ public:
};
```
rust:
```rust
impl Solution {
pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
if root.is_none(){
return 0;
}
let mut max_depth: i32 = 0;
let mut stack = vec![root.unwrap()];
while !stack.is_empty() {
let num = stack.len();
for _i in 0..num{
let top = stack.remove(0);
if top.borrow_mut().left.is_some(){
stack.push(top.borrow_mut().left.take().unwrap());
}
if top.borrow_mut().right.is_some(){
stack.push(top.borrow_mut().right.take().unwrap());
}
}
max_depth+=1;
}
max_depth
}
```
那么我们可以顺便解决一下n叉树的最大深度问题
@ -975,6 +949,50 @@ object Solution {
}
```
## rust
### 0104.二叉树的最大深度
递归:
```rust
impl Solution {
pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
if root.is_none() {
return 0;
}
std::cmp::max(
Self::max_depth(root.clone().unwrap().borrow().left.clone()),
Self::max_depth(root.unwrap().borrow().right.clone()),
) + 1
}
}
```
迭代:
```rust
impl Solution {
pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
if root.is_none(){
return 0;
}
let mut max_depth: i32 = 0;
let mut stack = vec![root.unwrap()];
while !stack.is_empty() {
let num = stack.len();
for _i in 0..num{
let top = stack.remove(0);
if top.borrow_mut().left.is_some(){
stack.push(top.borrow_mut().left.take().unwrap());
}
if top.borrow_mut().right.is_some(){
stack.push(top.borrow_mut().right.take().unwrap());
}
}
max_depth+=1;
}
max_depth
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -248,7 +248,7 @@ class Solution {
return root;
}
// 左闭右闭区间[left, right)
// 左闭右闭区间[left, right]
private TreeNode traversal(int[] nums, int left, int right) {
if (left > right) return null;

View File

@ -810,6 +810,38 @@ func getHeight(_ root: TreeNode?) -> Int {
}
```
### rust
递归
```rust
use std::cell::RefCell;
use std::rc::Rc;
impl Solution {
pub fn is_balanced(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
Self::get_depth(root) != -1
}
pub fn get_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
if root.is_none() {
return 0;
}
let right = Self::get_depth(root.as_ref().unwrap().borrow().left.clone());
let left = Self::get_depth(root.unwrap().borrow().right.clone());
if right == -1 {
return -1;
}
if left == -1 {
return -1;
}
if (right - left).abs() > 1 {
return -1;
}
1 + right.max(left)
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -574,66 +574,48 @@ object Solution {
rust:
```rust
impl Solution {
pub fn min_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
return Solution::bfs(root)
}
// 递归
pub fn dfs(node: Option<Rc<RefCell<TreeNode>>>) -> i32{
if node.is_none(){
return 0;
}
let parent = node.unwrap();
let left_child = parent.borrow_mut().left.take();
let right_child = parent.borrow_mut().right.take();
if left_child.is_none() && right_child.is_none(){
return 1;
}
let mut min_depth = i32::MAX;
if left_child.is_some(){
let left_depth = Solution::dfs(left_child);
if left_depth <= min_depth{
min_depth = left_depth
pub fn min_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
if let Some(node) = root {
match (node.borrow().left.clone(), node.borrow().right.clone()) {
(Some(n1), None) => 1 + Self::min_depth(Some(n1)),
(None, Some(n2)) => 1 + Self::min_depth(Some(n2)),
(Some(n1), Some(n2)) => {
1 + std::cmp::min(Self::min_depth(Some(n1)), Self::min_depth(Some(n2)))
}
_ => 1,
}
} else {
0
}
if right_child.is_some(){
let right_depth = Solution::dfs(right_child);
if right_depth <= min_depth{
min_depth = right_depth
}
}
min_depth + 1
}
// 迭代
pub fn bfs(node: Option<Rc<RefCell<TreeNode>>>) -> i32{
let mut min_depth = 0;
if node.is_none(){
return min_depth
// 需要 use std::collections::VecDeque;
pub fn min_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
let mut res = 0;
let mut queue = VecDeque::new();
if root.is_some() {
queue.push_back(root);
}
let mut stack = vec![node.unwrap()];
while !stack.is_empty(){
min_depth += 1;
let num = stack.len();
for _i in 0..num{
let top = stack.remove(0);
let left_child = top.borrow_mut().left.take();
let right_child = top.borrow_mut().right.take();
if left_child.is_none() && right_child.is_none(){
return min_depth;
while !queue.is_empty() {
res += 1;
for _ in 0..queue.len() {
let node = queue.pop_front().unwrap().unwrap();
if node.borrow().left.is_none() && node.borrow().right.is_none() {
return res;
}
if left_child.is_some(){
stack.push(left_child.unwrap());
if node.borrow().left.is_some() {
queue.push_back(node.borrow().left.clone());
}
if right_child.is_some(){
stack.push(right_child.unwrap());
if node.borrow().right.is_some() {
queue.push_back(node.borrow().right.clone());
}
}
}
min_depth
res
}
}
```
<p align="center">

View File

@ -310,6 +310,18 @@ class Solution:
return dp[(length-1) % 2][1]
```
> 动态规划:版本三
```python
class Solution:
def maxProfit(self, prices: List[int]) -> int:
length = len(prices)
dp0, dp1 = -prices[0], 0 #注意这里只维护两个常量因为dp0的更新不受dp1的影响
for i in range(1, length):
dp1 = max(dp1, dp0 + prices[i])
dp0 = max(dp0, -prices[i])
return dp1
```
Go:
> 贪心法:
```Go

View File

@ -6,7 +6,7 @@
## 139.单词拆分
# 139.单词拆分
[力扣题目链接](https://leetcode.cn/problems/word-break/)
@ -19,19 +19,19 @@
你可以假设字典中没有重复的单词。
示例 1
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
* 输入: s = "leetcode", wordDict = ["leet", "code"]
* 输出: true
* 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
  注意你可以重复使用字典中的单词。
* 输入: s = "applepenapple", wordDict = ["apple", "pen"]
* 输出: true
* 解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
* 注意你可以重复使用字典中的单词。
示例 3
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
* 输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
* 输出: false
## 思路
@ -158,24 +158,19 @@ dp[0]表示如果字符串为空的话,说明出现在字典里。
**如果求排列数就是外层for遍历背包内层for循环遍历物品**。
对这个结论还有疑问的同学可以看这篇[本周小结!(动态规划系列五)](https://programmercarl.com/%E5%91%A8%E6%80%BB%E7%BB%93/20210204动规周末总结.html),这篇本周小节中,我做了如下总结:
我在这里做一个一个总结:
求组合数:[动态规划518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)
求排列数:[动态规划377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和.html)、[动态规划70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html)
求最小数:[动态规划322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)、[动态规划279.完全平方数](https://programmercarl.com/0279.完全平方数.html)
本题最终要求的是是否都出现过,所以对出现单词集合里的元素是组合还是排列,并不在意!
而本题其实我们求的是排列数,为什么呢。 拿 s = "applepenapple", wordDict = ["apple", "pen"] 举例。
**那么本题使用求排列的方式,还是求组合的方式都可以**。
"apple", "pen" 是物品,那么我们要求 物品的组合一定是 "apple" + "pen" + "apple" 才能组成 "applepenapple"。
外层for循环遍历物品内层for遍历背包 或者 外层for遍历背包内层for循环遍历物品 都是可以的。
但本题还有特殊性,因为是要求子串,最好是遍历背包放在外循环,将遍历物品放在内循环。
如果要是外层for循环遍历物品内层for遍历背包就需要把所有的子串都预先放在一个容器里。如果不理解的话可以自己尝试这么写一写就理解了
**所以最终我选择的遍历顺序为:遍历背包放在外循环,将遍历物品放在内循环。内循环从前到后**。
"apple" + "apple" + "pen" 或者 "pen" + "apple" + "apple" 是不可以的,那么我们就是强调物品之间顺序。
所以说,本题一定是 先遍历 背包,在遍历物品。
5. 举例推导dp[i]
@ -210,22 +205,51 @@ public:
* 时间复杂度O(n^3)因为substr返回子串的副本是O(n)的复杂度这里的n是substring的长度
* 空间复杂度O(n)
## 拓展
关于遍历顺序,再给大家讲一下为什么 先遍历物品再遍历背包不行。
这里可以给出先遍历物品在遍历背包的代码:
```CPP
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> dp(s.size() + 1, false);
dp[0] = true;
for (int j = 0; j < wordDict.size(); j++) { // 物品
for (int i = wordDict[j].size(); i <= s.size(); i++) { // 背包
string word = s.substr(i - wordDict[j].size(), wordDict[j].size());
// cout << word << endl;
if ( word == wordDict[j] && dp[i - wordDict[j].size()]) {
dp[i] = true;
}
// for (int k = 0; k <= s.size(); k++) cout << dp[k] << " "; //这里打印 dp数组的情况
// cout << endl;
}
}
return dp[s.size()];
}
};
```
使用用例s = "applepenapple", wordDict = ["apple", "pen"]对应的dp数组状态如下
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20221123205105.png)
最后dp[s.size()] = 0 即 dp[13] = 0 而不是1因为先用 "apple" 去遍历的时候dp[8]并没有被赋值为1 (还没用"pen"),所以 dp[13]也不能变成1。
除非是先用 "apple" 遍历一遍,在用 "pen" 遍历,此时 dp[8]已经是1最后再用 "apple" 去遍历dp[13]才能是1。
如果大家对这里不理解建议可以把我上面给的代码拿去力扣上跑一跑把dp数组打印出来对着递推公式一步一步去看思路就清晰了。
## 总结
本题和我们之前讲解回溯专题的[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)非常像,所以我也给出了对应的回溯解法。
稍加分析,便可知道本题是完全背包,而且是求能否组成背包,所以遍历顺序理论上来讲 两层for循环谁先谁后都可以
但因为分割子串的特殊性,遍历背包放在外循环,将遍历物品放在内循环更方便一些。
本题其实递推公式都不是重点遍历顺序才是重点如果我直接把代码贴出来估计同学们也会想两个for循环的顺序理所当然就是这样甚至都不会想为什么遍历背包的for循环在外层。
不分析透彻不是Carl的风格啊哈哈
稍加分析,便可知道本题是完全背包,是求能否组成背包,而且这里要求物品是要有顺序的。
## 其他语言版本

View File

@ -51,7 +51,7 @@
```
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
@ -61,11 +61,11 @@
* 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
* 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
* 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
# 思路
《代码随想录》算法视频公开课:[栈的最后表演! | LeetCode150. 逆波兰表达式求值](https://www.bilibili.com/video/BV1kd4y1o7on),相信结合视频看本篇题解,更有助于大家对本题的理解。
《代码随想录》算法视频公开课:[栈的最后表演! | LeetCode150. 逆波兰表达式求值](https://www.bilibili.com/video/BV1kd4y1o7on),相信结合视频看本篇题解,更有助于大家对本题的理解。
在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)提到了 递归就是用栈来实现的。
@ -73,7 +73,7 @@
那么来看一下本题,**其实逆波兰表达式相当于是二叉树中的后序遍历**。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。
但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后遍历的方式把二叉树序列化了,就可以了。
但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后遍历的方式把二叉树序列化了,就可以了。
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中的对对碰游戏是不是就非常像了。**
@ -118,9 +118,9 @@ public:
我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。
例如4 + 13 / 5这就是中缀表达式计算机从左到右去扫描的话扫到13还要判断13后面是什么运算还要比较一下优先级然后13还和后面的5做运算做完运算之后还要向前回退到 4 的位置,继续做加法,你说麻不麻烦!
例如4 + 13 / 5这就是中缀表达式计算机从左到右去扫描的话扫到13还要判断13后面是什么运算还要比较一下优先级然后13还和后面的5做运算做完运算之后还要向前回退到 4 的位置,继续做加法,你说麻不麻烦!
那么将中缀表达式,转化为后缀表达式之后:["4", "13", "5", "/", "+"] ,就不一样了,计算机可以利用栈顺序处理,不需要考虑优先级了。也不用回退了, **所以后缀表达式对计算机来说是非常友好的。**
那么将中缀表达式,转化为后缀表达式之后:["4", "13", "5", "/", "+"] ,就不一样了,计算机可以利用栈顺序处理,不需要考虑优先级了。也不用回退了, **所以后缀表达式对计算机来说是非常友好的。**
可以说本题不仅仅是一道好题,也展现出计算机的思考方式。
@ -161,6 +161,24 @@ class Solution {
}
```
python3
```python
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for item in tokens:
if item not in {"+", "-", "*", "/"}:
stack.append(item)
else:
first_num, second_num = stack.pop(), stack.pop()
stack.append(
int(eval(f'{second_num} {item} {first_num}')) # 第一个出来的在运算符后面
)
return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的
```
Go:
```Go
func evalRPN(tokens []string) int {
@ -169,7 +187,7 @@ func evalRPN(tokens []string) int {
val, err := strconv.Atoi(token)
if err == nil {
stack = append(stack, val)
} else {
} else { // 如果err不为nil说明不是数字
num1, num2 := stack[len(stack)-2], stack[(len(stack))-1]
stack = stack[:len(stack)-2]
switch token {
@ -191,27 +209,31 @@ func evalRPN(tokens []string) int {
javaScript:
```js
/**
* @param {string[]} tokens
* @return {number}
*/
var evalRPN = function(tokens) {
const s = new Map([
["+", (a, b) => a * 1 + b * 1],
["-", (a, b) => b - a],
["*", (a, b) => b * a],
["/", (a, b) => (b / a) | 0]
]);
var evalRPN = function (tokens) {
const stack = [];
for (const i of tokens) {
if(!s.has(i)) {
stack.push(i);
continue;
for (const token of tokens) {
if (isNaN(Number(token))) { // 非数字
const n2 = stack.pop(); // 出栈两个数字
const n1 = stack.pop();
switch (token) { // 判断运算符类型,算出新数入栈
case "+":
stack.push(n1 + n2);
break;
case "-":
stack.push(n1 - n2);
break;
case "*":
stack.push(n1 * n2);
break;
case "/":
stack.push(n1 / n2 | 0);
break;
}
} else { // 数字
stack.push(Number(token));
}
stack.push(s.get(i)(stack.pop(),stack.pop()))
}
return stack.pop();
return stack[0]; // 因没有遇到运算符而待在栈中的结果
};
```
@ -280,24 +302,6 @@ function evalRPN(tokens: string[]): number {
};
```
python3
```python
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for item in tokens:
if item not in {"+", "-", "*", "/"}:
stack.append(item)
else:
first_num, second_num = stack.pop(), stack.pop()
stack.append(
int(eval(f'{second_num} {item} {first_num}')) # 第一个出来的在运算符后面
)
return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的
```
Swift
```Swift
func evalRPN(_ tokens: [String]) -> Int {

View File

@ -119,7 +119,7 @@ void removeExtraSpaces(string& s) {
1. leetcode上的测试集里字符串的长度不够长如果足够长性能差距会非常明显。
2. leetcode的测程序耗时不是很准确的。
版本一的代码是比较如何一般思考过程,就是 先移除字符串钱的空格,在移除中间的,在移除后面部分。
版本一的代码是一般的思考过程,就是 先移除字符串前的空格,再移除中间的,再移除后面部分。
不过其实还可以优化,这部分和[27.移除元素](https://programmercarl.com/0027.移除元素.html)的逻辑是一样一样的,本题是移除空格,而 27.移除元素 就是移除元素。
@ -145,7 +145,7 @@ void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间
此时我们已经实现了removeExtraSpaces函数来移除冗余空格。
实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)和[541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html)里已经讲过了。
实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)和[541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html)里已经讲过了。
代码如下:
@ -434,49 +434,51 @@ python:
```Python
class Solution:
#1.去除多余的空格
def trim_spaces(self,s):
n=len(s)
left=0
right=n-1
def trim_spaces(self, s):
n = len(s)
left = 0
right = n-1
while left<=right and s[left]==' ': #去除开头的空格
left+=1
while left<=right and s[right]==' ': #去除结尾的空格
right=right-1
tmp=[]
while left<=right: #去除单词中间多余的空格
if s[left]!=' ':
while left <= right and s[left] == ' ': #去除开头的空格
left += 1
while left <= right and s[right] == ' ': #去除结尾的空格
right = right-1
tmp = []
while left <= right: #去除单词中间多余的空格
if s[left] != ' ':
tmp.append(s[left])
elif tmp[-1]!=' ': #当前位置是空格,但是相邻的上一个位置不是空格,则该空格是合理的
elif tmp[-1] != ' ': #当前位置是空格,但是相邻的上一个位置不是空格,则该空格是合理的
tmp.append(s[left])
left+=1
left += 1
return tmp
#2.翻转字符数组
def reverse_string(self,nums,left,right):
while left<right:
nums[left], nums[right]=nums[right],nums[left]
left+=1
right-=1
#2.翻转字符数组
def reverse_string(self, nums, left, right):
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
return None
#3.翻转每个单词
#3.翻转每个单词
def reverse_each_word(self, nums):
start=0
end=0
n=len(nums)
while start<n:
while end<n and nums[end]!=' ':
end+=1
self.reverse_string(nums,start,end-1)
start=end+1
end+=1
start = 0
end = 0
n = len(nums)
while start < n:
while end < n and nums[end] != ' ':
end += 1
self.reverse_string(nums, start, end-1)
start = end + 1
end += 1
return None
#4.翻转字符串里的单词
def reverseWords(self, s): #测试用例"the sky is blue"
l = self.trim_spaces(s) #输出['t', 'h', 'e', ' ', 's', 'k', 'y', ' ', 'i', 's', ' ', 'b', 'l', 'u', 'e'
self.reverse_string( l, 0, len(l) - 1) #输出['e', 'u', 'l', 'b', ' ', 's', 'i', ' ', 'y', 'k', 's', ' ', 'e', 'h', 't']
self.reverse_each_word(l) #输出['b', 'l', 'u', 'e', ' ', 'i', 's', ' ', 's', 'k', 'y', ' ', 't', 'h', 'e']
return ''.join(l) #输出blue is sky the
def reverseWords(self, s): #测试用例"the sky is blue"
l = self.trim_spaces(s) #输出['t', 'h', 'e', ' ', 's', 'k', 'y', ' ', 'i', 's', ' ', 'b', 'l', 'u', 'e'
self.reverse_string(l, 0, len(l)-1) #输出['e', 'u', 'l', 'b', ' ', 's', 'i', ' ', 'y', 'k', 's', ' ', 'e', 'h', 't']
self.reverse_each_word(l) #输出['b', 'l', 'u', 'e', ' ', 'i', 's', ' ', 's', 'k', 'y', ' ', 't', 'h', 'e']
return ''.join(l) #输出blue is sky the
```

View File

@ -12,15 +12,16 @@
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1
输入:[1,2,3,1]
输出4
* 示例 1
* 输入:[1,2,3,1]
* 输出4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
  偷窃到的最高金额 = 1 + 3 = 4 。
示例 2
输入:[2,7,9,3,1]
输出12
* 示例 2
* 输入:[2,7,9,3,1]
* 输出12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
  偷窃到的最高金额 = 2 + 9 + 1 = 12 。
@ -33,7 +34,13 @@
## 思路
打家劫舍是dp解决的经典问题动规五部曲分析如下
大家如果刚接触这样的题目,会有点困惑,当前的状态我是偷还是不偷呢?
仔细一想,当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了。
所以这里就更感觉到,当前状态和前面状态会有一种依赖关系,那么这种依赖关系都是动规的递推公式。
当然以上是大概思路打家劫舍是dp解决的经典问题接下来我们来动规五部曲分析如下
1. 确定dp数组dp table以及下标的含义

View File

@ -169,39 +169,18 @@ Python
```python
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
# 定义一个无限大的数
res = float("inf")
Sum = 0
index = 0
for i in range(len(nums)):
Sum += nums[i]
res = float("inf") # 定义一个无限大的数
Sum = 0 # 滑动窗口数值之和
i = 0 # 滑动窗口起始位置
for j in range(len(nums)):
Sum += nums[j]
while Sum >= s:
res = min(res, i-index+1)
Sum -= nums[index]
index += 1
return 0 if res==float("inf") else res
```
```python
# 滑动窗口
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
if nums is None or len(nums) == 0:
return 0
lenf = len(nums) + 1
total = 0
i = j = 0
while (j < len(nums)):
total = total + nums[j]
j += 1
while (total >= target):
lenf = min(lenf, j - i)
total = total - nums[i]
res = min(res, j-i+1)
Sum -= nums[i]
i += 1
if lenf == len(nums) + 1:
return 0
else:
return lenf
return 0 if res == float("inf") else res
```
Go
```go
func minSubArrayLen(target int, nums []int) int {
@ -232,22 +211,23 @@ func minSubArrayLen(target int, nums []int) int {
JavaScript:
```js
var minSubArrayLen = function(target, nums) {
// 长度计算一次
const len = nums.length;
let l = r = sum = 0,
res = len + 1; // 子数组最大不会超过自身
while(r < len) {
sum += nums[r++];
// 窗口滑动
while(sum >= target) {
// r始终为开区间 [l, r)
res = res < r - l ? res : r - l;
sum-=nums[l++];
let start, end
start = end = 0
let sum = 0
let len = nums.length
let ans = Infinity
while(end < len){
sum += nums[end];
while (sum >= target) {
ans = Math.min(ans, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return res > len ? 0 : res;
return ans === Infinity ? 0 : ans
};
```

View File

@ -797,6 +797,52 @@ object Solution {
}
```
rust:
// 递归
```rust
use std::cell::RefCell;
use std::rc::Rc;
impl Solution {
pub fn count_nodes(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
if root.is_none() {
return 0;
}
1 + Self::count_nodes(Rc::clone(root.as_ref().unwrap()).borrow().left.clone())
+ Self::count_nodes(root.unwrap().borrow().right.clone())
}
}
```
// 迭代
```rust
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::VecDeque;
impl Solution {
pub fn count_nodes(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
let mut res = 0;
let mut queue = VecDeque::new();
if root.is_some() {
queue.push_back(root);
}
while !queue.is_empty() {
for _ in 0..queue.len() {
let node = queue.pop_front().unwrap().unwrap();
if node.borrow().left.is_some() {
queue.push_back(node.borrow().left.clone());
}
if node.borrow().right.is_some() {
queue.push_back(node.borrow().right.clone());
}
res += 1;
}
}
res
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -29,7 +29,7 @@
# 思路
《代码随想录》算法公开课:[队列的基本操作! | LeetCode225. 用队列实现栈](https://www.bilibili.com/video/BV1Fd4y1K7sm),相信结合视频看本篇题解,更有助于大家对链表的理解。
《代码随想录》算法公开课:[队列的基本操作! | LeetCode225. 用队列实现栈](https://www.bilibili.com/video/BV1Fd4y1K7sm),相信结合视频看本篇题解,更有助于大家对链表的理解。
(这里要强调是单向队列)
@ -44,7 +44,7 @@
所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用来备份的!
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用来备份的!
如下面动画所示,**用两个队列que1和que2实现队列的功能que2其实完全就是一个备份的作用**把que1最后面的元素以外的元素都备份到que2然后弹出最后面的元素再把其他元素从que2导回que1。
@ -116,7 +116,7 @@ public:
其实这道题目就是用一个队列就够了。
**一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时去弹出元素就是栈的顺序了。**
**一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时去弹出元素就是栈的顺序了。**
C++优化代码

View File

@ -104,7 +104,7 @@ public:
### 深度优先遍历
[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中给出了前中后序迭代方式的写法,所以本地可以很轻松的切出如下迭代法的代码:
[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中给出了前中后序迭代方式的写法,所以本题可以很轻松的写出如下迭代法的代码:
C++代码迭代法(前序遍历)
@ -126,7 +126,7 @@ public:
}
};
```
如果这个代码看不懂的话可以回顾一下[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)。
如果这个代码看不懂的话可以回顾一下[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)。
我们在[二叉树:前中后序迭代方式的统一写法](https://programmercarl.com/二叉树的统一迭代法.html)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。
@ -258,7 +258,6 @@ public:
### Java
```Java
//DFS递归
class Solution {
@ -294,8 +293,8 @@ class Solution {
while (size-- > 0) {
TreeNode node = deque.poll();
swap(node);
if (node.left != null) {deque.offer(node.left);}
if (node.right != null) {deque.offer(node.right);}
if (node.left != null) deque.offer(node.left);
if (node.right != null) deque.offer(node.right);
}
}
return root;
@ -364,13 +363,12 @@ class Solution:
### Go
递归版本的前序遍历
```Go
func invertTree(root *TreeNode) *TreeNode {
if root ==nil{
if root == nil {
return nil
}
root.Left,root.Right=root.Right,root.Left//交换
root.Left, root.Right = root.Right, root.Left //交换
invertTree(root.Left)
invertTree(root.Right)
@ -383,12 +381,14 @@ func invertTree(root *TreeNode) *TreeNode {
```go
func invertTree(root *TreeNode) *TreeNode {
if root==nil{
if root == nil {
return root
}
invertTree(root.Left)//遍历左节点
invertTree(root.Right)//遍历右节点
root.Left,root.Right=root.Right,root.Left//交换
invertTree(root.Left) //遍历左节点
invertTree(root.Right) //遍历右节点
root.Left, root.Right = root.Right, root.Left //交换
return root
}
```
@ -397,18 +397,19 @@ func invertTree(root *TreeNode) *TreeNode {
```go
func invertTree(root *TreeNode) *TreeNode {
stack:=[]*TreeNode{}
node:=root
for node!=nil||len(stack)>0{
for node!=nil{
node.Left,node.Right=node.Right,node.Left//交换
stack=append(stack,node)
node=node.Left
stack := []*TreeNode{}
node := root
for node != nil || len(stack) > 0 {
for node != nil {
node.Left, node.Right = node.Right, node.Left //交换
stack = append(stack,node)
node = node.Left
}
node=stack[len(stack)-1]
stack=stack[:len(stack)-1]
node=node.Right
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
node = node.Right
}
return root
}
```
@ -417,25 +418,26 @@ func invertTree(root *TreeNode) *TreeNode {
```go
func invertTree(root *TreeNode) *TreeNode {
stack:=[]*TreeNode{}
node:=root
stack := []*TreeNode{}
node := root
var prev *TreeNode
for node!=nil||len(stack)>0{
for node!=nil{
stack=append(stack,node)
node=node.Left
for node != nil || len(stack) > 0 {
for node != nil {
stack = append(stack, node)
node = node.Left
}
node=stack[len(stack)-1]
stack=stack[:len(stack)-1]
if node.Right==nil||node.Right==prev{
node.Left,node.Right=node.Right,node.Left//交换
prev=node
node=nil
}else {
stack=append(stack,node)
node=node.Right
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
if node.Right == nil || node.Right == prev {
node.Left, node.Right = node.Right, node.Left //交换
prev = node
node = nil
} else {
stack = append(stack, node)
node = node.Right
}
}
return root
}
```
@ -444,21 +446,21 @@ func invertTree(root *TreeNode) *TreeNode {
```go
func invertTree(root *TreeNode) *TreeNode {
if root==nil{
if root == nil{
return root
}
queue:=list.New()
node:=root
queue := list.New()
node := root
queue.PushBack(node)
for queue.Len()>0{
length:=queue.Len()
for i:=0;i<length;i++{
e:=queue.Remove(queue.Front()).(*TreeNode)
e.Left,e.Right=e.Right,e.Left//交换
if e.Left!=nil{
for queue.Len() > 0 {
length := queue.Len()
for i := 0; i < length; i++ {
e := queue.Remove(queue.Front()).(*TreeNode)
e.Left, e.Right = e.Right, e.Left //交换
if e.Left != nil {
queue.PushBack(e.Left)
}
if e.Right!=nil{
if e.Right != nil {
queue.PushBack(e.Right)
}
}
@ -487,31 +489,31 @@ var invertTree = function(root) {
```javascript
var invertTree = function(root) {
//我们先定义节点交换函数
const invertNode=function(root,left,right){
let temp=left;
left=right;
right=temp;
root.left=left;
root.right=right;
const invertNode = function(root, left, right) {
let temp = left;
left = right;
right = temp;
root.left = left;
root.right = right;
}
//使用迭代方法的前序遍历
let stack=[];
if(root===null){
let stack = [];
if(root === null) {
return root;
}
stack.push(root);
while(stack.length){
let node=stack.pop();
if(node!==null){
while(stack.length) {
let node = stack.pop();
if(node !== null) {
//前序遍历顺序中左右 入栈顺序是前序遍历的倒序右左中
node.right&&stack.push(node.right);
node.left&&stack.push(node.left);
node.right && stack.push(node.right);
node.left && stack.push(node.left);
stack.push(node);
stack.push(null);
}else{
node=stack.pop();
} else {
node = stack.pop();
//节点处理逻辑
invertNode(node,node.left,node.right);
invertNode(node, node.left, node.right);
}
}
return root;
@ -521,27 +523,27 @@ var invertTree = function(root) {
```javascript
var invertTree = function(root) {
//我们先定义节点交换函数
const invertNode=function(root,left,right){
let temp=left;
left=right;
right=temp;
root.left=left;
root.right=right;
const invertNode = function(root, left, right) {
let temp = left;
left = right;
right = temp;
root.left = left;
root.right = right;
}
//使用层序遍历
let queue=[];
if(root===null){
let queue = [];
if(root === null) {
return root;
}
queue.push(root);
while(queue.length){
let length=queue.length;
while(length--){
let node=queue.shift();
while(queue.length) {
let length = queue.length;
while(length--) {
let node = queue.shift();
//节点处理逻辑
invertNode(node,node.left,node.right);
node.left&&queue.push(node.left);
node.right&&queue.push(node.right);
invertNode(node, node.left, node.right);
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
}
return root;
@ -760,7 +762,6 @@ func invertTree1(_ root: TreeNode?) -> TreeNode? {
}
```
### Swift
深度优先递归。
@ -808,9 +809,6 @@ func invertTree(_ root: TreeNode?) -> TreeNode? {
return root
}
```
### Scala
深度优先遍历(前序遍历):
```scala
object Solution {
@ -857,6 +855,36 @@ object Solution {
}
```
### rust
```rust
impl Solution {
//* 递归 */
pub fn invert_tree(root: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {
if let Some(node) = root.as_ref() {
let (left, right) = (node.borrow().left.clone(), node.borrow().right.clone());
node.borrow_mut().left = Self::invert_tree(right);
node.borrow_mut().right = Self::invert_tree(left);
}
root
}
//* 迭代 */
pub fn invert_tree(root: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {
let mut stack = vec![root.clone()];
while !stack.is_empty() {
if let Some(node) = stack.pop().unwrap() {
let (left, right) = (node.borrow().left.clone(), node.borrow().right.clone());
stack.push(right.clone());
stack.push(left.clone());
node.borrow_mut().left = right;
node.borrow_mut().right = left;
}
}
root
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -38,14 +38,14 @@ queue.empty(); // 返回 false
## 思路
《代码随想录》算法公开课:[栈的基本操作! | LeetCode232.用栈实现队列](https://www.bilibili.com/video/BV1nY4y1w7VC),相信结合视频看本篇题解,更有助于大家对链表的理解。
《代码随想录》算法公开课:[栈的基本操作! | LeetCode232.用栈实现队列](https://www.bilibili.com/video/BV1nY4y1w7VC),相信结合视频看本篇题解,更有助于大家对链表的理解。
这是一道模拟题,不涉及到具体算法,考察的就是对栈和队列的掌握程度。
使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈**一个输入栈,一个输出栈**,这里要注意输入栈和输出栈的关系。
下面动画模拟以下队列的执行过程如下
下面动画模拟以下队列的执行过程:
执行语句:
queue.push(1);
@ -120,7 +120,7 @@ public:
这样的项目代码会越来越乱,**一定要懂得复用,功能相近的函数要抽象出来,不要大量的复制粘贴,很容易出问题!(踩过坑的人自然懂)**
工作中如果发现某一个功能自己要经常用,同事们可能也会用到,自己就花点时间把这个功能抽象成一个好用的函数或者工具类,不仅自己方便,也方了同事们。
工作中如果发现某一个功能自己要经常用,同事们可能也会用到,自己就花点时间把这个功能抽象成一个好用的函数或者工具类,不仅自己方便,也方便了同事们。
同事们就会逐渐认可你的工作态度和工作能力,自己的口碑都是这么一点一点积累起来的!在同事圈里口碑起来了之后,你就发现自己走上了一个正循环,以后的升职加薪才少不了你!哈哈哈
@ -231,56 +231,51 @@ class MyQueue:
Go
```Go
type MyQueue struct {
stack []int
back []int
stackIn []int //输入栈
stackOut []int //输出栈
}
/** Initialize your data structure here. */
func Constructor() MyQueue {
return MyQueue{
stack: make([]int, 0),
back: make([]int, 0),
stackIn: make([]int, 0),
stackOut: make([]int, 0),
}
}
/** Push element x to the back of queue. */
// 往输入栈做push
func (this *MyQueue) Push(x int) {
for len(this.back) != 0 {
val := this.back[len(this.back)-1]
this.back = this.back[:len(this.back)-1]
this.stack = append(this.stack, val)
}
this.stack = append(this.stack, x)
this.stackIn = append(this.stackIn, x)
}
/** Removes the element from in front of queue and returns that element. */
// 在输出栈做poppop时如果输出栈数据为空需要将输入栈全部数据导入如果非空则可直接使用
func (this *MyQueue) Pop() int {
for len(this.stack) != 0 {
val := this.stack[len(this.stack)-1]
this.stack = this.stack[:len(this.stack)-1]
this.back = append(this.back, val)
inLen, outLen := len(this.stackIn), len(this.stackOut)
if outLen == 0 {
if inLen == 0 {
return -1
}
for i := inLen - 1; i >= 0; i-- {
this.stackOut = append(this.stackOut, this.stackIn[i])
}
this.stackIn = []int{} //导出后清空
outLen = len(this.stackOut) //更新长度值
}
if len(this.back) == 0 {
return 0
}
val := this.back[len(this.back)-1]
this.back = this.back[:len(this.back)-1]
val := this.stackOut[outLen-1]
this.stackOut = this.stackOut[:outLen-1]
return val
}
/** Get the front element. */
func (this *MyQueue) Peek() int {
val := this.Pop()
if val == 0 {
return 0
if val == -1 {
return -1
}
this.back = append(this.back, val)
this.stackOut = append(this.stackOut, val)
return val
}
/** Returns whether the queue is empty. */
func (this *MyQueue) Empty() bool {
return len(this.stack) == 0 && len(this.back) == 0
return len(this.stackIn) == 0 && len(this.stackOut) == 0
}
```

View File

@ -38,7 +38,7 @@
难点是如何求一个区间里的最大值呢? (这好像是废话),暴力一下不就得了。
暴力方法,遍历一遍的过程中每次从窗口中找到最大的数值这样很明显是O(n × k)的算法。
暴力方法,遍历一遍的过程中每次从窗口中找到最大的数值这样很明显是O(n × k)的算法。
有的同学可能会想用一个大顶堆优先级队列来存放这个窗口里的k个数字这样就可以知道最大的最大值是多少了 **但是问题是这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆。**
@ -66,7 +66,7 @@ public:
**可惜了,没有! 我们需要自己实现这么个队列。**
然后分析一下,队列里的元素一定是要排序的,而且要最大值放在出队口,要不然怎么知道最大值呢。
然后分析一下,队列里的元素一定是要排序的,而且要最大值放在出队口,要不然怎么知道最大值呢。
但如果把窗口里的元素都放进队列里,窗口移动的时候,队列需要弹出元素。
@ -74,9 +74,9 @@ public:
大家此时应该陷入深思.....
**其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队里的元素数值是由大到小的。**
**其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队里的元素数值是由大到小的。**
那么这个维护元素单调递减的队列就叫做**单调队列即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来一个单调队列**
那么这个维护元素单调递减的队列就叫做**单调队列即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来实现一个单调队列**
**不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。**
@ -185,7 +185,7 @@ public:
};
```
来看一下时间复杂度,使用单调队列的时间复杂度是 O(n)。
来看一下时间复杂度,使用单调队列的时间复杂度是 O(n)。
有的同学可能想了,在队列中 push元素的过程中还有pop操作呢感觉不是纯粹的O(n)。

View File

@ -101,7 +101,7 @@ class Solution {
int[] record = new int[26];
for (int i = 0; i < s.length(); i++) {
record[s.charAt(i) - 'a']++;
record[s.charAt(i) - 'a']++; // 并不需要记住字符a的ASCII只要求出一个相对数值就可以了
}
for (int i = 0; i < t.length(); i++) {
@ -109,11 +109,11 @@ class Solution {
}
for (int count: record) {
if (count != 0) {
if (count != 0) { // record数组如果有的元素不为零0说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
return true;
return true; // record数组所有元素都为零0说明字符串s和t是字母异位词
}
}
```
@ -123,12 +123,11 @@ Python
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in range(len(s)):
for i in s:
#并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[ord(s[i]) - ord("a")] += 1
print(record)
for i in range(len(t)):
record[ord(t[i]) - ord("a")] -= 1
record[ord(i) - ord("a")] += 1
for i in t:
record[ord(i) - ord("a")] -= 1
for i in range(26):
if record[i] != 0:
#record数组如果有的元素不为零0说明字符串s和t 一定是谁多了字符或者谁少了字符。
@ -154,38 +153,23 @@ class Solution:
return s_dict == t_dict
```
Python写法三(没有使用数组作为哈希表只是介绍Counter这种更方便的解题思路)
```python
class Solution(object):
def isAnagram(self, s: str, t: str) -> bool:
from collections import Counter
a_count = Counter(s)
b_count = Counter(t)
return a_count == b_count
```
Go
```go
func isAnagram(s string, t string) bool {
if len(s)!=len(t){
return false
}
exists := make(map[byte]int)
for i:=0;i<len(s);i++{
if v,ok:=exists[s[i]];v>=0&&ok{
exists[s[i]]=v+1
}else{
exists[s[i]]=1
}
}
for i:=0;i<len(t);i++{
if v,ok:=exists[t[i]];v>=1&&ok{
exists[t[i]]=v-1
}else{
return false
}
}
return true
}
```
Go写法二没有使用slice作为哈希表用数组来代替
```go
func isAnagram(s string, t string) bool {
record := [26]int{}
for _, r := range s {
record[r-rune('a')]++
}
@ -193,7 +177,7 @@ func isAnagram(s string, t string) bool {
record[r-rune('a')]--
}
return record == [26]int{}
return record == [26]int{} // record数组如果有的元素不为零0说明字符串s和t 一定是谁多了字符或者谁少了字符。
}
```

View File

@ -794,6 +794,33 @@ object Solution {
}
}
```
rust:
```rust
// 递归
impl Solution {
pub fn binary_tree_paths(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<String> {
let mut res = vec![];
Self::recur(&root, String::from(""), &mut res);
res
}
pub fn recur(node: &Option<Rc<RefCell<TreeNode>>>, mut path: String, res: &mut Vec<String>) {
let r = node.as_ref().unwrap().borrow();
path += format!("{}", r.val).as_str();
if r.left.is_none() && r.right.is_none() {
res.push(path.to_string());
return;
}
if r.left.is_some() {
Self::recur(&r.left, path.clone() + "->", res);
}
if r.right.is_some() {
Self::recur(&r.right, path + "->", res);
}
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">

View File

@ -131,10 +131,8 @@ public:
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i * i <= n; i++) { // 遍历物品
for (int j = 1; j <= n; j++) { // 遍历背包
if (j - i * i >= 0) {
dp[j] = min(dp[j - i * i] + 1, dp[j]);
}
for (int j = i * i; j <= n; j++) { // 遍历背包
dp[j] = min(dp[j - i * i] + 1, dp[j]);
}
}
return dp[n];

View File

@ -53,8 +53,6 @@
2. 确定递推公式
得到dp[j]考虑coins[i]只有一个来源dp[j - coins[i]]没有考虑coins[i])。
凑足总额为j - coins[i]的最少个数为dp[j - coins[i]]那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j]考虑coins[i]
所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。

View File

@ -50,8 +50,8 @@ public:
};
```
* 时间复杂度:$O(n^2)$,这个时间复杂度不太标准,也不容易准确化,例如越往下的节点重复计算次数就越多
* 空间复杂度:$O(\log n)$,算上递推系统栈的空间
* 时间复杂度O(n^2),这个时间复杂度不太标准,也不容易准确化,例如越往下的节点重复计算次数就越多
* 空间复杂度O(log n),算上递推系统栈的空间
当然以上代码超时了,这个递归的过程中其实是有重复计算了。
@ -84,8 +84,8 @@ public:
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(\log n)$,算上递推系统栈的空间
* 时间复杂度O(n)
* 空间复杂度O(log n),算上递推系统栈的空间
### 动态规划

View File

@ -151,6 +151,23 @@ class Solution {
}
}
}
// 第二种方法用temp来交换数值更多人容易理解些
class Solution {
public void reverseString(char[] s) {
int l = 0;
int r = s.length - 1;
while(l < r){
char temp = s[l];
s[l] = s[r];
s[r] = temp;
l++;
r--;
}
}
}
```
Python
@ -173,11 +190,11 @@ class Solution:
Go
```Go
func reverseString(s []byte) {
left:=0
right:=len(s)-1
for left<right{
s[left],s[right]=s[right],s[left]
func reverseString(s []byte) {
left := 0
right := len(s)-1
for left < right {
s[left], s[right] = s[right], s[left]
left++
right--
}
@ -335,3 +352,4 @@ object Solution {
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>

View File

@ -271,7 +271,7 @@ func (h *IHeap) Pop() interface{}{
}
//方法二:利用O(logn)排序
//方法二:利用O(nlogn)排序
func topKFrequent(nums []int, k int) []int {
ans:=[]int{}
map_num:=map[int]int{}
@ -487,7 +487,34 @@ object Solution {
.map(_._1)
}
}
```
rust: 小根堆
```rust
use std::cmp::Reverse;
use std::collections::{BinaryHeap, HashMap};
impl Solution {
pub fn top_k_frequent(nums: Vec<i32>, k: i32) -> Vec<i32> {
let mut hash = HashMap::new();
let mut heap = BinaryHeap::with_capacity(k as usize);
nums.into_iter().for_each(|k| {
*hash.entry(k).or_insert(0) += 1;
});
for (k, v) in hash {
if heap.len() == heap.capacity() {
if *heap.peek().unwrap() < (Reverse(v), k) {
continue;
} else {
heap.pop();
}
}
heap.push((Reverse(v), k));
}
heap.into_iter().map(|(_, k)| k).collect()
}
}
```
<p align="center">

View File

@ -147,32 +147,24 @@ Python3
```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2)) # 两个数组先变成集合,求交集后还原为数组
val_dict = {}
ans = []
for num in nums1:
val_dict[num] = 1
for num in nums2:
if num in val_dict.keys() and val_dict[num] == 1:
ans.append(num)
val_dict[num] = 0
return ans
```
Go
```go
func intersection(nums1 []int, nums2 []int) []int {
m := make(map[int]int)
for _, v := range nums1 {
m[v] = 1
}
var res []int
// 利用count>0实现重复值只拿一次放入返回结果中
for _, v := range nums2 {
if count, ok := m[v]; ok && count > 0 {
res = append(res, v)
m[v]--
}
}
return res
}
```
```golang
//优化版利用set减少count统计
func intersection(nums1 []int, nums2 []int) []int {
set:=make(map[int]struct{},0)
set:=make(map[int]struct{},0) // 用map模拟set
res:=make([]int,0)
for _,v:=range nums1{
if _,ok:=set[v];!ok{

View File

@ -264,6 +264,25 @@ class Solution:
return max(dp[-1][0], dp[-1][1])
```
```python
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
# up i作为波峰最长的序列长度
# down i作为波谷最长的序列长度
n = len(nums)
# 长度为0和1的直接返回长度
if n<2: return n
for i in range(1,n):
if nums[i]>nums[i-1]:
# nums[i] 为波峰1. 前面是波峰up值不变2. 前面是波谷down值加1
# 目前up值取两者的较大值(其实down+1即可可以推理前一步down和up最多相差1所以down+1>=up)
up = max(up, down+1)
elif nums[i]<nums[i-1]:
# nums[i] 为波谷1. 前面是波峰up+12. 前面是波谷down不变取较大值
down = max(down, up+1)
return max(up, down)
```
### Go
**贪心**

View File

@ -4,9 +4,8 @@
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 动态规划Carl称它为排列总和
## 377. 组合总和 Ⅳ
# 377. 组合总和 Ⅳ
[力扣题目链接](https://leetcode.cn/problems/combination-sum-iv/)

View File

@ -147,11 +147,11 @@ class Solution:
arr = [0] * 26
for x in magazine:
for x in magazine: # 记录 magazine里各个字符出现次数
arr[ord(x) - ord('a')] += 1
for x in ransomNote:
if arr[ord(x) - ord('a')] == 0:
for x in ransomNote: # 在arr里对应的字符个数做--操作
if arr[ord(x) - ord('a')] == 0: # 如果没有出现过直接返回
return False
else:
arr[ord(x) - ord('a')] -= 1
@ -234,12 +234,12 @@ Go
```go
func canConstruct(ransomNote string, magazine string) bool {
record := make([]int, 26)
for _, v := range magazine {
for _, v := range magazine { // 通过recode数据记录 magazine里各个字符出现次数
record[v-'a']++
}
for _, v := range ransomNote {
for _, v := range ransomNote { // 遍历ransomNote在record里对应的字符个数做--操作
record[v-'a']--
if record[v-'a'] < 0 {
if record[v-'a'] < 0 { // 如果小于零说明ransomNote里出现的字符magazine没有
return false
}
}
@ -258,12 +258,12 @@ javaScript:
var canConstruct = function(ransomNote, magazine) {
const strArr = new Array(26).fill(0),
base = "a".charCodeAt();
for(const s of magazine) {
for(const s of magazine) { // 记录 magazine里各个字符出现次数
strArr[s.charCodeAt() - base]++;
}
for(const s of ransomNote) {
for(const s of ransomNote) { // 对应的字符个数做--操作
const index = s.charCodeAt() - base;
if(!strArr[index]) return false;
if(!strArr[index]) return false; // 如果没记录过直接返回false
strArr[index]--;
}
return true;

View File

@ -79,7 +79,17 @@
01背包中dp[j] 表示: 容量为j的背包所背的物品价值可以最大为dp[j]。
**套到本题dp[j]表示 背包总容量是j最大可以凑成j的子集总和为dp[j]**。
本题中每一个元素的数值即是重量,也是价值。
**套到本题dp[j]表示 背包总容量所能装的总重量是j放进物品后背的最大重量为dp[j]**。
那么如果背包容量为target dp[target]就是装满 背包之后的重量,所以 当 dp[target] == target 的时候,背包就装满了。
有录友可能想,那还有装不满的时候?
拿输入数组 [1, 5, 11, 5],距离, dp[7] 只能等于 6因为 只能放进 1 和 5。
而dp[6] 就可以等于6了放进1 和 5那么dp[6] == 6说明背包装满了。
2. 确定递推公式

View File

@ -168,13 +168,15 @@ Go
```go
func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
m := make(map[int]int)
m := make(map[int]int) //key:a+b的数值value:a+b数值出现的次数
count := 0
for _, v1 := range nums1 {
// 遍历nums1和nums2数组统计两个数组元素之和和出现的次数放到map中
for _, v1 := range nums1 {
for _, v2 := range nums2 {
m[v1+v2]++
}
}
// 遍历nums3和nums4数组找到如果 0-(c+d) 在map中出现过的话就把map中key对应的value也就是出现次数统计出来
for _, v3 := range nums3 {
for _, v4 := range nums4 {
count += m[-v3-v4]
@ -197,14 +199,14 @@ javaScript:
var fourSumCount = function(nums1, nums2, nums3, nums4) {
const twoSumMap = new Map();
let count = 0;
// 统计nums1和nums2数组元素之和和出现的次数放到map中
for(const n1 of nums1) {
for(const n2 of nums2) {
const sum = n1 + n2;
twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1)
}
}
// 找到如果 0-(c+d) 在map中出现过的话就把map中key对应的value也就是出现次数统计出来
for(const n3 of nums3) {
for(const n4 of nums4) {
const sum = n3 + n4;

View File

@ -46,17 +46,17 @@
## 移动匹配
当一个字符串sabcabc内部重复的子串组成,那么这个字符串的结构一定是这样的:
当一个字符串sabcabc内部重复的子串组成,那么这个字符串的结构一定是这样的:
![图一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728104518.png)
也就是又前后又相同的子串组成。
也就是由前后相同的子串组成。
那么既然前面有相同的子串,后面有相同的子串,用 s + s这样组成的字符串中后面的子串做前串前后的子串做后串就一定还能组成一个s如图
![图二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728104931.png)
所以判断字符串s是否有重复子串组成只要两个s拼接在一起里面还出现一个s的话就说明是又重复子串组成。
所以判断字符串s是否由重复子串组成只要两个s拼接在一起里面还出现一个s的话就说明是由重复子串组成。
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候**要刨除 s + s 的首字符和尾字符**这样避免在s+s中搜索出原来的s我们要搜索的是中间拼接出来的s。
@ -81,7 +81,7 @@ public:
## KMP
### 为什么会使用KMP
以下使用KMP方式讲解强烈建议大家先把一下两个视频看了理解KMP算法来看下面讲解,否则会很懵。
以下使用KMP方式讲解强烈建议大家先把以下两个视频看了理解KMP算法来看下面讲解,否则会很懵。
* [视频讲解版帮你把KMP算法学个通透理论篇](https://www.bilibili.com/video/BV1PD4y1o7nd/)
* [视频讲解版帮你把KMP算法学个通透求next数组代码篇](https://www.bilibili.com/video/BV1M5411j7Xx)
@ -93,12 +93,12 @@ KMP算法中next数组为什么遇到字符不匹配的时候可以找到上一
那么 最长相同前后缀和重复子串的关系又有什么关系呢。
可能很多录友又忘了 前缀和后缀的定义,回顾一下:
可能很多录友又忘了 前缀和后缀的定义,回顾一下:
* 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;
* 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里字符串sabababab 来举例ab就是最小重复单位如图所示
在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里字符串sabababab 来举例ab就是最小重复单位如图所示
![图三](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728205249.png)
@ -123,11 +123,11 @@ KMP算法中next数组为什么遇到字符不匹配的时候可以找到上一
### 简单推理
这里在给出一个数推导,就容易理解很多。
这里再给出一个数学推导,就容易理解很多。
假设字符串s使用多个重复子串构成这个子串是最小重复单位重复出现的子字符串长度是x所以s是由n * x组成。
因为字符串s的最长相同前后缀的长度一定是不包含s本身所以 最长相同前后缀长度必然是m * x而且 n - m = 1这里如果不懂看上面的推理
因为字符串s的最长相同前后缀的长度一定是不包含s本身所以 最长相同前后缀长度必然是m * x而且 n - m = 1这里如果不懂看上面的推理
所以如果 nx % (n - m)x = 0就可以判定有重复出现的子字符串。

View File

@ -3,9 +3,8 @@
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 动态规划:一和零!
## 474.一和零
# 474.一和零
[力扣题目链接](https://leetcode.cn/problems/ones-and-zeroes/)
@ -42,7 +41,7 @@
* [动态规划关于01背包问题你该了解这些](https://programmercarl.com/背包理论基础01背包-1.html)
* [动态规划关于01背包问题你该了解这些滚动数组](https://programmercarl.com/背包理论基础01背包-2.html)
这道题目,还是比较难的,也有点像程序员自己给自己出个脑筋急转弯,程序员何苦为难程序员呢哈哈
这道题目,还是比较难的,也有点像程序员自己给自己出个脑筋急转弯,程序员何苦为难程序员呢。
来说题,本题不少同学会认为是多重背包,一些题解也是这么写的。
@ -82,7 +81,7 @@ dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
对比一下就会发现字符串的zeroNum和oneNum相当于物品的重量weight[i]字符串本身的个数相当于物品的价值value[i])。
**这就是一个典型的01背包** 只不过物品的重量有了两个维度而已。
**这就是一个典型的01背包** 只不过物品的重量有了两个维度而已。
3. dp数组如何初始化
@ -155,8 +154,15 @@ public:
不少同学刷过这道提,可能没有总结这究竟是什么背包。
这道题的本质是有两个维度的01背包如果大家认识到这一点对这道题的理解就比较深入了。
此时我们讲解了0-1背包的多种应用
* [纯 0 - 1 背包](https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-2.html) 是求 给定背包容量 装满背包 的最大价值是多少。
* [416. 分割等和子集](https://programmercarl.com/0416.%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.html) 是求 给定背包容量,能不能装满这个背包。
* [1049. 最后一块石头的重量 II](https://programmercarl.com/1049.%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8FII.html) 是求 给定背包容量,尽可能装,最多能装多少
* [494. 目标和](https://programmercarl.com/0494.%E7%9B%AE%E6%A0%87%E5%92%8C.html) 是求 给定背包容量,装满背包有多少种方法。
* 本题是求 给定背包容量,装满背包最多有多少个物品。
所以在代码随想录中所列举的题目,都是 0-1背包不同维度上的应用大家可以细心体会

View File

@ -3,9 +3,11 @@
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 动态规划:目标和!
## 494. 目标和
# 494. 目标和
[力扣题目链接](https://leetcode.cn/problems/target-sum/)
@ -52,9 +54,9 @@
既然为target那么就一定有 left组合 - right组合 = target。
left + right等于sum而sum是固定的。
left + right = sum而sum是固定的。right = sum - left
公式来了, left - (sum - left) = target -> left = (target + sum)/2 。
公式来了, left - (sum - left) = target 推导出 left = (target + sum)/2 。
target是固定的sum是固定的left就可以求出来。
@ -117,22 +119,26 @@ public:
假设加法的总和为x那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = S
所以我们要求的是 x - (sum - x) = target
x = (S + sum) / 2
x = (target + sum) / 2
**此时问题就转化为装满容量为x背包有几种方法**。
大家看到(S + sum) / 2 应该担心计算的过程中向下取整有没有影响。
这里的x就是bagSize也就是我们后面要求的背包容量。
大家看到(target + sum) / 2 应该担心计算的过程中向下取整有没有影响。
这么担心就对了例如sum 是5S是2的话其实就是无解的所以
```CPP
```CPP
C++代码中输入的S 就是题目描述的 target
if ((S + sum) % 2 == 1) return 0; // 此时没有方案
```
同时如果 S的绝对值已经大于sum那么也是没有方案的。
```CPP
C++代码中输入的S 就是题目描述的 target
if (abs(S) > sum) return 0; // 此时没有方案
```
@ -156,17 +162,15 @@ dp[j] 表示填满j包括j这么大容积的包有dp[j]种方法
有哪些来源可以推出dp[j]呢?
不考虑nums[i]的情况下填满容量为j的背包有dp[j]种方法。
那么考虑nums[i]的话只要搞到nums[i]凑成dp[j]就有dp[j - nums[i]] 种方法。
只要搞到nums[i]凑成dp[j]就有dp[j - nums[i]] 种方法。
例如dp[j]j 为5
* 已经有一个1nums[i] 的话,有 dp[4]种方法 凑成 dp[5]
* 已经有一个2nums[i] 的话,有 dp[3]种方法 凑成 dp[5]
* 已经有一个3nums[i] 的话,有 dp[2]中方法 凑成 dp[5]
* 已经有一个4nums[i] 的话,有 dp[1]中方法 凑成 dp[5]
* 已经有一个5 nums[i])的话,有 dp[0]中方法 凑成 dp[5]
* 已经有一个1nums[i] 的话,有 dp[4]种方法 凑成 容量为5的背包
* 已经有一个2nums[i] 的话,有 dp[3]种方法 凑成 容量为5的背包
* 已经有一个3nums[i] 的话,有 dp[2]中方法 凑成 容量为5的背包
* 已经有一个4nums[i] 的话,有 dp[1]中方法 凑成 容量为5的背包
* 已经有一个5 nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
@ -182,9 +186,19 @@ dp[j] += dp[j - nums[i]]
从递归公式可以看出在初始化的时候dp[0] 一定要初始化为1因为dp[0]是在公式中一切递推结果的起源如果dp[0]是0的话递归结果将都是0。
dp[0] = 1理论上也很好解释装满容量为0的背包有1种方法就是装0件物品。
这里有录友可能认为从dp数组定义来说 dp[0] 应该是0也有录友认为dp[0]应该是1。
dp[j]其他下标对应的数值应该初始化为0从递归公式也可以看出dp[j]要保证是0的初始值才能正确的由dp[j - nums[i]]推导出来。
其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看就是应该等于多少。
如果数组[0] target = 0那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。
所以本题我们应该初始化 dp[0] 为 1。
可能有同学想了,那 如果是 数组[0,0,0,0,0] target = 0 呢。
其实 此时最终的dp[0] = 32也就是这五个零 子集的所有组合情况但此dp[0]非彼dp[0]dp[0]能算出32其基础是因为dp[0] = 1 累加起来的。
dp[j]其他下标对应的数值也应该初始化为0从递归公式也可以看出dp[j]要保证是0的初始值才能正确的由dp[j - nums[i]]推导出来。
4. 确定遍历顺序
@ -213,7 +227,6 @@ public:
if (abs(S) > sum) return 0; // 此时没有方案
if ((S + sum) % 2 == 1) return 0; // 此时没有方案
int bagSize = (S + sum) / 2;
if (bagsize < 0) return 0;
vector<int> dp(bagSize + 1, 0);
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
@ -238,7 +251,7 @@ public:
本题还是有点难度,大家也可以记住,在求装满背包有几种方法的情况下,递推公式一般为:
```
```CPP
dp[j] += dp[j - nums[i]];
```

View File

@ -3,9 +3,10 @@
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 动态规划:给你一些零钱,你要怎么凑?
## 518. 零钱兑换 II
# 518. 零钱兑换 II
[力扣题目链接](https://leetcode.cn/problems/coin-change-ii/)
@ -15,22 +16,25 @@
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
* 输入: amount = 5, coins = [1, 2, 5]
* 输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
* 5=5
* 5=2+2+1
* 5=2+1+1+1
* 5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
* 输入: amount = 3, coins = [2]
* 输出: 0
* 解释: 只用面额2的硬币不能凑成总金额3。
示例 3:
输入: amount = 10, coins = [10]
输出: 1
* 输入: amount = 10, coins = [10]
* 输出: 1
注意,你可以假设:
@ -47,7 +51,7 @@
对完全背包还不了解的同学,可以看这篇:[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html)
但本题和纯完全背包不一样,**纯完全背包是能否凑成总金额,而本题是要求凑成总金额的个数!**
但本题和纯完全背包不一样,**纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数!**
注意题目描述中是凑成总金额的硬币组合数,为什么强调是组合数呢?
@ -73,17 +77,21 @@ dp[j]凑成总金额j的货币组合数为dp[j]
2. 确定递推公式
dp[j] 考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]]考虑coins[i])相加。
dp[j] 就是所有的dp[j - coins[i]]考虑coins[i]的情况)相加。
所以递推公式dp[j] += dp[j - coins[i]];
**这个递推公式大家应该不陌生了我在讲解01背包题目的时候在这篇[动态规划:目标和!](https://programmercarl.com/0494.目标和.html)中就讲解了,求装满背包有几种方法,一般公式都是dp[j] += dp[j - nums[i]];**
**这个递推公式大家应该不陌生了我在讲解01背包题目的时候在这篇[494. 目标和](https://programmercarl.com/0494.目标和.html)中就讲解了求装满背包有几种方法公式都是dp[j] += dp[j - nums[i]];**
3. dp数组如何初始化
首先dp[0]一定要为1dp[0] = 1是 递归公式的基础。
首先dp[0]一定要为1dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话后面所有推导出来的值都是0了。
从dp[i]的含义上来讲就是凑成总金额0的货币组合数为1。
那么 dp[0] = 1 有没有含义,其实既可以说 凑成总金额0的货币组合数为1也可以说 凑成总金额0的货币组合数为0好像都没有毛病。
但题目描述中,也没明确说 amount = 0 的情况,结果应该是多少。
这里我认为题目描述还是要说明一下因为后台测试数据是默认amount = 0 的情况组合数为1的。
下标非0的dp[j]初始化为0这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]
@ -96,9 +104,9 @@ dp[j] 考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]](不
**但本题就不行了!**
因为纯完全背包求得是能否凑成总和,和凑成总和的元素有没有顺序没关系,即:有顺序也行,没有顺序也行!
因为纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序没关系,即:有顺序也行,没有顺序也行!
而本题要求凑成总和的组合数,元素之间要求没有顺序。
而本题要求凑成总和的组合数,元素之间明确要求没有顺序。
所以纯完全背包是能凑成总和就行,不用管怎么凑的。
@ -126,7 +134,7 @@ for (int i = 0; i < coins.size(); i++) { // 遍历物品
如果把两个for交换顺序代码如下
```
```CPP
for (int j = 0; j <= amount; j++) { // 遍历背包容量
for (int i = 0; i < coins.size(); i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
@ -169,7 +177,7 @@ public:
## 总结
本题的递推公式,其实我们在[动态规划:目标和!](https://programmercarl.com/0494.目标和.html)中就已经讲过了,**而难点在于遍历顺序!**
本题的递推公式,其实我们在[494. 目标和](https://programmercarl.com/0494.目标和.html)中就已经讲过了,**而难点在于遍历顺序!**
在求装满背包有几种方案的时候,认清遍历顺序是非常关键的。
@ -181,8 +189,6 @@ public:
## 其他语言版本

View File

@ -194,6 +194,29 @@ class Solution {
return new String(ch);
}
}
// 解法二还可以用temp来交换数值会的人更多些
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
for(int i = 0;i < ch.length;i += 2 * k){
int start = i;
// 判断尾数够不够k个来取决end指针的位置
int end = Math.min(ch.length - 1,start + k - 1);
while(start < end){
char temp = ch[start];
ch[start] = ch[end];
ch[end] = temp;
start++;
end--;
}
}
return new String(ch);
}
}
```
```java
// 解法3
@ -271,6 +294,8 @@ func reverseStr(s string, k int) string {
ss := []byte(s)
length := len(s)
for i := 0; i < length; i += 2 * k {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if i + k <= length {
reverse(ss[i:i+k])
} else {
@ -303,7 +328,7 @@ javaScript:
var reverseStr = function(s, k) {
const len = s.length;
let resArr = s.split("");
for(let i = 0; i < len; i += 2 * k) {
for(let i = 0; i < len; i += 2 * k) { // 每隔 2k 个字符的前 k 个字符进行反转
let l = i - 1, r = i + k > len ? len : i + k;
while(++l < --r) [resArr[l], resArr[r]] = [resArr[r], resArr[l]];
}
@ -469,3 +494,4 @@ impl Solution {
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>

View File

@ -228,28 +228,43 @@ func min(a, b int) int {
```
Javascript
```javascript
const minDistance = (word1, word2) => {
let dp = Array.from(new Array(word1.length + 1), () => Array(word2.length+1).fill(0));
for(let i = 1; i <= word1.length; i++) {
dp[i][0] = i;
// 方法一
var minDistance = (word1, word2) => {
let dp = Array.from(new Array(word1.length + 1), () =>
Array(word2.length + 1).fill(0)
);
for (let i = 1; i <= word1.length; i++) {
dp[i][0] = i;
}
for (let j = 1; j <= word2.length; j++) {
dp[0][j] = j;
}
for (let i = 1; i <= word1.length; i++) {
for (let j = 1; j <= word2.length; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(
dp[i - 1][j] + 1,
dp[i][j - 1] + 1,
dp[i - 1][j - 1] + 2
);
}
}
}
return dp[word1.length][word2.length];
};
for(let j = 1; j <= word2.length; j++) {
dp[0][j] = j;
}
for(let i = 1; i <= word1.length; i++) {
for(let j = 1; j <= word2.length; j++) {
if(word1[i-1] === word2[j-1]) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 2);
}
}
}
return dp[word1.length][word2.length];
// 方法二
var minDistance = function (word1, word2) {
let dp = new Array(word1.length + 1)
.fill(0)
.map((_) => new Array(word2.length + 1).fill(0));
for (let i = 1; i <= word1.length; i++)
for (let j = 1; j <= word2.length; j++)
if (word1[i - 1] === word2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
return word1.length + word2.length - dp[word1.length][word2.length] * 2;
};
```

View File

@ -108,9 +108,12 @@ public:
// 如果index大于链表的长度则返回空
// 如果index小于0则置为0作为链表的新头节点。
void addAtIndex(int index, int val) {
if (index > _size || index < 0) {
if (index > _size) {
return;
}
if (index < 0) {
index = 0;
}
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
@ -302,7 +305,7 @@ class MyLinkedList {
head = new ListNode(0);
}
//获取第index个节点的数值
//获取第index个节点的数值注意index是从0开始的第0个节点就是头结点
public int get(int index) {
//如果index非法返回-1
if (index < 0 || index >= size) {
@ -316,12 +319,12 @@ class MyLinkedList {
return currentNode.val;
}
//在链表最前面插入一个节点
//在链表最前面插入一个节点等价于在第0个元素前添加
public void addAtHead(int val) {
addAtIndex(0, val);
}
//在链表的最后插入一个节点
//在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val) {
addAtIndex(size, val);
}
@ -478,76 +481,90 @@ class MyLinkedList {
Python
```python
# 单链表
class Node:
def __init__(self, val):
self.val = val
class Node(object):
def __init__(self, x=0):
self.val = x
self.next = None
class MyLinkedList:
class MyLinkedList(object):
def __init__(self):
self._head = Node(0) # 虚拟头部节点
self._count = 0 # 添加的节点数
self.head = Node()
self.size = 0 # 设置一个链表长度的属性,便于后续操作,注意每次增和删的时候都要更新
def get(self, index: int) -> int:
def get(self, index):
"""
Get the value of the index-th node in the linked list. If the index is invalid, return -1.
:type index: int
:rtype: int
"""
if 0 <= index < self._count:
node = self._head
for _ in range(index + 1):
node = node.next
return node.val
else:
if index < 0 or index >= self.size:
return -1
cur = self.head.next
while(index):
cur = cur.next
index -= 1
return cur.val
def addAtHead(self, val: int) -> None:
def addAtHead(self, val):
"""
Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
:type val: int
:rtype: None
"""
self.addAtIndex(0, val)
new_node = Node(val)
new_node.next = self.head.next
self.head.next = new_node
self.size += 1
def addAtTail(self, val: int) -> None:
def addAtTail(self, val):
"""
Append a node of value val to the last element of the linked list.
:type val: int
:rtype: None
"""
self.addAtIndex(self._count, val)
new_node = Node(val)
cur = self.head
while(cur.next):
cur = cur.next
cur.next = new_node
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
def addAtIndex(self, index, val):
"""
Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
:type index: int
:type val: int
:rtype: None
"""
if index < 0:
index = 0
elif index > self._count:
self.addAtHead(val)
return
elif index == self.size:
self.addAtTail(val)
return
elif index > self.size:
return
# 计数累加
self._count += 1
add_node = Node(val)
prev_node, current_node = None, self._head
for _ in range(index + 1):
prev_node, current_node = current_node, current_node.next
else:
prev_node.next, add_node.next = add_node, current_node
def deleteAtIndex(self, index: int) -> None:
node = Node(val)
pre = self.head
while(index):
pre = pre.next
index -= 1
node.next = pre.next
pre.next = node
self.size += 1
def deleteAtIndex(self, index):
"""
Delete the index-th node in the linked list, if the index is valid.
:type index: int
:rtype: None
"""
if 0 <= index < self._count:
# 计数-1
self._count -= 1
prev_node, current_node = None, self._head
for _ in range(index + 1):
prev_node, current_node = current_node, current_node.next
else:
prev_node.next, current_node.next = current_node.next, None
if index < 0 or index >= self.size:
return
pre = self.head
while(index):
pre = pre.next
index -= 1
pre.next = pre.next.next
self.size -= 1
# 双链表
# 相对于单链表, Node新增了prev属性
class Node:

View File

@ -206,13 +206,13 @@ class Solution: # 贪心思路
result = 0
minPrice = prices[0]
for i in range(1, len(prices)):
if prices[i] < minPrice:
if prices[i] < minPrice: # 此时有更低的价格可以买入
minPrice = prices[i]
elif prices[i] >= minPrice and prices[i] <= minPrice + fee:
continue
else:
result += prices[i] - minPrice - fee
elif prices[i] > (minPrice + fee): # 此时有利润,同时假买入高价的股票,看看是否继续盈利
result += prices[i] - (minPrice + fee)
minPrice = prices[i] - fee
else: # minPrice<= prices[i] <= minPrice + fee 价格处于minPrice和minPrice+fee之间不做操作
continue
return result
```

View File

@ -50,7 +50,7 @@
![1047.删除字符串中的所有相邻重复项](https://code-thinking.cdn.bcebos.com/gifs/1047.删除字符串中的所有相邻重复项.gif)
从栈中弹出剩余元素此时是字符串ac因为从栈里弹出的元素是倒序的所以对字符串进行反转一下,就得到了最终的结果。
从栈中弹出剩余元素此时是字符串ac因为从栈里弹出的元素是倒序的所以对字符串进行反转一下,就得到了最终的结果。
C++代码 :
@ -102,9 +102,9 @@ public:
## 题外话
这道题目就像是我们玩过的游戏对对碰,如果相同的元素放在挨在一起就要消除。
这道题目就像是我们玩过的游戏对对碰,如果相同的元素挨在一起就要消除。
可能我们在玩游戏的时候感觉理所当然应该消除,但程序又怎么知道该如消除呢,特别是消除之后又有新的元素可能挨在一起。
可能我们在玩游戏的时候感觉理所当然应该消除,但程序又怎么知道该如消除呢,特别是消除之后又有新的元素可能挨在一起。
此时游戏的后端逻辑就可以用一个栈来实现(我没有实际考察对对碰或者爱消除游戏的代码实现,仅从原理上进行推断)。

View File

@ -15,17 +15,20 @@
每一回合从中选出任意两块石头然后将它们一起粉碎。假设石头的重量分别为 x 和 y x <= y。那么粉碎的可能结果如下
如果 x == y那么两块石头都会被完全粉碎
如果 x != y那么重量为 x 的石头将会完全粉碎而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。
示例:
输入:[2,7,4,1,8,1]
输出1
* 输入:[2,7,4,1,8,1]
* 输出1
解释:
组合 2 和 4得到 2所以数组转化为 [2,7,1,8,1]
组合 7 和 8得到 1所以数组转化为 [2,1,1,1]
组合 2 和 1得到 1所以数组转化为 [1,1,1]
组合 1 和 1得到 0所以数组转化为 [1],这就是最优值。
* 组合 2 和 4得到 2所以数组转化为 [2,7,1,8,1]
* 组合 7 和 8得到 1所以数组转化为 [2,1,1,1]
* 组合 2 和 1得到 1所以数组转化为 [1,1,1]
* 组合 1 和 1得到 0所以数组转化为 [1],这就是最优值。
提示:
@ -51,7 +54,11 @@
1. 确定dp数组以及下标的含义
**dp[j]表示容量这里说容量更形象其实就是重量为j的背包最多可以背dp[j]这么重的石头**。
**dp[j]表示容量这里说容量更形象其实就是重量为j的背包最多可以背最大重量为dp[j]**。
可以回忆一下01背包中dp[j]的含义容量为j的背包最多可以装的价值为 dp[j]。
相对于 01背包本题中石头的重量是 stones[i],石头的价值也是 stones[i] ,可以 “最多可以装的价值为 dp[j]” == “最多可以背的重量为dp[j]”
2. 确定递推公式
@ -61,7 +68,7 @@
一些同学可能看到这dp[j - stones[i]] + stones[i]中 又有- stones[i] 又有+stones[i],看着有点晕乎。
还是要牢记dp[j]的含义要知道dp[j - stones[i]]为 容量为j - stones[i]的背包最大所背重量
大家可以再去看 dp[j]的含义
3. dp数组如何初始化

View File

@ -76,8 +76,67 @@ public:
return count;
}
};
```
## 其他语言版本
### JavaScript:
```js
/**
* @param {number[][]} grid
* @return {number}
*/
var closedIsland = function(grid) {
let rows = grid.length;
let cols = grid[0].length;
// 存储四个方向
let dir = [[-1, 0], [0, -1], [1, 0], [0, 1]];
// 深度优先
function dfs(x, y) {
grid[x][y] = 1;
// 向四个方向遍历
for(let i = 0; i < 4; i++) {
let nextX = x + dir[i][0];
let nextY = y + dir[i][1];
// 判断是否越界
if (nextX < 0 || nextX >= rows || nextY < 0 || nextY >= cols) continue;
// 不符合条件
if (grid[nextX][nextY] === 1) continue;
// 继续递归
dfs(nextX, nextY);
}
}
// 从边界岛屿开始
// 从左侧和右侧出发
for(let i = 0; i < rows; i++) {
if (grid[i][0] === 0) dfs(i, 0);
if (grid[i][cols - 1] === 0) dfs(i, cols - 1);
}
// 从上侧和下侧出发
for(let j = 0; j < cols; j++) {
if (grid[0][j] === 0) dfs(0, j);
if (grid[rows - 1][j] === 0) dfs(rows - 1, j);
}
let count = 0;
// 排除所有与边界相连的陆地之后
// 依次遍历网格中的每个元素,如果遇到一个元素是陆地且状态是未访问,则遇到一个新的岛屿,将封闭岛屿的数目加 1
// 并访问与当前陆地连接的所有陆地
for(let i = 0; i < rows; i++) {
for(let j = 0; j < cols; j++) {
if (grid[i][j] === 0) {
count++;
dfs(i, j);
}
}
}
return count;
};
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>

View File

@ -14,7 +14,7 @@
<img src='https://img-blog.csdnimg.cn/20210219190809451.png' width=600 alt='二叉树大纲'> </img></div>
二叉树,大家对于二叉树其实都很熟悉了,本文呢我也不想教科书式的把二叉树的基础内容在啰嗦一遍,所以一下我讲的都是一些比较重点的内容。
二叉树,大家对于二叉树其实都很熟悉了,本文呢我也不想教科书式的把二叉树的基础内容再啰嗦一遍,所以以下我讲的都是一些比较重点的内容。
相信只要耐心看完,都会有所收获。
@ -83,7 +83,7 @@
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
链式存储如图:
@ -143,7 +143,7 @@
最后再说一说二叉树中深度优先和广度优先遍历实现方式,我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。
**之前我们讲栈与队列的时候,就说过栈其实就是递归的一种实现结构**,也就说前中后序遍历的逻辑其实都是可以借助栈使用非递归的方式来实现的。
**之前我们讲栈与队列的时候,就说过栈其实就是递归的一种实现结构**,也就说前中后序遍历的逻辑其实都是可以借助栈使用非递归的方式来实现的。
而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
@ -270,6 +270,29 @@ class TreeNode(_value: Int = 0, _left: TreeNode = null, _right: TreeNode = null)
var right: TreeNode = _right
}
```
rust:
```rust
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode<T> {
pub val: T,
pub left: Option<Rc<RefCell<TreeNode<T>>>>,
pub right: Option<Rc<RefCell<TreeNode<T>>>>,
}
impl<T> TreeNode<T> {
#[inline]
pub fn new(val: T) -> Self {
TreeNode {
val,
left: None,
right: None,
}
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -666,6 +666,83 @@ object Solution {
}
}
```
rust:
```rust
impl Solution{
// 前序
pub fn preorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut res = vec![];
let mut stack = vec![];
if root.is_some(){
stack.push(root);
}
while !stack.is_empty(){
if let Some(node) = stack.pop().unwrap(){
if node.borrow().right.is_some(){
stack.push(node.borrow().right.clone());
}
if node.borrow().left.is_some(){
stack.push(node.borrow().left.clone());
}
stack.push(Some(node));
stack.push(None);
}else{
res.push(stack.pop().unwrap().unwrap().borrow().val);
}
}
res
}
// 中序
pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut res = vec![];
let mut stack = vec![];
if root.is_some() {
stack.push(root);
}
while !stack.is_empty() {
if let Some(node) = stack.pop().unwrap() {
if node.borrow().right.is_some() {
stack.push(node.borrow().right.clone());
}
stack.push(Some(node.clone()));
stack.push(None);
if node.borrow().left.is_some() {
stack.push(node.borrow().left.clone());
}
} else {
res.push(stack.pop().unwrap().unwrap().borrow().val);
}
}
res
}
// 后序
pub fn postorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut res = vec![];
let mut stack = vec![];
if root.is_some() {
stack.push(root);
}
while !stack.is_empty() {
if let Some(node) = stack.pop().unwrap() {
stack.push(Some(node.clone()));
stack.push(None);
if node.borrow().right.is_some() {
stack.push(node.borrow().right.clone());
}
if node.borrow().left.is_some() {
stack.push(node.borrow().left.clone());
}
} else {
res.push(stack.pop().unwrap().unwrap().borrow().val);
}
}
res
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -640,6 +640,60 @@ object Solution {
}
}
```
rust:
```rust
use std::cell::RefCell;
use std::rc::Rc;
impl Solution {
//前序
pub fn preorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut res = vec![];
let mut stack = vec![root];
while !stack.is_empty() {
if let Some(node) = stack.pop().unwrap() {
res.push(node.borrow().val);
stack.push(node.borrow().right.clone());
stack.push(node.borrow().left.clone());
}
}
res
}
//中序
pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut res = vec![];
let mut stack = vec![];
let mut node = root;
while !stack.is_empty() || node.is_some() {
while let Some(n) = node {
node = n.borrow().left.clone();
stack.push(n);
}
if let Some(n) = stack.pop() {
res.push(n.borrow().val);
node = n.borrow().right.clone();
}
}
res
}
//后序
pub fn postorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut res = vec![];
let mut stack = vec![root];
while !stack.is_empty() {
if let Some(node) = stack.pop().unwrap() {
res.push(node.borrow().val);
stack.push(node.borrow().left.clone());
stack.push(node.borrow().right.clone());
}
}
res.into_iter().rev().collect()
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -36,13 +36,13 @@
**以下以前序遍历为例:**
1. **确定递归函数的参数和返回值**因为要打印出前序遍历节点的数值所以参数里需要传入vector在放节点的数值,除了这一点就不需要在处理什么数据了也不需要有返回值所以递归函数返回类型就是void代码如下
1. **确定递归函数的参数和返回值**因为要打印出前序遍历节点的数值所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值所以递归函数返回类型就是void代码如下
```cpp
void traversal(TreeNode* cur, vector<int>& vec)
```
2. **确定终止条件**:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了所以如果当前遍历的这个节点是空就直接return代码如下
2. **确定终止条件**在递归的过程中如何算是递归结束了呢当然是当前遍历的节点是空了那么本层递归就要结束了所以如果当前遍历的这个节点是空就直接return代码如下
```cpp
if (cur == NULL) return;
@ -525,6 +525,46 @@ object Solution {
}
}
```
rust:
```rust
use std::cell::RefCell;
use std::rc::Rc;
impl Solution {
pub fn preorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
let mut res = vec![];
Self::traverse(&root, &mut res);
res
}
//前序遍历
pub fn traverse(root: &Option<Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {
if let Some(node) = root {
res.push(node.borrow().val);
Self::traverse(&node.borrow().left, res);
Self::traverse(&node.borrow().right, res);
}
}
//后序遍历
pub fn traverse(root: &Option<Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {
if let Some(node) = root {
Self::traverse(&node.borrow().left, res);
Self::traverse(&node.borrow().right, res);
res.push(node.borrow().val);
}
}
//中序遍历
pub fn traverse(root: &Option<Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {
if let Some(node) = root {
Self::traverse(&node.borrow().left, res);
res.push(node.borrow().val);
Self::traverse(&node.borrow().right, res);
}
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -36,7 +36,7 @@ i指向新长度的末尾j指向旧长度的末尾。
这么做有两个好处:
1. 不用申请新数组。
2. 从后向前填充元素,避免了从前先后填充元素要来的 每次添加元素都要将添加元素之后的所有元素向后移动。
2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题
时间复杂度空间复杂度均超过100%的用户。

View File

@ -31,7 +31,7 @@
不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。
这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。
@ -41,7 +41,7 @@
2. 反转区间为n到末尾的子串
3. 反转整个字符串
最后就可以到左旋n的目的而不用定义新的字符串完全在本串上操作。
最后就可以到左旋n的目的而不用定义新的字符串完全在本串上操作。
例如 示例1中 输入字符串abcdefgn=2
@ -75,7 +75,7 @@ public:
在这篇文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在for循环的表达式上做做文章。
然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html)这里开始给反转加上了一些条件当需要固定规律一段一段去处理字符串的时候要想想在for循环的表达式上做做文章。
后来在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。

View File

@ -108,7 +108,7 @@ std::unordered_map 底层实现为哈希表std::map 和std::multimap 的底
其他语言例如java里的HashMap TreeMap 都是一样的原理。可以灵活贯通。
虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,但是std::set、std::multiset 依然使用哈希函数来做映射,只不过底层的符号表使用了红黑树来存储数据,所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。
虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,std::set、std::multiset 使用红黑树来索引和存储不过给我们的使用方式还是哈希法的使用方式即key和value。所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。
这里在说一下一些C++的经典书籍上 例如STL源码剖析说到了hash_set hash_map这个与unordered_setunordered_map又有什么关系呢

View File

@ -6,10 +6,18 @@
# 深度优先搜索理论基础
提到深度优先搜索dfs就不得不说和广度优先有什么区别bfs
录友们期待图论内容已久了,为什么鸽了这么久,主要是最近半年开始更新[代码随想录算法公开课](https://mp.weixin.qq.com/s/xncn6IHJGs45sJOChN6V_g)是开源在B站的算法视频已经帮助非常多基础不好的录友学习算法。
录视频其实是非常累的,也要花很多时间,所以图论这边就没抽出时间来。
后面计划先给大家讲图论里大家特别需要的深搜和广搜。
以下,开始讲解深度优先搜索理论基础:
## dfs 与 bfs 区别
提到深度优先搜索dfs就不得不说和广度优先有什么区别bfs
先来了解dfs的过程很多录友可能对dfs深度优先搜索bfs广度优先搜索分不清。
先给大家说一下两者大概的区别:
@ -35,7 +43,7 @@
![图三](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707094011.png)
路径2撤销了改变了方向走路径3红色线 接着也找到终点6。 那么撤销路径2改为路径3在dfs中其实就是回溯的过程这一点很重要很多录友,都不理解dfs代码中回溯是用来干什么的
路径2撤销了改变了方向走路径3红色线 接着也找到终点6。 那么撤销路径2改为路径3在dfs中其实就是回溯的过程这一点很重要很多录友不理解dfs代码中回溯是用来干什么的
又找到了一条从节点1到节点6的路径又到黄河了此时再回头下图图四中路径4撤销回溯的过程改为路径5。
@ -55,7 +63,6 @@
* 搜索方向,是认准一个方向搜,直到碰壁之后在换方向
* 换方向是撤销原路径,改为节点链接的下一个路径,回溯的过程。
## 代码框架
正式因为dfs搜索可一个方向并需要回溯所以用递归的方式来实现是最方便的。
@ -65,6 +72,7 @@
有递归的地方就有回溯,那么回溯在哪里呢?
就地递归函数的下面,例如如下代码:
```
void dfs(参数) {
处理节点
@ -160,8 +168,6 @@ if (终止条件) {
终止添加不仅是结束本层递归,同时也是我们收获结果的时候。
另外其实很多dfs写法没有写终止条件其实终止条件写在了 下面dfs递归的逻辑里了也就是不符合条件直接不会向下递归。这里如果大家不理解的话没关系后面会有具体题目来讲解。
* 841.钥匙和房间
* 200. 岛屿数量
3. 处理目前搜索节点出发的路径
@ -190,6 +196,9 @@ for (选择:本节点所连接的其他节点) {
以上如果大家都能理解了,其实搜索的代码就很好写,具体题目套用具体场景就可以了。
后面我也会给大家安排具体练习的题目,依旧是代码随想录的风格,循序渐进由浅入深!
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -12,7 +12,7 @@
![栈与队列理论1](https://img-blog.csdnimg.cn/20210104235346563.png)
那么我这里列出四个关于栈的问题大家可以思考一下。以下是以C++为例,相信使用其他编程语言的同学也对应思考一下,自己使用的编程语言里栈和队列是什么样的。
那么我这里列出四个关于栈的问题大家可以思考一下。以下是以C++为例,使用其他编程语言的同学也对应思考一下,自己使用的编程语言里栈和队列是什么样的。
1. C++中stack 是容器么?
2. 我们使用的stack是属于哪个版本的STL
@ -23,7 +23,7 @@
有的同学可能仅仅知道有栈和队列这么个数据结构却不知道底层实现也不清楚所使用栈和队列和STL是什么关系。
所以这里我给大家扫一遍基础知识,
所以这里我给大家扫一遍基础知识,
首先大家要知道 栈和队列是STLC++标准库)里面的两个数据结构。
@ -83,7 +83,7 @@ std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列
所以STL 队列也不被归类为容器而被归类为container adapter 容器适配器)。
我这里讲的都是C++ 语言中情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖内部原理,才能夯实基础。
我这里讲的都是C++ 语言中情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖内部原理,才能夯实基础。

View File

@ -82,7 +82,7 @@ dp状态图如下
就知道了01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了一维dp数组的两个for循环先后循序一定是先遍历物品再遍历背包容量。
**在完全背包中对于一维dp数组来说其实两个for循环嵌套顺序同样无所谓**
**在完全背包中对于一维dp数组来说其实两个for循环嵌套顺序是无所谓的**
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。

View File

@ -9,7 +9,7 @@
什么是链表链表是一种通过指针串联在一起的线性结构每一个节点由两部分组成一个是数据域一个是指针域存放指向下一个节点的指针最后一个节点的指针域指向null空指针的意思
的入口节点称为链表的头结点也就是head。
的入口节点称为链表的头结点也就是head。
如图所示:
![链表1](https://img-blog.csdnimg.cn/20200806194529815.png)

View File

@ -155,23 +155,28 @@ public class Solution {
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
"""
根据快慢法则,走的快的一定会追上走得慢的。
在这道题里,有的链表短,他走完了就去走另一条链表,我们可以理解为走的快的指针。
那么,只要其中一个链表走完了,就去走另一条链表的路。如果有交点,他们最终一定会在同一个
位置相遇
"""
if headA is None or headB is None:
return None
cur_a, cur_b = headA, headB # 用两个指针代替a和b
while cur_a != cur_b:
cur_a = cur_a.next if cur_a else headB # 如果a走完了那么就切换到b走
cur_b = cur_b.next if cur_b else headA # 同理b走完了就切换到a
return cur_a
lenA, lenB = 0, 0
cur = headA
while cur: # 求链表A的长度
cur = cur.next
lenA += 1
cur = headB
while cur: # 求链表B的长度
cur = cur.next
lenB += 1
curA, curB = headA, headB
if lenA > lenB: # 让curB为最长链表的头lenB为其长度
curA, curB = curB, curA
lenA, lenB = lenB, lenA
for _ in range(lenB - lenA): # 让curA和curB在同一起点上末尾位置对齐
curB = curB.next
while curA: # 遍历curA 和 curB遇到相同则直接返回
if curA == curB:
return curA
else:
curA = curA.next
curB = curB.next
return None
```
### Go
@ -248,19 +253,21 @@ var getListLen = function(head) {
}
var getIntersectionNode = function(headA, headB) {
let curA = headA,curB = headB,
lenA = getListLen(headA),
lenB = getListLen(headB);
if(lenA < lenB) {
// 下面交换变量注意加 “分号” ,两个数组交换变量在同一个作用域下时
lenA = getListLen(headA), // 求链表A的长度
lenB = getListLen(headB);
if(lenA < lenB) { // 让curA为最长链表的头lenA为其长度
// 交换变量注意加 “分号” ,两个数组交换变量在同一个作用域下时
// 如果不加分号,下面两条代码等同于一条代码: [curA, curB] = [lenB, lenA]
[curA, curB] = [curB, curA];
[lenA, lenB] = [lenB, lenA];
}
let i = lenA - lenB;
while(i-- > 0) {
let i = lenA - lenB; // 求长度差
while(i-- > 0) { // 让curA和curB在同一起点上末尾位置对齐
curA = curA.next;
}
while(curA && curA !== curB) {
while(curA && curA !== curB) { // 遍历curA 和 curB遇到相同则直接返回
curA = curA.next;
curB = curB.next;
}