algorithm-notes

Algorithm-learning-notes

算法笔记+个人代码(菜,仅供参考)

笔记未完成,正在更新

已完成:数组、排序、链表、哈希表、栈和队列、二叉树、回溯、贪心、动态规划

正在更新:图论、字符串、单调栈

版本:2023-12-07

基础算法总结

题目参考:https://www.programmercarl.com/

时空复杂度理论

使用OOΩ\OmegaΘ\Theta表示法,分别表示算法的效率上限(上界)、下限(下界)、和等限(确界),其数学上的具体定义见表

记号 定义 含义
OO f(n)=O(g(n))f(n)=O(g(n)) 若存在两个正常数ccn0n_0,使nn0n\ge n_0时,有0f(n)cg(n)0\le f(n)\le c\cdot g(n) f(n)f(n)的渐进上限为g(n)g(n)
Ω\Omega f(n)=Ω(g(n))f(n)=\Omega (g(n)) 若存在两个正常数ccn0n_0,使nn0n\ge n_0时,有0cg(n)f(n)0 \le c\cdot g(n)\le f(n) f(n)f(n)的渐进下限为g(n)g(n)
Θ\Theta f(n)=Θ(g(n))f(n)=\Theta (g(n)) 若存在正常数c1c_1c2c_2n0n_0,使nn0n\ge n_0时,有0c1g(n)f(n)c2g(n)0\le c_1\cdot g(n)\le f(n)\le c_2\cdot g(n) f(n)f(n)的渐进确界为g(n)g(n)
oo f(n)=o(g(n))f(n)=o(g(n)) 若对任意正数 cc 都存在 n0n_0 ,使得nn0n\ge n_0 时有 0f(n)<cg(n)0\le f(n)< c\cdot g(n)
ω\omega f(n)=ω(g(n))f(n)=\omega (g(n)) 若对任意正数 cc 都存在 n0n_0 ,使得nn0n\ge n_0 时有 0cg(n)<f(n)0\le c\cdot g(n) < f(n)

f(n)=O(g(n))f(n)=O(g(n))f(n)=Ω(g(n))f(n)=\Omega(g(n)) ,则记作 f(n)=Θ(g(n))f(n)=\Theta(g(n))

如果c>0,n0>0\exist c>0,n_0>0 (n0n_0为整数),使对于nn0\forall n\ge n_0,有f(n)cg(n)f(n)\le c\cdot g(n)

那么,f(n)=O(g(n))f(n)=O(g(n))

例1: f(n)=2n2+3n+1f(n)=2n^2+3n+1

c=6,n0=100c=6,n_0=100n>100\forall n>100,有

f(n)=2n2+3n+12n2+3n2+n2=6n2=cn2\begin{align} f(n)&=2n^2+3n+1\\ &\le 2n^2+3n^2+n^2\\ &=6n^2\\ &=cn^2 \end{align}

由定义可证f(n)=O(n2)f(n)=O(n^2)

例2: f(n)=nlog2nf(n)=n\log_2n,证明f(n)=O(n1.02)f(n)=O(n^{1.02})

c=1,n0=3c=1,n_0=3n>3\forall n>3,有

n>3n>3log2n<n0.02\log_2n<n^{0.02}

f(n)=nlog2nnn0.02=n1.02\begin{align} f(n)&=n\log_2n\\ &\le n\cdot n^{0.02}\\ &=n^{1.02} \end{align}

由定义可证f(n)=O(n1.02)f(n)=O(n^{1.02})

时间复杂度的量级比较:

O(1)<O(log2n)<O(n)<O(n)<O(nlog2n)<O(n1.01)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)O(1)<O(\log_2n)<O(\sqrt n)<O(n)<O(n\log_2n)<O(n^{1.01})<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)

对于空间复杂度

S(n)=O(f(n))S(n)=O(f(n)),其中n为问题的规模,f(n)f(n)为语句关于nn所占存储空间的函数

(1)数组专题

数组1:二分查找

手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找(参考视频)

leetcode704

左闭右闭模板:

注意四个细节:

  • right=len-1

  • left<=right

  • right=mid-1

  • left=mid+1

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int search(vector<int>& nums, int target)
{
int len=nums.size();
int left=0;
int right=len-1;
while(left<=right)//左闭右闭
{
int mid=(left+right)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]>target)
right=mid-1;
else
left=mid+1;
}
return -1;
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def search(self, nums: List[int], target: int) -> int:
left=0
right=len(nums)-1
while left<=right: ## 左闭右闭
mid=(left+right)//2
if nums[mid]==target:
return mid
elif nums[mid]>target:
right=mid-1
else:
left=mid+1
return -1

左闭右开模板:

注意四个细节:

  • right=nums.size()
  • left<right
  • left=mid+1
  • right=mid

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int search(vector<int>& nums, int target)
{
int left=0;
int right=nums.size();
while(left<right) //左闭右开
{
int mid=(left+right)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]<target)
left=mid+1;
else
right=mid;
}
return -1;
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def search(self, nums: List[int], target: int) -> int:
left=0
right=len(nums)
while left<right: ## 左闭右开
mid=(left+right)//2
if nums[mid]==target:
return mid
elif nums[mid]<target:
left=mid+1
else:
right=mid
return -1

递归版二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int search(vector<int>& nums, int target) //递归版
{
function<int(int,int)>BinarySearch=[&](int le,int ri)
{
if(le>ri)
return -1;
int mid=(le+ri)/2;
if(nums[mid]==target)
{
return mid;
}
else if(nums[mid]>target)
return BinarySearch(le,mid-1);
else
return BinarySearch(mid+1,ri);
};
return BinarySearch(0,nums.size()-1);
}
};

数组3:有序数组的平方

双指针法经典题目 | LeetCode:977.有序数组的平方(参考视频)

leetcode977

推荐双指针法,由绝对值最小的数开始往两边遍历

当然也可以由两边向中间遍历数组

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
le=len(nums)
idx=0
for idx in range(le):
if idx+1<le and abs(nums[idx+1])>abs(nums[idx]):
break
left=idx-1
right=idx+1
arr=[nums[idx]**2]
while 1:
if left>=0 and right<le:
if abs(nums[left])<abs(nums[right]):
arr.append(nums[left]**2)
left-=1
else:
arr.append(nums[right]**2)
right+=1
elif left>=0:
arr.append(nums[left]**2)
left-=1
elif right<le:
arr.append(nums[right]**2)
right+=1
else:
break
return arr

88ms, 17.9MB

使用二分查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
# 二分找到绝对值最小的下标
left, right = 0, len(nums) - 1
while left < right - 1:
mid = (left + right)//2
if nums[mid] <= 0:
left = mid
if nums[mid] >= 0:
right = mid
p = left if abs(nums[left]) < abs(nums[right]) else right
ret = [0]*len(nums)
ret[0] = nums[p] ** 2
left = p - 1
right = p + 1
# 计算平方
for i in range(1, len(nums)):
if left >= 0 and right < len(nums):
if abs(nums[left]) < abs(nums[right]):
ret[i] = nums[left]**2
left -= 1
else:
ret[i] = nums[right]**2
right += 1
elif left >= 0:
ret[i] = nums[left]**2
left -= 1
else:
ret[i] = nums[right]**2
right += 1
return ret

56ms, 18.5MB

数组4:长度最小的子数组|滑动窗口

leetcode209

拿下滑动窗口! | LeetCode 209 长度最小的子数(参考视频)

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
le=len(nums)
left=add=0
right=-1
ret=inf
while right+1<le:
while add<=target and right+1<le:
right+=1
add+=nums[right]
if add>=target:
ret=min(ret,right-left+1)
while add>target:
add-=nums[left]
left+=1
if add>=target:
ret=min(ret,right-left+1)
if ret==inf:
return 0
else :
return ret

(2)排序专题:

数组排序:leetcode912

单链表排序:leetcode148(测试用例很容易超时,matrix的面试题,当年我就没做出来:confounded:)

对链表进行插入排序:leetcode147(不容易超时)

image-20230726124830123

image-20231020150620423

1插入排序

支持数组和链表

数组的插入排序排序

空间复杂度O(1)O(1),因为只使用了常数个临时变量

最好时间复杂度:O(n)O(n),对应于数组已经有序的情况

最坏时间复杂度:O(n2)O(n^2),对应数组完全逆序的情况

平均时间复杂度:O(n2)O(n^2)

具有稳定性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<int> sortArray(vector<int>& nums) //插入排序
{
int n=nums.size();//数组长度
for(int i=1;i<n;++i)
{
if(nums[i-1]>nums[i])
{
int tmp=nums[i];//取出当前遍历的元素
int j=i-1;
for(;j>=0&&nums[j]>tmp;--j)//向左遍历,直到遍历到小于或等于tmp的元素
nums[j+1]=nums[j];//将比tmp大的元素右移一格
nums[j+1]=tmp;//将tmp插入新的位置
}
}
return nums;
}
};

(在leetcode912提交会超时)

插入排序优化:折半插入排序

当遍历到第i个1元素时,[0,i-1]的所有元素时有序的,可以利用二分查找的方法找到插入位置

然鹅, 时间复杂度没有质的飞跃

空间复杂度O(1)O(1)

最好时间复杂度:O(n)O(n),对应于数组已经有序的情况

最坏时间复杂度:O(n2)O(n^2),对应数组完全逆序的情况

平均时间复杂度:O(n2)O(n^2)

具有稳定性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
vector<int> sortArray(vector<int>& nums) //插入排序
{
int n=nums.size();//数组长度
for(int i=1;i<n;++i)
{
if(nums[i-1]>nums[i])
{
int left=0,right=i-1;
while(left<=right)//二分查找
{
int mid=(left+right)/2;
if(nums[i]>=nums[mid])//这里取等是为了算法的稳定性
left=mid+1;
else
right=mid-1;
}
//将[left,i-1]的所有元素右移
int tmp=nums[i];
for(int j=i-1;j>=left;--j)
nums[j+1]=nums[j];
nums[left]=tmp;
}
}
return nums;
}
};

(在leetcode912提交会超时)

链表的插入排序

空间复杂度:O(1)O(1),只额外开辟了常数级别的空间

最好时间复杂度:O(n)O(n),对应于数组已经有序的情况

最坏时间复杂度:O(n2)O(n^2),对应数组完全逆序的情况

平均时间复杂度:O(n2)O(n^2)

具有稳定性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {//插入排序
public:
ListNode* sortList(ListNode* head)
{
if(head==nullptr)//保证链表长度≥1
return head;
ListNode* dummyhead=new ListNode(0,head);
ListNode* i=head;
while(i->next!=nullptr)
{
if(i->val>i->next->val)
{
ListNode* tmp=i->next;//取出当前节点
i->next=tmp->next;//将当前节点从链表删除
ListNode* j=dummyhead;
while(j->next->val<=tmp->val)
j=j->next;
tmp->next=j->next;
j->next=tmp;//将当前tmp节点插入链表
}
else
i=i->next;
}
ListNode* ret=dummyhead->next;
delete dummyhead;
return ret;
}
};

(在leetcode148提交会超时,leetcode147提交通过,16ms,9.3MB)

2希尔排序

仅支持数组,不适用链表

先追求表中元素部分有序,再逐渐逼近全局有序

每一轮都按照一个给定的间隔进行插入排序,这个间隔应当逐渐减少,最后必须为1

以下的代码中, 步长(间隔)从n2\frac n 2开始递减,每次变为原来的12\frac 1 2,直到变为1

空间复杂度:O(1)O(1)

时间复杂度:和增量序列d1,d2,d3,d_1,d_2,d_3,\cdots的选择有关,目前无法用数学手段证明确切的时间复杂度

最坏时间复杂度O(n2)O(n^2),也就是取d=1d=1的时候,这时候希尔排序退化为插入排序

nn在某个范围内时,可达O(n1.3)O(n^{1.3})

不具有稳定性,例如:

原始序列: 65 49 49

第一趟:d=2 49 49 65

第一趟:d=1 49 49 65

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
vector<int> sortArray(vector<int>& nums) //希尔排序
{
int n=nums.size();//数组长度
for(int d=n/2;d>=1;d/=2)//步长变化
{
for(int i=d;i<n;++i)
{
if(nums[i-d]>nums[i])
{
int tmp=nums[i];
int j=i-d;
for(;j>=0&&nums[j]>tmp;j-=d)
nums[j+d]=nums[j];
nums[j+d]=tmp;
}
}
}
return nums;
}
};

(在leetcode912提交通过,224ms,65MB)

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
def sortArray(self, nums: List[int]) -> List[int]: ## 希尔排序
n=len(nums)
d=n//2
while d>0:
for i in range(d):
j=d+i
while j<n:
if nums[j-d]>nums[j]:
tmp=nums[j]
k=j-d
while k>=0 and nums[k]>tmp:
nums[k+d]=nums[k]
k-=d
nums[k+d]=tmp
j+=d
d//=2
return nums

(在leetcode912提交通过,1208ms,22MB)

3冒泡排序

数组的冒泡排序

可用于数组和链表

空间复杂度:O(1)O(1)

时间复杂度

最好情况(有序):O(n)O(n)

最坏情况(逆序):O(n2)O(n^2)

平均时间复杂度:O(n2)O(n^2)

具有稳定性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
vector<int> sortArray(vector<int>& nums) //冒泡排序
{
int n=nums.size();//数组长度
//每一轮都把最小的元素移动到前面
for(int i=0;i<n-1;++i)
{
int flag=0;//本趟冒泡是否发生交换的标志
for(int j=n-2;j>=i;--j)//一趟冒泡
{
if(nums[j]>nums[j+1])//如果逆序
{
flag=1;
swap(nums[j],nums[j+1]);//交换
}
}
if(flag==0)//本趟遍历没有交换,当前数组已经有序,不需要继续循环
break;
}
return nums;
}
};

(在leetcode912提交会超时)

链表的冒泡排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#define INT_MAX 0x7fffffff
class Solution {//冒泡排序
public:
ListNode* sortList(ListNode* head)
{
if(head==nullptr||head->next==nullptr)//保证链表长度>1
return head;
ListNode* dummyhead=new ListNode(0,head);//虚拟头节点
int cur_max=INT_MAX;
ListNode* pre=dummyhead,*left=head,*right=head->next;
while(right!=nullptr&&right->val<=cur_max)
{
int flag=0;
while(right!=nullptr&&right->val<=cur_max)
{
if(left->val>right->val)
{
left->next=right->next;
right->next=left;
pre->next=right;
pre=right;
right=left->next;
flag=1;
}
else
{
pre=left;
left=right;
right=right->next;
}
}
cur_max=left->val;
pre=dummyhead,left=dummyhead->next,right=left->next;
if(flag==0)
break;
}
ListNode* ret=dummyhead->next;
delete dummyhead;
return ret;
}
};

(在leetcode148提交会超时,leetcode147通过112ms,9.3MB)

4快速排序

最好空间复杂度:O(log2n)O(\log_2n)

最坏空间复杂度:O(n)O(n)

最好时间复杂度:O(nlog2n)O(n\log_2n)

最坏时间复杂度:O(n2)O(n^2)

平均时间复杂度O(nlog2n)O(n\log_2n)

如果每次选中的枢轴将待排序序列划分为均匀的两个部分,则递归深度最小,算法效率最高

若数组原本就有序或逆序,则快速排序性能最差

算法不稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
vector<int> sortArray(vector<int>& nums)//快速排序
{
int n=nums.size();
function<void(int,int)>quick_sort=[&](int low,int high)
{
if(low>=high)
return;
int left=low,right=high;
int pivot=nums[low];
while(low<high)
{
while(low<high&&nums[high]>=pivot)
--high;
nums[low]=nums[high];
while(low<high&&nums[low]<=pivot)
++low;
nums[high]=nums[low];
}
nums[low]=pivot;
quick_sort(left,low-1);
quick_sort(low+1,right);
};
quick_sort(0,n-1);
return nums;
}
};

(在leetcode912提交会超时)

**优化版:**随机选取pivot+优化内部while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n=nums.size();
srand((unsigned int)time(NULL));
function<void(int,int)>quick_sort=[&](int left,int right)
{
if(left>=right)
return;
int pivot_pos=rand()%(right-left+1);
swap(nums[pivot_pos+left],nums[left]);
int pivot=nums[left];
int low=left+1,high=right;
while(1)
{
while(low<=high&&nums[high]>pivot)
--high;
while(low<=high&&nums[low]<pivot)
++low;
if(low>high)
break;
swap(nums[low++],nums[high--]);
}
swap(nums[high],nums[left]);
quick_sort(left,high-1);
quick_sort(high+1,right);
};
quick_sort(0,nums.size()-1);
return nums;
}
};

(在leetcode912提交通过,172ms,64MB)

关键:while循环优化

1
2
3
4
5
6
7
8
9
10
while(1)
{
while(low<=high&&nums[high]>pivot)
--high;
while(low<=high&&nums[low]<pivot)
++low;
if(low>high)
break;
swap(nums[low++],nums[high--]);
}

nums[high]>pivot不取等,防止因为大量重复的数使得在这个循环就导致high一直减到等于low,递归树更加平衡

5选择排序

数组的选择排序:

空间复杂度O(1)O(1)

时间复杂度O(n2)O(n^2)

不稳定,例如:主要原因是使用了swap函数,如果牺牲一些时间,整体后移所有元素以避免使用swap,算法可以变为稳定的

原始序列 3 3 1

排序后 1 3 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
vector<int> sortArray(vector<int>& nums)//快速排序
{
int n=nums.size();
for(int i=0;i<n-1;++i)
{
int cur_min=INT_MAX;
int min_idx=i;
for(int j=i;j<n;++j)
{
if(nums[j]<cur_min)
{
cur_min=nums[j];
min_idx=j;
}
}
swap(nums[i],nums[min_idx]);
}
return nums;
}
};

(在leetcode912提交会超时)

链表的选择排序

空间复杂度O(1)O(1)

时间复杂度O(n2)O(n^2)

具有稳定性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public:
ListNode* sortList(ListNode* head) //选择排序
{
ListNode* dummyhead=new ListNode(0,head);//虚拟头节点
ListNode* i=dummyhead,*j=dummyhead;
while(i->next!=nullptr)
{
j=i;
int cur_min=INT_MAX;
ListNode* min_node=i;
while(j->next!=nullptr)
{
if(j->next->val<cur_min)
{
cur_min=j->next->val;
min_node=j;
}
j=j->next;
}
if(min_node==i)
i=i->next;
else
{
ListNode* tmp=min_node->next;
min_node->next=tmp->next;
tmp->next=i->next;
i->next=tmp;
i=tmp;
}
}
ListNode* ret=dummyhead->next;
delete dummyhead;
return ret;
}
};

(在leetcode148提交会超时,leetcode147通过,80ms,9.4MB)

6堆排序

只适用于数组

堆排序利用大根堆,本质是按照节点序号(从1开始)存储在数组中的完全二叉树

几个基本操作

  • i的左孩子 2i2i
  • i的右孩子 2i+12i+1
  • i的父节点 i2\lfloor\frac i 2\rfloor
  • i的所在层次 log2(i+1)\lceil \log_2(i+1)\rceillog2i+1\lfloor\log_2i\rfloor+1

若完全二叉树中有n个节点,则

  • 判断节点i有左孩子 2in2i\le n
  • 判断节点i有右孩子 2i+1n2i+1\le n
  • 判断节点i为叶子节点 i>n2i>\lfloor\frac n 2\rfloor

若n个关键字序列L[1...n]L[1...n]满足下面某一条性质,则称为堆(Heap)

  • 若满足:L[i]L[2i]L[i]\ge L[2i]L[i]L[2i+1]L[i]\ge L[2i+1](1in21\le i\le\frac n2) 大根堆
  • 若满足:L[i]L[2i]L[i]\le L[2i]L[i]L[2i+1]L[i]\le L[2i+1](1in21\le i\le\frac n2) 小根堆

如果下标从0开始,则:

  • i的左孩子 2i+12i+1
  • i的右孩子 2i+22i+2
  • i的父节点 i12\lfloor\frac {i-1} 2\rfloor
  • i的所在层次 log2(i+2)\lceil \log_2(i+2)\rceillog2(i+1)+1\lfloor\log_2{(i+1)}\rfloor+1

若完全二叉树中有n个节点,则

  • 判断节点i有左孩子 2i+1<n2i+1 < n
  • 判断节点i有右孩子 2i+2<n2i+2 < n
  • 判断节点i为叶子节点 in2i\ge\lfloor\frac n 2\rfloor
  • 判断i存在子节点 0in210\le i\le\lfloor\frac n2\rfloor-1
无脑递归版本:(空间复杂度偏高)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Solution {
public:
vector<int> sortArray(vector<int>& nums)//堆排序
{
int n=nums.size();
function<void(int)>recur=[&](int idx)
{
if(2*idx+1>=n)//没有子树
return;
else if(2*idx+2>=n)//只有左子树
{
if(nums[2*idx+1]>nums[idx])
{
swap(nums[2*idx+1],nums[idx]);
recur(2*idx+1);
}
}
else//有左右子树
{
if(nums[2*idx+1]>nums[2*idx+2])//左子树更大
{
if(nums[2*idx+1]>nums[idx])
{
swap(nums[2*idx+1],nums[idx]);
recur(2*idx+1);
}
}
else//右子树更大
{
if(nums[2*idx+2]>nums[idx])
{
swap(nums[2*idx+2],nums[idx]);
recur(2*idx+2);
}
}
}
};
for(int i=n/2-1;i>=0;--i)//构造大根堆
recur(i);
while(n>=2)
{
--n;
swap(nums[n],nums[0]);
recur(0);
}
return nums;
}
};

leetcode912通过,308ms,65MB

奇妙循环版本:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
vector<int> sortArray(vector<int>& nums)//堆排序(非递归版本)
{
int n=nums.size();
function<void(int,int)>head_adjust=[&](int idx,int len)
{
int k=2*idx+1;//idx对应的左孩子的下标
while(k<len)//idx的左孩子存在
{
if(k+1<len&&nums[k]<nums[k+1])//idx的右孩子存在且值大于左孩子
++k;//调整k,保证k指向左右孩子中的最大者
if(nums[idx]>=nums[k])
break;//idx节点最大,结束循环
swap(nums[idx],nums[k]);
idx=k;//以当前孩子为父节点,继续向下检查
k=2*idx+1;//指向当前孩子的左孩子的下标
}
};
for(int i=n/2-1;i>=0;--i)//构造大根堆
head_adjust(i,n);
for(int i=n-1;i>=1;--i)
{
swap(nums[0],nums[i]);
head_adjust(0,i);
}
return nums;
}
};

leetcode912通过,204ms,65MB

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
n=len(nums)
def head_adjust(i:int): ## 维护大根堆
child=2*i+1
while child < n:
if 2*i+2<n and nums[child]<nums[child+1]:
child+=1
if nums[i]>nums[child]:
break
nums[child],nums[i]=nums[i],nums[child]
i=child
child=2*i+1
for i in range((n-2)//2,-1,-1):
head_adjust(i)
n1=n
for i in range(n1-1,0,-1):
nums[0],nums[i]=nums[i],nums[0]
n-=1
head_adjust(0)
return nums

leetcode912通过,1708ms,21.88MB

复杂度分析:(对于循环版本的代码)

复杂度分析部分考虑下标从1开始

二叉树高h=log2n+1h=\lfloor\log_2n\rfloor+1

ii层最多有2i12^{i-1}个节点,只有第1~(h-1)层的节点才可能需要下坠处理,每次下坠最多对比2次,第i层节点下坠最多对比h-i次

把整棵树调整为大根堆, 关键字对比次数:

i=h112i12(hi)=i=h112i(hi)=j=1h12hjj=2hj=1h1j2j=2log2n+1(2h+12h1)2n2=4n\begin{aligned} \sum\limits_{i=h-1}^{1}2^{i-1}2(h-i) &=\sum\limits_{i=h-1}^{1}2^{i}(h-i)\\ &=\sum\limits_{j=1}^{h-1}2^{h-j}j\\ &=2^h\sum\limits_{j=1}^{h-1}\frac{j}{2^j}\\ &=2^{\lfloor\log_2n\rfloor+1}\cdot(2-\frac{h+1}{2^{h-1}})\\ &\le2n\cdot2\\ &=4n \end{aligned}

所以建堆的过程中时间复杂度O(n)O(n)

排序的过程中总共需要处理n-1个节点,每个节点最多需要下坠h-1层,时间复杂度O(nlog2n)O(n\log_2n)

总的时间复杂度O(nlog2n)O(n\log_2n)

空间复杂度O(1)O(1)

堆排序不稳定,例如

原始序列 1 2 2

排序后 1 2 2

应用:合并K个升序链表

leetcode23

利用小根堆的性质解决

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#define ninf 20000
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists)
{
ListNode* dummyhead=new ListNode(0,nullptr);//虚拟头节点
ListNode* p=dummyhead;
int n=lists.size();
if(n==0)
return nullptr;
//将lists构造为一个小根堆(按照链表中第一个元素的大小排序)
function<void(int)>recur=[&](int idx)
{
if(idx>n/2-1)//没有孩子
return;
int fa,le,ri;//分别是父节点、左节点、右节点,得到它们的权值用于排序
if(lists[idx]==nullptr)
fa=ninf;
else
fa=lists[idx]->val;
if(2*idx+1>=n||lists[2*idx+1]==nullptr)
le=ninf;
else
le=lists[2*idx+1]->val;
if(2*idx+2>=n||lists[2*idx+2]==nullptr)
ri=ninf;
else
ri=lists[2*idx+2]->val;
if(le<ri)
{
if(le<fa)
{
swap(lists[2*idx+1],lists[idx]);
recur(2*idx+1);
}
}
else
{
if(ri<fa)
{
swap(lists[2*idx+2],lists[idx]);
recur(2*idx+2);
}
}
};//用于构造和维护小根堆的递归函数
for(int i=n/2-1;i>=0;--i)
recur(i);
while(lists[0]!=nullptr)
{
p->next=lists[0];
p=p->next;
lists[0]=p->next;
p->next=nullptr;
recur(0);
}
ListNode* ret=dummyhead->next;
delete dummyhead;
return ret;
}
};

7归并排序

复杂度分析

2路归并的归并树,形态上就是一棵倒立的二叉树

二叉树的第h层最多有2h12^{h-1}个节点,若树高为h,满足n2h1n\le 2^{h-1}

h1=log2nh-1=\lceil\log_2n\rceil

n个元素进行二路归并排序,归并趟数=log2n\lceil\log_2n\rceil

每趟归并的时间复杂度O(n)O(n),算法的时间复杂度O(nlog2n)O(n\log_2n)

递归工作栈的空间复杂度为O(log2n)O(\log_2n),辅助数组的空间复杂度O(n)O(n)

总的空间复杂度O(n)O(n)

归并排序具有稳定性

对数组归并排序

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public:
vector<int> sortArray(vector<int>& nums)//归并排序
{
int n=nums.size();
vector<int>tmp(n);
function<void(int,int)>merge_sort=[&](int left,int right)
{
if(left>=right)
return;
int mid=(left+right)/2;
merge_sort(left,mid);
merge_sort(mid+1,right);
for(int i=left;i<=right;++i)//将[left,right]的元素临时移动到tmp数组
tmp[i]=nums[i];
int i=left,j=mid+1,k=left;
while(i<=mid&&j<=right)
{
if(tmp[i]<=tmp[j])
{
nums[k]=tmp[i];
++i;
}
else
{
nums[k]=tmp[j];
++j;
}
++k;
}
for(;i<=mid;++i,++k)
nums[k]=tmp[i];
for(;j<=right;++j,++k)
nums[k]=tmp[j];
};
merge_sort(0,n-1);
return nums;
}
};

leetcode912通过,312ms,67.1MB

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution:
def sortArray(self, nums: List[int]) -> List[int]: ## 归并排序
def merge_sort(left:int,right:int):
if left>=right:
return
mid=(left+right)//2
merge_sort(left,mid)
merge_sort(mid+1,right)
ll=nums[left:mid+1]
lr=nums[mid+1:right+1]
i=0
j=0
k=left
while i<=mid-left and j<=right-mid-1:
if ll[i]<=lr[j]:
nums[k]=ll[i]
i+=1
else:
nums[k]=lr[j]
j+=1
k+=1
while i<=mid-left:
nums[k]=ll[i]
i+=1
k+=1
while j<=right-mid-1:
nums[k]=lr[j]
j+=1
k+=1
merge_sort(0,len(nums)-1)
return nums

leetcode912通过,1404ms,32.36MB

对链表归并排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Solution {
public:
ListNode* sortList(ListNode* head)
{
ListNode* dummyhead=new ListNode(0,head);//虚拟头节点
function<void(ListNode*,ListNode*&)>merge_sort=[&](ListNode* left,ListNode*& right)//左开右闭区间
{
if(left==right||left->next==right)
return;
ListNode* mid=left, * fast=left;//采用快慢指针法找到链表的中点
while(fast->next!=right)
{
fast=fast->next;
mid=mid->next;
if(fast->next==right)
break;
fast=fast->next;
}
merge_sort(left,mid);
merge_sort(mid,right);
ListNode* ld=new ListNode(0,left->next),*rd=new ListNode(0,mid->next),*end=right->next;
ListNode* p=left;
mid->next=nullptr;
right->next=nullptr;
left->next=end;
while(ld->next!=nullptr&&rd->next!=nullptr)
{
ListNode* tmp;
if(ld->next->val<=rd->next->val)
{
tmp=ld->next;
ld->next=tmp->next;
}
else
{
tmp=rd->next;
rd->next=tmp->next;
}
tmp->next=p->next;
p->next=tmp;
p=tmp;
}
while(ld->next!=nullptr)
{
ListNode* tmp;
tmp=ld->next;
ld->next=tmp->next;
tmp->next=p->next;
p->next=tmp;
p=tmp;
}
while(rd->next!=nullptr)
{
ListNode* tmp;
tmp=rd->next;
rd->next=tmp->next;
tmp->next=p->next;
p->next=tmp;
p=tmp;
}
right=p;
delete ld,rd;
};
ListNode* p=dummyhead;
while(p->next!=nullptr)
p=p->next;
merge_sort(dummyhead,p);
ListNode* ret=dummyhead->next;
delete dummyhead;
return ret;
}
};

leetcode148通过,368ms,95.18MB

8基数排序

复杂度分析

可以证明,基于比较的排序,时间复杂度不可能优于O(nlogn)O(n\log n),基数排序不基于比较

通常针对链表实现,假设长度为n的线性表中每个节点aja_j的关键字由d元组(kjd1,kjd2,kjd3,,kj1,kj0)(k_j^{d-1},k_j^{d-2},k_j^{d-3},\cdots,k_j^{1},k_j^{0})组成

其中 , 0kjir1(0j<n,0id1)0\le k_j^{i}\le r-1\quad (0\le j<n,0\le i\le d-1), rr 称为基数

空间复杂度 O(n)O(n)

链表初始化时间复杂度 O(r)O(r) ,一趟分配的时间复杂度 O(n)O(n) ,一趟收集的时间复杂度 O(r)O(r) ,总共 dd 趟,时间复杂度 O(d(n+r))O(d(n+r))

对于leetcode148,105Node.val105-10^5 \le Node.val \le 10^5,n[0,5×104]n\in[0,5\times10^4]

排序时加上10510^5,保证所有数为正数,对于十进制数r=10,d不超过6

所有该题时间复杂度O(n)O(n),空间复杂度O(n)O(n)

基数排序是稳定的, 因为基你太稳

对链表基数排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Solution {
public:
ListNode* sortList(ListNode* head)//基数排序
{
ListNode* dummyhead=new ListNode(0,head);
ListNode* p=dummyhead;
while(p->next!=nullptr)
{
p->next->val+=100000;
p=p->next;
}
vector<queue<ListNode*>>vec(10);
for(int i=1;i<=100000;i*=10)
{
int flag=0;
while(dummyhead->next!=nullptr)
{
if(dummyhead->next->val/(i*10)!=0)
flag=1;
vec[dummyhead->next->val/i%10].push(dummyhead->next);
dummyhead->next=dummyhead->next->next;
}
ListNode* p=dummyhead;
for(int j=0;j<10;++j)
{
while(!vec[j].empty())
{
vec[j].front()->next=p->next;
p->next=vec[j].front();
p=p->next;
vec[j].pop();
}
}
if(flag==0)
break;
}
p=dummyhead;
while(p->next!=nullptr)
{
p->next->val-=100000;
p=p->next;
}
ListNode* ret=dummyhead->next;
delete dummyhead;
return ret;
}
};

leetcode148通过,216ms,63.4MB

9外部排序

image-20231015110236652

优化1:采用多路归并可以减少归并趟数,从而减少磁盘I/O(读写)次数

rr个初始归并段,做k路归并,则归并数可用kk叉树表示

kk叉树的第hh层最多有kh1k^{h-1}个结点,则rkh1r\le k^{h-1},归并趟数h1logkrh-1\ge\lceil \log_kr\rceil

k路平衡归并:

  1. 最多只能有kk个段归并为一个;
  2. 每一趟归并中,若有 mm 个归并段参与归并,则经过这一趟处理得到mk\lceil \frac mk\rceil个新的归并段

k=4k=4的情况:

image-20231015114623590

多路归并带来的负面影响:

  1. kk路归并时,需要开辟kk个输入缓冲区,内存开销增加。
  2. 每挑选一个关键字需要对比关键字(k1)(k-1)次,内部归并所需时间增加

**优化2:**减少初始归并段rr的数量

生成初始归并段的方法:若总共有NN条记录,内存工作区可以容纳LL条记录,则初始归并段数量r=NLr=\frac NL

(3)查找专题

查找过程中的主要操作是关键字的比较,查找过程中的关键字的平均比较次数(平均查找长度ASL(Average search length))作为衡量一个查找算法效率高低的标准,ASL定义为:

ASL=i=1nPiCiASL=\sum_{i=1}^n P_iC_i

  • ASLASL是对存储结构中对象总数n的函数
  • PiP_i是检索第i个元素的概率
  • CiC_i是找到第i个元素所需的关键码值与给定值的比较次数

顺序查找

对于长度为nn的顺序表:

ASL=1ni=1ni=n+12ASL=\frac 1n\sum_{i=1}^ni=\frac{n+1}2

时间复杂度O(n)O(n)

二分查找

[实现代码](## 数组1:二分查找)

前提:查找表中所有记录是有序的(升序或降序)

对二分查找有效性的证明:

因为算法在top>bottom的条件下持续运行,只需证明在该条件下区间大小[bottom,top]是严格变小的

graph TB;
1+top-bottom --> 1+top-mid-1;
1+top-bottom --> 1+mid-bottom;

证明其一:

bottom<top2bottom<bottom+1bottom<bottom+top2bottom<bottom+top2+1bottom<mid+11+topbottom>1+topmid1bottom<top\rightarrow\\ 2bottom<bottom+1\rightarrow\\ bottom<\frac{bottom+top}2\rightarrow\\ bottom<\lfloor\frac{bottom+top}2\rfloor+1\rightarrow\\ bottom<mid+1\rightarrow\\ 1+top-bottom>1+top-mid-1

另一侧证明同理

二分答案1:爱吃香蕉的珂珂

leetcode875

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
up=max(piles)
low=1
high=up
while low<high:
mid=(low+high)//2
t=0
for ban in piles:
t+=ban//mid
if ban % mid!=0:
t+=1
if t>h:
break
if t<=h:
high=mid
else:
low=mid+1
return low

二分答案2:使结果不超过阈值的最小除数

leetcode1283

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def smallestDivisor(self, nums: List[int], threshold: int) -> int:
m_ele=max(nums)
low=1
high=m_ele
while low<high:
mid=(low+high)//2
add=0
for n in nums:
add+=n//mid
if n%mid!=0:
add+=1
if add <= threshold:
high=mid
else:
low=mid+1
return low

二分答案3:完成旅途的最少时间

leetcode2187

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def minimumTime(self, time: List[int], totalTrips: int) -> int:
min_element=min(time)
high=totalTrips*min_element
low=1
while low<high:
mid=(low+high)//2
add=0
for t in time:
add+=mid//t
if add>=totalTrips:
high=mid
else:
low=mid+1
return low

大佬的一行代码,出自灵神题解

1
2
3
class Solution:
def minimumTime(self, time: List[int], totalTrips: int) -> int:
return bisect_left(range(totalTrips * min(time)), totalTrips, key=lambda x: sum(x // t for t in time))

bisect用法

二分答案4:每个小孩最多能分到多少糖果

leetcode2226

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def maximumCandies(self, candies: List[int], k: int) -> int:
low=0
high=max(candies)
while low < high:
mid=(low+high)//2
if (low+high)%2!=0:
mid+=1
if sum(t//mid for t in candies)>=k:
low=mid
else:
high=mid-1
return low

二分答案5:准时到达的列车最小时速

leetcode1870

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def minSpeedOnTime(self, dist: List[int], hour: float) -> int:
if hour<=len(dist)-1:
return -1
n=len(dist)
high=max(max(dist),ceil(dist[n-1]/(hour-n+1)))
low=1
while low < high:
mid=(low+high)//2
if sum(ceil(d/mid) for d in dist) - ceil(dist[n-1]/mid) + dist[n-1]/mid <=hour:
high=mid
else:
low=mid+1
return low

二分答案6:在 D 天内送达包裹的能力

leetcode1011

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
high=sum(weights)
low=max(weights)
while low < high:
mid=(low+high)//2
t=1
cnt=0
for w in weights:
if cnt+w<=mid:
cnt+=w
else:
cnt=w
t+=1
if t>days:
break
if t>days:
low=mid+1
else:
high=mid
return low

二分答案7:分配给商店的最多商品的最小值

leetcode2064

根据贪心原则,一旦确定x,每个商店分配的商品数就应该尽可能靠近x,否则就可能存在一个比x更优的结果

所以在x确定之后,能够分配的商店数量为sum(ceil(q/mid) for q in quantities)

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def minimizedMaximum(self, n: int, quantities: List[int]) -> int:
high=max(quantities)
low=1
while low < high:
mid = (low+high)//2
if sum(ceil(q/mid) for q in quantities)<=n:
high=mid
else:
low=mid+1
return low

二分答案8:袋子里最少数目的球

leetcode1760

二分下界:每个袋子最少一个球,low=1

二分上界:最坏的情况是不进行操作,开销为球最多的袋子中的球数,high=max(nums)

假设已经确定开销为mid,球数为n的袋子至少需要操作ceil(n/mid)-1次,总开销为sum(ceil(n/mid)-1 for n in nums)

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def minimumSize(self, nums: List[int], maxOperations: int) -> int:
low=1
high=max(nums)
while low < high:
mid=(low+high)//2
if sum(ceil(n/mid)-1 for n in nums)<=maxOperations:
high=mid
else:
low=mid+1
return low

二分答案9:制作 m 束花所需的最少天数

leetcode1482

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution:
def minDays(self, bloomDay: List[int], m: int, k: int) -> int:
if len(bloomDay)<m*k:
return -1
low=min(bloomDay)
high=max(bloomDay)
while low < high:
mid=(low+high)//2
cnt=0
bunch=0
for flow in bloomDay:
if flow<=mid:
cnt+=1
if cnt==k:
cnt=0
bunch+=1
else:
cnt=0
if bunch>=m:
break
if bunch >= m:
high=mid
else:
low=mid+1
return low

二分答案10:可以到达的最远建筑

leetcode1642

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution:
def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
low=0
high=len(heights)-1
while low < high:
mid=ceil((low+high)/2)
arr=[]
for i in range(1,mid+1):
diff=heights[i]-heights[i-1]
if diff>0:
arr.append(diff)
flag=True
if len(arr)>ladders:
ar=sorted(arr)
if sum(ar[i] for i in range(len(ar)-ladders))>bricks:
flag=False
if flag:
low=mid
else:
high=mid-1
return low

二分答案11:可移除字符的最大数目

leetcode1898

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def maximumRemovals(self, s: str, p: str, removable: List[int]) -> int:
low=0
high=len(removable)
while low < high:
mid=ceil((low+high)/2)
arr=list(s)
for i in range(mid):
arr[removable[i]]="0"
i=0
j=0
while i<len(arr) and j<len(p):
if arr[i]==p[j]:
j+=1
i+=1
if j==len(p):
low=mid
else:
high=mid-1
return low

二分答案12:水位上升的泳池中游泳

leetcode778

二分+dfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution:
def swimInWater(self, grid: List[List[int]]) -> int:
low=0
high=max(max(grid_) for grid_ in grid)
n=len(grid)
while low < high:
mid=(low+high)//2
arr=[[0]*n for _ in range(n)]
i=0
j=0
def dfs(i,j):
if i==n-1 and j==n-1:
return True
if mid < grid[i][j]:
return False
flag=False
arr[i][j]=1
if i-1>=0 and arr[i-1][j]==0 and mid>=grid[i-1][j]:
flag=dfs(i-1,j)
if flag:
return True
if i+1<n and arr[i+1][j]==0 and mid>=grid[i+1][j]:
flag=dfs(i+1,j)
if flag:
return True
if j-1>=0 and arr[i][j-1]==0 and mid>=grid[i][j-1]:
flag=dfs(i,j-1)
if flag:
return True
if j+1<n and arr[i][j+1]==0 and mid>=grid[i][j+1]:
flag=dfs(i,j+1)
return flag
if dfs(0,0):
high=mid
else:
low=mid+1
return low

(4)链表专题

链表1:移除链表元素

leetcode203

使用虚拟头节点:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val)
{
ListNode* dummyhead=new ListNode(-1,head);
ListNode* pre=dummyhead;
ListNode* cur=head;
while(cur!=nullptr)
{
if(cur->val==val)
{
pre->next=cur->next;
delete cur;
cur=pre->next;
}
else
{
pre=pre->next;
cur=cur->next;
}
}
head=dummyhead->next;
delete dummyhead;
return head;
}
};

不使用虚拟头节点:

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## Definition for singly-linked list.
## class ListNode:
## def __init__(self, val=0, next=None):
## self.val = val
## self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
while head!=None and head.val==val:
head=head.next
p=head
while p!=None and p.next!=None:
if p.next.val==val:
p.next=p.next.next
else:
p=p.next
return head

链表2:设计链表

leetcode707

单向链表(使用虚拟头节点):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// struct ListNode 
// {
// int val;
// ListNode *next;
// ListNode() : val(0), next(nullptr) {}
// ListNode(int x) : val(x), next(nullptr) {}
// ListNode(int x, ListNode *next) : val(x), next(next) {}
// };
class MyLinkedList {
public:
ListNode* root;
MyLinkedList()
{
root=new ListNode;
}
int get(int index)
{
if(index<0)
return -1;
ListNode* p=root;
int i=-1;
while(i<index&&p->next!=nullptr)
{
p=p->next;
++i;
}
if(i<index)
return -1;
else
return p->val;
}

void addAtHead(int val)
{
ListNode* add=new ListNode(val,root->next);
root->next=add;
}

void addAtTail(int val)
{
ListNode* p=root;
while(p->next!=nullptr)
p=p->next;
p->next=new ListNode(val);
}

void addAtIndex(int index, int val)
{
if(index<0)
return;
--index;
ListNode* p=root;
int i=-1;
while(i<index&&p->next!=nullptr)
{
p=p->next;
++i;
}
if(i<index)
return;
else
{
ListNode* add=new ListNode(val,p->next);
p->next=add;
}
}
void deleteAtIndex(int index)
{
if(index<0)
return;
--index;
ListNode* p=root;
int i=-1;
while(i<index&&p->next!=nullptr)
{
p=p->next;
++i;
}
if(i<index)
return;
else
{
if(p->next!=nullptr)
{
ListNode* rubbish=p->next;
p->next=p->next->next;
delete rubbish;
}
}

}
};

链表3:反转链表

leetcode206

方法1:栈

时间复杂度O(n)O(n),空间复杂度O(n)O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
if(head==nullptr)
return head;
stack<ListNode*>sta;
while(head!=nullptr)
{
sta.push(head);
head=head->next;
}
head=sta.top();
sta.pop();
ListNode* p=head;
while(!sta.empty())
{
p->next=sta.top();
p=p->next;
sta.pop();
}
p->next=nullptr;
return head;
}
};

方法2:双指针

时间复杂度O(n)O(n),空间复杂度O(1)O(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
ListNode* pre=nullptr,*cur=head;
while(cur!=nullptr)
{
ListNode* tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
};

方法3:递归

时间复杂度O(n)O(n),空间复杂度O(n)O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
ListNode* pre=nullptr,*cur=head;
function<ListNode*(ListNode*,ListNode*)>recursion=[&](ListNode* cur,ListNode* pre)
{
if(cur==nullptr)
return pre;
ListNode* tmp=cur->next;
cur->next=pre;
return recursion(tmp,cur);
};
return recursion(head,nullptr);
}
};

链表4:两两交换链表中的节点

leetcode24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
ListNode* swapPairs(ListNode* head)
{
if(head==nullptr||head->next==nullptr)
return head;
ListNode* dummyhead=new ListNode(-1,head);
ListNode* pre=dummyhead;
ListNode* first=head;
ListNode* second=head->next;
while(1)
{
first->next=second->next;
second->next=first;
pre->next=second;
pre=first;
first=pre->next;
if(first==nullptr||first->next==nullptr)
break;
second=first->next;
}
first=dummyhead->next;
delete dummyhead;
return first;
}
};

链表5:删除链表的倒数第N个节点

leetcode19

快慢指针法:先将右指针右移n个节点,再将左右指针一起右移,直到右指针的下一个位置为nullptr,此时左指针指向待删除节点的上一个位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode* dummyhead=new ListNode(-1,head);
ListNode* left=dummyhead,*right=dummyhead;
for(int i=0;i<n;++i)
right=right->next;
while(right->next!=nullptr)
{
right=right->next;
left=left->next;
}
ListNode* rubbish=left->next;
left->next=left->next->next;
delete rubbish;
ListNode* ret=dummyhead->next;
delete dummyhead;
return ret;
}
};

链表6:相交链表

leetcode160

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode* p=headA;
int alen=1,blen=1;
while(p->next!=nullptr)
{
p=p->next;
++alen;
}
p=headB;
while(p->next!=nullptr)
{
p=p->next;
++blen;
}
ListNode* p1;
if(alen>blen)
{
p=headA;
for(int i=0;i<alen-blen;++i)
{
p=p->next;
if(p==headB)
return headB;
}
p1=headB;
}
else
{
p=headB;
for(int i=0;i<blen-alen;++i)
{
p=p->next;
if(p==headA)
return headA;
}
p1=headA;
}
while(p!=nullptr&&p1!=nullptr)
{
if(p==p1)
return p;
p=p->next;
p1=p1->next;
}
return nullptr;
}
};

链表7:环形链表II*

leetcode142

1
2
3
4
5
[--------a-------][---------b--------]
_____________________________________ <--快慢指针在这里相遇
|___________________|
[----------c--------]

快慢指针法:

快指针一次移动2个节点,慢指针一次移动1个节点,移动完成后才检查二者是否相遇,两者相对速度1个节点,所以不会存在快指针越过慢指针而没有检测到的情况

对于慢指针 t=a+b

可以证明慢指针在圈里的路程不足一圈,不是t=a+b+k(b+c), 因为慢指针进入圈内时,快指针已经在圈内,慢指针要转完一圈,快指针就会转两圈,所以在慢指针转完一圈之前,快指针就会和慢指针相遇

对于快指针 2t=a+b+n(b+c)

所以a=(n-1)(b+c)+c

让两个指针分别从头节点和相遇点出发,二者一定会在环的入口处相遇

时间复杂度O(n)O(n), 空间复杂度O(1)O(1)

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
ListNode *detectCycle(ListNode *head)
{
ListNode* fast=head,*slow=head;
while(1)
{
if(fast==nullptr||slow==nullptr)
return nullptr;
fast=fast->next;
if(fast==nullptr)
return nullptr;
fast=fast->next;
slow=slow->next;
if(fast==slow)
break;
}
ListNode* p=head;
while(p!=fast)
{
p=p->next;
fast=fast->next;
}
return p;
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast=head
slow=head
while 1:
if fast==None or slow==None:
return None
fast=fast.next
if fast==None:
return None
fast=fast.next
slow=slow.next
if fast==slow:
break
p=head
while p!=fast:
p=p.next
fast=fast.next
return p

(5)哈希表专题

哈希查找分析

装填因子α=表中记录数n散列表长度m\alpha =\frac{表中记录数n}{散列表长度m}

二次探测、伪随机探测、再哈希法的平均查找长度是

S成功1αln(1α)S_{成功}\approx -\frac 1\alpha \ln(1-\alpha)

S失败11αS_{失败}\approx \frac 1{1-\alpha}

在表中任选一个位置,不为空的概率为α\alpha,为空的概率为1α1-\alpha

查找k个位置后结束,对应的概率为αk1(1α)\alpha ^{k-1}(1-\alpha)

查找失败的概率为

k=1kαk1(1α)=11α\sum_{k=1}^{\infin}k\alpha^{k-1}(1-\alpha)=\frac 1{1-\alpha}

查找关键字kk的探测序列和插入关键字kk的探测序列是相同的,假设kk是第k+1k+1个被插入到散列表中的关键字,则此前散列表的装填因子为im\frac im,查找kk的探测次数的期望为11im\frac 1{1-\frac im}。则查找成功的探测次数的期望为

1ni=0n1mmi=mni=0n11mi=1αk=mn+1m1k1αmnm1xdx=1αlnmmn=1αln11α=1αln(1α)\begin{aligned} \frac 1n \sum_{i=0}^{n-1}\frac{m}{m-i} &= \frac mn \sum_{i=0}^{n-1}\frac 1{m-i}\\ &= \frac 1\alpha \sum_{k=m-n+1}^m \frac 1k \\ &\le \frac 1\alpha \int_{m-n}^m \frac 1x dx\\ &=\frac 1\alpha \ln\frac m{m-n}\\ &= \frac 1\alpha \ln \frac 1{1-\alpha}\\ &= -\frac 1\alpha\ln(1-\alpha) \end{aligned}

哈希表1: 有效的字母异位词

leetcode242

简单题,大佬们可以直接跳过

用数组代替哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
bool isAnagram(string s, string t)
{
vector<int>vec(26);
for(char& ch:s)
++vec[ch-'a'];
for(char& ch:t)
--vec[ch-'a'];
for(int& n:vec)
if(n!=0)
return false;
return true;
}
};

哈希表2:两个数组的交集

leetcode349

简单题,大佬们可以直接跳过

python:

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
hash=dict()
for n in nums1:
hash[n]=1
ret=[]
for n in nums2:
if n in hash and hash[n]==1:
ret.append(n)
hash[n]=0
return ret

哈希表3:快乐数

leetcode202

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using ll=long long;
class Solution {
public:
bool isHappy(int n)
{
unordered_map<ll,int>hash;
while(n!=1)
{
if(hash[n]==1)
return false;
else
hash[n]=1;
ll num=0;
while(n!=0)
{
ll tmp=n%10;
num+=tmp*tmp;
n/=10;
}
n=num;
}
return true;
}
};

哈希表4:两数之和

leetcode1

经典好题

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
unordered_map<int,int> hash;
int i=1;
for(auto num:nums)
{
if(hash[target-num]!=0)
return {i-1,hash[target-num]-1};
else
hash[num]=i;
++i;
}
return {};
}
};

python:

1
2
3
4
5
6
7
8
9
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
le=len(nums)
dic=dict()
for i in range(le):
if nums[i] in dic:
return [dic[nums[i]],i]
else:
dic[target-nums[i]]=i

哈希表5:四数相加II

leetcode454

四个for循环O(n4)O(n^4)会超时

两组两个for循环,配合哈希表可以通过,时间复杂度O(n2)O(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4)
{
unordered_map<int,int>hash;
int n=nums1.size();
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
++hash[-nums1[i]-nums2[j]];
int ret=0;
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
ret+=hash[nums3[i]+nums4[j]];
return ret;
}
};

哈希表6:赎金信

leetcode383

简单题,大佬们可以直接跳过

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hash={}
for ch in magazine:
if ch not in hash:
hash[ch]=1
else:
hash[ch]+=1
for ch in ransomNote:
if ch not in hash or hash[ch]==0:
return False
else:
hash[ch]-=1
return True

哈希表6.9:两数之和 II - 输入有序数组

leetcode167

这一题是给下一题打基础用的

推荐看灵神的视频

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target)
{
int left=0,right=numbers.size()-1;
int sum;
while(1)
{
sum=numbers[left]+numbers[right];
if(sum==target)
{
return vector<int>{left+1,right+1};
}
else if(sum>target)
--right;
else
++left;
}
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left=0
right=len(numbers)-1
while 1:
tmp=numbers[left]+numbers[right]
if tmp==target:
return [left+1,right+1]
elif tmp>target:
right-=1
else:
left+=1

课后作业

哈希表7:三数之和

leetcode15

这题不好做 :tired_face:

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
sort(nums.begin(),nums.end());
vector<vector<int>>ret;
for(int i=0;i<nums.size();++i)
{
while(i>0&&i<nums.size()&&nums[i]==nums[i-1])
++i;
if(i==nums.size())
break;
int left=i+1,right=nums.size()-1;
while(left<right)
{
int sum=-(nums[left]+nums[right]);
if(sum==nums[i]&&left<right)
{
vector<int>tmp={nums[i],nums[left],nums[right]};
ret.emplace_back(tmp);
while(right>0&&left<right&&nums[right-1]==nums[right])
--right;
if(right>0)
--right;
}
else if(sum<nums[i])
--right;
else
++left;
}
}
return ret;
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ret=[]
le=len(nums)
idx=0
while idx<le-2:
while idx>0 and idx<le and nums[idx]==nums[idx-1]:
idx+=1
if(idx>=le-2):
break
target=-nums[idx]
left=idx+1
right=le-1
while left<right:
add=nums[left]+nums[right]
if add==target:
ret.append([nums[idx],nums[left],nums[right]])
while right>left and nums[right]==nums[right-1]:
right-=1
right-=1
elif add>target:
right-=1
else:
left+=1
idx+=1
return ret

(6)字符串专题

字符串1:反转字符串

leetcode344

双指针交换就行

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
void reverseString(vector<char>& s)
{
int left=0;
int right=s.size()-1;
while(left<right)
{
s[left]=s[left]^s[right];
s[right]=s[left]^s[right];
s[left]=s[left]^s[right];
++left;
--right;
}
}
};

Javascript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @param {character[]} s
* @return {void} Do not return anything, modify s in-place instead.
*/
var reverseString = function(s)
{
let n=s.length;
let left=0,right=n-1;
while(left<right)
{
let tmp=s[left];
s[left]=s[right];
s[right]=tmp;
++left;
--right;
}
};

字符串2:反转字符串II

leetcode541

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
string reverseStr(string s, int k)
{
int n=0;
int flag=1;
for(;flag;++n)
{
auto beg=s.begin()+2*n*k;
if(2*n*k+k>=s.size())
flag=0;
auto end=s.begin()+min((int)s.size(),2*n*k+k);
reverse(beg,end);
}
return s;
}
};

字符串3:替换空格

leetcode剑指offer05

算法很简单,可以拿这个检验一下自己对库函数的熟悉程度

Javascript:不用库函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @param {string} s
* @return {string}
*/
var replaceSpace = function(s)
{
let tmp="";
for(var i=0;i<s.length;++i)
{
if(s[i]!=' ')
tmp+=s[i];
else
tmp+="%20";
}
return tmp;
};

直接调用库函数:

1
2
3
4
5
6
7
8
/**
* @param {string} s
* @return {string}
*/
var replaceSpace = function(s)
{
return s.replaceAll(' ',"%20");
};

python也有这个库函数:

1
2
3
class Solution:
def replaceSpace(self, s: str) -> str:
return s.replace(" ","%20")

字符串4:反转字符串中的单词

leetcode151

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function(s)
{
let arr=[];
let tmp="",ret="";
for(var i=0;i<s.length;++i)
{
if(s[i]!==" ")
{
tmp+=s[i];
}
else if(tmp!="")
{
arr.push(tmp);
tmp="";
}
}
if(tmp!="")
arr.push(tmp);
for(var i=arr.length-1;i>=0;--i)
{
ret+=arr[i];
if(i!=0)
ret+=" ";
}
return ret;
};

大佬的调用内置函数解法:

1
2
3
var reverseWords = function(s) {
return s.trim().split(/\s+/).reverse().join(' ');
};
  • trim()函数用于去除字符串两端的空格(空白字符),并返回处理后的字符串。

  • 以下出自CSDN split( /\s+/)什么意思
    split( /\s+/)的意思是将字符串以满足/\s+/这个正则表达式的字符来分割为一个数组。
    分割满足条件包括:
    制表符,换行符,回车符,垂直制表符,换页符在内的一个至无穷个类空格字符。
    其中:
    \s表示:匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]
    +表示:匹配前面的子表达式一次或多次。
    split():是js的一个用于把一个字符串分割成字符串数组的方法。

  • join()方法就是将array数据中每个元素都转为字符串,用自定义的连接符分割

字符串6:找出字符串中第一个匹配项的下标

leetcode28

KMP算法的经典例题

方法1:KMP算法

next数组 样例1:

a a b a a f
0 1 0 1 2 0

next数组 样例2:

a a b a a a b
0 1 0 1 2 2 3

遍历的过程中,遍历next数组的指针指向下标为j处时,遇到不匹配,就回退到下标next[j-1]

next[i]=k的含义是在needle中前i个字母的最大相同前后缀长度,即满足needle[0:k]==needle[i-k+1:i+1] (i-k+1>=k)的最大k值 (注: needle[a:b]的含义是needle字符串中下标>=a<b的子串)

next数组的构造过程

i指向后缀表的末尾,j指向前缀表的末尾,每轮循环需要判断前后缀表的末尾元素能否加入前后缀表中,也就是比较needle[i]needle[j]是否相等

前缀表和后缀表等长,末尾元素加入前后缀表后,长度都是j+1

初始化i=1j=0

详细注释版 C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Solution {
public:
int strStr(string haystack, string needle) //KMP
{
int n=haystack.size(),m=needle.size();
//根据needle构造next数组
vector<int>next(m);
int i=1,j=0;//初始化
while(i<m)
{
if(needle[i]==needle[j])//在上一轮匹配的基础上,当前i,j指向的两个字母相同,可以延长前后缀的长度
{
next[i]=j+1;//j+1表示当前前后缀的长度比上一个回合增加1
++i;
++j;//i,j同步后移
}
else if(j==0)//needle[0]!=needle[i],next[i]填0,并将i后移
{
++i;
}
else//尝试缩短前后缀的长度,说不定能匹配
{
j=next[j-1];//needle[0:i]满足 最长前后缀的长度为j,但是当前的i,j指向的字母不相等,无法在原来的基础上延长。next[j-1]<=j,且满足next[j-1]也是needle[0:i]前后缀的长度的一种合法取值(虽然不是最长的)。令j=next[j-1],尝试缩短前后缀的长度,说不定能匹配
}
}
//打印next数组检验
// for(int& num:next)
// cout<<num<<' ';
//匹配字符串
i=0,j=0;
while(i<n&&j<m)
{
if(haystack[i]==needle[j])//匹配
{
++i;
++j;
}
else if(j==0)
++i;
else
j=next[j-1];//遍历next数组的指针指向下标为j处时,遇到不匹配,就回退到下标next[j-1]处。next[j-1]<=j,且满足next[j-1]也是needle[0:i]前后缀的长度的一种合法取值(虽然不是最长的)。
}
if(j==m)
return i-m;
else
return -1;
}
};

(细心的你会发现,构造next数组和匹配字符串过程代码是相似的)

简洁优雅版 C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public:
int strStr(string haystack, string needle) //KMP
{
int n=haystack.size(),m=needle.size();
//构造next数组
vector<int>next(m);
int i=1,j=0;
while(i<m)
{
if(needle[i]==needle[j])
next[i++]=++j;
else if(j==0)
next[i++]=0;
else
j=next[j-1];
}
//匹配字符串
i=0,j=0;
while(i<n&&j<m)
{
if(haystack[i]==needle[j])
{
++i;
++j;
}
else if(j==0)
++i;
else
j=next[j-1];
}
if(j==m)
return i-m;
else
return -1;
}
};

时间复杂度

参考KMP算法-时间复杂度分析

n是模式串haystack的长度,m是待匹配的字符串needle的长度

计算next数组时的比较次数介于[m,2m]。
遍历比较的比较次数介于[n,2n],最坏情形形如T=“aaaabaaaab”,P=“aaaaa”。
所以算法时间复杂度时O(m+n)O(m+n).

空间复杂度O(m)O(m)

方法2:Rabin-Karp算法

这个算法实际上就是哈希表,ChatGPT的解释:

Rabin-Karp算法是一种字符串匹配算法,用于在一个文本串中查找一个模式串的出现位置。

算法的基本思想是通过哈希函数将模式串和文本串的子串进行比较。首先计算模式串的哈希值,然后依次计算文本串中所有长度为模式串长度的子串的哈希值,并与模式串的哈希值进行比较。如果哈希值相同,再逐个比较子串和模式串的每个字符是否相同,直到找到完全匹配的子串或者遍历完所有子串。

算法的优点是可以在平均情况下达到线性时间复杂度O(n+m),其中n是文本串的长度,m是模式串的长度。然而,在最坏情况下,算法的时间复杂度为O((n-m+1)m),即当哈希值的冲突比较多时,效率会下降。

总结来说,Rabin-Karp算法通过哈希函数加速字符串匹配,适用于处理长文本串和短模式串的情况。它在实际应用中常被用于字符串搜索、文本编辑器等领域。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int strStr(string haystack, string needle)
{
unordered_map<string,int>hash;
hash[needle]=1;
int m=needle.size(),n=haystack.size();
for(int i=0;i<=n-m;++i)
if(hash[haystack.substr(i,m)]==1)
return i;
return -1;
}
};

字符串7:重复的子字符串

leetcode459

方法1:双指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
i=0 ## 慢指针
j=1 ## 快指针
n=len(s)
tmp=0 ## 记录flag改变时j的值,以便恢复
flag=0 ## flag=0表示未找到重复的部分 =1表示找到了
while j<n:
if flag==0:
if s[i]==s[j] and n%j==0:
flag=1
tmp=j
i+=1
j+=1
else:
j+=1
else:
if s[i]!=s[j]:
i=0
j=tmp+1
flag=0
else:
i+=1
j+=1
return flag==1

方法2:转化为字符串匹配问题

1
2
3
4
5
6
7
class Solution {
public:
bool repeatedSubstringPattern(string s)
{
return (s+s).find(s,1)!=s.size();
}
};

方法3:转化为字符串匹配问题,并利用KMP算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solution {
public:
int strStr(string haystack, string needle) //KMP
{
int n=haystack.size(),m=needle.size();
//构造next数组
vector<int>next(m);
int i=1,j=0;
while(i<m)
{
if(needle[i]==needle[j])
next[i++]=++j;
else if(j==0)
next[i++]=0;
else
j=next[j-1];
}
//打印next数组检验
// for(int& num:next)
// cout<<num<<' ';
//匹配字符串
i=0,j=0;
while(i<n&&j<m)
{
if(haystack[i]==needle[j])
{
++i;
++j;
}
else if(j==0)
++i;
else
j=next[j-1];
}
if(j==m)
return i-m;
else
return -1;
}
bool repeatedSubstringPattern(string s)
{
string hay=s+s;
hay.erase(0,1);
return strStr(hay,s)!=s.size()-1;
}
};

字符串8:有效数字

leetcode65

方法1:暴力if-else

笨人的这段代码比较乱,建议去看方法2,更易理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class Solution {
public:
bool isNumber(string s)
{
int idx=0;
if(s[idx]=='+'||s[idx]=='-')
++idx;
int flag1=0;//是否存在整数位
int flag2=0;//判断小数点是否出现,对应小数点前没有数字的情况
int flag3=0;//判断小数点是否出现,对应小数点前有数字的情况
int flag4=0;//小数点后有没有整数位
if(idx==s.size())
return false;
if(s[idx]=='.')
{
++idx;
flag2=1;
}
if(idx==s.size())
return false;
while(idx<s.size())
{
if(s[idx]>='0'&&s[idx]<='9')
{
++idx;
flag1=1;
}
else
break;
}
if(idx==s.size())
{
return flag1;
}
if(flag2==1&&flag1==0)
return false;
if(flag2==1&&idx<s.size()&&s[idx]=='.')
return false;
if(flag2==0&&idx<s.size()&&s[idx]=='.')
{
++idx;
flag3=1;
}
while(idx<s.size())
{
if(s[idx]>='0'&&s[idx]<='9')
{
++idx;
flag4=1;
}
else
break;
}
if(flag1==0&&flag4==0)
return false;

if(idx<s.size()&&(s[idx]=='e'||s[idx]=='E'))
{
++idx;
if(idx==s.size())
return false;
if(s[idx]=='+'||s[idx]=='-')
++idx;
if(idx==s.size())
return false;
int flag5=0;
while(idx<s.size())
{
if(s[idx]>='0'&&s[idx]<='9')
{
++idx;
flag5=1;
}
else
break;
}
if(flag5==1&&idx==s.size())
return true;
}
if(idx==s.size())
return true;
return false;
}
};

0ms 5.8MB

方法2:有限状态自动机

参考文献

  1. 初始状态
  2. 符号位
  3. 整数部分
  4. 左侧有整数的小数点
  5. 左侧无整数的小数点(根据前面的第二条额外规则,需要对左侧有无整数的两种小数点做区分)
  6. 小数部分
  7. 字符 e
  8. 指数部分的符号位
  9. 指数部分的整数部分

(图片出自力扣官方题解)

image-20230812093747065

最后只有cur==2||cur==3||cur==5||cur==8时返回true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class Solution {
public:
bool isNumber(string s)
{
int cur=0;//当前状态
int idx=0;//当前下标
for(;idx<s.size();++idx)
{
switch(cur)
{
case 0:
if(s[idx]=='+'||s[idx]=='-')
cur=1;
else if(s[idx]=='.')
cur=4;
else if(s[idx]>='0'&&s[idx]<='9')
cur=2;
else
return false;
break;
case 1:
if(s[idx]>='0'&&s[idx]<='9')
cur=2;
else if(s[idx]=='.')
cur=4;
else
return false;
break;
case 2:
if(s[idx]>='0'&&s[idx]<='9')
cur=2;
else if(s[idx]=='.')
cur=3;
else if(s[idx]=='e'||s[idx]=='E')
cur=6;
else
return false;
break;
case 3:
if(s[idx]=='e'||s[idx]=='E')
cur=6;
else if(s[idx]>='0'&&s[idx]<='9')
cur=5;
else
return false;
break;
case 4:
if(s[idx]>='0'&&s[idx]<='9')
cur=5;
else
return false;
break;
case 5:
if(s[idx]>='0'&&s[idx]<='9')
cur=5;
else if(s[idx]=='e')
cur=6;
else
return false;
break;
case 6:
if(s[idx]=='+'||s[idx]=='-')
cur=7;
else if(s[idx]>='0'&&s[idx]<='9')
cur=8;
else
return false;
break;
case 7:
if(s[idx]>='0'&&s[idx]<='9')
cur=8;
else
return false;
break;
case 8:
if(s[idx]>='0'&&s[idx]<='9')
cur=8;
else
return false;
break;
}
}
//此时idx==s.size()
return cur==2||cur==3||cur==5||cur==8;
}
};

0ms 5.9MB

方法3:正则表达式

C++的正则表达式库

正则表达式大全

代码粘贴自评论区大佬

正在表达式的构造耗时很长,所以建议采用第二段代码,避免重复构造

1
2
3
4
5
6
class Solution {
public:
bool isNumber(string str) {
return regex_match(str, regex("[+-]?(\\d+\\.?\\d*|\\.\\d+)([Ee][+-]?\\d+)?"));
}
};

1884ms,259.3MB

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
static const regex pattern;

bool isNumber(string str) {
return regex_match(str, pattern);
}
};

const regex Solution::pattern("[+-]?(\\d+\\.?\\d*|\\.\\d+)([Ee][+-]?\\d+)?");

12ms 9MB

字符串9:验证IP地址

正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
static const regex pattern4;
static const regex pattern6;
string validIPAddress(string queryIP)
{
if(regex_match(queryIP,pattern4))
return "IPv4";
else if(regex_match(queryIP,pattern6))
return "IPv6";
else
return "Neither";
}
};
const regex Solution::pattern4("((25[0-5]|2[0-4][0-9]|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4][0-9]|1\\d\\d|[1-9]?\\d)");
const regex Solution::pattern6("([0-9a-fA-F]{1,4}\\:){7}([0-9a-fA-F]{1,4})");

字符串10:实现Trie(前缀树)

leetcode208

字典树的经典例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class TrieNode{
public:
char ch;
vector<TrieNode*>next;
TrieNode(char ch_=0):ch(ch_),next(27,nullptr){}
};
class Trie {
public:
TrieNode* root;
Trie() {
root=new TrieNode;
}

void insert(string word) {
TrieNode* cur=root;
for(char& w:word)
{
int idx=w-'a';
if(cur->next[idx]!=nullptr)
cur=cur->next[idx];
else
{
cur->next[idx]=new TrieNode(w);
cur=cur->next[idx];
}
}
cur->next[26]=new TrieNode;//标识单词的结尾
}

bool search(string word) {
TrieNode* cur=root;
for(char& w:word)
{
int idx=w-'a';
if(cur->next[idx]==nullptr)
return false;
else
cur=cur->next[idx];
}
return cur->next[26]!=nullptr;
}

bool startsWith(string prefix) {
TrieNode* cur=root;
for(char& w:prefix)
{
int idx=w-'a';
if(cur->next[idx]==nullptr)
return false;
else
cur=cur->next[idx];
}
return true;
}
};

/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/

字符串11:连接词

leetcode472

前缀树+记忆化搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class TrieNode{//字典树节点
public:
vector<TrieNode*>next;
TrieNode():next(27,nullptr){}
};
class Solution {
public:
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
TrieNode* root=new TrieNode;
for(string& str:words)//将所有的单词插入前缀树(字典树)
{
if(str.size()==0)
continue;
TrieNode* cur=root;
for(char& ch:str)
{
int idx=ch-'a';
if(cur->next[idx]==nullptr)
{
cur->next[idx]=new TrieNode;
cur=cur->next[idx];
}
else
cur=cur->next[idx];
}
cur->next[26]=new TrieNode;//标识单词的结尾
}
vector<string>ret;
for(string& str:words)//查找符合题意的字符串
{
if(str.size()==0)
continue;
queue<pair<int,int>>que;//利用队列
que.push(make_pair(-1,-2));
vector<int>isvisited(str.size());
while(!que.empty())
{

int pos=que.front().first+1;
int pre=que.front().second;
que.pop();
if(pos!=str.size())//记忆化搜索,否则会超时
{
if(isvisited[pos]==0)
isvisited[pos]=1;
else
continue;
}
TrieNode*cur=root;
int i;
for(i=pos;i<str.size();++i)
{
int idx=str[i]-'a';
if(cur->next[idx]!=nullptr)
cur=cur->next[idx];
else
break;
if(cur->next[26]!=nullptr)
que.push(make_pair(i,pos));

}
if(pos==str.size()&&pre>0)
{
ret.emplace_back(str);
break;
}
}
}
return ret;
}
};

前缀树+DFS+记忆化搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class TrieNode{
public:
bool isend;//是不是单词的结尾
vector<TrieNode*>next;
TrieNode(bool isend_=false):isend(isend_),next(26,nullptr){}
};
class Solution {
public:
TrieNode* root;
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
root=new TrieNode;
sort(words.begin(),words.end(),[&](string& a,string& b){
return a.size()<b.size();
});
vector<string>ret;
for(string& str:words)
{
// if(str.size()==0)
// continue;
vector<bool>isvisited(str.size(),false);
bool flag=false;
function<void(int)>dfs=[&](int i)
{
if(flag)
return;
isvisited[i]=true;
TrieNode* p=root;
for(int j=i;j<str.size();++j)
{

int idx=str[j]-'a';
if(p->next[idx]!=nullptr)
{
p=p->next[idx];
}
else
{
return;
}
if(j==str.size()-1)
{
if(i!=0&&p->isend)
{
ret.emplace_back(str);
flag=true;
}
return;
}
if(p->isend)
{
if(!isvisited[j+1])
dfs(j+1);
}
if(flag)
return;
}
};
dfs(0);
TrieNode* p=root;
for(char& ch:str)
{
int idx=ch-'a';
if(p->next[idx]!=nullptr)
p=p->next[idx];
else
{
p->next[idx]=new TrieNode;
p=p->next[idx];
}
}
p->isend=true;
}
return ret;
}
};

字符串12:字符流

leetcode1032

AC自动机的经典例题

OI-wiki:AC自动机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class TrieNode{
public:
TrieNode* fail;
vector<TrieNode*>next;
bool isend;
TrieNode():fail(nullptr),next(26,nullptr),isend(false){}
};
class StreamChecker {
public:
TrieNode* root;
TrieNode* pos;
StreamChecker(vector<string>& words)
{
root=new TrieNode;
root->fail=root;
pos=root;
for(string& word:words)//第一次遍历:构造字典树
{
TrieNode* p=root;
for(char& ch:word)
{
int idx=ch-'a';
if(p->next[idx]==nullptr)
p->next[idx]=new TrieNode;
p=p->next[idx];
}
p->isend=true;
}
//设置fail指针 注意:这里必须广度优先遍历,不允许深度优先遍历
queue<TrieNode*>q;
q.push(root);
while(!q.empty())
{
TrieNode* front=q.front();
q.pop();
for(int i=0;i<26;++i)
{
if(front->next[i]==nullptr)
continue;
else
q.push(front->next[i]);
TrieNode* f=front->fail;
while(f!=root&&f->next[i]==nullptr)
f=f->fail;
if(front!=root&&f->next[i]!=nullptr)
front->next[i]->fail=f->next[i];
else
front->next[i]->fail=root;
}
}
}
bool query(char letter)
{
int idx=letter-'a';
while(pos!=root&&pos->next[idx]==nullptr)
{
pos=pos->fail;
}
TrieNode* tmp=pos;
if(pos->next[idx]!=nullptr)
pos=pos->next[idx];
while(tmp!=root)
{
if(tmp->next[idx]!=nullptr&&tmp->next[idx]->isend)
return true;
else
tmp=tmp->fail;
}
if(tmp->next[idx]!=nullptr&&tmp->next[idx]->isend)
return true;
return false;
}
};

/**
* Your StreamChecker object will be instantiated and called as such:
* StreamChecker* obj = new StreamChecker(words);
* bool param_1 = obj->query(letter);
*/

(7)栈和队列专题

栈是一种后进先出的数据结构(Last in, First out. LIFO)

应用:括号匹配问题等

队列是一种先进先出的数据结构(First in,First out. FIFO)

应用:cpu资源的竞争问题、解决主机和外部设备速度不匹配的问题、电子商务系统中的缓冲队列

优先队列:

用堆实现,具备最高进先出(largest-in,first-out)的特点

操作有查找、插入一个新元素和删除三种

栈和队列4:有效的括号

leetcode20

相当基础的栈的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public:
stack<char>st;
bool isValid(string s)
{
char ch;
for(int i=0;s[i]!=0;i++)
{
if(s[i]=='('||s[i]=='['||s[i]=='{')
st.push(s[i]);
else
{
if(st.empty())
return false;
else
{
ch=st.top();
st.pop();
if(abs(ch-s[i])<3&&ch!=s[i])
continue;
else
return false;
}
}
}
if(st.empty())
return true;
else
return false;
}
};

栈和队列5:删除字符串中的所有相邻重复项

leetcode1047

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
string removeDuplicates(string s)
{
stack<char>sta;
int len=0;
for(char& ch:s)
{
if(sta.empty()||sta.top()!=ch)
{
sta.push(ch);
++len;
}
else
{
sta.pop();
--len;
}
}
string ret(len,0);
for(int i=len-1;i>=0;--i)
{
ret[i]=sta.top();
sta.pop();
}
return ret;
}
};

栈和队列6:逆波兰表达式求值

leetcode150

以下为王道考研数据结构的笔记:栈在表达式求值中的应用

序号表示各个运算符的执行顺序

1
2
3
4
((15 ÷ (7-(1 + 1)))×3) - (2+(1 + 1))   中缀表达式
③ ② ① ④ ⑦ ⑥ ⑤
① ② ③ ④ ⑤ ⑥ ⑦
15 7 1 1 + - ÷ 3 × 2 1 1 + + - 后缀表达式

image-20230725205911577

image-20230725210612767

image-20230725213918526

image-20230725222156240

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
int evalRPN(vector<string>& tokens)
{
stack<int>sta;
for(string& s:tokens)
{
if(s=="+"||s=="-"||s=="*"||s=="/")
{
int a=sta.top();
sta.pop();
int b=sta.top();
sta.pop();
switch(s[0])
{
case '*':
sta.push(b*a);
break;
case '/':
sta.push(b/a);
break;
case '+':
sta.push(b+a);
break;
case '-':
sta.push(b-a);
break;
}
}
else
{
int num=stoi(s);
sta.push(num);
}
}
return sta.top();
}
};

栈和队列7:基本计算器

leetcode224

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class Solution {
public:
int calculate(string s)
{
stack<char>oper;
stack<int>nums;
int tmp=0;
for(int i=0;i<s.size();++i)
{
char ch=s[i];
if(ch>='0'&&ch<='9')
{
tmp*=10;
tmp+=ch-'0';
if(i+1==s.size()||s[i+1]<'0'||s[i+1]>'9')
{
nums.push(tmp);
tmp=0;
}
}
else
{
if(ch=='(')
oper.push('(');
else if(ch=='+'||ch=='-')
{
if(ch=='-')
{
int j=i-1;
while(j>=0&&s[j]==' ')
--j;
if(j==-1||s[j]=='(')
nums.push(0);
}
while(!oper.empty()&&(oper.top()=='+'||oper.top()=='-'))
{
int right=nums.top();
nums.pop();
int left=nums.top();
nums.pop();
if(oper.top()=='+')
nums.push(left+right);
else
nums.push(left-right);
oper.pop();
}
oper.push(ch);
}
else if(ch==')')
{
while(!oper.empty())
{
if(oper.top()=='(')
{
oper.pop();
break;
}
int right=nums.top();
nums.pop();
int left=nums.top();
nums.pop();
if(oper.top()=='+')
nums.push(left+right);
else
nums.push(left-right);
oper.pop();
}
}
}
}
while(!oper.empty())
{
int right=nums.top();
nums.pop();
int left=nums.top();
nums.pop();
if(oper.top()=='+')
nums.push(left+right);
else
nums.push(left-right);
oper.pop();
}
return nums.top();
}
};

栈和队列8:基本计算器II

leetcode227

代码在上一题的基础上改了一下,可以支持对正负数的±*/和括号运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
class Solution {
public:
int calculate(string s)
{
stack<char>oper;
stack<int>nums;
int tmp=0;
for(int i=0;i<s.size();++i)
{
char ch=s[i];
if(ch>='0'&&ch<='9')
{
tmp*=10;
tmp+=ch-'0';
if(i+1==s.size()||s[i+1]<'0'||s[i+1]>'9')
{
nums.push(tmp);
tmp=0;
}
}
else
{
if(ch=='(')
oper.push('(');
else if(ch=='+'||ch=='-')
{
if(ch=='-')
{
int j=i-1;
while(j>=0&&s[j]==' ')
--j;
if(j==-1||s[j]=='(')
nums.push(0);
}
while(!oper.empty()&&(oper.top()=='+'||oper.top()=='-'||oper.top()=='*'||oper.top()=='/'))
{
int right=nums.top();
nums.pop();
int left=nums.top();
nums.pop();
if(oper.top()=='+')
nums.push(left+right);
else if(oper.top()=='-')
nums.push(left-right);
else if(oper.top()=='*')
nums.push(left*right);
else
nums.push(left/right);
oper.pop();
}
oper.push(ch);
}
else if(ch=='*'||ch=='/')
{
while(!oper.empty()&&(oper.top()=='*'||oper.top()=='/'))
{
int right=nums.top();
nums.pop();
int left=nums.top();
nums.pop();
if(oper.top()=='*')
nums.push(left*right);
else
nums.push(left/right);
oper.pop();
}
oper.push(ch);
}
else if(ch==')')
{
while(!oper.empty())
{
if(oper.top()=='(')
{
oper.pop();
break;
}
int right=nums.top();
nums.pop();
int left=nums.top();
nums.pop();
if(oper.top()=='+')
nums.push(left+right);
else
nums.push(left-right);
oper.pop();
}
}
}
}
while(!oper.empty())
{
int right=nums.top();
nums.pop();
int left=nums.top();
nums.pop();
if(oper.top()=='+')
nums.push(left+right);
else if(oper.top()=='-')
nums.push(left-right);
else if(oper.top()=='*')
nums.push(left*right);
else
nums.push(left/right);
oper.pop();
}
return nums.top();
}
};

==优先队列==

优先队列1:股票价格波动

leetcode2034

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class StockPrice {
public:
unordered_map<int,int>hash;
priority_queue<int>big;
priority_queue<int,vector<int>,greater<int>>low;
unordered_map<int,int>delb;
unordered_map<int,int>dell;
int cur;
StockPrice() {
cur=-1;
}

void update(int timestamp, int price) {
cur=max(cur,timestamp);
if(hash.find(timestamp)==hash.end())
{
hash[timestamp]=price;
big.push(price);
low.push(price);
}
else
{
int old=hash[timestamp];
++delb[old];
++dell[old];
hash[timestamp]=price;
big.push(price);
low.push(price);
}
}

int current() {
return hash[cur];
}

int maximum() {
int b=big.top();
while(delb[b]!=0)
{
--delb[b];
big.pop();
b=big.top();
}
return b;
}

int minimum() {
int m=low.top();
while(dell[m]!=0)
{
--dell[m];
low.pop();
m=low.top();
}
return m;
}
};

(8)二叉树专题

树的理论基础

树的性质

度为m的树 m叉树
任意结点的度m\le m 任意结点的度m\le m
至少有一个结点的度 = m 允许所有节点的度都 < m
  1. 树中的结点数等于所有结点的度数之和加1

  2. 度为mm的树第ii层至多有mi1m^{i-1}个结点(i1i\ge 1

  3. 高度为hhmm叉树至多有mh1m1\frac {m^h-1}{m-1}个结点,证明:

m0+m1++mh1=mh1m1m^0+m^1+\dots+m^{h-1}=\frac{m^h-1}{m-1}

  1. 高度为hhmm叉树至少有hh个结点

    高度为hh、度为mm的树至少有h+m1h+m-1个结点

  2. 具有nn个结点的mm叉树最小高度为logm(n(m1)+1)\lceil \log_m(n(m-1)+1)\rceil,证明:

高度最小的情况:所有结点都有mm个孩子

(前h1层最多有几个结点)mh11m1<nmh1m1(前h层最多有几个结点)(前h-1层最多有几个结点)\frac {m^{h-1}-1}{m-1} < n \le \frac{m^h-1}{m-1}(前h层最多有几个结点)

mh1<n(m1)+1mhm^{h-1}<n(m-1)+1 \le m^h

h1<logm(n(m1)+1)hh-1<\log_m(n(m-1)+1)\le h

hmin=logm(n(m1)+1)h_{min}=\lceil \log_m(n(m-1)+1)\rceil

二叉树的性质

设非空二叉树中度为0、1、2的结点个数分别是n0n_0, n1n_1, n2n_2,树中结点总数为nn

n0=n2+1(1)n_0=n_2+1\tag{1}

n=n0+n1+n2(2)n=n_0+n_1+n_2\tag{2}

n=n1+2n2+1(树的节点数等于总度数+1)(3)n=n_1+2n_2+1 (树的节点数等于总度数+1)\tag{3}

(3)-(2)得到(1)式

  1. 二叉树第 i 层至多有2i12^{i-1}个结点(i1i\ge 1)
  2. 高度为 h 的二叉树至多有2h12^h-1个结点
  3. 具有 n 个结点的完全二叉树的高度 h 为log2(n+1)\lceil \log_2(n+1)\rceillog2n+1\lfloor\log_2n\rfloor+1 , 证明:

2h11<n2h12^{h-1}-1 < n\le2^h-1

h1<log2(n+1)hh-1<\log_2(n+1)\le h

h=log2(n+1)h=\lceil \log_2(n+1)\rceil

2h1n<2h2^{h-1}\le n <2^h

h1log2n<hh-1\le \log_2n <h

h=log2n+1h=\lfloor\log_2n\rfloor+1

  1. 具有 n 个结点的二叉树,有 n+1 个空链域

    证明:n 个结点的二叉树总共有 2n 个链域,总度数为 n-1,有 n-1 个指针是指向结点的

    所以有 2n-(n-1) = n+1 个指针是空指针

满二叉树

一棵高度为hh,且含有2h12^h-1个结点的二叉树

完全二叉树

当且仅当其每个结点都与高度为 h 的满二叉树中编号 1~n 的结点一一对应时,称为完全二叉树

  • 只有最后两层可能有叶子结点

  • 最多只有一个度为1的结点

  • 如果某个结点度为1,则它的孩子结点一定是左孩子

  • 对于完全二叉树,可以由结点数 n 推出度为0、1和2的结点个数n0n_0n1n_1n2n_2

    证明:

    完全二叉树最多只有一个度为1的结点,n1=01n_1=0或1

    又因为对于任意二叉树都有n0=n2+1n_0=n_2+1n0+n2=2n2+1n_0+n_2=2n_2+1一定是一个奇数

    n=n0+n1+n2n=n_0+n_1+n_2,可知:

    • 如果完全二叉树有偶数个结点n=2kn=2k,则

      n1=1n_1=1n0=kn_0=kn2=k1n_2=k-1

    • 如果完全二叉树有奇数个结点n=2k1n=2k-1,则

      n1=0n_1=0n0=kn_0=kn2=k1n_2=k-1

完全二叉树的编号问题

如果按层序从1开始编号

  • i的左孩子 2i2i
  • i的右孩子 2i+12i+1
  • i的父结点 i2\lfloor\frac i 2\rfloor
  • i的所在层次 log2(i+1)\lceil \log_2(i+1)\rceillog2i+1\lfloor\log_2i\rfloor+1

若完全二叉树中有n个结点,则

  • 判断结点i有左孩子 2in2i\le n
  • 判断结点i有右孩子 2i+1n2i+1\le n
  • 判断结点i为叶子结点 i>n2i>\lfloor\frac n 2\rfloor
  • 判断结点i为分支结点 1in21 \le i\le\lfloor\frac n2\rfloor

如果按层序从0开始编号

  • i的左孩子 2i+12i+1
  • i的右孩子 2i+22i+2
  • i的父结点 i12\lfloor\frac {i-1} 2\rfloor
  • i的所在层次 log2(i+2)\lceil \log_2(i+2)\rceillog2(i+1)+1\lfloor\log_2{(i+1)}\rfloor+1

若完全二叉树中有n个结点,则

  • 判断结点i有左孩子 2i+1<n2i+1 < n
  • 判断结点i有右孩子 2i+2<n2i+2 < n
  • 判断结点i为叶子结点 in2i\ge\lfloor\frac n 2\rfloor
  • 判断结点i为分支结点 0i<n20\le i < \lfloor \frac n2\rfloor
平衡二叉树

树上任一结点的左子树和右子树的深度之差不超过1

二叉树2.递归遍历

先序遍历 leetcode144

中序遍历 leetcode94

后序遍历 leetcode145

先序遍历模板

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root)
{
vector<int>ret;
function<void(TreeNode*)>dfs=[&](TreeNode* head)
{
if(head==nullptr)
return;
ret.emplace_back(head->val);
dfs(head->left);
dfs(head->right);
};
dfs(root);
return ret;
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## Definition for a binary tree node.
## class TreeNode:
## def __init__(self, val=0, left=None, right=None):
## self.val = val
## self.left = left
## self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
arr=[]
def dfs(TreeNode):
if TreeNode==None:
return
arr.append(TreeNode.val)
dfs(TreeNode.left)
dfs(TreeNode.right)
dfs(root)
return arr

中序遍历模板:

和上面代码基本一样,交换它们的顺序:

1
2
3
dfs(head->left);
ret.emplace_back(head->val);
dfs(head->right);

后序遍历:

1
2
3
dfs(head->left);
dfs(head->right);
ret.emplace_back(head->val);

二叉树3.非递归遍历

用栈模拟递归的过程

写出二叉树的非递归遍历很难么?这次让你不再害怕非递归!|二叉树的非递归遍历 | 二叉树的遍历迭代法 | 前序与中(参考视频)

先序遍历:

注意:因为栈是先进后出的,先将右子节点入栈,再让左子节点入栈

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root)
{
stack<TreeNode*>sta;
sta.push(root);
vector<int>ret;
while(!sta.empty())
{
TreeNode* cur=sta.top();
sta.pop();
if(cur==nullptr)
continue;
ret.emplace_back(cur->val);
sta.push(cur->right);
sta.push(cur->left);
}
return ret;
}
};

Python:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
arr=[]
stack=[root]
while len(stack)!=0:
cur=stack.pop()
if cur==None:
continue
arr.append(cur.val)
stack.append(cur.right)
stack.append(cur.left)
return arr

后序遍历:

前序遍历是按照 中左右 遍历的

如果把前序遍历代码中

1
2
sta.push(cur->right);
sta.push(cur->left);

颠倒顺序, 变成

1
2
sta.push(cur->left);
sta.push(cur->right);

遍历顺序变成 中右左

最后的arr数组进行反转,遍历序列就会变成 左右中 后序遍历序列

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root)
{
stack<TreeNode*>sta;
sta.push(root);
vector<int>ret;
while(!sta.empty())
{
TreeNode* cur=sta.top();
sta.pop();
if(cur==nullptr)
continue;
ret.emplace_back(cur->val);
sta.push(cur->left);
sta.push(cur->right);
}
reverse(ret.begin(),ret.end());
return ret;
}
};

Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
arr=[]
stack=[root]
while len(stack)!=0:
cur=stack.pop()
if cur==None:
continue
arr.append(cur.val)
stack.append(cur.left)
stack.append(cur.right)
arr=arr[::-1]
return arr

中序遍历:*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root)
{
int flag=1;//1为左模式,2为右模式
stack<TreeNode*>sta;
vector<int>ret;
if(root==nullptr)
return ret;
TreeNode* p=root;
sta.push(p);//孩子节点的入栈由父节点负责,头节点是例外
while(1)
{
if(flag==1)//左模式
{
if(p->left!=nullptr)
{
sta.push(p->left);
p=p->left;
}
else
{
flag=2;
ret.emplace_back(p->val);
sta.pop();
}
}
else//右模式
{
if(p->right!=nullptr)
{
flag=1;
sta.push(p->right);
p=p->right;
}
else
{
if(sta.empty())
break;
p=sta.top();
sta.pop();
ret.emplace_back(p->val);
}
}
}
return ret;
}
};

代码随想录的简洁代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int>ret;
stack<TreeNode*>s;
s.push(root);
if(root==nullptr)
return ret;
TreeNode* p=root->left;
while(p!=nullptr || !s.empty())
{
if(p!=nullptr)
{
s.push(p);
p=p->left;//左
}
else
{
p=s.top();
s.pop();
ret.emplace_back(p->val);//中
p=p->right;//右
}
}
return ret;
}
};

二叉树5:层序遍历

leetcode102

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct TreeNode treenode;
class Solution {
public:
vector<vector<int>> ret;
queue<pair<treenode*,int>> que;
vector<vector<int>> levelOrder(TreeNode* root)
{
que.push(make_pair(root,0));
if(root==NULL)
return ret;
while(!que.empty())
{
treenode* cur=que.front().first;
int floor=que.front().second;
ret.resize(floor+1);
que.pop();
if(cur->left!=NULL)
que.push(make_pair(cur->left,floor+1));
if(cur->right!=NULL)
que.push(make_pair(cur->right,floor+1));
ret[floor].emplace_back(cur->val);
}
return ret;
}
};

二叉树6:翻转二叉树

leetcode226

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
TreeNode* invertTree(TreeNode* root)
{
function<void(TreeNode*)>dfs=[&](TreeNode* cur)
{
swap(cur->left,cur->right);
if(cur->left!=nullptr)
dfs(cur->left);
if(cur->right!=nullptr)
dfs(cur->right);
};
if(root!=nullptr)
dfs(root);
return root;
}
};

二叉树8:对称二叉树

leetcode101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
bool isSymmetric(TreeNode* root)
{
bool ret=true;
function<void(TreeNode*,TreeNode*)>dfs=[&](TreeNode*p1,TreeNode*p2)
{
if(!ret)
return;
if(p1!=nullptr&&p2!=nullptr)
{
if(p1->val!=p2->val)
{
ret=false;
return;
}
dfs(p1->right,p2->left);
dfs(p1->left,p2->right);
}
else if(p1==nullptr&&p2==nullptr)
return;
else
ret=false;
};
dfs(root->left,root->right);
return ret;
}
};

二叉树9:二叉树的最大深度

leetcode104

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int maxDepth(TreeNode* root)
{
int ret=0;
function<void(TreeNode*,int)>dfs=[&](TreeNode* cur,int idx)
{
ret=max(ret,idx);
if(cur->left!=nullptr)
dfs(cur->left,idx+1);
if(cur->right!=nullptr)
dfs(cur->right,idx+1);
};
if(root!=nullptr)
dfs(root,1);
return ret;
}
};

二叉树10:二叉树的最小深度

leetcode111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int minDepth(TreeNode* root)
{
int ret=0x7fffffff;
function<void(TreeNode*,int)>dfs=[&](TreeNode* cur,int idx)
{
if(cur->left!=nullptr)
dfs(cur->left,idx+1);
if(cur->right!=nullptr)
dfs(cur->right,idx+1);
if(cur->left==nullptr&&cur->right==nullptr)
{
ret=min(ret,idx);
}
};
if(root!=nullptr)
dfs(root,1);
else
ret=0;
return ret;
}
};

二叉树11:完全二叉树的节点个数

leetcode222

方法1:二分查找+位运算

(思路同力扣官方题解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public:
int countNodes(TreeNode* root)
{
if(root==nullptr)
return 0;
int ret=0;
int h=2;//计算二叉树的层数
TreeNode* p=root;
while(p->right!=nullptr)
{
p=p->right;
++h;
}
int left=1,right;
for(int i=1;i<h;++i)
left*=2;
right=left*2-1;
while(left<=right)
{
int aver=(left+right)/2;
vector<int>bits(h);
int tmp=aver;
for(int i=h-1;i>=0;--i)
{
bits[i]=tmp%2;
tmp/=2;
}
p=root;
for(int i=1;i<h;++i)
{
if(bits[i]==1)
p=p->right;
else
p=p->left;
}
if(p==nullptr)
right=aver-1;
else
left=aver+1;
}
return right;
}
};

时间复杂度:O(log2n)O(\log^2n)

二叉树的深度h=log2(n+1)h=\lceil\log_2(n+1)\rceil

首先需要O(h)O(h)的时间确定二叉树的深度

然后进行二分查找, 每次查找都要访问hh个节点, 时间复杂度O(h)O(h), 在编号为2h12^{h-1}2h12^{h}-1的节点之间进行二分查找(在2h12^{h-1}个节点之间进行二分查找),时间复杂度O(log(2h1))=O(h1)O(\log(2^{h-1}))=O(h-1).

所以总的时间复杂度O(h2)=O(log2n)O(h^2)=O(\log^2n)

因为开辟了长为hh的数组,空间复杂度为O(logn)O(\log n)

方法2:寻找满二叉树

参考视频

这个方法利用递归, 更容易写:yum:

利用性质:一棵深度为hh的满二叉树,节点数量为2h12^h-1

另外, 在C++中, 2n2^n可以写作1 << n

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int countNodes(TreeNode* root)
{
if(root==nullptr)
return 0;
int ldepth=1,rdepth=1;
TreeNode* p=root;
while(p->left!=nullptr)
{
p=p->left;
++ldepth;
}
p=root;
while(p->right!=nullptr)
{
p=p->right;
++rdepth;
}
if(ldepth==rdepth)
return (1 << ldepth) - 1;
else return countNodes(root->left)+countNodes(root->right)+1;
}
};

时间复杂度:O(log2n)O(\log^2n)

考虑最坏的情况, 也就是二叉树最大层次只有一个节点的情况, 总共需要递归hh层, 每层递归的时间复杂度为O(h)O(h), 所以总的时间复杂度为O(h2)=O(log2n)O(h^2)=O(\log^2n)

空间复杂度:O(log(n))O(\log(n))

在最坏的情况下, 需要递归hh层, 故空间复杂度O(log(n))O(\log(n))

二叉树12:平衡二叉树

leetcode110

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
bool isBalanced(TreeNode* root)
{
bool ret=true;
function<int(TreeNode*)>depth=[&](TreeNode* head)
{
if(!ret)
return -1;
if(head==nullptr)
return 0;
int ld=depth(head->left);
int rd=depth(head->right);
ret&=abs(ld-rd)<2;
return max(ld,rd)+1;
};
depth(root);
return ret;
}
};

二叉树13:二叉树的所有路径

leetcode257

回溯法+dfs

使用to_string()函数(包含于头文件#include<string>),快速将整数转化为字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root)
{
vector<string>ret;
string str;
function<void(TreeNode*)>dfs=[&](TreeNode* cur)
{
string tmp=to_string(cur->val);
if(cur!=root)
str+="->";
str+=tmp;
if(cur->left==nullptr&&cur->right==nullptr)
{
ret.emplace_back(str);
str=str.substr(0,max<int>(0,str.size()-2-tmp.size()));
return;
}
if(cur->left!=nullptr)
dfs(cur->left);
if(cur->right!=nullptr)
dfs(cur->right);
str=str.substr(0,max<int>(0,str.size()-2-tmp.size()));
};
dfs(root);
return ret;
}
};

二叉树15:左叶子之和

leetcode404

这题还挺简单的:smirk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root)
{
int ret=0;
//tag=1时表示为左孩子
function<void(TreeNode*,int)>dfs=[&](TreeNode* cur,int tag)
{
if(tag==1&&cur->left==nullptr&&cur->right==nullptr)
{
ret+=cur->val;
return;
}
if(cur->left!=nullptr)
dfs(cur->left,1);
if(cur->right!=nullptr)
dfs(cur->right,0);
};
dfs(root,0);
return ret;
}
};

二叉树16:找树左下角的值

leetcode513

方法1:失败案例(原因:利用对于完全二叉树的节点编号解题,编号过大造成溢出)

idx的意义是节点在对应完全二叉树中的编号n

深度h和n的关系为h=log2n+1h=\lfloor\log_2n\rfloor+1

左孩子的编号满足n=n<<1n_左=n<<1

右孩子的编号满足n=(n<<1)+1n_右=(n<<1)+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using ll = unsigned long long; 
class Solution {
public:
int findBottomLeftValue(TreeNode* root)
{
//idx的意义是节点在对应完全二叉树中的编号
int max_depth=1;
int ret=-1;
ll ret_idx=0;//越小越好
function<void(TreeNode*,int)>dfs=[&](TreeNode* cur,ll idx)
{
if(cur->left==nullptr&&cur->right==nullptr)
{
int depth=(int)(log(idx)/log(2))+1;
max_depth=max(max_depth,depth);
if(depth==max_depth)
{
if(ret_idx<(1<<(depth-1)))
ret_idx=(1<<(depth))-1;
if(idx<=ret_idx)
{
ret_idx=idx;
ret=cur->val;
}
}
return;
}
if(cur->left!=nullptr)
dfs(cur->left,idx<<1);
if(cur->right!=nullptr)
dfs(cur->right,(idx<<1)+1);
};
dfs(root,1);
return ret;
}
};

方法2: 乖乖用层序遍历,不整花里胡哨的方法才能AC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
int findBottomLeftValue(TreeNode* root)
{
int max_depth=0;
int ret=-1;
queue<pair<TreeNode*,int>>que;
que.push({root,1});
while(!que.empty())
{
auto cur=que.front();
que.pop();
if(cur.second>max_depth)
{
max_depth=cur.second;
ret=cur.first->val;
}
if(cur.first->left!=nullptr)
que.push({cur.first->left,cur.second+1});
if(cur.first->right!=nullptr)
que.push({cur.first->right,cur.second+1});
}
return ret;
}
};

方法3:整点花里胡哨的,用回溯法

利用性质: 在深度优先搜索中,同一层位于左侧的节点总是最先被搜索到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
int findBottomLeftValue(TreeNode* root)
{
int ret=-1;
int max_depth=0;
function<void(TreeNode*,int)>dfs=[&](TreeNode* cur,int depth)
{
if(cur->left==nullptr&&cur->right==nullptr)
{
if(depth>max_depth)
{
max_depth=depth;
ret=cur->val;
}
return;
}
if(cur->left!=nullptr)
dfs(cur->left,depth+1);
if(cur->right!=nullptr)
dfs(cur->right,depth+1);
};
dfs(root,1);
return ret;
}
};

二叉树17:路径总和

leetcode112路径总和

简单题,回溯解决,大佬可以跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum)
{
if(root==nullptr)
return false;
int add=root->val;
bool flag=false;
function<void(TreeNode*)>dfs=[&](TreeNode* cur)
{
if(flag)
return;
if(cur->left==nullptr&&cur->right==nullptr)
{
if(add==targetSum)
flag=true;
}
if(cur->left!=nullptr)
{
add+=cur->left->val;
dfs(cur->left);
add-=cur->left->val;
}
if(cur->right!=nullptr)
{
add+=cur->right->val;
dfs(cur->right);
add-=cur->right->val;
}
};
dfs(root);
return flag;
}
};

leetcode113路径总和II

还是回溯,上面的代码稍微改动即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum)
{
vector<vector<int>>ret;
vector<int>vec;
if(root==nullptr)
return ret;
vec.emplace_back(root->val);
int add=root->val;
function<void(TreeNode*)>dfs=[&](TreeNode* cur)
{
if(cur->left==nullptr&&cur->right==nullptr)
{
if(add==targetSum)
ret.emplace_back(vec);
return;
}
if(cur->left!=nullptr)
{
add+=cur->left->val;
vec.emplace_back(cur->left->val);
dfs(cur->left);
vec.pop_back();
add-=cur->left->val;
}
if(cur->right!=nullptr)
{
add+=cur->right->val;
vec.emplace_back(cur->right->val);
dfs(cur->right);
vec.pop_back();
add-=cur->right->val;
}
};
dfs(root);
return ret;
}
};

二叉树18:从中序与后序遍历序列构造二叉树, 从前序与中序遍历序列构造二叉树

leetcode106从中序与后序遍历序列构造二叉树

回溯法,有点复杂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder)
{
function<TreeNode*(int,int,int,int)>dfs=[&](int ileft,int iright,int pleft,int pright)
{
if(ileft>iright||pleft>pright)
return (TreeNode*)nullptr;
int igap=find(inorder.begin()+ileft,inorder.begin()+iright+1,postorder[pright])-inorder.begin();
return new TreeNode(postorder[pright],dfs(ileft,igap-1,pleft,pleft+igap-ileft-1),dfs(igap+1,iright,pleft+igap-ileft,pright-1));
};
return dfs(0,inorder.size()-1,0,postorder.size()-1);
}
};

leetcode105 从前序与中序遍历序列构造二叉树

和上题一样的思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
function<TreeNode*(int,int,int,int)>dfs=[&](int pl,int pr,int il,int ir)
{
if(pl>pr||il>ir)
return (TreeNode*)nullptr;
int gap=find(inorder.begin()+il,inorder.begin()+ir+1,preorder[pl])-inorder.begin();
return new TreeNode(preorder[pl],dfs(pl+1,gap-il+pl,il,gap-1),dfs(gap-il+pl+1,pr,gap+1,ir));
};
return dfs(0,preorder.size()-1,0,inorder.size()-1);
}
};

二叉树19:最大二叉树

leetcode654

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums)
{
function<TreeNode*(int,int)>dfs=[&](int le,int ri)
{
if(le>ri)
return (TreeNode*)nullptr;
int max_element=0x80000000,max_index=-1;
for(int i=le;i<=ri;++i)
{
if(nums[i]>max_element)
{
max_index=i;
max_element=nums[i];
}
}
return new TreeNode(max_element,dfs(le,max_index-1),dfs(max_index+1,ri));
};
return dfs(0,nums.size()-1);
}
};

二叉树21:合并二叉树

leetcode617

一样的回溯思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2)
{
function<TreeNode*(TreeNode*,TreeNode*)>dfs=[&](TreeNode* p1,TreeNode* p2)
{
if(p1!=nullptr&&p2!=nullptr)
return new TreeNode(p1->val+p2->val,dfs(p1->left,p2->left),dfs(p1->right,p2->right));
if(p2==nullptr&&p1!=nullptr)
return new TreeNode(p1->val,dfs(p1->left,nullptr),dfs(p1->right,nullptr));
if(p1==nullptr&&p2!=nullptr)
return new TreeNode(p2->val,dfs(nullptr,p2->left),dfs(nullptr,p2->right));
return (TreeNode*)nullptr;
};
TreeNode* p1=root1,*p2=root2;
return dfs(p1,p2);
}
};

二叉树22:二叉搜索树中的搜索

leetcode700

简单题,大佬请跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val)
{
TreeNode* p=root;
while(p!=nullptr)
{
if(val==p->val)
return p;
else if(val<p->val)
p=p->left;
else
p=p->right;
}
return nullptr;
}
};

二叉树23:验证二叉搜索树

leetcode98

别小看这题,坑巨多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
bool isValidBST(TreeNode* root)
{
bool ret=true;
int downinf=1,upinf=1;
function<void(TreeNode*,int,int)>dfs=[&](TreeNode* cur,int up,int down)
{
if(!ret||cur==nullptr)
return;
if(cur->val<=down&&downinf==0||cur->val>=up&&upinf==0)
ret=false;
if(upinf==1)
{
upinf=0;
dfs(cur->left,cur->val,down);
upinf=1;
}
else
dfs(cur->left,cur->val,down);
if(downinf==1)
{
downinf=0;
dfs(cur->right,up,cur->val);
downinf=1;
}
else
dfs(cur->right,up,cur->val);
};
dfs(root,0,0);
return ret;
}
};

二叉树24:二叉搜索树的最小绝对差

leetcode530

相当于求二叉树中序遍历序列相邻元素的最小绝对差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int getMinimumDifference(TreeNode* root)
{
int last=-1000000;
int ret=0x7fffffff;
function<void(TreeNode*)>dfs=[&](TreeNode* cur)//中序遍历
{
if(cur==nullptr)
return;
dfs(cur->left);
ret=min(ret,abs(cur->val-last));
last=cur->val;
dfs(cur->right);
};
dfs(root);
return ret;
}
};

二叉树25:二叉搜索树中的众数

leetcode501

采用中序遍历就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Solution {
public:
vector<int> findMode(TreeNode* root)
{
int pre=-200000;
int cnt=1,ret=1;
vector<int>vec;
function<void(TreeNode*)>dfs=[&](TreeNode* cur)
{
if(cur==nullptr)
return;
dfs(cur->left);
if(cur->val==pre)
{
++cnt;
}
else if(pre!=-200000)
{
if(cnt>ret)
{
vec.resize(0);
vec.emplace_back(pre);
ret=cnt;
}
else if(cnt==ret)
{
vec.emplace_back(pre);
}
cnt=1;
pre=cur->val;
}
else
{
cnt=1;
pre=cur->val;
}
dfs(cur->right);
};
dfs(root);
if(cnt>ret)
{
vec.resize(0);
vec.emplace_back(pre);
ret=cnt;
}
else if(cnt==ret)
{
vec.emplace_back(pre);
}
cnt=1;
return vec;
}
};

二叉树26:二叉树的最近公共祖先

leetcode236

直接回溯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
TreeNode* ret=nullptr;
int depth=-1;
function<int(TreeNode*,int)>dfs=[&](TreeNode* cur,int dep)
{
if(cur==nullptr)
return 0;
int status=0;
if(cur==p)
status|=2;
else if(cur==q)
status|=1;
status|=dfs(cur->left,dep+1);
status|=dfs(cur->right,dep+1);
if(status==3&&dep>depth)
{
depth=dep;
ret=cur;
}
return status;
};
dfs(root,0);
return ret;
}
};

二叉树28:二叉搜索树的最近公共祖先

leetcode235

和上一题差不多,这题是二叉搜索树,可以利用性质简化

用指针t从根节点开始采用二分搜索,直到发现t==p||t==q,或者pq分别位于t两侧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
TreeNode* t=root;
while(t!=nullptr)
{
if(t==p||t==q)
return t;
if(t->val>p->val&&t->val>q->val)
t=t->left;
else if(t->val<p->val&&t->val<q->val)
t=t->right;
else
return t;
}
return nullptr;
}
};

二叉树29:二叉搜索树的插入操作

leetcode701

简单题,大佬可跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val)
{
TreeNode* p=root;
if(p==nullptr)
return new TreeNode(val);
while(1)
{
if(val>p->val)
{
if(p->right!=nullptr)
p=p->right;
else
{
p->right=new TreeNode(val);
return root;
}
}
else
{
if(p->left!=nullptr)
p=p->left;
else
{
p->left=new TreeNode(val);
return root;
}
}
}
return root;
}
};

二叉树30:删除二叉搜索树中的节点

leetcode450

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key)
{
TreeNode* dummyhead=new TreeNode(0,root,nullptr);
TreeNode* pre=dummyhead;
TreeNode* cur=root;
while(cur!=nullptr&&cur->val!=key)
{
pre=cur;
if(cur->val>key)
cur=cur->left;
else
cur=cur->right;
}
if(cur==nullptr)
{
delete dummyhead;
return root;
}
if(cur->left==nullptr)
{
if(pre->left==cur)
pre->left=cur->right;
else
pre->right=cur->right;
}
else if(cur->right==nullptr)
{
if(pre->left==cur)
pre->left=cur->left;
else
pre->right=cur->left;
}
else
{
TreeNode* tmp=cur->left->right;
TreeNode* p=cur->right;
cur->left->right=cur->right;
if(pre->left==cur)
pre->left=cur->left;
else
pre->right=cur->left;
while(p->left!=nullptr)
p=p->left;
p->left=tmp;
}
delete cur;
TreeNode* ret=dummyhead->left;
delete dummyhead;
return ret;
}
};

(9)回溯专题

回溯法(backtracking)

回溯2:组合

leetcode77

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
vector<vector<int>> combine(int n, int k)
{
vector<vector<int>>ret;
vector<int>sel;
sel.reserve(n);
function<void(int)> dfs=[&](int idx)
{
if(n-idx+1<k-sel.size())//剪枝
return;
if(idx>n+1)//递归上限
return;
if(sel.size()==k)
{
ret.emplace_back(sel);
return;
}
dfs(idx+1);//不取当前数字
sel.emplace_back(idx);
dfs(idx+1);//取当前数字
sel.pop_back();
};
dfs(1);
return ret;
}
};

python: 要注意几个global的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
num=0  ## 统计已经加入数组arr的元素数量
ret=[]
arr=[]
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
global num,ret,arr
ret=[]
arr=[]
num=0
def dfs(idx: int):
global num,ret,arr
if num>k:
return
if idx==n+1:
if num==k:
ret.append(arr.copy())
return
dfs(idx+1)
num+=1
arr.append(idx)
dfs(idx+1)
num-=1
arr.pop(num)
dfs(1)
return ret

回溯4:组合总和III

leetcode216

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
vector<vector<int>> combinationSum3(int k, int n)
{
vector<vector<int>>ret;
vector<int>sel;
sel.reserve(k);
int add=0;
function<void(int)>dfs=[&](int idx)
{
if(idx>10)
return;
if(add==n&&sel.size()==k)
{
ret.emplace_back(sel);
return;
}
dfs(idx+1);
add+=idx;
sel.emplace_back(idx);
dfs(idx+1);
add-=idx;
sel.pop_back();
};
dfs(1);
return ret;
}
};

回溯5:电话号码的字母组合

leetcode17

方法1: 两层for循环模拟枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
vector<string> letterCombinations(string digits)
{
vector<string> str={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
vector<string> vec;
for(int i=0;i<digits.size();++i)
{
int idx=digits[i]-'0';
if(i==0)
{
for(int j=0;j<str[idx].size();++j)
{
vec.emplace_back(str[idx].substr(j,1));
}
}
else
{
int j=0;
for(;j<vec.size()&&vec[j].size()<=i;++j)
{
for(auto ch:str[idx])
{
string tmp=vec[j];
tmp+=ch;
vec.emplace_back(tmp);
}
}
vec.erase(vec.begin(),vec.begin()+j);
}
}
return vec;
}
};

方法2:递归回溯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
vector<string> letterCombinations(string digits)
{
vector<vector<char>>vec={{},{},{'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'},{'m','n','o'},{'p','q','r','s'},{'t','u','v'},{'w','x','y','z'}};
vector<string>ret;
string s;
function<void(int)>dfs=[&](int idx)
{
if(idx==digits.size())
{
if(s.size()!=0)
ret.emplace_back(s);
return;
}
vector<char>&tmp=vec[digits[idx]-'0'];
for(char& ch:tmp)
{
s.push_back(ch);
dfs(idx+1);
s.pop_back();
}
};
dfs(0);
return ret;
}
};

回溯7:组合总和

leetcode39

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target)
{
vector<vector<int>>ret;
vector<int>vec;
int add=0;
function<void(int)>dfs=[&](int idx)
{
if(idx==candidates.size())
{
if(add==target)
ret.emplace_back(vec);
return;
}
dfs(idx+1);
int cnt=0;
while(1)
{
if(add+candidates[idx]<=target)
{
add+=candidates[idx];
vec.emplace_back(candidates[idx]);
++cnt;
dfs(idx+1);
}
else
break;
}
for(int i=0;i<cnt;++i)
{
add-=candidates[idx];
vec.pop_back();
}
};
dfs(0);
return ret;
}
};

回溯8:组合总和II

leetcode40

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target)
{
vector<vector<int>>ret;
vector<int>vec;
int add=0;
sort(candidates.begin(),candidates.end());//避免出现重复的组合
function<void(int)>dfs=[&](int idx)
{
if(add>target)
return;
if(add==target)
{
ret.emplace_back(vec);
return;
}
if(idx==candidates.size())
return;
vec.emplace_back(candidates[idx]);
add+=candidates[idx];
dfs(idx+1);
vec.pop_back();
add-=candidates[idx];
int i=idx+1;
while(i<candidates.size()&&candidates[i]==candidates[i-1])
++i;
dfs(i);
};
dfs(0);
return ret;
}
};

回溯9:分割回文串

leetcode131

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
public:
int len;
string str;
vector<vector<string>> ret;
bool ispalind(string s)
{
int slen=s.size();
int i=0,j=slen-1;
for(;i<j;++i,--j)
{
if(s[i]!=s[j])
return false;
}
return true;
}
void dfs(int idx,vector<int>punc)
{
if(idx==len)
{
vector<string> tmp;
int last=0;
for(int i=1;i<=punc.size();++i)
{
if(i==punc.size()||punc[i]==1)
{
string st=str.substr(last,i-last);
if(!ispalind(st))
return;
tmp.emplace_back(st);
last=i;
}
}
ret.emplace_back(tmp);
return;
}
else
{
dfs(idx+1,punc);
punc[idx]=1;
dfs(idx+1,punc);
}
}
vector<vector<string>> partition(string s)
{
len=s.size();
str=s;
vector<int>punc(len);
dfs(1,punc);
return ret;
}
};

回溯10:复原IP地址

leetcode93

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
public:
vector<string> restoreIpAddresses(string s)
{
int n=s.size();
string tmp;
vector<string>ret;
int cnt=0;
function<void(int)>dfs=[&](int i)
{
if(cnt==4||i==n)
{
if(cnt==4&&i==n)
ret.emplace_back(tmp);
return;
}
for(int j=i;j<n&&j<i+3;++j)
{
string add=s.substr(i,j-i+1);
int num=stoi(add);
if(num<=255&&s[i]!='0'||s[i]=='0'&&j==i)
{
int flag=0;
if(tmp.size()!=0)
{
tmp+='.';
flag=1;
}
tmp+=add;
++cnt;
dfs(j+1);
--cnt;
if(flag)
tmp.erase(tmp.size()-add.size()-1,add.size()+1);
else
tmp.erase(tmp.size()-add.size(),add.size());
}
}
};
dfs(0);
return ret;
}
};

回溯11:子集

leetcode78

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
vector<vector<int>> ret;
vector<int>num;
int n;
void make(vector<int> tem,int p)
{
if(p==n)
{
ret.push_back(tem);
return;
}
make(tem,p+1);
tem.push_back(num[p]);
make(tem,p+1);
}
vector<vector<int>> subsets(vector<int>& nums)
{
n=nums.size();
num=nums;
vector<int> tem;
make(tem,0);
return ret;
}
};

回溯13:子集II

leetcode90

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums)
{
vector<vector<int>>ret;
vector<int>tmp;
sort(nums.begin(),nums.end());
int n=nums.size();
function<void(int)>dfs=[&](int idx)
{
if(idx==n)
{
ret.emplace_back(tmp);
return;
}
tmp.emplace_back(nums[idx]);
dfs(idx+1);
tmp.pop_back();
while(idx+1<n&&nums[idx+1]==nums[idx])
++idx;
dfs(idx+1);
};
dfs(0);
return ret;
}
};

回溯14:递增子序列

leetcode491

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
vector<vector<int>> findSubsequences(vector<int>& nums)
{
int n=nums.size();
vector<vector<int>>ret;
vector<int>tmp;
function<void(int)>dfs=[&](int idx)
{
if(idx==n)
{
if(tmp.size()>=2)
ret.emplace_back(tmp);
return;
}
unordered_map<int,int>hash;
for(int i=idx;i<n;++i)
{
if(hash[nums[i]]==0&&(tmp.size()==0||nums[i]>=tmp.back()))
{
hash[nums[i]]=1;
tmp.emplace_back(nums[i]);
dfs(i+1);
tmp.pop_back();
}
}
if(tmp.size()>=2)
ret.emplace_back(tmp);
};
dfs(0);
return ret;
}
};

回溯15:全排列

leetcode46

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums)
{
vector<vector<int>> ret;
vector<int>isvalid(nums.size(),0);
vector<int>cur;
auto dfs=[&]()
{
if(cur.size()==nums.size())
{
ret.emplace_back(cur);
return;
}
for(int i=0;i<nums.size();++i)
{
if(isvalid[i]==0)
{
cur.emplace_back(nums[i]);
isvalid[i]=1;
dfs();
cur.pop_back();
isvalid[i]=0;
}
}
};
dfs();
return ret;
}
};

回溯16:全排列II

leetcode47

方法1:在得到全排列数组后进行整体去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using ll=long long;
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums)
{
vector<ll>quan(nums.size());
quan[0]=1;
for(int i=1;i<nums.size();++i)
quan[i]=quan[i-1]*11;
unordered_map<ll,int>hash;
vector<int>vec(nums.size());
vector<int>cur;
vector<vector<int>>ret;
ll key=0;
function<void(int)>dfs=[&](int idx)
{
if(idx==nums.size())
{
if(hash[key]==0)
{
hash[key]=1;
ret.emplace_back(cur);
}
return;
}
for(int i=0;i<nums.size();++i)
{
if(vec[i]==0)
{
vec[i]=1;
cur.emplace_back(nums[i]);
key+=quan[idx]*nums[i];
dfs(idx+1);
key-=quan[idx]*nums[i];
vec[i]=0;
cur.pop_back();
}
}
};
dfs(0);
return ret;
}
};

60ms,8.8MB

方法2:在递归的同一层内避免插入相同元素到数组中,有效避免出现重复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums)
{
int n=nums.size();
vector<int>isvisited(n);
vector<int>tmp;
vector<vector<int>>ret;
function<void(int)>dfs=[&](int idx)
{
if(idx==n)
{
ret.emplace_back(tmp);
return;
}
unordered_map<int,int>hash;
for(int i=0;i<n;++i)
{
if(isvisited[i]==0)
{
if(hash[nums[i]]==0)
{
hash[nums[i]]=1;
isvisited[i]=1;
tmp.emplace_back(nums[i]);
dfs(idx+1);
tmp.pop_back();
isvisited[i]=0;
}
}
}
};
dfs(0);
return ret;
}
};

8ms,10.9MB

回溯19:重新安排行程

leetcode332

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public:
vector<string> findItinerary(vector<vector<string>>& tickets)
{
int n=tickets.size();
sort(tickets.begin(),tickets.end(),[&](vector<string>&a,vector<string>&b){return a[1]<b[1];});
string loca="JFK";
vector<int>isUsed(n);
vector<string>ret;
vector<string>ret1;
ret.emplace_back(loca);
int flag=0;
function<void(int)>dfs=[&](int idx)
{
if(flag)
return;
if(idx==n)
{
ret1=ret;
flag=1;
return;
}
for(int i=0;i<n;++i)
{
if(tickets[i][0]==loca&&isUsed[i]==0)
{
isUsed[i]=1;
string tmp=loca;
loca=tickets[i][1];
ret.emplace_back(loca);
dfs(idx+1);
ret.pop_back();
loca=tmp;
isUsed[i]=0;
}
}
};
dfs(0);
return ret1;
}
};

回溯20:N皇后

leetcode51

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Solution {
public:
vector<vector<string>> solveNQueens(int n)
{
vector<vector<string>>ret;
vector<string>graph(n);
for(auto& _vec:graph)
_vec.assign(n,'.');
vector<int>row(n);
function<void(int)>dfs=[&](int col)
{
if(col==n)
{
ret.emplace_back(graph);
return;
}
for(int i=0;i<n;++i)
{
if(row[i]!=0)
continue;
int flag=0;
for(int j=0;j<n;++j)
{
int k=i+col-j;
if(k<0||k>=n)
continue;
if(graph[j][k]=='Q')
{
flag=1;
break;
}
}
if(flag==1)
continue;
for(int j=0;j<n;++j)
{
int k=j+col-i;
if(k<0||k>=n)
continue;
if(graph[j][k]=='Q')
{
flag=1;
break;
}
}
if(flag==1)
continue;
row[i]=1;
graph[i][col]='Q';
dfs(col+1);
row[i]=0;
graph[i][col]='.';
}
};
dfs(0);
return ret;
}
};

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
ret=[["."]*n for _ in range(n)]
ans=[]
arr=[0]*n
def recur(row):
if row==n:
tmp=[""]*n
for i in range(n):
tmp[i]=tmp[i].join(ret[i])
ans.append(tmp)
return
for col in range(n):
#检查列是否有皇后
if arr[col]!=0:
continue
flag=0
#检查对角线
x=row-1
y=col-1
while x>=0 and y>=0:
if ret[x][y]=="Q":
flag=1
break
x-=1
y-=1
if flag:
continue
#检查反对角线
x=row-1
y=col+1
while x>=0 and y<n:
if ret[x][y]=="Q":
flag=1
break
x-=1
y+=1
if flag:
continue
#放置皇后
arr[col]=1
ret[row][col]="Q"
recur(row+1)
arr[col]=0
ret[row][col]="."
recur(0)
return ans

回溯21:解数独

leetcode37

数独棋盘:

0 1 2 3 4 5 6 7 8
0
1
2
3
4
5
6
7
8

判断是否属于同一个3×33\times 3宫:比较x/3*10+y/3的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Solution {
public:
void solveSudoku(vector<vector<char>>& board)
{
int flag=0;//已经填好的标记
function<void(int,int)>dfs=[&](int x,int y)
{
if(flag)
return;
if(x==9)
{
flag=1;
return;
}
if(board[x][y]=='.')
{
vector<int>isvalid(10);
for(int i=0;i<=8;++i)//处理同行同列
{
if(board[i][y]!='.')
isvalid[board[i][y]-'0']=1;
if(board[x][i]!='.')
isvalid[board[x][i]-'0']=1;
}
for(int i=0;i<3;++i)
{
for(int j=0;j<3;++j)
{
if(board[x/3*3+i][y/3*3+j]!='.')
isvalid[board[x/3*3+i][y/3*3+j]-'0']=1;
}
}
for(int i=1;i<=9;++i)
{
if(isvalid[i]==0)
{
board[x][y]=i+'0';
if(y==8)//访问下一个格子
dfs(x+1,0);
else
dfs(x,y+1);
if(flag==1)
return;
else
board[x][y]='.';
}
}
}
else
{
if(y==8)//访问下一个格子
dfs(x+1,0);
else
dfs(x,y+1);
}
};
dfs(0,0);
}
};

(10)贪心专题

贪心2:分发饼干

leetcode455

排序 + 双指针 + 贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s)
{
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int i=0,j=0;
int ret=0;
for(;i<g.size()&&j<s.size();++i)
{
while(j<s.size()&&s[j]<g[i])
++j;
if(j<s.size()&&s[j]>=g[i])
{
++j;
++ret;
}
}
return ret;
}
};

贪心3:摆动序列

leetcode376

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
le=len(nums)
i=1
flag=1 ## 1表示增,2表示减
ret=1
while i<le and nums[i]==nums[i-1]:
i+=1
if i<le and nums[i]>nums[i-1]:
flag=1
elif i<le:
flag=0
else:
return 1
i=1
while i<le:
if flag==1:
while i<le and nums[i]>=nums[i-1]:
i+=1
ret+=1
flag=0
else:
while i<le and nums[i]<=nums[i-1]:
i+=1
ret+=1
flag=1
return ret

贪心4:最大子数组和

leetcode53

这里考虑局部最优解

从左到右遍历一次nums数组,遍历到第i个元素时,add的含义是选择nums[i]的条件下考虑前i个元素的最大子数组和

然后用ret记录add的最大值

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int maxSubArray(vector<int>& nums)
{
int ret=nums[0],add=nums[0];
for(int i=1;i<nums.size();++i)
{
add=max(add+nums[i],nums[i]);
ret=max(ret,add);
}
return ret;
}
};

python:

1
2
3
4
5
6
7
8
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
add=0
ret=-100000
for num in nums:
add=max(add+num,num)
ret=max(ret,add)
return ret

贪心6:买卖股票的最佳时机 II

leetcode122

动态规划章节也包含了这道题

方法1:贪心

相当于截取prices数组中的所有上升子数组,求出它们的首尾元素之差并求和

C:

1
2
3
4
5
6
7
8
9
10
int maxProfit(int* prices, int pricesSize)
{
int ret=0;
for(int i=1;i<pricesSize;++i)
{
if(prices[i]>prices[i-1])
ret+=prices[i]-prices[i-1];
}
return ret;
}

python:

1
2
3
4
5
6
7
8
9
class Solution:
def maxProfit(self, prices: List[int]) -> int:
pre=inf
profit=0
for num in prices:
if num-pre>0:
profit+=num-pre
pre=num
return profit

方法2:奇妙的动态规划

dfs[i][0]表示第i天结束不持有股票的最大利润pair.first

dfs[i][1]表示第i天结束持有股票的最大利润pair.second

C++:一维数组递推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int maxProfit(vector<int>& prices)
{
int len=prices.size();
vector<pair<int,int>> dfs(len);
dfs[0].first=0;
dfs[0].second=-prices[0];
for(int i=1;i<len;++i)
{
dfs[i].first=max(dfs[i-1].first,dfs[i-1].second+prices[i]);
dfs[i].second=max(dfs[i-1].second,dfs[i-1].first-prices[i]);
}
return dfs[len-1].first;
}
};

python:将一维数组压缩为更新两个变量

1
2
3
4
5
6
7
8
9
10
class Solution:
def maxProfit(self, prices: List[int]) -> int:
have=-prices[0] ## 当前持有股票的最大利润
notHave=0 ## 当前不持有股票的最大利润
for i in range(1,len(prices)):
tp1=max(have,notHave-prices[i])
tp2=max(notHave,have+prices[i])
have=tp1
notHave=tp2
return notHave

贪心7:跳跃游戏

简单贪心,只需维护bound作为当前能到达的最远位置

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool canJump(int* nums, int numsSize)
{
if(numsSize==1)
return true;
for(int i=0;i<numsSize-1;i++)
{
if(nums[i]==0)
{
int p=0;
for(int j=i;j>=0;j--)
{
if(nums[i-j]>j)
p=1;
}
if(p==0)
return false;
}
}
return true;
}

python:

1
2
3
4
5
6
7
8
class Solution:
def canJump(self, nums: List[int]) -> bool:
bound=0
for idx,num in enumerate(nums):
if idx>bound:
return False
bound=max(bound,idx+num)
return True

贪心8:跳跃游戏II

leetcode45

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int jump(int* nums, int numsSize)
{
int dis1=0,dis2=0;
if(numsSize==1)
return 0;
for(int i=1;i<numsSize;i++)
{
int far=0;
for(int j=dis2;j<=dis1;j++)
{
far=far>nums[j]+j?far:nums[j]+j;
}
dis2=dis1+1;
dis1=far;
if(dis1>=numsSize-1)
return i;
}
return 0;
}

贪心9:K次取反后最大化的数组和

leetcode1005

你可能都没意识到你用了贪心算法, 就不小心AC了

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
nums=sorted(nums)
idx=0
while idx<len(nums) and nums[idx]<0 and k>0:
nums[idx]*=-1
idx+=1
k-=1
if k==0:
return sum(nums)
elif idx==len(nums):
nums[idx-1]*=(-1)**k
return sum(nums)
nums=sorted(nums)
nums[0]*=(-1)**k
return sum(nums)

贪心11: 加油站

leetcode134

难度蹭蹭上涨了

据说用C++写O(n2)O(n^2)的暴力解法可以通过,感兴趣的可以试试:wink:

贪心:

C++:

时间复杂度O(n)O(n),空间复杂度O(n)O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
{
int n=gas.size();
vector<int>vec(n);
for(int i=0;i<n;++i)
vec[i]=gas[i]-cost[i];
int begin=0,end=0;//双指针,左闭右闭
int add=0;//当前油箱汽油量
int flag=0;//flag==1代表end已经越过n回到0
while(begin!=end||flag==0)
{
add+=vec[end];
if(add<0)
{
if(flag==1)
return -1;
add=0;
++end;
if(end==n)
return -1;
begin=end;
continue;
}
++end;
if(end==n)
{
end=0;
flag=1;
}
}
return begin;
}
};

贪心12:分发糖果

leetcode135

方法1:自己的复杂方法

ratings数组视为一个波, 各个下标处对应的数值为该处波的高度

一次遍历,找到所有的波谷下标装入队列

然后从队列逐个取出下标,从波谷向两侧遍历直到到达波峰, 对于严格上升的点糖果数为上一个位置的糖果数+1,平缓的点糖果数为1

一个位置的糖果数可能被多次计算,取其中的最大值

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
public:
int candy(vector<int>& ratings)
{
int n=ratings.size();
if(n==1)
return 1;
queue<int>que;//记录所有波谷的下标
if(ratings[0]<=ratings[1])
que.push(0);
for(int i=1;i<n-1;++i)
{
if(ratings[i-1]>=ratings[i]&&ratings[i]<=ratings[i+1])
que.push(i);
}
if(ratings[n-2]>=ratings[n-1])
que.push(n-1);
vector<int>candy(n,1);
while(!que.empty())
{
int cur=que.front();
que.pop();
candy[cur]=1;
int left=cur-1;
for(;left>=0;--left)
{
if(ratings[left]>ratings[left+1])
candy[left]=max(candy[left],candy[left+1]+1);
else if(ratings[left]<ratings[left+1])
break;
}
int right=cur+1;
for(;right<n;++right)
{
if(ratings[right]>ratings[right-1])
candy[right]=max(candy[right],candy[right-1]+1);
else if(ratings[right]<ratings[right-1])
break;
}
}
return accumulate(candy.begin(),candy.end(),0);
}
};

方法2:大佬们的方法

正反两次遍历

python:

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def candy(self, ratings: List[int]) -> int:
n=len(ratings)
candies=[1]*n
for i in range(n): ## 只考虑左孩子比右孩子分数低的情况
if i != 0 and ratings[i]>ratings[i-1]:
candies[i]=max(candies[i],candies[i-1]+1)
for i in range(n-1,-1,-1): ## 只考虑左孩子比右孩子分数高的1情况
if i != n-1 and ratings[i]>ratings[i+1]:
candies[i]=max(candies[i],candies[i+1]+1)
return sum(candies)

贪心13:柠檬水找零

leetcode860

简单模拟就行,用生活经验,有10元就优先用10元找零,5元是万能的

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
n5=0 ## 5美元钞票数
n10=0 ## 10美元钞票数
for bill in bills:
if bill==5:
n5+=1
elif bill==10:
n5-=1
n10+=1
else:
if n10>0:
n10-=1
n5-=1
else:
n5-=3
if n5<0:
return False
return True

贪心14:根据身高重建队列*

leetcode406

有点难:sob:

搬运自讨论区的精彩解释:https://leetcode.cn/problems/queue-reconstruction-by-height/discussion/comments/1809851

上了第一节体育课,老师给大家排好了体操的队伍,可是大家脑子都很笨,记不清自己在哪,老师说,你就看前面有几个比自己高的就行!就像这样:

1
[[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]

该上第二节课的时候,大家记住了前面有几个比自己高的,却还是忘记了怎么排,老师见状让学生从高到低排好队,身高一样的,比自己高的越多,越往后面站,像这样:

1
[[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]]

每次让最高的学生出来找自己的位置,第一个高个子[7,0]自然站到了第一个位置:

1
[[7,0]]

而第二个高个子[7,1]知道有一个人大于等于自己的身高,站在了第一个人身后:

1
[[7,0],[7,1]]

第三个人[6,1]想了想,有一个人比自己高,那自己肯定站在第二位,于是就插队,现在也站到了第一个人身后:

1
[[7,0],[6,1],[7,1]]

第四个人[5,0]想了想,没人比自己高,那自己肯定站在第一位,于是就插队,站到了队头:

1
[[5,0],[7,0],[6,1],[7,1]]

第五个人[5,2]想了想,有两个人比自己高,于是就插队,站到了第二个人后面,也就是第三个位置:

1
[[5,0],[7,0],[5,2],[6,1],[7,1]]

第六个人[4,4]看了看眼前的队伍,比自己高的人都在里面,他安心的数着前面有四个人比自己高,心安理得的站到了第四个人身后:

1
[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

其实这道题的大概思路就是这样,只有先让身高高的先进入队伍,后面身高低的才能根据前面高的来找自己的位置

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people)
{
sort(people.begin(),people.end(),[&](vector<int>&a,vector<int>&b)
{
return a[0]>b[0]||a[0]==b[0]&&a[1]<b[1];
});//首先按照h从大到小排序,h相同时按照k从小到大排序
for(int i=0;i<people.size();++i)
{
if(people[i][1]==i)
continue;
auto tmp=people[i];
for(int j=i;j>tmp[1];--j)
swap(people[j],people[j-1]);
}
return people;
}
};

贪心17:用最少数量的箭引爆气球*

leetcode452

很经典的区间问题

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points)
{
sort(points.begin(),points.end(),[&](vector<int>& a,vector<int>& b){
return a[0]<b[0];
});
int i=0,ret=0;
int len=points.size();
while(i<len)
{
int left=points[i][0],right=points[i][1];
int j=i+1;
for(;j<len&&points[j][0]<=right;++j)
{
left=points[j][0];
right=min(points[j][1],right);
}
++ret;
i=j;
}
return ret;
}
};

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
points.sort()
n=len(points)
i,cnt=0,0
while i<n:
left,right=points[i]
j=i+1
while j<n and points[j][0]<=right:
left=points[j][0]
right=min(right,points[j][1])
j+=1
i=j
cnt+=1
return cnt

贪心18:无重叠区间

leetcode435

首先按照右边界的坐标从小到大排序,如果右边界坐标相等,就按照左边界从大到小排序

然后从左到右遍历, 对于每个被遍历到的区间,都删除掉它后面所有与之相交的区间

被删除的区间不会再参与遍历

几种已经排好序的样例如下(带x的被删除掉)

1
2
3
4
5
6
|--|
|--|
|-----| x
|-----| x
|-------|
|-----|x
1
2
3
4
 |-|
|-----| x
|-----|
|-------------|x

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals)
{
int len=intervals.size();
sort(intervals.begin(),intervals.end(),[&](vector<int>&a,vector<int>&b){
return a[1]<b[1]||a[1]==b[1]&&a[0]>b[0];
});
int i=0,ret=0;
while(i<len)
{
int j=i+1;
for(;j<len;++j)
{
if(intervals[j][0]<intervals[i][1])
++ret;
else
break;
}
i=j;
}
return ret;
}
};

贪心19:划分字母区间

leetcode763

和上面两题相似,也是区间重叠问题

C++: 自己的糟糕代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
vector<int> partitionLabels(string s)
{
unordered_map<char,int>hash;//记录字母在数组vec中的下标
vector<pair<int,int>>vec;//记录字母所有出现位置的左边界和右边界
int len=0;//vec数组的长度
for(int i=0;i<s.size();++i)
{
if(hash.find(s[i])==hash.end())
{
hash[s[i]]=len;
++len;
vec.emplace_back(make_pair(i,i));
}
else
vec[hash[s[i]]].second=i;
}
sort(vec.begin(),vec.end(),[&](pair<int,int>&a,pair<int,int>&b){
return a.first<b.first;
});
int left=0,right=0;
vector<int>ret;
for(auto& p:vec)
{
if(p.first>right)
{
ret.emplace_back(right-left+1);
left=p.first;
right=p.second;
}
else
right=max(right,p.second);
}
ret.emplace_back(right-left+1);
return ret;
}
};

大佬的优雅代码(搬运自https://leetcode.cn/problems/partition-labels/discussion/comments/31356):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<int> partitionLabels(string S) {
vector<int> result;
unordered_map<char, int> map; //记录char c 和其最后出现位置的 map
int start = 0, end = 0;
for (int i = 0; i < S.size(); i ++) {
map[S[i]] = i;
}
for (int i = 0; i < S.size(); i ++) {
end = max(end, map[S[i]]);
if (i == end) {
result.push_back(end - start + 1);
start = i + 1;
}
}
return result;
}
};

贪心20:合并区间

leetcode56

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals)
{
sort(intervals.begin(),intervals.end(),[&](vector<int>&a,vector<int>&b){
return a[0]<b[0];
});
int left=intervals[0][0],right=intervals[0][1];
vector<vector<int>>ret;
for(auto& vec:intervals)
{
if(vec[0]<=right)
{
right=max(right,vec[1]);
}
else
{
ret.emplace_back(vector<int>{left,right});
left=vec[0];
right=vec[1];
}
}
ret.emplace_back(vector<int>{left,right});
return ret;
}
};

贪心22:单调递增的数字

leetcode738

方法1:从左到右遍历(作者的笨办法)

将n各个位填入数组(字符串也行),从左到右遍历:

当发现当前位数字比前一位数字小时,就开始修改字符串,修改完字符串就能返回结果

修改规则为:前一位数字减1,当前位数字及之后所有位的数字一律为9

例如6328,遍历到3时,将前1位减1得5,后面一律为9,得5999

考虑一种情况,n=332,遍历到下标2时发现数字比前一位小,将下标为1处的3改为2,后面置为9后,得329不符合题意,这说明还需要另外加一个循环从下标为1处向前遍历,如果当前位置数字比前一位小了,就把当前位置数字置为9,前一位数字减一,处理后数字变为299

C++: 0ms,59MB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
int monotoneIncreasingDigits(int n)
{
string s=to_string(n);
for(int i=1;i<s.size();++i)
{
if(s[i]<s[i-1])
{
--s[i-1];
for(int j=i-1;j>=1;--j)
{
if(s[j]<s[j-1])
{
s[j]='9';
--s[j-1];
}
else
break;
}
for(int j=i;j<s.size();++j)
s[j]='9';
break;
}
}
n=0;
for(char& ch:s)
{
n*=10;
n+=ch-'0';
}
return n;
}
};

方法2:从右往左遍历(大佬的优雅方法)

C++: 4ms,5.9MB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int monotoneIncreasingDigits(int n)
{
string s=to_string(n);
int flag;
for(int i=s.size()-1;i>=1;--i)
if(s[i-1]>s[i])
{
flag=i;
--s[i-1];
}
for(int i=flag;i<s.size();++i)
s[i]='9';
return stoi(s);
}
};

大佬虽然优雅,但耗时有点长

贪心23:监控二叉树

leetcode968

val: 1->安装监控
2->接受下方节点监控
3->接受上方节点监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
int minCameraCover(TreeNode* root)
{
int ret=0;//摄像头总数
//采用后序遍历
function<void(TreeNode*)>dfs=[&](TreeNode* cur)
{
if(cur->left!=nullptr)
dfs(cur->left);
if(cur->right!=nullptr)
dfs(cur->right);
if(cur->left!=nullptr&&cur->left->val==3||cur->right!=nullptr&&cur->right->val==3)//左孩子和右孩子中存在接受上方监控的节点
{
cur->val=1;//给当前节点安装监控
++ret;
}
else if(cur->left!=nullptr&&cur->left->val==1||cur->right!=nullptr&&cur->right->val==1)//左孩子和右孩子中存在装有监控的节点
cur->val=2;//接受下方节点的监控
else
cur->val=3;//接受上方节点的监控
};
dfs(root);
if(root->val==3)//可能出现root节点接受上方节点监控的情况,这时需要给root安装监控
++ret;
return ret;
}
};

(11)图论专题

最小生成树:连接所有节点的最小费用

leetcode1584

本题为边稠密图,更适合普里姆算法

prim普里姆算法

从某一个顶点开始构建生成树,每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止

SysuGLM的解释:

Prim 算法是一种用于解决无向图最小生成树问题的贪心算法。它的基本思想是从一个顶点开始,不断地寻找与当前生成树距离最近的顶点,将其加入到生成树中,直到所有顶点都加入到生成树中为止。该算法是由捷克数学家 Vojtěch Jarník 于 1930 年发现的,并在 1957 年由美国计算机科学家 Robert C. Prim 重新发现,因此得名 Prim 算法。
以下是 Prim 算法的基本步骤:

  1. 初始化一个空的最小生成树 T。
  2. 任选图中一个顶点 v,将其加入到 T 中。
  3. 在图中寻找距离 T 最近的顶点 u,将边 (u, v) 加入到 T 中。
  4. 重复步骤 3,直到所有顶点都加入到 T 中。
  5. 得到的 T 即为原图的最小生成树。

Prim 算法的时间复杂度取决于具体的实现方式。最简单的实现方式是用邻接矩阵表示图,其时间复杂度为 O(n^2),其中 n 为图的顶点数。使用优先队列和邻接表可以进一步优化算法的性能,将时间复杂度降低到 O(nlogn)。

设总共V个节点,时间复杂度O(V2)O(V^2), 适用于边稠密图

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points)//prim算法
{
int n=points.size();//点的总数
vector<int>isjoin(n);//是否已被连接
vector<int>lowcost(n);//连入图的最小开销
lowcost[0]=0;
isjoin[0]=1;
for(int i=1;i<n;++i)
lowcost[i]=abs(points[i][0]-points[0][0])+abs(points[i][1]-points[0][1]);//初始化图,到0节点的最小距离
int ret=0;
for(int i=1;i<n;++i)
{
int cur_min=INT_MAX;
int cur_idx;
for(int j=1;j<n;++j)//寻找当前入图开销最小的节点
{
if(isjoin[j]==1)//排除已经入图的节点
continue;
if(lowcost[j]<cur_min)
{
cur_min=lowcost[j];
cur_idx=j;
}
}
ret+=cur_min;
isjoin[cur_idx]=1;
for(int j=1;j<n;++j)//更新所有节点的入图距离
{
if(isjoin[j]==1)
continue;
lowcost[j]=min(lowcost[j],abs(points[j][0]-points[cur_idx][0])+abs(points[j][1]-points[cur_idx][1]));
}
}
return ret;
}
};

leetcode1584通过,68ms, 9.84MB

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def minCostConnectPoints(self, points: List[List[int]]) -> int:
n,ret=len(points),0 ## 点的总数 最小总费用
cost,used=[0]*n,[False]*n ## 是否已被连接 连入图的最小开销
used[0]=True
for i in range(1,n): ## 初始化图,到0节点的距离
cost[i]= abs(points[i][0]-points[0][0])+abs(points[i][1]-points[0][1])
for i in range(n-1):
min_ele=inf
min_idx=-1
for j in range(n): ## 寻找当前入图开销最小的节点
if not used[j] and cost[j]<min_ele:
min_idx=j
min_ele=cost[j]
ret+=min_ele
used[min_idx]=True
for j in range(n): ## 更新所有节点的入图距离
if not used[j]:
cost[j]=min(cost[j],abs(points[j][0]-points[min_idx][0])+abs(points[j][1]-points[min_idx][1]))
return ret

leetcode1584通过,684ms, 16.41MB

对于本题,有n个顶点,时间复杂度O(n2)O(n^2)

Kruskal算法

SysuGLM的解释:

Kruskal 算法是一种贪心算法,用于求解连通网的最小生成树。具体步骤如下:

  1. 将所有的边按照权重从小到大排序。
  2. 初始化一个空集合,用于存放已选择的边。
  3. 按照排序后的顺序依次遍历每条边,检查当前边的两个顶点是否属于同一个连通分量(可以使用并查集数据结构进行快速判断)。如果不属于同一个连通分量,那么将这条边加入到已选择的边的集合中,并合并这两个顶点所在的连通分量;如果已经属于同一个连通分量,那么跳过这条边。
  4. 当已选择的边的数量等于顶点数减一时,算法结束。此时已选择的边构成了连通网的最小生成树。
    这种算法的时间复杂度为 O(ElogE),其中 E 是边的数量。

该算法利用Quick Find版本的并查集, 有关并查集Quick Find和Quick Union版本的区别, 参考本文。如果利用Quick Union版本的并查集,会超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
public:
vector<int>bing;
int find(int cur)
{
while(bing[cur]!=cur)
cur=bing[cur];
return cur;
}
int minCostConnectPoints(vector<vector<int>>& points) {
int n=points.size();
vector<pair<int,int>>edge;//图中所有可能的边
edge.reserve(n*(n-1)/2);
for(int i=0;i<n;++i)
for(int j=i+1;j<n;++j)
edge.emplace_back(make_pair(i,j));
sort(edge.begin(),edge.end(),[&](pair<int,int>&a,pair<int,int>&b){
return abs(points[a.first][0]-points[a.second][0])+
abs(points[a.first][1]-points[a.second][1])<
abs(points[b.first][0]-points[b.second][0])+
abs(points[b.first][1]-points[b.second][1]);
});//按照边从短到长排序
bing.resize(n);//初始化并查集数组
for(int i=0;i<n;++i)
bing[i]=i;
int ret=0;
for(auto& p:edge)
{
int pf=find(p.first);
int ps=find(p.second);
if(pf==ps)
continue;
else
{
ret+=abs(points[p.first][0]-points[p.second][0])+abs(points[p.first][1]-points[p.second][1]);
bing[ps]=pf;
bing[p.second]=pf;
}
}
return ret;
}
};

leetcode1584通过,1808ms, 24.37MB

对于本题,共n个顶点,每两个顶点之间都有边连接,E=n2|E|=n^2,所有时间复杂度是O(n2logn)O(n^2\log n)

最短路径问题:

image-20230817173317209

图出自王道考研数据结构

参考视频【王道计算机考研 数据结构】

例题:

BFS算法比较基础,且只能处理不带权的图,这里就不介绍了

Dijkstra算法

当前算法适用于稠密图

求带权图的最短路径长度,局限是不能处理负权图

对于一系列顶点V0,V1,...,Vn1V_0,V_1,...,V_{n-1}组成的有向带权图,求V0V_0到各个顶点的最短路径长度

初始化三个数组:

  • bool final[n] 标记各个顶点是否已经找到最短路径,初始化为False
  • int dist[n] 到达各个顶点的最短路径长度,初始化为++\infin
  • int path[n] 各个顶点在路径上的前驱,初始化为-1

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#define inf 100000000
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) //Dijkstra算法
{
vector<vector<int>>g(n+1,vector<int>(n+1,inf));//邻接矩阵
for(auto&time:times)
g[time[0]][time[1]]=time[2];
vector<int>dist(n+1,inf);//到达各个顶点的最短路径长度
vector<bool>used(n+1,false);//标记各个顶点是否已经找到最短路径,初始化为false
vector<int>path(n+1,-1);//各个顶点在路径上的前驱,初始化为-1,在本题可有可无
dist[k]=0;//从k开始搜索
for(int i=0;i<n;++i)
{
//找到当前dist数组中值最小的位置下标
int idx=-1,cur_min=inf;
for(int j=1;j<=n;++j)
{
if(!used[j]&&dist[j]<cur_min)
{
cur_min=dist[j];
idx=j;
}
}
if(idx==-1)
break;
used[idx]=true;
//考虑经过idx的路径,修改dist数组并更新path数组
for(int j=1;j<=n;++j)
{
if(!used[j]&&dist[j]>dist[idx]+g[idx][j])
{
dist[j]=dist[idx]+g[idx][j];
path[j]=idx;
}
}
}
// for(int i=1;i<=n;++i)
// cout<<path[i]<<' ';
int ret=*max_element(dist.begin()+1,dist.end());
return ret==inf?-1:ret;
}
};

leetcode743通过,100ms,36.9MB

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution:  ## Dijkstra算法
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
dist=[inf]*(n+1) ## 到达各个顶点的最短路径长度
used=[False]*(n+1) ## 标记各个顶点是否已经找到最短路径,初始化为false
path=[-1]*(n+1) ## 各个顶点在路径上的前驱,初始化为-1,在本题可有可无
dist[k]=0 ## 从k开始搜索
arr=[[inf]*(n+1) for _ in range(n+1)] ## 邻接矩阵
for time in times:
arr[time[0]][time[1]]=time[2]
for t in range(n):
## 找到当前dist值最小的元素下标cur
cur=-1
cur_min=inf
for idx,distance in enumerate(dist):
if not used[idx] and distance<cur_min:
cur_min=distance
cur=idx
used[cur]=True
#根据以cur为起点能到达的点,更新dist数组
for new_point,distance in enumerate(arr[cur]):
if dist[cur]+distance<dist[new_point]:
dist[new_point]=dist[cur]+distance
path[new_point]=cur
max_element=max(dist[1:len(dist)])
## print(path[1:len(path)])
return -1 if max_element==inf else max_element

leetcode743通过,60ms,17.7MB

复杂度分析

节点总数为V|V|,要遍历一次所有顶点,对于每一次遍历,要更新与当前节点直接相连,并且final值为false的全部节点的dist值,该过程遍历V|V|次;还要找出从当前节点出发,下一个最近的节点,遍历V|V|

时间复杂度O(V2)O(|V|^2),空间复杂度O(V2)O(|V|^2).(邻接矩阵占用的空间)

当前算法只能求一个顶点到其他所有顶点的最短路径,如果需要求所有顶点到其他顶点的最短路径,则需要将该算法循环V|V|次,总的时间复杂度O(V3)O(|V|^3)

Dijkstra算法优化:使用小根堆priority_queue

当前算法适用于稀疏图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#define inf 100000000
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) //Dijkstra算法优化
{
vector<vector<int>>g(n+1,vector<int>(n+1,inf));//邻接矩阵
for(auto&time:times)
g[time[0]][time[1]]=time[2];
vector<int>dist(n+1,inf);//到达各个顶点的最短路径长度
vector<bool>used(n+1,false);//标记各个顶点是否已经找到最短路径,初始化为false
vector<int>path(n+1,-1);//各个顶点在路径上的前驱,初始化为-1,在本题可有可无
dist[k]=0;
using pii=pair<int,int>;
priority_queue<pii,vector<pii>,greater<>>pq;//小根堆, first为dist值, second为dist下标, 排序由first决定
pq.push(make_pair(0,k));
while(!pq.empty())
{
int distance=pq.top().first;
int cur=pq.top().second;
pq.pop();
if(distance>dist[cur])//可能存在有的结点被多次加入优先队列,这里需要提出dist值曾经被更新的结点
continue;
used[cur]=true;
for(int i=1;i<=n;++i)
{
if(!used[i]&&dist[cur]+g[cur][i]<dist[i])
{
dist[i]=dist[cur]+g[cur][i];
path[i]=cur;
pq.push(make_pair(dist[i],i));
}
}
}
// for(int i=1;i<=n;++i)
// cout<<path[i]<<' ';
int maximum=*max_element(dist.begin()+1,dist.end());
return maximum==inf?-1:maximum;
}
};

leetcode743通过,112ms,36MB

复杂度分析

设顶点数为V|V|,边数为E|E|,小根堆的最大长度为E|E|,小根堆的插入和删除时间复杂度为O(logE)O(\log |E|),总共进行E|E|次遍历,总时间复杂度O(ElogE)O(|E|\log |E|)

dist数组占用空间O(V)O(|V|),vec数组和小根堆占用空间O(E)O(|E|),总空间复杂度O(E+V)O(|E|+|V|)

Floyd算法

Floyd算法更加通用,可以解决带负权值的图,但前提是最短路径存在

graph RL;
A==3==>B
B==4==>C
C==-9==>A

如上图所示,要求A到C的最短路径,如果选择的路径是沿着箭头一直前进,每循环一圈路径长度减2,A到C的路径可以无穷小,不存在最短路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define inf 100000
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) //floyd算法
{
vector<vector<int>>arr(n+1,vector<int>(n+1,inf));//代表各顶点间的最短路径长度,初始化为邻接表
for(auto& time:times)
arr[time[0]][time[1]]=time[2];//不允许在其他节点中转,最短路径是
vector<vector<int>>path(n+1,vector<int>(n+1,-1));//两个节点之间的中转点,在本题可有可无
for(int i=1;i<=n;++i)//依次考虑以第i个节点为中转,更新
{
for(int j=1;j<=n;++j)//遍历所有可能的方向
{
for(int m=1;m<=n;++m)
{
if(arr[j][m]>arr[j][i]+arr[i][m])
{
arr[j][m]=arr[j][i]+arr[i][m];
path[j][m]=i;
}
}
}
}
int ret=0;
for(int i=1;i<=n;++i)
{
if(i==k)
continue;
ret=max(ret,arr[k][i]);
}
return ret==inf?-1:ret;
}
};

leetcode743通过,160ms,37MB

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:  ## Floyd 算法
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
arr=[[inf]*(n+1) for _ in range(n+1)] ## 邻接表
path=[[-1]*(n+1) for _ in range(n+1)] ## 记录中转点,在本题可有可无
for time in times:
arr[time[0]][time[1]]=time[2]
for t in range(1,n+1): ## 中转点
for i in range(1,n+1): ## 行下标
for j in range(1,n+1): ## 列下标
if arr[i][t]+arr[t][j]<arr[i][j]:
path[i][j]=t
arr[i][j]=arr[i][t]+arr[t][j]
arr[k][k]=0
ret=max(arr[k][1:n+1]) ## 找到从k出发到其他顶点的最大距离
return ret if ret!=inf else -1

leetcode743通过,352ms,18.56MB

复杂度分析

该过程相当于对一个三维数组的动态规划(在内存使用上可以只使用二维数组),时间复杂度O(V3)O(|V|^3),空间复杂度O(V2)O(|V|^2)

Bellman-ford算法

(引用自百度百科)

贝尔曼-福特算法与迪科斯彻算法类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。在两个算法中,计算时每个边之间的估计距离值都比真实值大,并且被新找到路径的最小长度替代。 然而,迪科斯彻算法以贪心法选取未被处理的具有最小权值的节点,然后对其的出边进行松弛操作;而贝尔曼-福特算法简单地对所有边进行松弛操作,共V1|V|-1

次,其中V|V|是图的点的数量。在重复地计算中,已计算得到正确的距离的边的数量不断增加,直到所有边都计算得到了正确的路径。这样的策略使得贝尔曼-福特算法比迪科斯彻算法适用于更多种类的输入。

CSDN Bellman-ford算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define inf 10000000
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) //Bellman-ford算法
{
vector<int>dist(n+1,inf);
dist[k]=0;
for(int i=0;i<n-1;++i)//循环n-1次,一定能松弛所有顶点,否则没有被松弛的收不到信号
{
for(auto& time:times)//遍历所有边
{
if(dist[time[0]]+time[2]<dist[time[1]])//松弛操作
dist[time[1]]=dist[time[0]]+time[2];
}
}
int ret=*max_element(dist.begin()+1,dist.end());
return ret==inf?-1:ret;
}
};

leetcode743通过,152ms,35.7MB

时间复杂度O(VE)O(|V||E|),其中V|V|是顶点数,E|E|是边数

SPFA算法

SPFA算法是对Bellman-ford算法的优化

shortest path faster algorithm

参考文献CSDN SPFA算法,画图详细解释了该算法,适合小白

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define inf 100000000
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k)
{
queue<int>que;
vector<int>dist(n+1,inf);
que.push(k);
dist[k]=0;
using Pair=pair<int,int>;
vector<vector<Pair>>vec(n+1);//从i节点到vec[i].first有一条长度为vec[i].second的路径
for(auto& time:times)
vec[time[0]].emplace_back(time[1],time[2]);
while(!que.empty())
{
int front=que.front();
que.pop();
auto& ve=vec[front];
for(auto& p:ve)
{
if(dist[p.first]>dist[front]+p.second)
{
dist[p.first]=dist[front]+p.second;
que.push(p.first);
}
}
}
int ret=*max_element(dist.begin()+1,dist.end());
return ret==inf?-1:ret;
}
};

leetcode743通过,84ms,39MB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
#define inf 0x7fffffff
int main()//SPFA算法
{
int n,m,s;
cin>>n>>m>>s;
int u,v,w;
using Pair=pair<int,int>;
vector<vector<Pair>> g(n+1);//从i节点到g[i].first有一条长度为g[i].second的路径
for(int i=0;i<m;++i)
{
cin>>u>>v>>w;
g[u].emplace_back(v,w);
}
vector<int> dist(n+1,inf);
dist[s]=0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int front=q.front();
q.pop();
for(auto& p:g[front])
{
if(dist[p.first]>dist[front]+p.second)
{
dist[p.first]=dist[front]+p.second;
q.push(p.first);
}
}
}
for(int i=1;i<=n;++i)
cout<<dist[i]<<" ";
cout<<endl;
return 0;
}

洛谷P3371 【模板】单源最短路径(弱化版)通过

SPFA的复杂度大约是O(kE)O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。

引用出处

拓扑排序

参考王道考研数据结构

AOV网(Activity On Vertex Network):用顶点表示活动的网

用DAG图(有向无环图)表示一个工程,顶点表示活动,有向边<Vi,Vj><V_i,V_j>表示活动ViV_i必须先于活动VjV_j进行

graph LR;
准备厨具 --> 打鸡蛋
准备厨具 --> 切番茄
买菜 -->打鸡蛋
买菜-->洗番茄
洗番茄-->切番茄
打鸡蛋-->下锅炒
切番茄-->下锅炒
下锅炒-->吃番茄炒蛋

表示“番茄炒蛋工程”的AOV网

拓扑排序的一种结果:

graph LR;
准备厨具-->买菜
买菜-->洗番茄
洗番茄-->切番茄
切番茄-->打鸡蛋
打鸡蛋-->下锅炒
下锅炒-->吃番茄炒蛋

拓扑排序的实现:

  1. 从AOV网中选择一个没有前驱==(入度为0)==的顶点并输出
  2. 在网中删除该顶点和所有以它为起点的有向边
  3. 重复步骤1和2直到当前的==AOV网为空==或==当前网中不存在无前驱的顶点(说明有回路)==为止

出现回路的情况:

graph LR;
洗番茄--->切番茄
切番茄--->洗番茄
切番茄-->下锅炒
下锅炒-->吃番茄炒蛋

当前所有顶点的入度>0,说明原图存在回路

课程表

leetcode207

方法1:BFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites)
{
vector<vector<int>>vec(numCourses);//vec[i]存储完成课程i之后才能完成的课程
vector<int>indegree(numCourses);//indegree[i]存储课程i在AOV网中的全部前驱节点
for(auto& pre:prerequisites)
{
vec[pre[1]].emplace_back(pre[0]);
++indegree[pre[0]];
}
stack<int>sta;//当前所有入度为零的节点下标
for(int i=0;i<numCourses;++i)
if(indegree[i]==0)
sta.push(i);//初始化栈
int cnt=0;//统计当前已经遍历的节点数量
while(!sta.empty())
{
int cur=sta.top();
++cnt;
sta.pop();
auto& ve=vec[cur];
for(int& ve_:ve)
{
--indegree[ve_];
if(indegree[ve_]==0)
sta.push(ve_);
}
}
return cnt==numCourses;
}
};

方法2:DFS

参考力扣官方题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites)
{
vector<vector<int>>vec(numCourses);//vec[i]存储完成课程i之后才可以完成的课程
vector<bool>isvisited(numCourses,false);//记录节点i是否正在被访问
vector<bool>isInStack(numCourses,false);//记录节点i是否已经入栈
for(auto& pre:prerequisites)
{
vec[pre[1]].emplace_back(pre[0]);//初始化vec数组
}
int flag=0;//当flag==1时,说明正在被访问的结果遭到二次访问,说明图中有环,返回空数组
function<void(int)>dfs=[&](int idx){
if(flag)
return;
isvisited[idx]=true;
auto& ve=vec[idx];//当前节点直接指向的节点
for(int& ve_:ve)
{
if(!isvisited[ve_])//确保正在被访问的结果没有遭到二次访问
{
if(!isInStack[ve_])
dfs(ve_);
}
else
{
flag=1;
return;
}
}
isvisited[idx]=false;
isInStack[idx]=true;
};
for(int i=0;i<numCourses;++i)
if(!isInStack[i])
{
dfs(i);
if(flag)
return false;
}
return true;

}
};

课程表II

leetcode210

方法1:BFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites)
{
vector<vector<int>>vec(numCourses);//vec[i]存储完成课程i之后才可以完成的课程
vector<int>indegree(numCourses);//indegree[i]存储课程i节点的所有入度
vector<int>ret;//返回的排序数组
ret.reserve(numCourses);//预留空间
stack<int>sta;
for(auto& pre:prerequisites)
{
vec[pre[1]].emplace_back(pre[0]);//初始化vec数组
++indegree[pre[0]];//初始化indegree数组
}
for(int i=0;i<numCourses;++i)
if(indegree[i]==0)
sta.push(i);
int cnt=0;//已经遍历过的节点数量
while(!sta.empty())
{
++cnt;
int cur=sta.top();
sta.pop();
ret.emplace_back(cur);
auto& ve=vec[cur];//完成当前课程之后才可以完成的课程
for(int& ve_:ve)
{
--indegree[ve_];
if(indegree[ve_]==0)
sta.push(ve_);
}
}
if(cnt==numCourses)
return ret;
else
return vector<int>();
}
};

方法2:DFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites)
{
vector<vector<int>>vec(numCourses);//vec[i]存储完成课程i之后才可以完成的课程
vector<bool>isvisited(numCourses,false);//记录节点i是否正在被访问
vector<bool>isInStack(numCourses,false);//记录节点i是否已经入栈
stack<int>sta;//存储逆拓扑排序序列
for(auto& pre:prerequisites)
{
vec[pre[1]].emplace_back(pre[0]);//初始化vec数组
}
int flag=0;//当flag==1时,说明正在被访问的结果遭到二次访问,说明图中有环,返回空数组
function<void(int)>dfs=[&](int idx){
if(flag)
return;
isvisited[idx]=true;
auto& ve=vec[idx];//当前节点直接指向的节点
for(int& ve_:ve)
{
if(!isvisited[ve_])//确保正在被访问的结果没有遭到二次访问
{
if(!isInStack[ve_])
dfs(ve_);
}
else
{
flag=1;
return;
}
}
sta.push(idx);
isvisited[idx]=false;
isInStack[idx]=true;
};
for(int i=0;i<numCourses;++i)
if(!isInStack[i])
{
dfs(i);
if(flag)
return vector<int>();
}
vector<int>ret;
ret.reserve(numCourses);
while(!sta.empty())
{
ret.emplace_back(sta.top());
sta.pop();
}
return ret;
}
};

图论1:统计点对的数目

leetcode1782

参考灵神的题解

利用算法:[哈希表6.9:两数之和 II - 输入有序数组](#哈希表6.9:两数之和 II - 输入有序数组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public:
vector<int> countPairs(int n, vector<vector<int>>& edges, vector<int>& queries)
{
vector<int>deg(n);
unordered_map<int,int>cnt_e;
for(auto& edge:edges)
{
int x=edge[0]-1,y=edge[1]-1;
if(x>y)
swap(x,y);
++deg[x];
++deg[y];
++cnt_e[x<<16|y];
}
vector<int>sorted_deg=deg;
vector<int>ans(queries.size());
sort(sorted_deg.begin(),sorted_deg.end());
for(int j=0;j<queries.size();++j)
{
int q=queries[j];
int left=0,right=n-1;
while(left<right)
{
if(sorted_deg[left]+sorted_deg[right]>q)
{
ans[j]+=right-left;
--right;
}
else
++left;
}
for(auto& e:cnt_e)
{
int x=e.first>>16;
int y=e.first&0xffff;
int num=e.second;
if(deg[x]+deg[y]>q&&deg[x]+deg[y]-num<=q)
--ans[j];
}
}
return ans;
}
};

图论2:课程表IV

leetcode1462

Floyd算法:

python

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:  ## floyd 算法
def checkIfPrerequisite(self, numCourses: int, prerequisites: List[List[int]], queries: List[List[int]]) -> List[bool]:
arr=[[False]*numCourses for _ in range(numCourses)]
for pre,now in prerequisites:
arr[pre][now]=True
for k in range(numCourses):
for i in range(numCourses):
for j in range(numCourses):
arr[i][j]=arr[i][j] or arr[i][k] and arr[k][j]
ret=[False]*len(queries)
for idx,[pre,now] in enumerate(queries):
ret[idx]=arr[pre][now]
return ret

广度优先搜索+记忆化

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Solution {
public:
vector<bool> checkIfPrerequisite(int numCourses, vector<vector<int>>& prerequisites, vector<vector<int>>& queries)
{
vector<vector<int>>arr(numCourses);
for(auto& ar:arr)
{
ar.reserve(numCourses);
}
for(auto& pre:prerequisites)
{
arr[pre[1]].emplace_back(pre[0]);
}
vector<bool>ret;
ret.reserve(queries.size());
for(auto& que:queries)
{
int pre=que[0],now=que[1];
bool flag=false;
unordered_map<int,int>hash;
queue<int>q;
for(int& fin:arr[now])
{
q.push(fin);
if(fin==pre)
{
flag=true;
break;
}
}
int cnt=arr[now].size();
while(!q.empty()&&!flag)
{
--cnt;
int cur=q.front();
q.pop();
if(hash[cur]==1)
continue;
hash[cur]=1;
if(cnt<0)
arr[now].emplace_back(cur);
for(int&fin:arr[cur])
{
if(fin==pre)
{
flag=true;
break;
}
q.push(fin);
}
}
ret.emplace_back(flag);
}
return ret;
}
};

图论3:所有可能的路径

leetcode797

dfs搜索所有路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph)
{
vector<vector<int>> ret;
vector<int>tmp;
function<void(int)>dfs=[&](int cur){
tmp.emplace_back(cur);
if(cur==graph.size()-1)
{
ret.emplace_back(tmp);
tmp.pop_back();
return;
}
for(int& next:graph[cur])
{
dfs(next);
}
tmp.pop_back();
};
dfs(0);
return ret;
}
};

图论4:岛屿数量

leetcode200

dfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
int numIslands(vector<vector<char>>& grid)
{
int m=grid.size();
int n=grid[0].size();
function<bool(int,int)>judge=[&](int x,int y)
{
return x>=0&&x<m&&y>=0&&y<n&&grid[x][y]=='1';
};
int ret=0;

for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
if(judge(i,j))
{
function<void(int,int)>dfs=[&](int x,int y)
{
grid[x][y]='2';
if(judge(x+1,y))
dfs(x+1,y);
if(judge(x-1,y))
dfs(x-1,y);
if(judge(x,y+1))
dfs(x,y+1);
if(judge(x,y-1))
dfs(x,y-1);
};
dfs(i,j);
++ret;
}
}
}
return ret;
}
};

图论5:岛屿的最大面积

leetcode695

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid)
{
int m=grid.size();
int n=grid[0].size();
function<bool(int,int)>judge=[&](int x,int y)
{
return x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1;
};
int ret=0;

for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
if(judge(i,j))
{
int cur=0;
function<void(int,int)>dfs=[&](int x,int y)
{
++cur;
grid[x][y]=-1;
if(judge(x+1,y))
dfs(x+1,y);
if(judge(x-1,y))
dfs(x-1,y);
if(judge(x,y+1))
dfs(x,y+1);
if(judge(x,y-1))
dfs(x,y-1);
};
dfs(i,j);
ret=max(ret,cur);
}
}
}
return ret;
}
};

图论6:飞地的数量

leetcode1020

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Solution {
public:
int numEnclaves(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
function<bool(int,int)>judge=[&](int x,int y){//只判断越界
return x>=0&&x<m&&y>=0&&y<n;
};
int ret=0;
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
if(grid[i][j]==1)
{
int cnt=0;
bool flag=false;
function<void(int,int)>dfs=[&](int x,int y){
++cnt;
++ret;
grid[x][y]=0;
if(judge(x-1,y))
{
if(grid[x-1][y]==1)
dfs(x-1,y);
}
else
flag=true;
if(judge(x+1,y))
{
if(grid[x+1][y]==1)
dfs(x+1,y);
}
else
flag=true;
if(judge(x,y-1))
{
if(grid[x][y-1]==1)
dfs(x,y-1);
}
else
flag=true;
if(judge(x,y+1))
{
if(grid[x][y+1]==1)
dfs(x,y+1);
}
else
flag=true;
};
dfs(i,j);
if(flag)
ret-=cnt;
}
}
}
return ret;
}
};

相似题目:统计封闭岛屿的数目

leetcode1254

图论9:太平洋大西洋水流问题

leetcode417

从四边形边界开始dfs搜索,搜索的条件为下一个待搜索的位置高度比当前所在位置高

共进行二轮搜索,第一轮从最左列和最上行出发,第二轮从最下行和最右列出发

最后返回两轮都被搜索到的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#define judge(x,y) ((x)<m&&(y)<n&&(x)>=0&&(y)>=0&&tmp[(x)][(y)]==0)
class Solution {
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
int m=heights.size();
int n=heights[0].size();
vector<vector<int>>arr(m,vector<int>(n));
vector<vector<int>>tmp(m,vector<int>(n));
function<void(int,int)>dfs=[&](int x,int y){
tmp[x][y]=1;
++arr[x][y];
if(judge(x+1,y)&&heights[x+1][y]>=heights[x][y])
dfs(x+1,y);
if(judge(x-1,y)&&heights[x-1][y]>=heights[x][y])
dfs(x-1,y);
if(judge(x,y+1)&&heights[x][y+1]>=heights[x][y])
dfs(x,y+1);
if(judge(x,y-1)&&heights[x][y-1]>=heights[x][y])
dfs(x,y-1);
};
for(int j=0;j<n;++j)//扫描最上行
if(judge(0,j))
dfs(0,j);
for(int i=0;i<m;++i)//扫描最左列
if(judge(i,0))
dfs(i,0);
for(int i=0;i<m;++i)
for(int j=0;j<n;++j)
tmp[i][j]=0;
for(int j=0;j<n;++j)//扫描最下行
if(judge(m-1,j))
dfs(m-1,j);
for(int i=0;i<m;++i)//扫描最右列
if(judge(i,n-1))
dfs(i,n-1);
vector<vector<int>>ret;
for(int i=0;i<m;++i)
for(int j=0;j<n;++j)
if(arr[i][j]==2)
ret.push_back({i,j});
return ret;
}
};

图论10:最大人工岛

leetcode827

使用并查集

第一次遍历整个矩阵中值为1的部分,连接所有岛屿

第二次遍历整个矩阵中值为0的部分,考虑将当前遍历的点变为岛屿,可以连接的岛屿面积有多大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#define judge(x,y) ((x)>=0&&(x)<n&&(y)>=0&&(y)<n&&grid[(x)][(y)]==1)
class Solution {
public:
int n;
vector<int>djs;//Disjoint-set
vector<int>area;//面积
int findParent(int x,int y)
{
int cur=y+x*n;
while(djs[cur]!=cur)
cur=djs[cur];
return cur;
}
void join(int x1,int y1,int x2,int y2)
{
int c1=y1+n*x1;
int c2=y2+n*x2;
int p1=findParent(x1,y1);
int p2=findParent(x2,y2);
if(p1>p2)
swap(p1,p2);
else if(p1==p2)
return;
djs[p2]=p1;
djs[c2]=p1;
djs[c1]=p1;
area[p1]+=area[p2];
area[p2]=0;
}
int largestIsland(vector<vector<int>>& grid) {
n=grid.size();
djs.resize(n*n);
for(int i=0;i<n*n;++i)
djs[i]=i;
area.resize(n*n,1);
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
if(grid[i][j]==0)
continue;
if(judge(i-1,j))
join(i,j,i-1,j);
if(judge(i+1,j))
join(i,j,i+1,j);
if(judge(i,j-1))
join(i,j,i,j-1);
if(judge(i,j+1))
join(i,j,i,j+1);
}
}
int ret=0;
int cnt=0;//统计岛屿总数,判断是不是n*n
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
if(grid[i][j]==1)
{
++cnt;
continue;
}
int up=-1,down=-1,left=-1,right=-1;
if(judge(i-1,j))
up=findParent(i-1,j);
if(judge(i+1,j))
down=findParent(i+1,j);
if(judge(i,j-1))
left=findParent(i,j-1);
if(judge(i,j+1))
right=findParent(i,j+1);
int add=1;
if(up!=-1)
add+=area[up];
if(down!=-1&&down!=up)
add+=area[down];
if(left!=-1&&left!=up&&left!=down)
add+=area[left];
if(right!=-1&&right!=up&&right!=down&&right!=left)
add+=area[right];
ret=max(ret,add);
}
}
if(cnt==n*n)
ret=cnt;
return ret;
}
};

时间复杂度O(n2)O(n^2)

图论11:单词接龙

leetcode127

利用BFS搜索可能的单词转换路径

挑战:用C语言解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//手撸STL queue队列(链表实现)
typedef int qtype;
struct queue
{
struct qnode* rear;
struct qnode* front;
};
struct qnode
{
qtype data;
struct qnode* next;
};
void QMake(struct queue* q)
{
q->front = (struct qnode*)malloc(sizeof(struct qnode));
q->rear = q->front;
q->front->next = NULL;
}
bool QPush(struct queue* q, qtype data)
{
struct qnode* p = (struct qnode*)malloc(sizeof(struct qnode));
p->data = data;
p->next = NULL;
q->rear->next = p;
q->rear = p;
return true;
}
qtype QRead(struct queue* q)
{
if (q->front->next == NULL)
return -1;
return q->front->next->data;
}
qtype QPop(struct queue* q)
{
if (q->front->next == NULL)
return -1;
struct qnode* rubbish = q->front->next;
if (q->rear == rubbish)
{
q->rear = q->front;
}
qtype ret = rubbish->data;
q->front->next = rubbish->next;
free(rubbish);
return ret;
}
int QLength(struct queue* q)
{
int ret = 0;
struct qnode* p;
p = q->front->next;
while (p!=NULL)
{
ret++;
p = p->next;
}
return ret;
}
bool QEmpty(struct queue* q)
{
return QLength(q) == 0;
}
//正片开始
int ladderLength(char* beginWord, char* endWord, char** wordList, int wordListSize)
{
int be = -1, p = 0;//be为beginword在邻接矩阵中的位置
int whether_beginword_in_graph = 1;
int wordlen = strlen(beginWord);//单词长度
for (int i = 0; i < wordListSize; i++)
{
if (strcmp(wordList[i], endWord) == 0)
p = 1;
if (strcmp(wordList[i], beginWord) == 0)
be = i;
}
if (p == 0)
return 0;//endword不在字典中
int** graph;//创建邻接表
int* if_searched;//判断元素是否被搜索过
int* search;//记录搜索轮数
if (be == -1)//beginword是否在邻接表中
{
if_searched = malloc(sizeof(int) * (wordListSize + 1));
search = malloc(sizeof(int) * (wordListSize + 1));
memset(if_searched, 0, (wordListSize + 1)*sizeof(int));
memset(search, 0, (wordListSize + 1) * sizeof(int));
be = wordListSize;
whether_beginword_in_graph = 0;
graph = malloc((wordListSize + 1) * sizeof(int*));
for (int i = 0; i < wordListSize + 1; i++)
graph[i] = malloc((wordListSize + 1) * sizeof(int));
char* wordi, * wordj;
for (int i = 0; i < wordListSize + 1; i++)
{
for (int j = 0; j < wordListSize + 1; j++)
{
wordi = (i == wordListSize ? beginWord : wordList[i]);
wordj = (j == wordListSize ? beginWord : wordList[j]);
int cnt = 0;
for (int k = 0; k < wordlen; k++)
{
if (wordi[k] != wordj[k])
cnt++;
}
graph[i][j] = cnt == 1 ? 1 : 0;//cnt=1时为近义词
}
}
int curword;//当前正在处理的单词标号
struct queue myque;
QMake(&myque);
QPush(&myque, be);
if_searched[be] = 1;
search[be] = 1;
while (1)
{
if (QEmpty(&myque))
break;
curword = QPop(&myque);
for (int j = 0; j < wordListSize + 1; j++)
{
if (graph[curword][j] == 1 && if_searched[j] == 0)
{
search[j] = search[curword] + 1;
if_searched[j] = 1;
QPush(&myque, j);
if (strcmp(wordList[j], endWord) == 0)
{
return search[j];
}
}
}
}
}
else
{
if_searched = malloc(sizeof(int) * (wordListSize));
search = malloc(sizeof(int) * (wordListSize + 1));
memset(if_searched, 0, wordListSize*sizeof(int));
memset(search, 0, wordListSize*sizeof(int));
graph = malloc((wordListSize) * sizeof(int*));
for (int i = 0; i < wordListSize; i++)
graph[i] = malloc((wordListSize) * sizeof(int));
char* wordi, * wordj;
for (int i = 0; i < wordListSize; i++)
{
for (int j = 0; j < wordListSize; j++)
{
wordi = wordList[i];
wordj = wordList[j];
int cnt = 0;
for (int k = 0; k < wordlen; k++)
{
if (wordi[k] != wordj[k])
cnt++;
}
graph[i][j] = cnt == 1 ? 1 : 0;//cnt=1时为近义词
}
}
int curword;//当前正在处理的单词标号
struct queue myque;
QMake(&myque);
QPush(&myque, be);
if_searched[be] = 1;
search[be] = 1;
while (1)
{
if (QEmpty(&myque))
break;
curword = QPop(&myque);
for (int j = 0; j < wordListSize; j++)
{
if (graph[curword][j] == 1 && if_searched[j] == 0)
{
search[j] = search[curword] + 1;
if_searched[j] = 1;
QPush(&myque, j);
if (strcmp(wordList[j], endWord) == 0)
{
return search[j];
}
}
}
}
}
return 0;
}

图论12:钥匙和房间

leetcode841

BFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
queue<int>q;
q.push(0);
int n=rooms.size();
if(n==0)
return true;
vector<int>v(n);
v[0]=1;
int cnt=0;
while(!q.empty())
{
int cur=q.front();
q.pop();
++cnt;
vector<int>&r=rooms[cur];
for(int& i:r)
if(v[i]==0)
{
q.push(i);
v[i]=1;
}
}
return cnt==n;
}
};

图论16:冗余连接

leetcode684

使用并查集,从前到后遍历所有边,连接当前边的两个结点,直到当前两个结点的父结点相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
int n;
vector<int>v;
int find_parent(int cur)
{
while(v[cur]!=cur)
cur=v[cur];
return cur;
}
void join(int a,int b)
{
int pa=find_parent(a);
int pb=find_parent(b);
v[pb]=pa;
}
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n=edges.size();
v.resize(n+1);
for(int i=1;i<=n;++i)
v[i]=i;
for(auto& ed:edges)
{
int p1=find_parent(ed[0]);
int p2=find_parent(ed[1]);
if(p1==p2)
return ed;
else
join(p1,p2);
}
return vector<int>();
}
};

(12)动态规划专题

==代码随想录的刷题顺序:==

动态规划3:爬楼梯

点击跳转

动态规划4:使用最小花费爬楼梯

点击跳转

动态规划6:不同路径

leetcode62

方法1:数学,利用组合数直接计算Cm+n2m1C_{m+n-2}^{m-1}

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef long long ll;
int uniquePaths(int m, int n)
{
int a=m+n-2,b=m-1<n-1?m-1:n-1;
if(b==0)
return 1;
ll ret=1;
for(int i=0;i<b;++i)
{
ret=ret*(a-i)/(i+1);
}
return ret;
}

方法2:动态规划

python:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
@cache
def dfs(x,y):
if x==1 and y==1:
return 1
if x==1:
return dfs(x,y-1)
if y==1:
return dfs(x-1,y)
return dfs(x-1,y)+dfs(x,y-1)
return dfs(m,n)

动态规划7:不同路径II

leetcode63

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obstacleGridColSize)
{
if(obstacleGrid[0][0]==1)
return 0;
int row=obstacleGridSize;
int col=obstacleGridColSize[0];
int** arr=(int**)malloc(sizeof(int*)*(row+1));
for(int i=0;i<=row;++i)
{
arr[i]=(int*)malloc(sizeof(int)*(col+1));
memset(arr[i],0,(col+1)*sizeof(int));
}
arr[1][1]=1;
for(int i=1;i<=row;++i)
{
int j=1;
if(i==1)
j=2;
for(;j<=col;++j)
{
if(obstacleGrid[i-1][j-1]==1)
continue;
arr[i][j]=arr[i-1][j]+arr[i][j-1];
}
}
int ret=arr[row][col];
for(int i=0;i<=row;++i)
free(arr[i]);
free(arr);
return ret;
}

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
@cache
def dfs(x,y):
if obstacleGrid[x-1][y-1]==1:
return 0
if x==1 and y==1:
return 1
if x==1:
return dfs(x,y-1)
if y==1:
return dfs(x-1,y)
return dfs(x-1,y)+dfs(x,y-1)
return dfs(len(obstacleGrid),len(obstacleGrid[0]))

动态规划8:整数拆分

点击跳转

动态规划9:不同的二叉搜索树

点击跳转

==0-1背包系列 13~17==

动态规划13:分割等和子集

点击跳转

动态规划14:最后一块石头的重量II

点击跳转

动态规划16:目标和

点击跳转

动态规划17:一和零

点击跳转

==完全背包系列 19~26==

动态规划19:零钱兑换II

点击跳转

动态规划21:组合总和IV

点击跳转

动态规划23:零钱兑换

点击跳转

动态规划24:完全平方数

点击跳转

动态规划26:单词拆分

leetcode139

方法1:BFS(广度优先搜索)+记忆化搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict)
{
unordered_map<int,int>hash;
queue<int> que;
int wlen=wordDict.size();
int slen=s.size();
que.push(0);
while(!que.empty())
{
int cur=que.front();
if(cur==slen)
return true;
que.pop();
vector<int>wor(wlen,1);
int findnum=wlen;
for(int i=0;cur+i<=slen&&findnum>0;++i)
{
for(int j=0;j<wlen;++j)
{
if(wor[j]==0)
continue;
if(i>=wordDict[j].size())
{
if(hash[cur+wordDict[j].size()]==0)
que.push(cur+wordDict[j].size());
hash[cur+wordDict[j].size()]=1;
--findnum;
wor[j]=0;
continue;
}
if(s[cur+i]!=wordDict[j][i])
{
--findnum;
wor[j]=0;
}
}
}
}
return false;
}
};

方法2:完全背包

(出自代码随想录)

单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。

拆分时可以重复使用字典中的单词,说明就是一个完全背包!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
@cache
def dp(i,c):
if c==-1:
return 1
if i<0:
return 0
flag=1
le=len(wordDict[i])
if c<le-1:
return dp(i-1,c)
for idx,ch in enumerate(wordDict[i]):
if s[c-le+1+idx]!=ch:
flag=0
break
if flag==1:
return dp(i-1,c)+dp(len(wordDict)-1,c-le)
return dp(i-1,c)
return dp(len(wordDict)-1,len(s)-1)!=0

==打家劫舍问题29-31==

动态规划29:打家劫舍

点击跳转

动态规划30:打家劫舍II

[点击跳转](#213-打家劫舍 II)

动态规划31:打家劫舍III

leetcode337

每个节点都有偷和不偷两种选择,用pair对组存储两种情况下的最大收益

如果偷了当前节点,则左右节点都不能偷,只有一种情况

如果不偷当前节点,有四种情况:偷左右节点、偷左不偷右、偷右不偷左、左右都不偷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int rob(TreeNode* root)
{
int ret=0;
//pair中存储的分别是偷和不偷的最大收益
function<pair<int,int>(TreeNode*)>dfs=[&](TreeNode* cur)
{
if(cur==nullptr)
return make_pair(0,0);
auto pl=dfs(cur->left);
auto pr=dfs(cur->right);
int steal=cur->val+pl.second+pr.second;
int noSteal=max(pl.first+pr.second,max(pl.second+pr.first,max(pl.first+pr.first,pl.second+pr.second)));
ret=max(ret,max(steal,noSteal));
return make_pair(steal,noSteal);
};
dfs(root);
return ret;
}
};

==股票问题32-38==

动态规划32.买卖股票的最佳时机

leetcode121

方法1:贪心

用双指针实现,左指针取最小值,右指针取最大值,差值就是最大利润

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define fmax(x,y) ((x)>(y)?(x):(y))
int maxProfit(int* prices, int pricesSize)
{
int min=0,max=0;
int ret=0;
for(int i=1;i<pricesSize;++i)
{
if(prices[i]<prices[min])
{
ret=fmax(ret,prices[max]-prices[min]);
min=i;
max=i;
}
else if(prices[i]>prices[max])
{
max=i;
ret=fmax(ret,prices[max]-prices[min]);
}
}
return ret;
}

方法2:动态规划

维护两个变量,分别是have今天结束后 持有股票的最大利润,notHave今天结束后 不持有股票的最大利润

python:

1
2
3
4
5
6
7
8
9
10
class Solution:
def maxProfit(self, prices: List[int]) -> int:
have=-prices[0] ## 当前持有股票的最大利润
notHave=0 ## 当前不持有股票的最大利润
for i in range(1,len(prices)):
tp1=max(have,-prices[i])
tp2=max(notHave,have+prices[i])
have=tp1
notHave=tp2
return notHave

动态规划34.买卖股票的最佳时机II

详见[贪心章节](#贪心6:买卖股票的最佳时机 II)

总结一下32题和34题的动态规划区别

32题的特点是只能买一次

  • 今天持有股票的最大利润=max{昨天持有股票的最大利润(继承了昨天的情况),今天买股票后的利润(-今天的股价)}
  • 今天不持有股票的最大利润=max{昨天不持有股票的最大利润(继承了昨天的情况),今天卖股票后的利润(卖掉昨天持有的)}

34题的特点是无限次购买:

  • 今天持有股票的最大利润=max{昨天持有股票的最大利润(继承了昨天的情况),==在昨天不持有股票的基础上==今天买股票后的利润(==昨天不持有股票的利润==-今天的股价)}

  • 今天不持有股票的最大利润=max{昨天不持有股票的最大利润(继承了昨天的情况),今天卖股票后的利润(卖掉昨天持有的)}

除了标黄的部分,两题的递推公式都是一样的

对比代码:

32题:

tp1=max(have,-prices[i])

tp2=max(notHave,have+prices[i])

34题:

tp1=max(have,==notHave==-prices[i])

tp2=max(notHave,have+prices[i])

动态规划35.买卖股票的最佳时机III

leetcode123

本题是下一题的削减版,对应下一题的k=2情况

动态规划:

注意,如果你用的是C++,-inf不能简单地用INT_MIN代替,因为动态规划过程中可能会涉及到对INT_MAX减去一个正数的操作

3 3 5 0 0 3 1 4
0 第一次持有 -3 -3 -3 0 0 0 0 0
1 第一次卖出 -inf 0 2 2 2 3 3 4
2 第二次持有 -inf -inf -5 2 2 2 2 2
3 第二次卖出 -inf -inf -inf -5 2 5 5 6
1
2
3
4
5
dp[i][0]=max{dp[i-1][0],-prices[i]}
dp[i][1]=max{dp[i-1][1],dp[i-1][0]+prices[i]}
dp[i][2]=max{dp[i-1][2],dp[i-1][1]-prices[i]}
dp[i][3]=max{dp[i-1][3],dp[i-1][2]+prices[i]}
......

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution:
def maxProfit(self, prices: List[int]) -> int:
k=2 ## 最多完成k笔交易
n=len(prices)
## 构造数组
dp=[[0]]*n
for i in range(n):
dp[i]=[0]*(2*k)
## 数组初始化第一行
dp[0][0]=-prices[0]
for i in range(1,2*k):
dp[0][i]=-inf
## 开始递推
for i in range(1,n):
dp[i][0]=max(dp[i-1][0],-prices[i]) #第1列的递推
for j in range(1,2*k):
if j%2==0: ## 偶数列,表示第j//2次持有
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]-prices[i])
else: ## 奇数列,表示第j//2+1次卖出
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+prices[i])
ret=0
for i in range(2*k):
ret=max(ret,dp[n-1][i])
return ret

C++: 实现上有些区别,比如将每次持有和卖出的两种情况压缩到对组内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public:
int maxProfit(vector<int>& prices)
{
using ll=long long;
int k=2;
if(prices.size()==0)
return 0;
vector<vector<pair<ll,ll>>> dfs(prices.size());
for(auto&vec:dfs)
vec.resize(k+1);
for(auto& pa:dfs[0])
{
pa.first=INT_MIN;
pa.second=INT_MIN;
}
dfs[0][0].first=0;
dfs[0][1].second=-prices[0];
for(int i=1;i<prices.size();++i)
{
for(int j=0;j<=k;++j)
{
dfs[i][j].first=max(dfs[i-1][j].first,dfs[i-1][j].second+prices[i]);
if(j==0)
dfs[i][j].second=dfs[i-1][j].second;
else
dfs[i][j].second=max(dfs[i-1][j].second,dfs[i-1][j-1].first-prices[i]);
}
}
ll ret=0;
for(int i=0;i<=k;++i)
{
ret=max(ret,dfs[prices.size()-1][i].first);
}
return ret;
}
};

动态规划36:买卖股票的最佳时机IV

leetcode188

解析见上一题

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
n=len(prices)
## 构造数组
dp=[[0]]*n
for i in range(n):
dp[i]=[0]*(2*k)
## 数组初始化第一行
dp[0][0]=-prices[0]
for i in range(1,2*k):
dp[0][i]=-inf
## 开始递推
for i in range(1,n):
dp[i][0]=max(dp[i-1][0],-prices[i]) #第1列的递推
for j in range(1,2*k):
if j%2==0: ## 偶数列,表示第j//2次持有
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]-prices[i])
else: ## 奇数列,表示第j//2+1次卖出
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+prices[i])
ret=0
for i in range(2*k):
ret=max(ret,dp[n-1][i])
return ret

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using ll=long long;
class Solution {
public:
int maxProfit(int k, vector<int>& prices)
{
if(prices.size()==0||k==0)
return 0;
vector<vector<pair<ll,ll>>> dfs(prices.size());
for(auto&vec:dfs)
vec.resize(k+1);
for(auto& pa:dfs[0])
{
pa.first=INT_MIN;
pa.second=INT_MIN;
}
dfs[0][0].first=0;
dfs[0][1].second=-prices[0];
for(int i=1;i<prices.size();++i)
{
for(int j=0;j<=k;++j)
{
dfs[i][j].first=max(dfs[i-1][j].first,dfs[i-1][j].second+prices[i]);
if(j==0)
dfs[i][j].second=dfs[i-1][j].second;
else
dfs[i][j].second=max(dfs[i-1][j].second,dfs[i-1][j-1].first-prices[i]);
}
}
ll ret=0;
for(int i=0;i<=k;++i)
{
ret=max(ret,dfs[prices.size()-1][i].first);
}
return ret;
}
};

动态规划37:买卖股票的最佳时机含冷冻期

leetcode309

方法1:划分出三种状态

1 2 3 0 2
0 持有股票 -1 -1 -1 1 1
1 保持卖出股票 0 0 1 2 2
2 卖出股票 -inf 1 2 -1 3
1
2
3
dp[i][0]=max{dp[i-1][0],dp[i-1][1]-prices[i]}
dp[i][1]=max{dp[i-1][1],dp[i-1][2]}
dp[i][2]=dp[i-1][0]+prices[i]

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
## 构造dp数组
dp=[[0]*3 for _ in range(n)]
## 初始化第一行
dp[0][0]=-prices[0]
dp[0][1]=0
dp[0][2]=-inf
for i in range(1,n):
dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i])
dp[i][1]=max(dp[i-1][1],dp[i-1][2])
dp[i][2]=dp[i-1][0]+prices[i]
ret=max(dp[n-1][1],dp[n-1][2])
return 0 if ret<0 else ret

方法2:两种状态(带下划线的初始化要特殊考虑)

1 2 3 0 2
0 不持有股票 0 1 2 2 3
1 持有股票 -1 -1 -1 1 1
1
2
dp[i][0]=max{dp[i-1][0],dp[i-1][1]+prices[i]}
dp[i][1]=max{dp[i-1][1],dp[i-2][0]-prices[i]}

python:记忆化递归

1
2
3
4
5
6
7
8
9
10
class Solution:
def maxProfit(self, prices: List[int]) -> int:
@cache
def dfs(i,hold):
if i<0:
return -inf if hold else 0
if hold:
return max(dfs(i-1,True),dfs(i-2,False)-prices[i])
return max(dfs(i-1,False),dfs(i-1,True)+prices[i])
return dfs(len(prices)-1,False)

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int maxProfit(vector<int>& prices)
{
int len=prices.size();
if(len<=1)
return 0;
vector<pair<int,int>>dfs(len);
dfs[0].first=0;
dfs[0].second=-prices[0];
dfs[1].second=max(-prices[1],dfs[0].second);
dfs[1].first=max(0,dfs[0].second+prices[1]);
for(int i=2;i<len;++i)
{
dfs[i].first=max(dfs[i-1].first,dfs[i-1].second+prices[i]);
dfs[i].second=max(dfs[i-1].second,dfs[i-2].first-prices[i]);
}
return dfs[len-1].first;
}
};

动态规划38:买卖股票的最佳时机含手续费

leetcode714

fee=2

1 3 2 8 4 9
0 不持有股票 0 0 0 5 5 8
1 持有股票 -3 -3 -3 -3 -1 -1
1
2
dp[i][0]=max{dp[i-1][0],dp[i-1][1]+prices[i]}
dp[i][1]=max{dp[i-1][1],dp[i-1][0]-prices[i]-fee}

python:递归

1
2
3
4
5
6
7
8
9
10
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
@cache
def dfs(i,hold):
if i<0:
return -inf if hold else 0
if hold:
return max(dfs(i-1,True),dfs(i-1,False)-prices[i]-fee)
return max(dfs(i-1,False),dfs(i-1,True)+prices[i])
return dfs(len(prices)-1,False)

C++:递推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int maxProfit(vector<int>& prices, int fee)
{
int len=prices.size();
vector<pair<int,int>> dfs(len);
dfs[0].first=0;
dfs[0].second=-prices[0]-fee;
for(int i=1;i<len;++i)
{
dfs[i].first=max(dfs[i-1].first,dfs[i-1].second+prices[i]);
dfs[i].second=max(dfs[i-1].second,dfs[i-1].first-fee-prices[i]);
}
return dfs[len-1].first;
}
};

==子序列问题==

动态规划41:最长递增子序列

leetcode300

n为nums数组长度

方法1:动态规划

dp[i]表示以i为结尾的最长子序列

时间复杂度O(n2)O(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int lengthOfLIS(vector<int>& nums)
{
int len=nums.size();
vector<int> dp(len,1);
int ret=1;
for(int i=1;i<len;++i)
{
int tmp=0;
for(int j=0;j<i;++j)
{
if(nums[j]<nums[i])
tmp=max(tmp,dp[j]);
}
if(tmp>0)
dp[i]=tmp+1;
ret=max(ret,dp[i]);
}
return ret;
}
};

方法2:贪心+二分查找

g[i]为长度为i+1的子序列末尾元素的最小值

贪心+二分查找,时间复杂度O(nlog(n))O(n\log(n))

g数组性质:1、严格递增 2、只更新一个位置,更新的位置是第一个>=nums[i]的数的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int lengthOfLIS(vector<int>& nums)
{
int len=nums.size();
vector<int>greed={nums[0]};
for(int i=1;i<len;++i)
{
auto it=lower_bound(greed.begin(),greed.end(),nums[i]);
if(it==greed.end())
greed.emplace_back(nums[i]);
else
*it=nums[i];
}
return greed.size();
}
};

动态规划42:最长连续递增序列

leetcode674

简单题,大佬们可以跳过

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
n=len(nums)
ret=1
tmp=1
for i in range(1,n):
if nums[i]>nums[i-1]:
tmp+=1
ret=max(ret,tmp)
else:
tmp=1
return ret

动态规划43:最长公共子序列

leetcode718

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
n1=len(nums1)
n2=len(nums2)
dp=[[0]*(n2+1) for _ in range(n1+1)]
ret=0
for i in range(1,n1+1):
for j in range(1,n2+1):
if nums1[i-1]==nums2[j-1]:
dp[i][j]=dp[i-1][j-1]+1
ret=max(ret,dp[i][j])
return ret

动态规划44:最长公共子序列

点击跳转

动态规划45:不相交的线

leetcode1035

10 5 2 1 5 2
2 0 0 1 1 1 1
5 0 1 1 1 2 2
1 0 1 1 2 2 2
2 0 1 2 2 2 3
5 0 1 2 2 3 3

dp[i][j]={dp[i1][j1]+1,nums1[i]=nums2[j]max{dp[i1][j],dp[i][j1]},nums1[i]nums2[j]dp[i][j]=\begin{cases} dp[i-1][j-1]+1 ,nums1[i]=nums2[j]\\ max\{dp[i-1][j],dp[i][j-1]\},nums1[i]\ne nums2[j] \end{cases}

python:

1
2
3
4
5
6
7
8
9
10
class Solution:
def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int:
@cache
def dp(i,j):
if i<0 or j<0:
return 0
if nums1[i]==nums2[j]:
return dp(i-1,j-1)+1
return max(dp(i-1,j),dp(i,j-1))
return dp(len(nums1)-1,len(nums2)-1)

动态规划46:最大子序和

leetcode53

代码随想录:

dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]

  1. 确定递推公式

dp[i]只有两个方向可以推出来:

  • dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
  • nums[i],即:从头开始计算当前连续子序列和

一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int maxSubArray(vector<int>& nums)
{
int ret=nums[0],add=nums[0];
for(int i=1;i<nums.size();++i)
{
add=max(add+nums[i],nums[i]);
ret=max(ret,add);
}
return ret;
}
};

python

1
2
3
4
5
6
7
8
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
add=0
ret=-100000
for num in nums:
add=max(add+num,num)
ret=max(ret,add)
return ret

动态规划47:判断子序列

leetcode392

本题和45:不相交的线很像

1
2
3
4
5
6
7
8
9
10
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
@cache
def dp(i,j):
if i<0 or j<0:
return 0
if s[i]==t[j]:
return dp(i-1,j-1)+1
return max(dp(i-1,j),dp(i,j-1))
return dp(len(s)-1,len(t)-1)==len(s)

动态规划48:不同的子序列

leetcode115

dp(i,j)表示 字符串t的前i+1个字母(t[0:i+1]) 在 字符串s的前j+1个字母(s[0:j+1])的子序列中 出现的次数

dp(i,j) s[j] b a b g b a g
t[i] 1 1 1 1 1 1 1 1
b 0 1 1 2 2 3 3 3
a 0 0 1 1 1 1 4 4
g 0 0 0 0 1 1 1 5

dp(i,j)={dp(i,j1)+dp(i1,j1),t[i]=s[j]dp(i,j1),t[i]s[j]dp(i,j)= \begin{cases} dp(i,j-1)+dp(i-1,j-1),t[i]=s[j]\\ dp(i,j-1),t[i]\ne s[j] \end{cases}

解析:

t[i]!=s[j]时,当前的两个字符不匹配,dp(i,j)=dp(i,j-1)t[0:i+1]和s[0:j+1]的匹配问题可以转化为t[0:i+1]s[0:j]的匹配问题。

举个例子,s=“bab”和t=“ba”不同子序列数,等价于s=“ba”和t=“ba”的不同子序列数,因为前者两个串末尾不同,可以看作s1新增的末尾字母b对不同子序列数不构成影响

t[i]==s[j]时,当前的两个字符匹配,dp(i,j)=dp(i,j-1)+dp(i-1,j-1)

举个例子,s=“babgba”和t=“ba”的不同子序列数,等于s=“babgb”和g=“ba”的子序列数,加上s=“babgb”,t=“b”的子序列数。

  • s=“babgb”,t=“b”的子序列数,包括了在字母a加入字符串t和s之前,s和t的子序列匹配数。加入a实际上是在之前已经匹配的子序列的基础上,延长了那些子序列。可以看出这里字母b匹配了三次,给两个串同时加上a,就变成了ba匹配了三次
  • s=“babgb”和g=“ba”的子序列数,是考虑在字母加入字符串s之前,s和t的子序列匹配数。可以看出s和t已经匹配了一对ba,再给s加入a对之前的匹配不构成影响。

python:(记忆化递归)

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def numDistinct(self, s: str, t: str) -> int: ## 记忆化递归dp
@cache
def dp(i,j):
if i<0:
return 1
if j<0:
return 0
if t[i]==s[j]:
return dp(i,j-1)+dp(i-1,j-1)
return dp(i,j-1)
return dp(len(t)-1,len(s)-1)

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int numDistinct(string s, string t) //递推版dp
{
using ll=unsigned long long;
int slen=s.size(),tlen=t.size();
//初始化二维数组
vector<vector<ll>>dp(tlen+1);
dp[0].resize(slen+1,1);
for(int i=1;i<=tlen;++i)
dp[i].resize(slen+1);
//递推
for(int i=1;i<=tlen;++i)
for(int j=1;j<=slen;++j)
if(t[i-1]==s[j-1])
dp[i][j]=dp[i][j-1]+dp[i-1][j-1];
else
dp[i][j]=dp[i][j-1];
return dp[tlen][slen];
}
};

Javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @param {string} s
* @param {string} t
* @return {number}
*/
var numDistinct = function(s, t) //递推版dp
{
let dp=new Array();
for(let i=0;i<=t.length;++i)
dp[i]=new Array();
for(let i=0;i<=s.length;++i)
dp[0][i]=1;
for(let i=1;i<=t.length;++i)
dp[i][0]=0;
for(let i=1;i<=t.length;++i)
for(let j=1;j<=s.length;++j)
{
if(t[i-1]==s[j-1])
dp[i][j]=dp[i][j-1]+dp[i-1][j-1];
else
dp[i][j]=dp[i][j-1];
}
return dp[t.length][s.length];
};

动态规划49:两个字符串的删除操作

leetcode583

这题本质上和动态规划43:最长公共子序列相同

python:

1
2
3
4
5
6
7
8
9
10
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
@cache
def dp(i,j):
if i<0 or j<0:
return 0
if word1[i]==word2[j]:
return dp(i-1,j-1)+1
return max(dp(i-1,j),dp(i,j-1))
return len(word1)+len(word2)-2*dp(len(word1)-1,len(word2)-1)

C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define max(x,y) (x)>(y)?(x):(y)
int longestCommonSubsequence(char * text1, char * text2)
{
char s1[1005]=" ",s2[1005]=" ";
strcat(s1,text1);
strcat(s2,text2);
int l1=strlen(text1);
int l2=strlen(text2);
int** mat=(int**)malloc((l1+1)*sizeof(int*));
for(int i=0;i<=l1;++i)
mat[i]=(int*)malloc((l2+1)*sizeof(int));
for(int i=0;i<=l1;++i)
mat[i][0]=0;
for(int i=0;i<=l2;++i)
mat[0][i]=0;
for(int i=1;i<=l1;++i)
{
for(int j=1;j<=l2;++j)
{
if(s1[i]==s2[j])
mat[i][j]=mat[i-1][j-1]+1;
else
mat[i][j]=max(mat[i-1][j],mat[i][j-1]);
}
}
int ret=mat[l1][l2];
for(int i=0;i<=l1;++i)
free(mat[i]);
free(mat);
return ret;
}
int minDistance(char * word1, char * word2)
{
return strlen(word1)+strlen(word2)-2*longestCommonSubsequence(word1,word2);
}

动态规划50:编辑距离

点击跳转

动态规划52:回文子串

leetcode647

中心扩散解法:时间复杂度O(n2)O(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
int countSubstrings(string s)
{
int n=s.size();
int ret=n;
for(int i=1;i<n-1;++i)
{
int left=i-1,right=i+1;
while(left>=0&&right<n)
{
if(s[left]==s[right])
++ret;
else
break;
--left;
++right;
}
}
for(int i=0;i<n-1;++i)
{
int left=i,right=i+1;
while(left>=0&&right<n)
{
if(s[left]==s[right])
++ret;
else
break;
--left;
++right;
}
}
return ret;
}
};

动态规划解法也是O(n2)O(n^2),就懒得写了

动态规划53:最长回文子序列

leetcode516

dp(i,j)表示在区间[i,j]内最长回文子序列的长度

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
@cache
def dp(i,j):
if i==j:
return 1
if i>j:
return 0
if s[i]==s[j]:
return dp(i+1,j-1)+2
return max(dp(i,j-1),dp(i+1,j))
return dp(0,len(s)-1)

动态规划54:切披萨的方案数

leetcode1444

考察二维差分和三维动态规划

此题定义了pi数组,pi[i][j]意义为pizza数组中左上角(i,j)到右下角(row-1,col-1)矩形区域内苹果总数

dp(i,j,c)的意义为左上角(i,j)到右下角(row-1,col-1)矩形区域切c刀的方案总数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution:
def ways(self, pizza: List[str], k: int) -> int:
mod=10**9+7
row=len(pizza)
col=len(pizza[0])
pi=[[0]*col for _ in range(row)]
if pizza[row-1][col-1]=="A":
pi[row-1][col-1]=1
for i in range(row-2,-1,-1):
pi[i][col-1]=pi[i+1][col-1]
if pizza[i][col-1]=="A":
pi[i][col-1]+=1
for i in range(col-2,-1,-1):
pi[row-1][i]=pi[row-1][i+1]
if pizza[row-1][i]=="A":
pi[row-1][i]+=1
for i in range(row-2,-1,-1):
for j in range(col-2,-1,-1):
pi[i][j]=pi[i+1][j]+pi[i][j+1]-pi[i+1][j+1]
if pizza[i][j]=="A":
pi[i][j]+=1
@cache
def dp(i,j,c):
if c==0:
return 1 if pi[i][j]>0 else 0
tmp=0
for i1 in range(i+1,row):
if pi[i][j]-pi[i1][j]!=0 and pi[i1][j]!=0:
tmp+=dp(i1,j,c-1)
tmp%=mod
for j1 in range(j+1,col):
if pi[i][j]-pi[i][j1]!=0 and pi[i][j1]!=0:
tmp+=dp(i,j1,c-1)
tmp%=mod
return tmp
return dp(0,0,k-1)%mod

==灵神的刷题顺序:==

动态规划:从记忆化搜索到递推

动态规划入门:从记忆化搜索到递推【基础算法精讲 17】(参考视频)

198打家劫舍

leetcode链接

维护一个数组vec[i]表示从前i个房子中能获得的最大金额和

vec[i]=max(vec[i-2]+nums[i],vec[i-1])

如果选择偷当前第i间的,最优收益是偷前i-2间的收益加上本间的收益

如果不偷当前第i间,最优收益是偷前i-1间的收益

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int rob(vector<int>& nums)
{
vector<int>vec(nums.size());
//先处理边界情况,确保nums.size()>2
if(nums.size()==1)
return nums[0];
else if(nums.size()==2)
return max(nums[0],nums[1]);
vec[0]=nums[0];
vec[1]=max(nums[0],nums[1]);
int ret=0;//维护vec[]数组中的最大值
for(int i=2;i<nums.size();++i)
{
vec[i]=max(vec[i-1],vec[i-2]+nums[i]);
ret=max(ret,vec[i]);
}
return ret;
}
};

Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def rob(self, nums: List[int]) -> int:
li=[]
le=len(nums)
if le==1:
return nums[0]
elif le==2:
return max(nums[0],nums[1])
li.append(nums[0])
li.append(max(nums[0],nums[1]))
i=2
while i<le:
li.append(max(li[i-1],li[i-2]+nums[i]))
i+=1
return li[le-1]

(灵神的递归做法)

1
2
3
4
5
6
7
8
9
class Solution:  ## 递归做法
def rob(self, nums: List[int]) -> int:
@(cache)
def dfs(idx):
if idx<0:
return 0
cur=max(dfs(idx-1),dfs(idx-2)+nums[idx])
return cur
return dfs(len(nums)-1)

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums)
{
if(nums.length==1)
return nums[0];
let dp=new Array();
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(let i=2;i<nums.length;++i)
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
return Math.max(dp[nums.length-1],dp[nums.length-2]);
};

课后作业:

70. 爬楼梯

leetcode链接

vec[i]表示到达第i级楼梯至少需要几步

一次可以跨1或2阶,故vec[i]=vec[i-1]+vec[i-2]

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int a[46];
int digui(int n)
{
if(a[n]!=0)
return a[n];
else
{
a[n]=digui(n-1)+digui(n-2);
return a[n];
}
}
int climbStairs(int n)
{
a[1]=1;
a[2]=2;
if(n==1)
{
//a[1]=1;
return 1;
}
if(n==2)
{
//a[2]=2;
return 2;
}
return digui(n);
}

python:

1
2
3
4
5
6
7
8
9
10
class Solution:  ## 等价于斐波那契数列
def climbStairs(self, n: int) -> int:
@cache
def dfs(i):
if i==1:
return 1
elif i==2:
return 2
return dfs(i-1)+dfs(i-2)
return dfs(n)

746. 使用最小花费爬楼梯

leetcode链接

用数组li[k]表示到达第k级的最小花费

因为出发没有花费,li[0]=li[1]=0

li[k]=min(li[k-1]+cost[k-1],li[k-2]+cost[k-2])

python:

1
2
3
4
5
6
7
8
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
i=0
le=len(cost)
li=[0]*(le+1)
for k in range(2,le+1):
li[k]=min(li[k-1]+cost[k-1],li[k-2]+cost[k-2])
return li[le]

2466.统计构造好字符串的方案数

leetcode链接

dfs[i]表示长度为i的字符串的好字符串数目

dfs[0]=1,dfs[k]=0(k<0)

dfs[i]=dfs[i-zero]+dfs[i-one]

为了C++防溢出(python防爆内存),需要在计算过程中对m=109+7m=10^9+7取余

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using ll=long long;
class Solution {
public:
int countGoodStrings(int low, int high, int zero, int one)
{
ll m=1000000007;
vector<ll>dfs(high+1);
for(int i=1;i<=high;++i)
{
if(i-zero==0)
dfs[i]+=1;
else if(i-zero>0)
dfs[i]+=dfs[i-zero];
if(i-one==0)
dfs[i]+=1;
else if(i-one>0)
dfs[i]+=dfs[i-one];
dfs[i]%=m;
}
ll ret=0;
for(int i=low;i<=high;++i)
{
ret+=dfs[i];
ret%=m;
}
return ret;
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
m=10**9+7
class Solution:
def countGoodStrings(self, low: int, high: int, zero: int, one: int) -> int:
@cache
def dfs(i):
if i<0:
return 0
elif i==0:
return 1
return dfs(i-zero)%m+dfs(i-one)%m
ret=0
for k in range(low,high+1):
ret+=dfs(k)
ret=ret%m
return ret%(10**9+7)

213.打家劫舍 II

leetcode链接

本题和打家劫舍类似,可以套用上次的代码,因为首尾相接,从[0,n]间房获得的最大收益等价于

max{从[0,n-1]间房获得的最大收益,从[1,n]间房获得的最大收益}

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int rob1(int* nums, int numsSize)
{
int first=0,second=0,max=0;
for(int i=0;i<numsSize;++i)
{
if(i>=2)
{
int this=first+nums[i]>second?first+nums[i]:second;
first=second;
second=this;
}
else
{
if(i==0)
first=nums[0];
else
second=nums[1]>nums[0]?nums[1]:nums[0];
}
}
return first>second?first:second;
}

int rob(int* nums, int numsSize)
{
if(numsSize==1)
return nums[0];
int ans1=rob1(nums,numsSize-1);
int ans2=rob1(nums+1,numsSize-1);
return ans1>ans2?ans1:ans2;
}

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
def rob(self, nums: List[int]) -> int:
le=len(nums)
if(le==1):
return nums[0]
n1=nums[0:le-1]
n2=nums[1:le]
@cache
def dfs1(i):
if i<0:
return 0
return max(dfs1(i-1),dfs1(i-2)+n1[i])
@cache
def dfs2(i):
if i<0:
return 0
return max(dfs2(i-1),dfs2(i-2)+n2[i])
return max(dfs1(le-2),dfs2(le-2))

Javascript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @param {number[]} nums
* @return {number}
*/
var rob1 = function(nums)
{
if(nums.length==1)
return nums[0];
let dp=new Array();
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(let i=2;i<nums.length;++i)
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
return Math.max(dp[nums.length-1],dp[nums.length-2]);
};
var rob = function(nums)
{
if(nums.length==1)
return nums[0];
var back=nums.pop();
var ret1=rob1(nums);
nums.push(back);
nums.shift();
var ret2=rob1(nums);
return Math.max(ret1,ret2);
};

拆分类动态规划

343.整数拆分

leetcode链接

参考视频

解法1:动态规划

定义dp[i]是整数i拆分后相乘能得到的最大整数(i>=2)

dp[i]=max(j*(i-j),j*dp[i-j]) (1<=j<i)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int integerBreak(int n)
{
vector<int>dp(60,0);
dp[1]=1;
dp[2]=1;
for(int i=3;i<=n;++i)
{
for(int j=1;j<=i/2;++j)
{
dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
};

解法2: 数学

粘贴自讨论区:

If an optimal product contains a factorf >= 4, then you can replace it with factors2and f-2 without losing optimality, as 2*(f-2) = 2f-4 >= f. So you never need a factor greater than or equal to 4, meaning you only need factors 1, 2 and 3 (and 1 is of course wasteful and you’d only use it for n=2 and n=3, where it’s needed).

For the rest I agree, 3*3 is simply better than 2*2*2, so you’d never use 2more than twice.

C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int integerBreak(int n)
{
switch(n)
{
case 2:return 1;
case 3:return 2;
case 4:return 4;
}
int a=n/3;
int b=n%3;
int ret=0;
if(b==2)
ret=pow(3,a)*2;
else if(b==1)
ret=pow(3,a-1)*4;
else
ret=pow(3,a);
return ret;
}

96.不同的二叉搜索树

leetcode链接

参考视频

C++: 数组动规

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int numTrees(int n)
{
vector<int>dp(20,0);
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;++i)
{
for(int j=0;j<i/2;++j)
dp[i]+=dp[i-j-1]*dp[j];
dp[i]*=2;
if(i%2!=0)
dp[i]+=dp[i/2]*dp[i/2];
}
return dp[n];
}
};

python: 记忆化递归

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def numTrees(self, n: int) -> int:
@cache
def dp(idx):
if idx==0:
return 1
tmp=0
for i in range(0,idx):
tmp+=dp(i)*dp(idx-1-i)
return tmp
return dp(n)

0-1背包和完全背包

01背包:416. 分割等和子集 474. 一和零 494. 目标和 879. 盈利计划 1049. 最后一块石头的重量 II 1230. 抛掷硬币

完全背包:1449. 数位成本和为目标值的最大数字 322. 零钱兑换 518. 零钱兑换 II 279. 完全平方数


0-1背包 完全背包 基础算法精讲 18(视频链接)

494. 目标和(0-1背包)

leetcode链接

解法1. 回溯(暴力)解法:可以1952ms踩线通过

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target)
{
int cnt=0,add=0,left=0;
left=accumulate(nums.begin(),nums.end(),0);
function<void(int idx)>dfs=[&](int idx)
{
if(abs(target-add)>left)
return;
if(idx==nums.size())
{
if(add==target)
++cnt;
return;
}
add+=nums[idx];
left-=nums[idx];
dfs(idx+1);
add-=2*nums[idx];
dfs(idx+1);
add+=nums[idx];
left+=nums[idx];
};
dfs(0);
return cnt;
}
};
解法2:灵神的0-1背包记忆递归模板

记忆递归模板

示例出自CSDN

有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

为方便讲解和理解,下面讲述的例子均先用具体的数字代入,即:eg:number=4,capacity=8

i(物品编号) 1 2 3 4
w(体积) 2 3 4 5
v(价值) 3 4 5 6

上述例子的dp数组打印: (dfs(i,c)=max(dfs(i-1,c),dfs(i-1,c-w[i])+v[i]))

dfs 0 1 2 3 4 5 6 7 8
-1 0 0 0 0 0 0 0 0 0
0 0 0 3 3 3 3 3 3 3
1 0 0 3 4 4 7 7 7 7
2 0 0 3 4 5 7 8 9 9
3 0 0 3 4 5 7 8 9 10

image-20230705180715673

灵神0-1背包递归模板(python):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from functools import cache


def zero_one_knapsack(capacity: int, w: list[int], v: list[int]):
"""
灵神0-1背包模板
:param capacity: 背包容量
:param w: 物品重量
:param v: 物品价值
:return: 取物品的最大价值
"""
n = len(w)

@cache
def dfs(i, c):
"""
深度优先搜索
:param i: 只考虑前i件物品
:param c: 剩余容量为c
:return: 能获得的最大价值
"""
if i < 0:
return 0
if c - w[i] < 0:
return dfs(i - 1, c)
return max(dfs(i - 1, c), dfs(i - 1, c - w[i]) + v[i])

return dfs(n - 1, capacity)

上述模板的C++版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<iostream>
#include<vector>
#include<unordered_map>
#include<functional>
#include<string>
using namespace std;
int zero_one_knapsack(int capacity,vector<int>&w,vector<int>&v)
{
unordered_map<string,int>cache;//用于实现记忆化递归的哈希函数
function<int(int,int)>dfs;
function<int(int,int)>cdfs=[&](int i,int c)//直接可以调用,带有记忆化效果
{
string key=to_string(i)+string(",")+to_string(c);
if(cache.find(key)==cache.end())
cache[key]=dfs(i,c);
return cache[key];
};
dfs=[&](int i,int c)
{
if(i<0)
return 0;
if(c-w[i]<0)
return cdfs(i-1,c);
return max(cdfs(i-1,c),cdfs(i-1,c-w[i])+v[i]);
};
// for(int i=-1;i<(int)w.size();++i)
// {
// for(int j=0;j<=capacity;++j)
// cout<<cdfs(i,j)<<' ';
// cout<<endl;
// }
return cdfs(w.size()-1,capacity);
}

本题需要对上述模板进行变形,变形方式如下图

image-20230705202036945

解题

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
n=len(nums)
add=0
for k in nums:
add+=k
if add-target<0 or (add-target)%2==1:
return 0
add=(add-target)//2
@cache
def dfs(i:int,c:int):
if i<0:
return 1 if c==0 else 0
if c<nums[i]:
return dfs(i-1,c)
return dfs(i-1,c)+dfs(i-1,c-nums[i])
return dfs(n-1,add)

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target)
{
int sum=accumulate(nums.begin(),nums.end(),0);
if(sum<target||(sum-target)%2!=0)
return 0;
sum=(sum-target)/2;
//核心代码模板
unordered_map<string,int>cache;
function<int(int,int)>dfs;
function<int(int,int)>cdfs=[&](int i,int c)
{
string key=to_string(i)+string(",")+to_string(c);
if(cache.find(key)==cache.end())
cache[key]=dfs(i,c);
return cache[key];
};
dfs=[&](int i,int c)
{
if(i<0)
return c==0?1:0;
if(c-nums[i]<0)
return cdfs(i-1,c);
return cdfs(i-1,c)+cdfs(i-1,c-nums[i]);
};
//
return cdfs(nums.size()-1,sum);
}
};
解法3: 数组递推的动态规划解法

本题相当于v[i]=1,w[i]=nums[i]的情况

s=(i=0nnums[i])targets=(\sum\limits_{i=0}^nnums[i])-target

本题等价于在nums中任意取几个数相加,满足和等于s2\frac s 2,求任意取几个数相加的方法总数

例如nums=[1,2,3,1,2],target=-1

只需在nums中任意取几个数相加,满足和等于5

arr[i][j] 0 1 2 3 4 5
-1 1 0 0 0 0 0
0(1) 1 1 0 0 0 0
1(2) 1 1 1 1 0 0
2(3) 1 1 1 2 1 1
3(1) 1 2 2 3 3 2
4(2) 1 2 3 5 5 5

arr[i][j]表示只考虑numsi个元素时,相加得到j的最多方法数

arr[i][j]=arr[i-1][j]+arr[i-1][j-nums[i]]

注意坑: s为奇数或s<0都应当返回0

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target)
{
int sum=accumulate(nums.begin(),nums.end(),0);
int add=(sum-target)/2;
if(sum-target<0||(sum-target)%2==1)
return 0;
vector<vector<int>>vec(nums.size()+1);
for(auto& vec_:vec)
vec_.resize(add+1);
vec[0][0]=1;
for(int i=1;i<=nums.size();++i)
{
int j=0;
for(;j<nums[i-1]&&j<=add;++j)
vec[i][j]=vec[i-1][j];
for(;j<=add;++j)
vec[i][j]=vec[i-1][j]+vec[i-1][j-nums[i-1]];
}
return vec[nums.size()][add];
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
add=0
for k in nums:
add+=k
if (add-target)%2!=0 or add-target<0:
return 0
add=(add-target)//2
size=len(nums)
li = [0] * (size + 1)
for k in range(size+1):
li[k]=[0]*(add+1)
li[0][0]=1
for i in range(1,size+1):
for j in range(add+1):
if j-nums[i-1]>=0:
li[i][j]=li[i-1][j]+li[i-1][j-nums[i-1]]
else:
li[i][j]=li[i-1][j]
return li[size][add]

322. 零钱兑换

leetcode链接

这是一个完全背包问题

举例:

1
2
输入:coins = [1, 2, 3, 4, 5], amount = 7
输出:3

arr[i][j]表示当前状态下所需最少硬币数

arr[i][j]=min(arr[i-1][j],arr[i][j-coins[i-1]]+1)i1i\ge 1

i coins 0 1 2 3 4 5 6 7
0 0 \infin \infin \infin \infin \infin \infin \infin
1 1 0 1 2 3 4 5 6 7
2 2 0 1 1 2 2 3 3 4
3 3 0 1 1 1 2 2 2 3
4 4 0 1 1 1 1 2 2 2
5 5 0 1 1 1 1 1 2 2

C++:二维数组递推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define big 0x7ffffff0 
class Solution {
public:
int coinChange(vector<int>& coins, int amount)
{
vector<vector<int>>vec(coins.size()+1);
vec[0].resize(amount+1,big);
vec[0][0]=0;
for(int i=1;i<=coins.size();++i)
vec[i].resize(amount+1);
for(int i=1;i<=coins.size();++i)
{
int j=0;
for(;j<coins[i-1]&&j<=amount;++j)
vec[i][j]=vec[i-1][j];
for(;j<=amount;++j)
vec[i][j]=min(vec[i-1][j],vec[i][j-coins[i-1]]+1);
}
return vec[coins.size()][amount]==big?-1:vec[coins.size()][amount];
}
};

Python:(记忆化递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
n=len(coins)
@cache
def dfs(i,c):
if c==0:
return 0
if i<0 or c<0:
return inf
return min(dfs(i-1,c),dfs(i,c-coins[i])+1)
ret=dfs(n-1,amount)
if ret==inf:
return -1
else:
return ret

优化:压缩为一维数组

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
#define big 0x7ffffff0
class Solution {
public:
int coinChange(vector<int>& coins, int amount)
{
vector<int> vec(amount+1,big);
vec[0]=0;
for(int& coi:coins)
for(int i=coi;i<=amount;++i)
vec[i]=min(vec[i],vec[i-coi]+1);
return vec[amount]==big?-1:vec[amount];
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
arr=[inf]*(amount+1)
arr[0]=0
for idx,coin in enumerate(coins):
for i in range(coin,amount+1):
arr[i]=min(arr[i],arr[i-coin]+1)
if arr[amount]==inf:
return -1
else:
return arr[amount]

==课后作业:==

416. 分割等和子集

leetcode链接

对于nums=[1,5,11,5]的测试用例

0 1 2 3 4 5 6 7 8 9 10 11
1 0 0 0 0 0 0 0 0 0 0 0
1 1 1 0 0 0 0 0 0 0 0 0 0
5 1 1 0 0 0 1 1 0 0 0 0 0
11 1 1 0 0 0 1 1 0 0 0 0 1
5 1 1 0 0 0 2 2 0 0 0 1 2

arr(i,c)=arr(i-1,c-num[i])+arr(i-1,c)

完成第5行时最后一列非0,可以返回True

记忆化递归

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def canPartition(self, nums: List[int]) -> bool:
s=sum(nums)
if s%2==1:
return False
s//=2
@cache
def dfs(i,c):
if c==0:
return 1
if i<0:
return 0
if c-nums[i]<0:
return dfs(i-1,c)
return dfs(i-1,c-nums[i])+dfs(i-1,c)
for k in range(len(nums)): ## k从小到大进行枚举
if(dfs(k,s)!=0):
return True
return False

代码中k从小到大进行枚举,相当于按照nums数组由短到长枚举,很多数组只考虑前面一小段就符合条件,这样可以减少递归层级,防止超出内存限制

正常的二维数组动规:

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public:
bool canPartition(vector<int>& nums)
{
int sum=accumulate(nums.begin(),nums.end(),0);
if(sum%2==1)
return false;
sum/=2;
int m=nums.size();
int n=sum;
vector<vector<int>>arr(m+1);
for(auto& ar:arr)
ar.resize(n+1);
int flag=0;
for(int i=1;i<=m;++i)
{
for(int j=1;j<=n;++j)
{
if(j>=nums[i-1])
arr[i][j]=max(arr[i-1][j],arr[i-1][j-nums[i-1]]+nums[i-1]);
else
arr[i][j]=arr[i-1][j];
if(arr[i][j]==sum)
{
flag=1;
goto Theend;
}
}
}
Theend:
if(flag==1)
return true;
else
return false;
}
};

Javascript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @param {number[]} nums
* @return {boolean}
*/
var canPartition = function(nums)
{
let sum=eval(nums.join('+'));
if(sum%2!=0)
return false;
sum/=2;
len=nums.length;
let arr=new Array();
arr[0]=new Array();
arr[0][0]=1;
for(let j=1;j<=sum;++j)
arr[0][j]=0;
for(let i=1;i<=len;++i)
{
arr[i]=new Array();
for(let j=0;j<=sum;++j)
{
if(j-nums[i-1]<0)
arr[i][j]=arr[i-1][j];
else
arr[i][j]=arr[i-1][j]+arr[i-1][j-nums[i-1]];
}
if(arr[i][sum]>0)
return true;
}
return false;
};

压缩为一维数组:

Python:使用临时数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def canPartition(self, nums: List[int]) -> bool:
s=sum(nums)
if s%2!=0:
return False
s//=2
arr=[0]*(s+1)
arr[0]=1
for idx,num in enumerate(nums):
arr2=arr.copy() ## 注意:这里要用.copy(),否则会深拷贝
for i in range(num,s+1):
arr2[i]+=arr[i-num]
if arr2[s]!=0:
return True
arr=arr2.copy()
return False

C++:carl模板的倒序递推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
bool canPartition(vector<int>& nums)
{
int sum=accumulate(nums.begin(),nums.end(),0);
if(sum%2!=0)
return false;
sum/=2;
cout<<sum<<endl;
vector<int>dp(sum+1);
for(int i=0;i<nums.size();++i)
{
for(int j=sum;j>=nums[i];--j)//从后往前遍历,就是0-1背包
{
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
}
if(dp[sum]==sum)
return true;
}
return false;
}
};

注意:本题如果用C++,只能用递推公式dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);

而不能用dp[j]+=dp[j-nums[i]],因为后者dp[i]的含义是当前枚举到的数中加和为i的所有组合总数,会溢出

分割等和子集问题的变换:

1049.最后一块石头的重量II

leetcode链接

对于stones=[a1,a2,,an]stones=[a_1,a_2,\cdots,a_n]

可以找到一种分割子集的方法,使两个子集[b1,b2,,bi][b_1,b_2,\cdots,b_i][c1,c2,,cj][c_1,c_2,\cdots,c_j]的和相差最小,相差记为ss

b1+b2++bi=c1+c2++cj+sb_1+b_2+\cdots+b_i=c_1+c_2+\cdots+c_j+s

(b1,b2,,bi,c1,c2,,cjstonesb_1,b_2,\cdots,b_i,c_1,c_2,\cdots,c_j\in stones)

ss就是最后石头的最小可能重量

最后会把石头分成两堆,重量分别为addsu-add(add<=su-add)

add的理论最大值为su//add(//是整除),只需让addsu//add开始枚举递减,直到找到一种石头组合是部分总重量为add

对应的最后一块石头的重量为su-add-add=su-2*add

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
su=sum(stones)
le=len(stones)
add=su//2
@cache
def dfs(i,c):
if c==0:
return 1
if i<0:
return 0
if(c<stones[i]):
return dfs(i-1,c)
return dfs(i-1,c)+dfs(i-1,c-stones[i])
while dfs(le-1,add)==0:
add-=1
return abs(su-2*add)

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#define mul 100000
class Solution {
public:
int lastStoneWeightII(vector<int>& stones)
{
int su=accumulate(stones.begin(),stones.end(),0);
int le=stones.size();
int add=su/2;
unordered_map<int,int>cache;
function<int(int,int)>dfs;
function<int(int,int)>cdfs=[&](int i,int c)
{
int key=i*mul+c;
if(cache.find(key)==cache.end())
cache[key]=dfs(i,c);
return cache[key];
};
dfs=[&](int i,int c)
{
if(c==0)
return 1;
if(i<0)
return 0;
if(c-stones[i]<0)
return cdfs(i-1,c);
return cdfs(i-1,c)+cdfs(i-1,c-stones[i]);
};
while(cdfs(le-1,add)==0)
--add;
return su-2*add;
}
};

279. 完全平方数

leetcode链接

测试用例n=12:

arr=[1,4,9]

dfs 0 1 2 3 4 5 6 7 8 9 10 11 12
0 \infin \infin \infin \infin \infin \infin \infin \infin \infin \infin \infin \infin
1 0 1 2 3 4 5 6 7 8 9 10 11 12
4 0 1 2 3 1 2 3 4 2 3 4 5 3
9 0 1 2 3 1 2 3 4 2 1 2 3 3

dfs(i,c)=min(dfs(i-1,c),dfs(i,c-arr[i])+1)

递归会爆内存,所以只贴压缩为一维数组的递推代码

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int numSquares(int n)
{
vector<int> arr(n+1);
int g=0;
generate(arr.begin(),arr.end(),[&](){return g++;});
for(int i=2;i*i<=n;++i)
for(int j=1;j<=n;++j)
if(j-i*i>=0)
arr[j]=min(arr[j],arr[j-i*i]+1);
return arr[n];
}
};

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def numSquares(self, n: int) -> int:
arr=[1]
num=1
while arr[num-1]<=n:
num+=1
arr.append(num**2)
dfs=[inf]*(n+1)
dfs[0]=0
for idx,nu in enumerate(arr):
for i in range(nu,n+1):
dfs[i]=min(dfs[i],dfs[i-nu]+1)
return dfs[n]

518. 零钱兑换 II

leetcode链接

0 1 2 3 4 5
1 0 0 0 0 0
1 1 1 1 1 1 1
2 1 1 2 2 3 3
5 1 1 2 2 3 4

vec[i][j]=vec[i-1][j]+vec[i][j-coins[i-1]];

二维数组法

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int change(int amount, vector<int>& coins)
{
vector<vector<int>>vec(coins.size()+1);
vec[0].resize(amount+1);
for(int i=1;i<=coins.size();++i)
vec[i].resize(amount+1);
vec[0][0]=1;
for(int i=1;i<=coins.size();++i)
{
int j=0;
for(;j<coins[i-1]&&j<=amount;++j)
vec[i][j]=vec[i-1][j];
for(;j<=amount;++j)
vec[i][j]=vec[i-1][j]+vec[i][j-coins[i-1]];
}
return vec[coins.size()][amount];
}
};

(记忆化)递归法

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def change(self, amount: int, coins: List[int]) -> int: ## 灵神记忆化递归模板
n=len(coins)
@cache
def dfs(i,c):
if c==0:
return 1
if i<0:
return 0
if c-coins[i]<0:
return dfs(i-1,c)
return dfs(i-1,c)+dfs(i,c-coins[i])
return dfs(n-1,amount)

压缩为一维数组:

C++:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int change(int amount, vector<int>& coins)
{
vector<int>arr(amount+1,0);
arr[0]=1;
for(int& coin:coins)
for(int i=coin;i<=amount;++i)
arr[i]+=arr[i-coin];
return arr[amount];
}
};

python:

1
2
3
4
5
6
7
8
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
arr=[0]*(amount+1)
arr[0]=1
for idx,coin in enumerate(coins):
for i in range(coin,amount+1):
arr[i]+=arr[i-coin]
return arr[amount]
377.组合总和IV

leetcode链接

与上题几乎一样,区别使上一题求组合数,不考虑顺序,本题求排列数,考虑顺序

只需交换两个for循环的顺序

dp数组表:(nums=[1,2,3],target=4)

0 1 2 3 4
1 0 0 0 0
1 1 1 1 2 4
2 1 1 2 3 6
3 1 1 2 4 7

二维数组:

dp(i,c)={dp(i1,c),c<nums[i]dp(i1,c)+dp(nums.size()1,cnums[i]),cnums[i]dp(i,c)=\begin{cases} dp(i-1,c),c<nums[i]\\ dp(i-1,c)+dp(nums.size()-1,c-nums[i]),c\ge nums[i] \end{cases}

1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
@cache
def dfs(i,c):
if c==0:
return 1
if i<0:
return 0
if c-nums[i]<0:
return dfs(i-1,c)
return dfs(len(nums)-1,c-nums[i])+dfs(i-1,c)
return dfs(len(nums)-1,target)

一维数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using ll=unsigned long long;
class Solution {
public:
int combinationSum4(vector<int>& nums, int target)
{
vector<ll>dp(target+1,0);
dp[0]=1;
for(int j=1;j<=target;++j)
{
for(int i=0;i<nums.size();++i)
{
if(j-nums[i]>=0)
dp[j]+=dp[j-nums[i]];
}
}
return dp[target];
}
};

474. 一和零

leetcode链接

这是一个0-1背包问题,区别是背包容量是一个二维的情况,即要同时考虑0和1的容量

w0[i]表示str[i]含有0的个数

w1[i]表示str[i]含有1的个数

dfs(i,c,d)表示只考虑前i个字符串,0的剩余容量为c,1的剩余容量为d的情况下,字串的最大长度

dfs(i,c,d)=max{dfs(i-1,c,d),dfs(i-1,c-w0[i],d-w1[i])+1}

方法1:灵神模板记忆化递归

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
le=len(strs)
w0=[0]*le
w1=[0]*le
for idx,s in enumerate(strs):
w0[idx]=s.count("0")
w1[idx]=s.count("1")
@cache
def dfs(i,c,d):
if i<0:
return 0
if c<w0[i] or d<w1[i]:
return dfs(i-1,c,d)
return max(dfs(i-1,c,d),dfs(i-1,c-w0[i],d-w1[i])+1)
return dfs(le-1,m,n)

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define m1 (101*101)
#define m2 101
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n)
{
int le=strs.size();
vector<int>w0=vector<int>(le);
vector<int>w1=vector<int>(le);
for(int i=0;i<strs.size();++i)
{
w0[i]=count(strs[i].begin(),strs[i].end(),'0');
w1[i]=count(strs[i].begin(),strs[i].end(),'1');
}
unordered_map<int,int>cache;
function<int(int,int,int)>dfs;
function<int(int,int,int)>cdfs=[&](int i,int n0,int n1)
{
int key=i*m1+n0*m2+n1;
if(cache.find(key)==cache.end())
cache[key]=dfs(i,n0,n1);
return cache[key];
};
dfs=[&](int i,int n0,int n1)
{
if(i<0)
return 0;
if(n0-w0[i]<0||n1-w1[i]<0)
return cdfs(i-1,n0,n1);
return max(cdfs(i-1,n0,n1),cdfs(i-1,n0-w0[i],n1-w1[i])+1);
};
return cdfs(le-1,m,n);
}
};

方法2:三维数组动规

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n)
{
int len=strs.size();
vector<int>w0(len);
vector<int>w1(len);
for(int i=0;i<len;++i)
{
w0[i]=count(strs[i].begin(),strs[i].end(),'0');
w1[i]=count(strs[i].begin(),strs[i].end(),'1');
}
vector<vector<vector<int>>> vec(len);
for(auto& vec_1:vec)
{
vec_1.resize(m+1);
for(auto& vec_2:vec_1)
vec_2.resize(n+1);
}
for(int i=0;i<len;++i)
{
for(int j=0;j<=m;++j)
{
for(int k=0;k<=n;++k)
{
if(i==0)
{
if(j<w0[i]||k<w1[i])
vec[i][j][k]=0;
else
vec[i][j][k]=1;
continue;
}
if(j-w0[i]<0||k-w1[i]<0)
{
vec[i][j][k]=vec[i-1][j][k];
continue;
}
vec[i][j][k]=max(vec[i-1][j][k],vec[i-1][j-w0[i]][k-w1[i]]+1);
}
}
}
return vec[len-1][m][n];
}
};

879.盈利计划

leetcode链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution:
def profitableSchemes(self, n: int, minProfit: int, group: List[int], profit: List[int]) -> int:
m=10**9+7
le=len(group)
## 首先计算符合人数要求的所有方案
@cache
def dp1(i,num):
if i<0:
return 1
if num-group[i]<0:
return dp1(i-1,num)
return (dp1(i-1,num)+dp1(i-1,num-group[i]))%m
ans1=dp1(le-1,n)
## 然后计算利润小于minProfit的方案
@cache
def dp2(i,g,p):
if i==-1:
return 1 if p==0 else 0
if g-group[i]<0 or p-profit[i]<0:
return dp2(i-1,g,p)%m
return (dp2(i-1,g-group[i],p-profit[i])+dp2(i-1,g,p))%m
maxprofit=sum(profit)
ans2=0
for i in range(minProfit):
ans2+=dp2(len(group)-1,n,i)
ans2%=m
return (ans1+m-ans2)%m

最长公共子序列

最长公共子序列 编辑距离【基础算法精讲 19】 (参考视频)

1143.最长公共子序列

leetcode链接

image-20230706112658119

可以得到递推式:

image-20230706112721998

可以证明,简化后结果为

dfs(i,j)={dfs(i1,j1)+1(s[i]=t[j])max{dfs(i1,j),dfs(i,j1)}(s[i]t[j])dfs(i,j)= \begin{cases} dfs(i-1,j-1)+1\qquad (s[i]=t[j])\\ max\{dfs(i-1,j),dfs(i,j-1)\}\qquad (s[i]\neq t[j]) \end{cases}

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m=len(text1)
n=len(text2)
arr=[[]]*(m+1)
for idx,arr_ in enumerate(arr):
arr[idx]=[0]*(n+1)
for i in range(1,m+1):
for j in range(1,n+1):
if text1[i-1]==text2[j-1]:
arr[i][j]=arr[i-1][j-1]+1
else:
arr[i][j]=max(arr[i-1][j],arr[i][j-1])
return arr[m][n]

72. 编辑距离

leetcode链接

dp(i,j)={min{dp(i1,j),dp(i,j1),dp(i1,j1)}+1,word1[i]word2[j]dp(i1,j1),word1[i]=word2[j]dp(i,j)= \begin{cases} min\{dp(i-1,j),dp(i,j-1),dp(i-1,j-1)\}+1,word1[i]\ne word2[j]\\ dp(i-1,j-1),word1[i]=word2[j] \end{cases}

dp(i,j) \varnothing r o s
\varnothing 0 1 2 3
h 1 1 2 3
o 2 2 1 2
r 3 2 2 2
s 4 3 3 2
e 5 4 4 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
@cache
def dp(i,j):
if i==-1 and j==-1:
return 0
if i==-1:
return dp(i,j-1)+1
if j==-1:
return dp(i-1,j)+1
if word1[i]==word2[j]:
return dp(i-1,j-1)
return min(dp(i-1,j),dp(i,j-1),dp(i-1,j-1))+1
return dp(len(word1)-1,len(word2)-1)

课后作业:

583. 两个字符串的删除操作

leetcode链接

点击跳转

712. 两个字符串的最小ASCII删除和

leetcode链接

dp(i,j)={dp(i1,j1)+ascii(s1[i]),s1[i]=s2[j]max{dp(i1,j),dp(i,j1)},s1[i]s2[i]dp(i,j)=\begin{cases} dp(i-1,j-1)+ascii(s1[i]),s1[i]=s2[j]\\ max\{dp(i-1,j),dp(i,j-1)\},s1[i]\ne s2[i] \end{cases}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def minimumDeleteSum(self, s1: str, s2: str) -> int:
@cache
def dp(i,j):
if i<0 or j<0:
return 0
if s1[i]!=s2[j]:
return max(dp(i-1,j),dp(i,j-1))
return dp(i-1,j-1)+ord(s1[i])
add=0
for ch in s1:
add+=ord(ch)
for ch in s2:
add+=ord(ch)
return add-2*dp(len(s1)-1,len(s2)-1)

1458. 两个子序列的最大点积

leetcode链接

3 0 -6
0 0 0 0
2 0 6 6 6
1 0 6 6 6
-2 0 6 6 18
5 0 15 15 18

dp(i,j)=max{dp(i1,j1)+nums1[i]nums2[j],dp(i1,j),dp(i,j1)}dp(i,j)=max\{dp(i-1,j-1)+nums1[i]*nums2[j],dp(i-1,j),dp(i,j-1)\}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def maxDotProduct(self, nums1: List[int], nums2: List[int]) -> int:
@cache
def dp(i,j): ## 记忆化递归
if i<0 or j<0:
return 0
return max(dp(i-1,j-1)+nums1[i]*nums2[j],dp(i-1,j),dp(i,j-1))
ret=dp(len(nums1)-1,len(nums2)-1)
if ret==0: ## ret==0可能是因为答案为负数,此时需要分别找出两个数组中绝对值最小的元素,并相乘
min1=inf
for n in nums1:
if abs(n)<abs(min1):
min1=n
min2=inf
for n in nums2:
if abs(n)<abs(min2):
min2=n
return min1*min2
else:
return ret

97. 交错字符串

leetcode链接

三维dp

1
2
3
4
5
6
7
8
9
10
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
@cache
def dp(i,j,k):
if i==-1 and j==-1 and k==-1:
return True
elif k==-1:
return False
return i!=-1 and dp(i-1,j,k-1) and s1[i]==s3[k] or j!=-1 and dp(i,j-1,k-1) and s2[j]==s3[k]
return dp(len(s1)-1,len(s2)-1,len(s3)-1)

(13)单调栈专题

单调栈1:每日温度

leetcode739

C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures)
{
vector<int> vec=temperatures;
int n=vec.size();
stack<pair<int,int>> sta;
vector<int>ret(n);
for(int i=n-1;i>=0;--i)
{
while(!sta.empty()&&sta.top().first<=vec[i])
sta.pop();
if(sta.empty())
ret[i]=0;
else
ret[i]=sta.top().second-i;
sta.push(make_pair(vec[i],i));
}
return ret;
}
};

python:

1
2
3
4
5
6
7
8
9
10
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
stack=[[inf,-1]] ## 单调栈,其中元素必须递减
ret=[0]*len(temperatures)
for idx,num in enumerate(temperatures):
while stack[len(stack)-1][0]<num:
ret[stack[len(stack)-1][1]]=idx-stack[len(stack)-1][1]
stack.pop(len(stack)-1)
stack.append([num,idx])
return ret

单调栈2:下一个更大元素I

leetcode496

原理和上一题一样,时间复杂度为O(nums1.length + nums2.length)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2)
{
unordered_map<int,int> hash;
for(int i=0;i<nums2.size();++i)
{
hash[nums2[i]]=i;
}
stack<int> sta;
vector<int>ans(nums2.size());
for(int i=nums2.size()-1;i>=0;--i)
{
while(!sta.empty()&&sta.top()<=nums2[i])
sta.pop();
if(sta.empty())
ans[i]=-1;
else
ans[i]=sta.top();
sta.push(nums2[i]);
}
vector<int> ret(nums1.size());
for(int i=0;i<nums1.size();++i)
{
ret[i]=ans[hash[nums1[i]]];
}
return ret;
}
};

algorithm-notes
https://blog.algorithmpark.xyz/2023/07/04/algorithm-notes/index/
作者
CJL
发布于
2023年7月4日
更新于
2024年9月30日
许可协议