Merge branch 'master' into master
This commit is contained in:
commit
eeeb64dc32
|
|
@ -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 [];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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--
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ cd a/b/c/../../
|
|||
|
||||
**一些同学,在面试中看到这种题目上来就开始写代码,然后就越写越乱。**
|
||||
|
||||
建议要写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。
|
||||
建议在写代码之前要分析好有哪几种不匹配的情况,如果不在动手之前分析好,写出的代码也会有很多问题。
|
||||
|
||||
先来分析一下 这里有三种不匹配的情况,
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ next数组就是一个前缀表(prefix table)。
|
|||
|
||||
**前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。**
|
||||
|
||||
为了清楚的了解前缀表的来历,我们来举一个例子:
|
||||
为了清楚地了解前缀表的来历,我们来举一个例子:
|
||||
|
||||
要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。
|
||||
|
||||
|
|
@ -110,9 +110,9 @@ next数组就是一个前缀表(prefix table)。
|
|||
|
||||

|
||||
|
||||
动画里,我特意把 子串`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++
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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数组状态如下:
|
||||
|
||||

|
||||
|
||||
最后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的风格啊,哈哈
|
||||
|
||||
|
||||
|
||||
|
||||
稍加分析,便可知道本题是完全背包,是求能否组成背包,而且这里要求物品是要有顺序的。
|
||||
|
||||
## 其他语言版本
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
```
|
||||
|
||||
|
||||
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
|
||||
逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。
|
||||
|
||||
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
|
||||
|
||||
|
|
@ -61,11 +61,11 @@
|
|||
|
||||
* 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
|
||||
|
||||
* 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
|
||||
* 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
|
||||
|
||||
# 思路
|
||||
|
||||
《代码随想录》算法视频公开课:[栈的最后表演! | LeetCode:150. 逆波兰表达式求值](https://www.bilibili.com/video/BV1kd4y1o7on),相信结合视频在看本篇题解,更有助于大家对本题的理解。
|
||||
《代码随想录》算法视频公开课:[栈的最后表演! | LeetCode:150. 逆波兰表达式求值](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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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)以及下标的含义
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
# 思路
|
||||
|
||||
|
||||
《代码随想录》算法公开课:[队列的基本操作! | LeetCode:225. 用队列实现栈](https://www.bilibili.com/video/BV1Fd4y1K7sm),相信结合视频在看本篇题解,更有助于大家对链表的理解。
|
||||
《代码随想录》算法公开课:[队列的基本操作! | LeetCode:225. 用队列实现栈](https://www.bilibili.com/video/BV1Fd4y1K7sm),相信结合视频再看本篇题解,更有助于大家对链表的理解。
|
||||
|
||||
|
||||
(这里要强调是单向队列)
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
|
||||
|
||||
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用又来备份的!
|
||||
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用来备份的!
|
||||
|
||||
如下面动画所示,**用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用**,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ public:
|
|||
|
||||
其实这道题目就是用一个队列就够了。
|
||||
|
||||
**一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。**
|
||||
**一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。**
|
||||
|
||||
C++优化代码
|
||||
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -38,14 +38,14 @@ queue.empty(); // 返回 false
|
|||
|
||||
## 思路
|
||||
|
||||
《代码随想录》算法公开课:[栈的基本操作! | LeetCode:232.用栈实现队列](https://www.bilibili.com/video/BV1nY4y1w7VC),相信结合视频在看本篇题解,更有助于大家对链表的理解。
|
||||
《代码随想录》算法公开课:[栈的基本操作! | LeetCode:232.用栈实现队列](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. */
|
||||
// 在输出栈做pop,pop时如果输出栈数据为空,需要将输入栈全部数据导入,如果非空,则可直接使用
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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)。
|
||||
|
||||
|
|
|
|||
|
|
@ -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 一定是谁多了字符或者谁少了字符。
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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 中最小的。
|
||||
|
|
|
|||
|
|
@ -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),算上递推系统栈的空间
|
||||
|
||||
|
||||
### 动态规划
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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+1,2. 前面是波谷,down不变,取较大值
|
||||
down = max(down, up+1)
|
||||
return max(up, down)
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
**贪心**
|
||||
|
|
|
|||
|
|
@ -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/)
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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. 确定递推公式
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -46,17 +46,17 @@
|
|||
|
||||
## 移动匹配
|
||||
|
||||
当一个字符串s:abcabc,内部又重复的子串组成,那么这个字符串的结构一定是这样的:
|
||||
当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的:
|
||||
|
||||

|
||||
|
||||
也就是又前后又相同的子串组成。
|
||||
也就是由前后相同的子串组成。
|
||||
|
||||
那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s,如图:
|
||||
|
||||

|
||||
|
||||
所以判断字符串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数组为什么遇到字符不匹配的时候可以找到上一
|
|||
|
||||
那么 最长相同前后缀和重复子串的关系又有什么关系呢。
|
||||
|
||||
可能很多录友又忘了 前缀和后缀的定义,在回顾一下:
|
||||
可能很多录友又忘了 前缀和后缀的定义,再回顾一下:
|
||||
|
||||
* 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;
|
||||
* 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
|
||||
|
||||
在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里那字符串s:abababab 来举例,ab就是最小重复单位,如图所示:
|
||||
在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里拿字符串s:abababab 来举例,ab就是最小重复单位,如图所示:
|
||||
|
||||

|
||||
|
||||
|
|
@ -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,就可以判定有重复出现的子字符串。
|
||||
|
||||
|
|
|
|||
|
|
@ -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背包不同维度上的应用,大家可以细心体会!
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 是5,S是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,
|
||||
|
||||
* 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 dp[5]。
|
||||
* 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 dp[5]。
|
||||
* 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 dp[5]
|
||||
* 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 dp[5]
|
||||
* 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 dp[5]
|
||||
* 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
|
||||
* 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
|
||||
* 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
|
||||
* 已经有一个4(nums[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]];
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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]一定要为1,dp[0] = 1是 递归公式的基础。
|
||||
首先dp[0]一定要为1,dp[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:
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
## 其他语言版本
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
|
||||

|
||||
|
||||
从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒序的,所以在对字符串进行反转一下,就得到了最终的结果。
|
||||
从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒序的,所以再对字符串进行反转一下,就得到了最终的结果。
|
||||
|
||||
C++代码 :
|
||||
|
||||
|
|
@ -102,9 +102,9 @@ public:
|
|||
|
||||
## 题外话
|
||||
|
||||
这道题目就像是我们玩过的游戏对对碰,如果相同的元素放在挨在一起就要消除。
|
||||
这道题目就像是我们玩过的游戏对对碰,如果相同的元素挨在一起就要消除。
|
||||
|
||||
可能我们在玩游戏的时候感觉理所当然应该消除,但程序又怎么知道该如果消除呢,特别是消除之后又有新的元素可能挨在一起。
|
||||
可能我们在玩游戏的时候感觉理所当然应该消除,但程序又怎么知道该如何消除呢,特别是消除之后又有新的元素可能挨在一起。
|
||||
|
||||
此时游戏的后端逻辑就可以用一个栈来实现(我没有实际考察对对碰或者爱消除游戏的代码实现,仅从原理上进行推断)。
|
||||
|
||||
|
|
|
|||
|
|
@ -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数组如何初始化
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ i指向新长度的末尾,j指向旧长度的末尾。
|
|||
这么做有两个好处:
|
||||
|
||||
1. 不用申请新数组。
|
||||
2. 从后向前填充元素,避免了从前先后填充元素要来的 每次添加元素都要将添加元素之后的所有元素向后移动。
|
||||
2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
|
||||
|
||||
时间复杂度,空间复杂度均超过100%的用户。
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。
|
||||
|
||||
|
||||
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现,反转单词顺序的目的。
|
||||
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。
|
||||
|
||||
这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。
|
||||
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
2. 反转区间为n到末尾的子串
|
||||
3. 反转整个字符串
|
||||
|
||||
最后就可以得到左旋n的目的,而不用定义新的字符串,完全在本串上操作。
|
||||
最后就可以达到左旋n的目的,而不用定义新的字符串,完全在本串上操作。
|
||||
|
||||
例如 :示例1中 输入:字符串abcdefg,n=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)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
|
||||
|
||||
|
|
|
|||
|
|
@ -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_set,unordered_map又有什么关系呢?
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
|
||||

|
||||
|
||||
路径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"/>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||

|
||||
|
||||
那么我这里在列出四个关于栈的问题,大家可以思考一下。以下是以C++为例,相信使用其他编程语言的同学也对应思考一下,自己使用的编程语言里栈和队列是什么样的。
|
||||
那么我这里再列出四个关于栈的问题,大家可以思考一下。以下是以C++为例,使用其他编程语言的同学也对应思考一下,自己使用的编程语言里栈和队列是什么样的。
|
||||
|
||||
1. C++中stack 是容器么?
|
||||
2. 我们使用的stack是属于哪个版本的STL?
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
有的同学可能仅仅知道有栈和队列这么个数据结构,却不知道底层实现,也不清楚所使用栈和队列和STL是什么关系。
|
||||
|
||||
所以这里我在给大家扫一遍基础知识,
|
||||
所以这里我再给大家扫一遍基础知识,
|
||||
|
||||
首先大家要知道 栈和队列是STL(C++标准库)里面的两个数据结构。
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列
|
|||
|
||||
所以STL 队列也不被归类为容器,而被归类为container adapter( 容器适配器)。
|
||||
|
||||
我这里讲的都是C++ 语言中情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖起内部原理,才能夯实基础。
|
||||
我这里讲的都是C++ 语言中的情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖其内部原理,才能夯实基础。
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ dp状态图如下:
|
|||
|
||||
就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。
|
||||
|
||||
**在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序同样无所谓!**
|
||||
**在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!**
|
||||
|
||||
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
|
||||
|
||||
链接的入口节点称为链表的头结点也就是head。
|
||||
链表的入口节点称为链表的头结点也就是head。
|
||||
|
||||
如图所示:
|
||||

|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue