本文最后更新于 2024-09-30T03:12:50+00:00
Algorithm-learning-notes
算法笔记+个人代码(菜,仅供参考)
笔记未完成,正在更新
已完成:数组、排序、链表、哈希表、栈和队列、二叉树、回溯、贪心、动态规划
正在更新:图论、字符串、单调栈
版本:2023-12-07
基础算法总结
题目参考:https://www.programmercarl.com/
时空复杂度理论
使用O O O 、Ω \Omega Ω 、Θ \Theta Θ 表示法,分别表示算法的效率上限(上界)、下限(下界)、和等限(确界),其数学上的具体定义见表
记号
定义
含义
O O O
f ( n ) = O ( g ( n ) ) f(n)=O(g(n)) f ( n ) = O ( g ( n )) 若存在两个正常数c c c 和n 0 n_0 n 0 ,使n ≥ n 0 n\ge n_0 n ≥ n 0 时,有0 ≤ f ( n ) ≤ c ⋅ g ( n ) 0\le f(n)\le c\cdot g(n) 0 ≤ f ( n ) ≤ c ⋅ g ( n )
f ( n ) f(n) f ( n ) 的渐进上限为g ( n ) g(n) g ( n )
Ω \Omega Ω
f ( n ) = Ω ( g ( n ) ) f(n)=\Omega (g(n)) f ( n ) = Ω ( g ( n )) 若存在两个正常数c c c 和n 0 n_0 n 0 ,使n ≥ n 0 n\ge n_0 n ≥ n 0 时,有0 ≤ c ⋅ g ( n ) ≤ f ( n ) 0 \le c\cdot g(n)\le f(n) 0 ≤ c ⋅ g ( n ) ≤ f ( n )
f ( n ) f(n) f ( n ) 的渐进下限为g ( n ) g(n) g ( n )
Θ \Theta Θ
f ( n ) = Θ ( g ( n ) ) f(n)=\Theta (g(n)) f ( n ) = Θ ( g ( n )) 若存在正常数c 1 c_1 c 1 ,c 2 c_2 c 2 和n 0 n_0 n 0 ,使n ≥ n 0 n\ge n_0 n ≥ n 0 时,有0 ≤ c 1 ⋅ g ( n ) ≤ f ( n ) ≤ c 2 ⋅ g ( n ) 0\le c_1\cdot g(n)\le f(n)\le c_2\cdot g(n) 0 ≤ c 1 ⋅ g ( n ) ≤ f ( n ) ≤ c 2 ⋅ g ( n )
f ( n ) f(n) f ( n ) 的渐进确界为g ( n ) g(n) g ( n )
o o o
f ( n ) = o ( g ( n ) ) f(n)=o(g(n)) f ( n ) = o ( g ( n )) 若对任意正数 c c c 都存在 n 0 n_0 n 0 ,使得n ≥ n 0 n\ge n_0 n ≥ n 0 时有 0 ≤ f ( n ) < c ⋅ g ( n ) 0\le f(n)< c\cdot g(n) 0 ≤ f ( n ) < c ⋅ g ( n )
ω \omega ω
f ( n ) = ω ( g ( n ) ) f(n)=\omega (g(n)) f ( n ) = ω ( g ( n )) 若对任意正数 c c c 都存在 n 0 n_0 n 0 ,使得n ≥ n 0 n\ge n_0 n ≥ n 0 时有 0 ≤ c ⋅ g ( n ) < f ( n ) 0\le c\cdot g(n) < f(n) 0 ≤ c ⋅ g ( n ) < f ( n )
若 f ( n ) = O ( g ( 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 ) = Θ ( g ( n ) ) f(n)=\Theta(g(n)) f ( n ) = Θ ( g ( n ))
如果∃ c > 0 , n 0 > 0 \exist c>0,n_0>0 ∃ c > 0 , n 0 > 0 (n 0 n_0 n 0 为整数),使对于∀ n ≥ n 0 \forall n\ge n_0 ∀ n ≥ n 0 ,有f ( n ) ≤ c ⋅ g ( n ) f(n)\le c\cdot g(n) f ( n ) ≤ c ⋅ g ( n )
那么,f ( n ) = O ( g ( n ) ) f(n)=O(g(n)) f ( n ) = O ( g ( n ))
例1: f ( n ) = 2 n 2 + 3 n + 1 f(n)=2n^2+3n+1 f ( n ) = 2 n 2 + 3 n + 1
令c = 6 , n 0 = 100 c=6,n_0=100 c = 6 , n 0 = 100 ,∀ n > 100 \forall n>100 ∀ n > 100 ,有
f ( n ) = 2 n 2 + 3 n + 1 ≤ 2 n 2 + 3 n 2 + n 2 = 6 n 2 = c n 2 \begin{align}
f(n)&=2n^2+3n+1\\
&\le 2n^2+3n^2+n^2\\
&=6n^2\\
&=cn^2
\end{align}
f ( n ) = 2 n 2 + 3 n + 1 ≤ 2 n 2 + 3 n 2 + n 2 = 6 n 2 = c n 2
由定义可证f ( n ) = O ( n 2 ) f(n)=O(n^2) f ( n ) = O ( n 2 )
例2: f ( n ) = n log 2 n f(n)=n\log_2n f ( n ) = n log 2 n ,证明f ( n ) = O ( n 1.02 ) f(n)=O(n^{1.02}) f ( n ) = O ( n 1.02 )
令c = 1 , n 0 = 3 c=1,n_0=3 c = 1 , n 0 = 3 ,∀ n > 3 \forall n>3 ∀ n > 3 ,有
n > 3 n>3 n > 3 时log 2 n < n 0.02 \log_2n<n^{0.02} log 2 n < n 0.02
f ( n ) = n log 2 n ≤ n ⋅ n 0.02 = n 1.02 \begin{align}
f(n)&=n\log_2n\\
&\le n\cdot n^{0.02}\\
&=n^{1.02}
\end{align}
f ( n ) = n log 2 n ≤ n ⋅ n 0.02 = n 1.02
由定义可证f ( n ) = O ( n 1.02 ) f(n)=O(n^{1.02}) f ( n ) = O ( n 1.02 )
时间复杂度的量级比较:
O ( 1 ) < O ( log 2 n ) < O ( n ) < O ( n ) < O ( n log 2 n ) < O ( n 1.01 ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) 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)
O ( 1 ) < O ( log 2 n ) < O ( n ) < O ( n ) < O ( n log 2 n ) < 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)) S ( n ) = O ( f ( n )) ,其中n为问题的规模,f ( n ) f(n) f ( n ) 为语句关于n n n 所占存储空间的函数
(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 (不容易超时)
1插入排序
支持数组和链表
数组的插入排序排序
空间复杂度O ( 1 ) O(1) O ( 1 ) ,因为只使用了常数个临时变量
最好时间复杂度:O ( n ) O(n) O ( n ) ,对应于数组已经有序的情况
最坏时间复杂度:O ( n 2 ) O(n^2) O ( n 2 ) ,对应数组完全逆序的情况
平均时间复杂度:O ( n 2 ) O(n^2) 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) nums[j+1 ]=nums[j]; nums[j+1 ]=tmp; } } return nums; } };
(在leetcode912提交会超时)
插入排序优化:折半插入排序
当遍历到第i
个1元素时,[0,i-1]
的所有元素时有序的,可以利用二分查找的方法找到插入位置
然鹅, 时间复杂度没有质的飞跃
空间复杂度O ( 1 ) O(1) O ( 1 )
最好时间复杂度:O ( n ) O(n) O ( n ) ,对应于数组已经有序的情况
最坏时间复杂度:O ( n 2 ) O(n^2) O ( n 2 ) ,对应数组完全逆序的情况
平均时间复杂度:O ( n 2 ) O(n^2) 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 ; } 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 ( 1 ) ,只额外开辟了常数级别的空间
最好时间复杂度:O ( n ) O(n) O ( n ) ,对应于数组已经有序的情况
最坏时间复杂度:O ( n 2 ) O(n^2) O ( n 2 ) ,对应数组完全逆序的情况
平均时间复杂度:O ( n 2 ) O(n^2) 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 ) 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; } else i=i->next; } ListNode* ret=dummyhead->next; delete dummyhead; return ret; } };
(在leetcode148提交会超时,leetcode147提交通过,16ms,9.3MB)
2希尔排序
仅支持数组,不适用链表
先追求表中元素部分有序,再逐渐逼近全局有序
每一轮都按照一个给定的间隔进行插入排序,这个间隔应当逐渐减少,最后必须为1
以下的代码中, 步长(间隔)从n 2 \frac n 2 2 n 开始递减,每次变为原来的1 2 \frac 1 2 2 1 ,直到变为1
空间复杂度:O ( 1 ) O(1) O ( 1 )
时间复杂度:和增量序列d 1 , d 2 , d 3 , ⋯ d_1,d_2,d_3,\cdots d 1 , d 2 , d 3 , ⋯ 的选择有关,目前无法用数学手段证明确切的时间复杂度
最坏时间复杂度O ( n 2 ) O(n^2) O ( n 2 ) ,也就是取d = 1 d=1 d = 1 的时候,这时候希尔排序退化为插入排序
当n n n 在某个范围内时,可达O ( n 1.3 ) O(n^{1.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 ( 1 )
时间复杂度
最好情况(有序):O ( n ) O(n) O ( n )
最坏情况(逆序):O ( n 2 ) O(n^2) O ( n 2 )
平均时间复杂度:O ( n 2 ) O(n^2) 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 ) 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 ( log 2 n ) O(\log_2n) O ( log 2 n )
最坏空间复杂度:O ( n ) O(n) O ( n )
最好时间复杂度:O ( n log 2 n ) O(n\log_2n) O ( n log 2 n )
最坏时间复杂度:O ( n 2 ) O(n^2) O ( n 2 )
平均时间复杂度O ( n log 2 n ) O(n\log_2n) O ( n log 2 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 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 ( 1 )
时间复杂度O ( n 2 ) O(n^2) 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 ( 1 )
时间复杂度O ( n 2 ) O(n^2) 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的左孩子 2 i 2i 2 i
i的右孩子 2 i + 1 2i+1 2 i + 1
i的父节点 ⌊ i 2 ⌋ \lfloor\frac i 2\rfloor ⌊ 2 i ⌋
i的所在层次 ⌈ log 2 ( i + 1 ) ⌉ \lceil \log_2(i+1)\rceil ⌈ log 2 ( i + 1 )⌉ 或⌊ log 2 i ⌋ + 1 \lfloor\log_2i\rfloor+1 ⌊ log 2 i ⌋ + 1
若完全二叉树中有n个节点,则
判断节点i有左孩子 2 i ≤ n 2i\le n 2 i ≤ n
判断节点i有右孩子 2 i + 1 ≤ n 2i+1\le n 2 i + 1 ≤ n
判断节点i为叶子节点 i > ⌊ n 2 ⌋ i>\lfloor\frac n 2\rfloor i > ⌊ 2 n ⌋
若n个关键字序列L [ 1... n ] L[1...n] L [ 1... n ] 满足下面某一条性质,则称为堆(Heap)
若满足:L [ i ] ≥ L [ 2 i ] L[i]\ge L[2i] L [ i ] ≥ L [ 2 i ] 且L [ i ] ≥ L [ 2 i + 1 ] L[i]\ge L[2i+1] L [ i ] ≥ L [ 2 i + 1 ] (1 ≤ i ≤ n 2 1\le i\le\frac n2 1 ≤ i ≤ 2 n ) 大根堆
若满足:L [ i ] ≤ L [ 2 i ] L[i]\le L[2i] L [ i ] ≤ L [ 2 i ] 且L [ i ] ≤ L [ 2 i + 1 ] L[i]\le L[2i+1] L [ i ] ≤ L [ 2 i + 1 ] (1 ≤ i ≤ n 2 1\le i\le\frac n2 1 ≤ i ≤ 2 n ) 小根堆
如果下标从0开始 ,则:
i的左孩子 2 i + 1 2i+1 2 i + 1
i的右孩子 2 i + 2 2i+2 2 i + 2
i的父节点 ⌊ i − 1 2 ⌋ \lfloor\frac {i-1} 2\rfloor ⌊ 2 i − 1 ⌋
i的所在层次 ⌈ log 2 ( i + 2 ) ⌉ \lceil \log_2(i+2)\rceil ⌈ log 2 ( i + 2 )⌉ 或⌊ log 2 ( i + 1 ) ⌋ + 1 \lfloor\log_2{(i+1)}\rfloor+1 ⌊ log 2 ( i + 1 ) ⌋ + 1
若完全二叉树中有n个节点,则
判断节点i有左孩子 2 i + 1 < n 2i+1 < n 2 i + 1 < n
判断节点i有右孩子 2 i + 2 < n 2i+2 < n 2 i + 2 < n
判断节点i为叶子节点 i ≥ ⌊ n 2 ⌋ i\ge\lfloor\frac n 2\rfloor i ≥ ⌊ 2 n ⌋
判断i存在子节点 0 ≤ i ≤ ⌊ n 2 ⌋ − 1 0\le i\le\lfloor\frac n2\rfloor-1 0 ≤ i ≤ ⌊ 2 n ⌋ − 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 ; while (k<len) { if (k+1 <len&&nums[k]<nums[k+1 ]) ++k; if (nums[idx]>=nums[k]) break ; 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 = ⌊ log 2 n ⌋ + 1 h=\lfloor\log_2n\rfloor+1 h = ⌊ log 2 n ⌋ + 1
第i i i 层最多有2 i − 1 2^{i-1} 2 i − 1 个节点,只有第1~(h-1)层的节点才可能需要下坠处理,每次下坠最多对比2次,第i层节点下坠最多对比h-i次
把整棵树调整为大根堆, 关键字对比次数:
∑ i = h − 1 1 2 i − 1 2 ( h − i ) = ∑ i = h − 1 1 2 i ( h − i ) = ∑ j = 1 h − 1 2 h − j j = 2 h ∑ j = 1 h − 1 j 2 j = 2 ⌊ log 2 n ⌋ + 1 ⋅ ( 2 − h + 1 2 h − 1 ) ≤ 2 n ⋅ 2 = 4 n \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}
i = h − 1 ∑ 1 2 i − 1 2 ( h − i ) = i = h − 1 ∑ 1 2 i ( h − i ) = j = 1 ∑ h − 1 2 h − j j = 2 h j = 1 ∑ h − 1 2 j j = 2 ⌊ l o g 2 n ⌋ + 1 ⋅ ( 2 − 2 h − 1 h + 1 ) ≤ 2 n ⋅ 2 = 4 n
所以建堆的过程中时间复杂度O ( n ) O(n) O ( n )
排序的过程中总共需要处理n-1个节点,每个节点最多需要下坠h-1层,时间复杂度O ( n log 2 n ) O(n\log_2n) O ( n log 2 n )
总的时间复杂度O ( n log 2 n ) O(n\log_2n) O ( n log 2 n )
空间复杂度O ( 1 ) 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 ; 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层最多有2 h − 1 2^{h-1} 2 h − 1 个节点,若树高为h,满足n ≤ 2 h − 1 n\le 2^{h-1} n ≤ 2 h − 1
即h − 1 = ⌈ log 2 n ⌉ h-1=\lceil\log_2n\rceil h − 1 = ⌈ log 2 n ⌉
n个元素进行二路归并排序,归并趟数=⌈ log 2 n ⌉ \lceil\log_2n\rceil ⌈ log 2 n ⌉
每趟归并的时间复杂度O ( n ) O(n) O ( n ) ,算法的时间复杂度O ( n log 2 n ) O(n\log_2n) O ( n log 2 n )
递归工作栈的空间复杂度为O ( log 2 n ) O(\log_2n) O ( log 2 n ) ,辅助数组的空间复杂度O ( n ) O(n) 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) 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 ( n log n ) O(n\log n) O ( n log n ) ,基数排序不基于比较
通常针对链表实现,假设长度为n的线性表中每个节点a j a_j a j 的关键字由d元组( k j d − 1 , k j d − 2 , k j d − 3 , ⋯ , k j 1 , k j 0 ) (k_j^{d-1},k_j^{d-2},k_j^{d-3},\cdots,k_j^{1},k_j^{0}) ( k j d − 1 , k j d − 2 , k j d − 3 , ⋯ , k j 1 , k j 0 ) 组成
其中 , 0 ≤ k j i ≤ r − 1 ( 0 ≤ j < n , 0 ≤ i ≤ d − 1 ) 0\le k_j^{i}\le r-1\quad (0\le j<n,0\le i\le d-1) 0 ≤ k j i ≤ r − 1 ( 0 ≤ j < n , 0 ≤ i ≤ d − 1 ) , r r r 称为基数
空间复杂度 O ( n ) O(n) O ( n )
链表初始化时间复杂度 O ( r ) O(r) O ( r ) ,一趟分配的时间复杂度 O ( n ) O(n) O ( n ) ,一趟收集的时间复杂度 O ( r ) O(r) O ( r ) ,总共 d d d 趟,时间复杂度 O ( d ( n + r ) ) O(d(n+r)) O ( d ( n + r ))
对于leetcode148,− 1 0 5 ≤ N o d e . v a l ≤ 1 0 5 -10^5 \le Node.val \le 10^5 − 1 0 5 ≤ N o d e . v a l ≤ 1 0 5 ,n ∈ [ 0 , 5 × 1 0 4 ] n\in[0,5\times10^4] n ∈ [ 0 , 5 × 1 0 4 ]
排序时加上1 0 5 10^5 1 0 5 ,保证所有数为正数,对于十进制数r=10,d不超过6
所有该题时间复杂度O ( n ) O(n) 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外部排序
优化1 :采用多路归并可以减少归并趟数,从而减少磁盘I/O(读写)次数
对r r r 个初始归并段,做k路归并,则归并数可用k k k 叉树表示
k k k 叉树的第h h h 层最多有k h − 1 k^{h-1} k h − 1 个结点,则r ≤ k h − 1 r\le k^{h-1} r ≤ k h − 1 ,归并趟数h − 1 ≥ ⌈ log k r ⌉ h-1\ge\lceil \log_kr\rceil h − 1 ≥ ⌈ log k r ⌉
k路平衡归并:
最多只能有k k k 个段归并为一个;
每一趟归并中,若有 m m m 个归并段参与归并,则经过这一趟处理得到⌈ m k ⌉ \lceil \frac mk\rceil ⌈ k m ⌉ 个新的归并段
k = 4 k=4 k = 4 的情况:
多路归并带来的负面影响:
k k k 路归并时,需要开辟k k k 个输入缓冲区,内存开销增加。
每挑选一个关键字需要对比关键字( k − 1 ) (k-1) ( k − 1 ) 次,内部归并所需时间增加
**优化2:**减少初始归并段r r r 的数量
生成初始归并段的方法:若总共有N N N 条记录,内存工作区可以容纳L L L 条记录,则初始归并段数量r = N L r=\frac NL r = L N
(3)查找专题
查找过程中的主要操作是关键字的比较,查找过程中的关键字的平均比较次数(平均查找长度ASL(Average search length))作为衡量一个查找算法效率高低的标准,ASL定义为:
A S L = ∑ i = 1 n P i C i ASL=\sum_{i=1}^n P_iC_i
A S L = i = 1 ∑ n P i C i
A S L ASL A S L 是对存储结构中对象总数n的函数
P i P_i P i 是检索第i个元素的概率
C i C_i C i 是找到第i个元素所需的关键码值与给定值的比较次数
顺序查找
对于长度为n n n 的顺序表:
A S L = 1 n ∑ i = 1 n i = n + 1 2 ASL=\frac 1n\sum_{i=1}^ni=\frac{n+1}2
A S L = n 1 i = 1 ∑ n i = 2 n + 1
时间复杂度O ( n ) O(n) O ( n )
二分查找
[实现代码](## 数组1:二分查找)
前提:查找表中所有记录是有序的(升序或降序)
对二分查找有效性的证明:
因为算法在top>bottom的条件下持续运行,只需证明在该条件下区间大小[bottom,top]是严格变小的
graph TB;
1+top-bottom --> 1+top-mid-1;
1+top-bottom --> 1+mid-bottom;
证明其一:
b o t t o m < t o p → 2 b o t t o m < b o t t o m + 1 → b o t t o m < b o t t o m + t o p 2 → b o t t o m < ⌊ b o t t o m + t o p 2 ⌋ + 1 → b o t t o m < m i d + 1 → 1 + t o p − b o t t o m > 1 + t o p − m i d − 1 bottom<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
b o tt o m < t o p → 2 b o tt o m < b o tt o m + 1 → b o tt o m < 2 b o tt o m + t o p → b o tt o m < ⌊ 2 b o tt o m + t o p ⌋ + 1 → b o tt o m < mi d + 1 → 1 + t o p − b o tt o m > 1 + t o p − mi d − 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 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 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 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 ) 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 ( n ) ,空间复杂度O ( 1 ) 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 ) 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 ( n ) , 空间复杂度O ( 1 ) 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} α = 散列表长度 m 表中记录数 n
二次探测、伪随机探测、再哈希法的平均查找长度是
S 成功 ≈ − 1 α ln ( 1 − α ) S_{成功}\approx -\frac 1\alpha \ln(1-\alpha)
S 成功 ≈ − α 1 ln ( 1 − α )
S 失败 ≈ 1 1 − α S_{失败}\approx \frac 1{1-\alpha}
S 失败 ≈ 1 − α 1
在表中任选一个位置,不为空的概率为α \alpha α ,为空的概率为1 − α 1-\alpha 1 − α 。
查找k个位置后结束,对应的概率为α k − 1 ( 1 − α ) \alpha ^{k-1}(1-\alpha) α k − 1 ( 1 − α )
查找失败的概率为
∑ k = 1 ∞ k α k − 1 ( 1 − α ) = 1 1 − α \sum_{k=1}^{\infin}k\alpha^{k-1}(1-\alpha)=\frac 1{1-\alpha}
k = 1 ∑ ∞ k α k − 1 ( 1 − α ) = 1 − α 1
查找关键字k k k 的探测序列和插入关键字k k k 的探测序列是相同的,假设k k k 是第k + 1 k+1 k + 1 个被插入到散列表中的关键字,则此前散列表的装填因子为i m \frac im m i ,查找k k k 的探测次数的期望为1 1 − i m \frac 1{1-\frac im} 1 − m i 1 。则查找成功的探测次数的期望为
1 n ∑ i = 0 n − 1 m m − i = m n ∑ i = 0 n − 1 1 m − i = 1 α ∑ k = m − n + 1 m 1 k ≤ 1 α ∫ m − n m 1 x d x = 1 α ln m m − n = 1 α ln 1 1 − α = − 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}
n 1 i = 0 ∑ n − 1 m − i m = n m i = 0 ∑ n − 1 m − i 1 = α 1 k = m − n + 1 ∑ m k 1 ≤ α 1 ∫ m − n m x 1 d x = α 1 ln m − n m = α 1 ln 1 − α 1 = − α 1 ln ( 1 − α )
哈希表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 ( n 4 ) O(n^4) O ( n 4 ) 会超时
两组两个for循环,配合哈希表可以通过,时间复杂度O ( n 2 ) O(n^2) 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 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 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 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 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:
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=1
,j=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) { int n=haystack.size (),m=needle.size (); vector<int >next (m); int i=1 ,j=0 ; while (i<m) { if (needle[i]==needle[j]) { next[i]=j+1 ; ++i; ++j; } else if (j==0 ) { ++i; } 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 ; } };
(细心的你会发现,构造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) { int n=haystack.size (),m=needle.size (); 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 + n ) .
空间复杂度 O ( m ) 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=0 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) { int n=haystack.size (),m=needle.size (); 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 ; } 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:有限状态自动机
参考文献
初始状态
符号位
整数部分
左侧有整数的小数点
左侧无整数的小数点(根据前面的第二条额外规则,需要对左侧有无整数的两种小数点做区分)
小数部分
字符 e
指数部分的符号位
指数部分的整数部分
(图片出自力扣官方题解)
最后只有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 ; } } 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 ; } };
字符串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) { 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 ; } 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 ; } };
(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 + + - 后缀表达式
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
任意结点的度≤ m \le m ≤ m
至少有一个结点的度 = m
允许所有节点的度都 < m
树中的结点数等于所有结点的度数之和加1
度为m m m 的树第i i i 层至多有m i − 1 m^{i-1} m i − 1 个结点(i ≥ 1 i\ge 1 i ≥ 1 )
高度为h h h 的m m m 叉树至多有m h − 1 m − 1 \frac {m^h-1}{m-1} m − 1 m h − 1 个结点,证明:
m 0 + m 1 + ⋯ + m h − 1 = m h − 1 m − 1 m^0+m^1+\dots+m^{h-1}=\frac{m^h-1}{m-1}
m 0 + m 1 + ⋯ + m h − 1 = m − 1 m h − 1
高度为h h h 的m m m 叉树至少有h h h 个结点
高度为h h h 、度为m m m 的树至少有h + m − 1 h+m-1 h + m − 1 个结点
具有n n n 个结点的m m m 叉树最小高度为⌈ log m ( n ( m − 1 ) + 1 ) ⌉ \lceil \log_m(n(m-1)+1)\rceil ⌈ log m ( n ( m − 1 ) + 1 )⌉ ,证明:
高度最小的情况:所有结点都有m m m 个孩子
(前 h − 1 层最多有几个结点) m h − 1 − 1 m − 1 < n ≤ m h − 1 m − 1 (前 h 层最多有几个结点) (前h-1层最多有几个结点)\frac {m^{h-1}-1}{m-1} < n \le \frac{m^h-1}{m-1}(前h层最多有几个结点)
(前 h − 1 层最多有几个结点) m − 1 m h − 1 − 1 < n ≤ m − 1 m h − 1 (前 h 层最多有几个结点)
m h − 1 < n ( m − 1 ) + 1 ≤ m h m^{h-1}<n(m-1)+1 \le m^h
m h − 1 < n ( m − 1 ) + 1 ≤ m h
h − 1 < log m ( n ( m − 1 ) + 1 ) ≤ h h-1<\log_m(n(m-1)+1)\le h
h − 1 < log m ( n ( m − 1 ) + 1 ) ≤ h
h m i n = ⌈ log m ( n ( m − 1 ) + 1 ) ⌉ h_{min}=\lceil \log_m(n(m-1)+1)\rceil
h min = ⌈ log m ( n ( m − 1 ) + 1 )⌉
二叉树的性质
设非空二叉树中度为0、1、2的结点个数分别是n 0 n_0 n 0 , n 1 n_1 n 1 , n 2 n_2 n 2 ,树中结点总数为n n n
n 0 = n 2 + 1 (1) n_0=n_2+1\tag{1}
n 0 = n 2 + 1 ( 1 )
n = n 0 + n 1 + n 2 (2) n=n_0+n_1+n_2\tag{2}
n = n 0 + n 1 + n 2 ( 2 )
n = n 1 + 2 n 2 + 1 ( 树的节点数等于总度数 + 1 ) (3) n=n_1+2n_2+1 (树的节点数等于总度数+1)\tag{3}
n = n 1 + 2 n 2 + 1 ( 树的节点数等于总度数 + 1 ) ( 3 )
(3)-(2)得到(1)式
二叉树第 i 层至多有2 i − 1 2^{i-1} 2 i − 1 个结点(i ≥ 1 i\ge 1 i ≥ 1 )
高度为 h 的二叉树至多有2 h − 1 2^h-1 2 h − 1 个结点
具有 n 个结点的完全二叉树的高度 h 为⌈ log 2 ( n + 1 ) ⌉ \lceil \log_2(n+1)\rceil ⌈ log 2 ( n + 1 )⌉ 或⌊ log 2 n ⌋ + 1 \lfloor\log_2n\rfloor+1 ⌊ log 2 n ⌋ + 1 , 证明:
2 h − 1 − 1 < n ≤ 2 h − 1 2^{h-1}-1 < n\le2^h-1
2 h − 1 − 1 < n ≤ 2 h − 1
h − 1 < log 2 ( n + 1 ) ≤ h h-1<\log_2(n+1)\le h
h − 1 < log 2 ( n + 1 ) ≤ h
则h = ⌈ log 2 ( n + 1 ) ⌉ h=\lceil \log_2(n+1)\rceil h = ⌈ log 2 ( n + 1 )⌉
2 h − 1 ≤ n < 2 h 2^{h-1}\le n <2^h
2 h − 1 ≤ n < 2 h
h − 1 ≤ log 2 n < h h-1\le \log_2n <h
h − 1 ≤ log 2 n < h
则h = ⌊ log 2 n ⌋ + 1 h=\lfloor\log_2n\rfloor+1 h = ⌊ log 2 n ⌋ + 1
具有 n 个结点的二叉树,有 n+1 个空链域
证明:n 个结点的二叉树总共有 2n 个链域,总度数为 n-1,有 n-1 个指针是指向结点的
所以有 2n-(n-1) = n+1 个指针是空指针
满二叉树
一棵高度为h h h ,且含有2 h − 1 2^h-1 2 h − 1 个结点的二叉树
完全二叉树
当且仅当其每个结点都与高度为 h 的满二叉树中编号 1~n 的结点一一对应时,称为完全二叉树
只有最后两层可能有叶子结点
最多只有一个度为1的结点
如果某个结点度为1,则它的孩子结点一定是左孩子
对于完全二叉树,可以由结点数 n 推出度为0、1和2的结点个数n 0 n_0 n 0 、n 1 n_1 n 1 和n 2 n_2 n 2
证明:
完全二叉树最多只有一个度为1的结点,n 1 = 0 或 1 n_1=0或1 n 1 = 0 或 1
又因为对于任意二叉树都有n 0 = n 2 + 1 n_0=n_2+1 n 0 = n 2 + 1 ,n 0 + n 2 = 2 n 2 + 1 n_0+n_2=2n_2+1 n 0 + n 2 = 2 n 2 + 1 一定是一个奇数
n = n 0 + n 1 + n 2 n=n_0+n_1+n_2 n = n 0 + n 1 + n 2 ,可知:
如果完全二叉树有偶数个结点n = 2 k n=2k n = 2 k ,则
n 1 = 1 n_1=1 n 1 = 1 ,n 0 = k n_0=k n 0 = k ,n 2 = k − 1 n_2=k-1 n 2 = k − 1
如果完全二叉树有奇数个结点n = 2 k − 1 n=2k-1 n = 2 k − 1 ,则
n 1 = 0 n_1=0 n 1 = 0 ,n 0 = k n_0=k n 0 = k ,n 2 = k − 1 n_2=k-1 n 2 = k − 1
完全二叉树的编号问题
如果按层序从1开始编号
i的左孩子 2 i 2i 2 i
i的右孩子 2 i + 1 2i+1 2 i + 1
i的父结点 ⌊ i 2 ⌋ \lfloor\frac i 2\rfloor ⌊ 2 i ⌋
i的所在层次 ⌈ log 2 ( i + 1 ) ⌉ \lceil \log_2(i+1)\rceil ⌈ log 2 ( i + 1 )⌉ 或⌊ log 2 i ⌋ + 1 \lfloor\log_2i\rfloor+1 ⌊ log 2 i ⌋ + 1
若完全二叉树中有n个结点,则
判断结点i有左孩子 2 i ≤ n 2i\le n 2 i ≤ n
判断结点i有右孩子 2 i + 1 ≤ n 2i+1\le n 2 i + 1 ≤ n
判断结点i为叶子结点 i > ⌊ n 2 ⌋ i>\lfloor\frac n 2\rfloor i > ⌊ 2 n ⌋
判断结点i为分支结点 1 ≤ i ≤ ⌊ n 2 ⌋ 1 \le i\le\lfloor\frac n2\rfloor 1 ≤ i ≤ ⌊ 2 n ⌋
如果按层序从0开始编号
i的左孩子 2 i + 1 2i+1 2 i + 1
i的右孩子 2 i + 2 2i+2 2 i + 2
i的父结点 ⌊ i − 1 2 ⌋ \lfloor\frac {i-1} 2\rfloor ⌊ 2 i − 1 ⌋
i的所在层次 ⌈ log 2 ( i + 2 ) ⌉ \lceil \log_2(i+2)\rceil ⌈ log 2 ( i + 2 )⌉ 或⌊ log 2 ( i + 1 ) ⌋ + 1 \lfloor\log_2{(i+1)}\rfloor+1 ⌊ log 2 ( i + 1 ) ⌋ + 1
若完全二叉树中有n个结点,则
判断结点i有左孩子 2 i + 1 < n 2i+1 < n 2 i + 1 < n
判断结点i有右孩子 2 i + 2 < n 2i+2 < n 2 i + 2 < n
判断结点i为叶子结点 i ≥ ⌊ n 2 ⌋ i\ge\lfloor\frac n 2\rfloor i ≥ ⌊ 2 n ⌋
判断结点i为分支结点 0 ≤ i < ⌊ n 2 ⌋ 0\le i < \lfloor \frac n2\rfloor 0 ≤ i < ⌊ 2 n ⌋
平衡二叉树
树上任一结点的左子树和右子树的深度之差不超过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 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 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 ; 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 ( log 2 n ) O(\log^2n) O ( log 2 n )
二叉树的深度h = ⌈ log 2 ( n + 1 ) ⌉ h=\lceil\log_2(n+1)\rceil h = ⌈ log 2 ( n + 1 )⌉
首先需要O ( h ) O(h) O ( h ) 的时间确定二叉树的深度
然后进行二分查找, 每次查找都要访问h h h 个节点, 时间复杂度O ( h ) O(h) O ( h ) , 在编号为2 h − 1 2^{h-1} 2 h − 1 到2 h − 1 2^{h}-1 2 h − 1 的节点之间进行二分查找(在2 h − 1 2^{h-1} 2 h − 1 个节点之间进行二分查找),时间复杂度O ( log ( 2 h − 1 ) ) = O ( h − 1 ) O(\log(2^{h-1}))=O(h-1) O ( log ( 2 h − 1 )) = O ( h − 1 ) .
所以总的时间复杂度O ( h 2 ) = O ( log 2 n ) O(h^2)=O(\log^2n) O ( h 2 ) = O ( log 2 n )
因为开辟了长为h h h 的数组,空间复杂度为O ( log n ) O(\log n) O ( log n )
方法2:寻找满二叉树
参考视频
这个方法利用递归, 更容易写:yum:
利用性质:一棵深度为h h h 的满二叉树,节点数量为2 h − 1 2^h-1 2 h − 1
另外, 在C++中, 2 n 2^n 2 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 ( log 2 n ) O(\log^2n) O ( log 2 n )
考虑最坏的情况, 也就是二叉树最大层次只有一个节点的情况, 总共需要递归h h h 层, 每层递归的时间复杂度为O ( h ) O(h) O ( h ) , 所以总的时间复杂度为O ( h 2 ) = O ( log 2 n ) O(h^2)=O(\log^2n) O ( h 2 ) = O ( log 2 n )
空间复杂度:O ( log ( n ) ) O(\log(n)) O ( log ( n ))
在最坏的情况下, 需要递归h h h 层, 故空间复杂度O ( log ( n ) ) 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 ; 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 = ⌊ log 2 n ⌋ + 1 h=\lfloor\log_2n\rfloor+1 h = ⌊ log 2 n ⌋ + 1
左孩子的编号满足n 左 = n < < 1 n_左=n<<1 n 左 = n << 1
右孩子的编号满足n 右 = ( n < < 1 ) + 1 n_右=(n<<1)+1 n 右 = ( 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) { 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
,或者p
和q
分别位于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 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 × 3 3\times 3 3 × 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 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 ( n 2 ) O(n^2) O ( n 2 ) 的暴力解法可以通过,感兴趣的可以试试:wink:
贪心:
C++:
时间复杂度O ( n ) O(n) 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 ; 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 ): 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 n10=0 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
上了第一节体育课,老师给大家排好了体操的队伍,可是大家脑子都很笨,记不清自己在哪,老师说,你就看前面有几个比自己高的就行!就像这样:
该上第二节课的时候,大家记住了前面有几个比自己高的,却还是忘记了怎么排,老师见状让学生从高到低排好队,身高一样的,比自己高的越多,越往后面站,像这样:
每次让最高的学生出来找自己的位置,第一个高个子[7,0]
自然站到了第一个位置:
而第二个高个子[7,1]
知道有一个人大于等于自己的身高,站在了第一个人身后:
第三个人[6,1]
想了想,有一个人比自己高,那自己肯定站在第二位,于是就插队,现在也站到了第一个人身后:
第四个人[5,0]
想了想,没人比自己高,那自己肯定站在第一位,于是就插队,站到了队头:
第五个人[5,2]
想了想,有两个人比自己高,于是就插队,站到了第二个人后面,也就是第三个位置:
第六个人[4,4]
看了看眼前的队伍,比自己高的人都在里面,他安心的数着前面有四个人比自己高,心安理得的站到了第四个人身后:
其实这道题的大概思路就是这样,只有先让身高高的先进入队伍,后面身高低的才能根据前面高的来找自己的位置
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 ]; }); 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; vector<pair<int ,int >>vec; int len=0 ; 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; 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 ) ++ret; return ret; } };
(11)图论专题
最小生成树:连接所有节点的最小费用
leetcode1584
本题为边稠密图,更适合普里姆算法
prim普里姆算法
从某一个顶点开始构建生成树,每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止
SysuGLM的解释:
Prim 算法是一种用于解决无向图最小生成树问题的贪心算法。它的基本思想是从一个顶点开始,不断地寻找与当前生成树距离最近的顶点,将其加入到生成树中,直到所有顶点都加入到生成树中为止。该算法是由捷克数学家 Vojtěch Jarník 于 1930 年发现的,并在 1957 年由美国计算机科学家 Robert C. Prim 重新发现,因此得名 Prim 算法。
以下是 Prim 算法的基本步骤:
初始化一个空的最小生成树 T。
任选图中一个顶点 v,将其加入到 T 中。
在图中寻找距离 T 最近的顶点 u,将边 (u, v) 加入到 T 中。
重复步骤 3,直到所有顶点都加入到 T 中。
得到的 T 即为原图的最小生成树。
Prim 算法的时间复杂度取决于具体的实现方式。最简单的实现方式是用邻接矩阵表示图,其时间复杂度为 O(n^2),其中 n 为图的顶点数。使用优先队列和邻接表可以进一步优化算法的性能,将时间复杂度降低到 O(nlogn)。
设总共V个节点,时间复杂度O ( V 2 ) O(V^2) 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) { 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 ]); 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): 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 ( n 2 ) O(n^2) O ( n 2 )
Kruskal算法
SysuGLM的解释:
Kruskal 算法是一种贪心算法,用于求解连通网的最小生成树。具体步骤如下:
将所有的边按照权重从小到大排序。
初始化一个空集合,用于存放已选择的边。
按照排序后的顺序依次遍历每条边,检查当前边的两个顶点是否属于同一个连通分量(可以使用并查集数据结构进行快速判断)。如果不属于同一个连通分量,那么将这条边加入到已选择的边的集合中,并合并这两个顶点所在的连通分量;如果已经属于同一个连通分量,那么跳过这条边。
当已选择的边的数量等于顶点数减一时,算法结束。此时已选择的边构成了连通网的最小生成树。
这种算法的时间复杂度为 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 ∣ = n 2 |E|=n^2 ∣ E ∣ = n 2 ,所有时间复杂度是O ( n 2 log n ) O(n^2\log n) O ( n 2 log n )
最短路径问题:
图出自王道考研数据结构
参考视频【王道计算机考研 数据结构】
例题:
BFS算法比较基础,且只能处理不带权的图,这里就不介绍了
Dijkstra算法
当前算法适用于稠密图
求带权图的最短路径长度,局限是不能处理负权图
对于一系列顶点V 0 , V 1 , . . . , V n − 1 V_0,V_1,...,V_{n-1} V 0 , V 1 , ... , V n − 1 组成的有向带权图,求V 0 V_0 V 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) { 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 ); vector<int >path (n+1 ,-1 ); dist[k]=0 ; for (int i=0 ;i<n;++i) { 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 ; 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; } } } 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 : def networkDelayTime (self, times: List [List [int ]], n: int , k: int ) -> int : dist=[inf]*(n+1 ) used=[False ]*(n+1 ) path=[-1 ]*(n+1 ) dist[k]=0 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): 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 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)]) return -1 if max_element==inf else max_element
leetcode743通过,60ms,17.7MB
复杂度分析
节点总数为∣ V ∣ |V| ∣ V ∣ ,要遍历一次所有顶点,对于每一次遍历,要更新与当前节点直接相连,并且final值为false的全部节点的dist值,该过程遍历∣ V ∣ |V| ∣ V ∣ 次;还要找出从当前节点出发,下一个最近的节点,遍历∣ V ∣ |V| ∣ V ∣ 次
时间复杂度O ( ∣ V ∣ 2 ) O(|V|^2) O ( ∣ V ∣ 2 ) ,空间复杂度O ( ∣ V ∣ 2 ) O(|V|^2) O ( ∣ V ∣ 2 ) .(邻接矩阵占用的空间)
当前算法只能求一个顶点到其他所有顶点的最短路径,如果需要求所有顶点到其他顶点的最短路径,则需要将该算法循环∣ V ∣ |V| ∣ V ∣ 次,总的时间复杂度O ( ∣ V ∣ 3 ) O(|V|^3) 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) { 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 ); vector<int >path (n+1 ,-1 ); dist[k]=0 ; using pii=pair<int ,int >; priority_queue<pii,vector<pii>,greater<>>pq; 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]) 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)); } } } int maximum=*max_element (dist.begin ()+1 ,dist.end ()); return maximum==inf?-1 :maximum; } };
leetcode743通过,112ms,36MB
复杂度分析
设顶点数为∣ V ∣ |V| ∣ V ∣ ,边数为∣ E ∣ |E| ∣ E ∣ ,小根堆的最大长度为∣ E ∣ |E| ∣ E ∣ ,小根堆的插入和删除时间复杂度为O ( log ∣ E ∣ ) O(\log |E|) O ( log ∣ E ∣ ) ,总共进行∣ E ∣ |E| ∣ E ∣ 次遍历,总时间复杂度O ( ∣ E ∣ log ∣ E ∣ ) O(|E|\log |E|) O ( ∣ E ∣ log ∣ E ∣ )
dist数组占用空间O ( ∣ V ∣ ) O(|V|) O ( ∣ V ∣ ) ,vec数组和小根堆占用空间O ( ∣ E ∣ ) O(|E|) O ( ∣ E ∣ ) ,总空间复杂度O ( ∣ E ∣ + ∣ V ∣ ) 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) { 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) { 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 : 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 ]) return ret if ret!=inf else -1
leetcode743通过,352ms,18.56MB
复杂度分析
该过程相当于对一个三维数组的动态规划(在内存使用上可以只使用二维数组),时间复杂度O ( ∣ V ∣ 3 ) O(|V|^3) O ( ∣ V ∣ 3 ) ,空间复杂度O ( ∣ V ∣ 2 ) O(|V|^2) O ( ∣ V ∣ 2 )
Bellman-ford算法
(引用自百度百科)
贝尔曼-福特算法与迪科斯彻算法 类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。在两个算法中,计算时每个边之间的估计距离值都比真实值大,并且被新找到路径的最小长度替代。 然而,迪科斯彻算法以贪心法 选取未被处理的具有最小权值的节点,然后对其的出边进行松弛操作;而贝尔曼-福特算法简单地对所有边进行松弛操作,共∣ V ∣ − 1 |V|-1 ∣ V ∣ − 1
次,其中∣ V ∣ |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) { vector<int >dist (n+1 ,inf); dist[k]=0 ; for (int i=0 ;i<n-1 ;++i) { 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 ( ∣ V ∣ ∣ E ∣ ) O(|V||E|) O ( ∣ V ∣∣ E ∣ ) ,其中∣ V ∣ |V| ∣ V ∣ 是顶点数,∣ E ∣ |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 ); 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 () { int n,m,s; cin>>n>>m>>s; int u,v,w; using Pair=pair<int ,int >; vector<vector<Pair>> g (n+1 ); 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 ( k E ) O(kE) O ( k E ) ,k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。
引用出处
拓扑排序
参考王道考研数据结构
AOV网 (Activity On Vertex Network):用顶点表示活动的网
用DAG图(有向无环图)表示一个工程,顶点表示活动,有向边< V i , V j > <V_i,V_j> < V i , V j > 表示活动V i V_i V i 必须先于活动V j V_j V j 进行
graph LR;
准备厨具 --> 打鸡蛋
准备厨具 --> 切番茄
买菜 -->打鸡蛋
买菜-->洗番茄
洗番茄-->切番茄
打鸡蛋-->下锅炒
切番茄-->下锅炒
下锅炒-->吃番茄炒蛋
表示“番茄炒蛋工程”的AOV网
拓扑排序的一种结果:
graph LR;
准备厨具-->买菜
买菜-->洗番茄
洗番茄-->切番茄
切番茄-->打鸡蛋
打鸡蛋-->下锅炒
下锅炒-->吃番茄炒蛋
拓扑排序的实现:
从AOV网中选择一个没有前驱==(入度为0)==的顶点并输出
在网中删除该顶点和所有以它为起点的有向边
重复步骤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); vector<int >indegree (numCourses); 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); vector<bool >isvisited (numCourses,false ); vector<bool >isInStack (numCourses,false ); for (auto & pre:prerequisites) { vec[pre[1 ]].emplace_back (pre[0 ]); } int flag=0 ; 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); vector<int >indegree (numCourses); vector<int >ret; ret.reserve (numCourses); stack<int >sta; for (auto & pre:prerequisites) { vec[pre[1 ]].emplace_back (pre[0 ]); ++indegree[pre[0 ]]; } 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); vector<bool >isvisited (numCourses,false ); vector<bool >isInStack (numCourses,false ); stack<int >sta; for (auto & pre:prerequisites) { vec[pre[1 ]].emplace_back (pre[0 ]); } int flag=0 ; 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&°[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 : 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; 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 ; 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 ( n 2 ) O(n^2) 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 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 ; 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 ; int ** graph; int * if_searched; int * search; if (be == -1 ) { 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 ; } } 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 ; } } 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:数学,利用组合数直接计算C m + n − 2 m − 1 C_{m+n-2}^{m-1} C 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 ; 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题的特点是无限次购买 :
除了标黄的部分,两题的递推公式都是一样的
对比代码:
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 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]) for j in range (1 ,2 *k): if j%2 ==0 : dp[i][j]=max (dp[i-1 ][j],dp[i-1 ][j-1 ]-prices[i]) else : 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]) for j in range (1 ,2 *k): if j%2 ==0 : dp[i][j]=max (dp[i-1 ][j],dp[i-1 ][j-1 ]-prices[i]) else : 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=max{dp,dp-prices} dp=max{dp,dp} dp=dp+prices
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=[[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=max{dp,dp+prices} dp=max{dp,dp-prices}
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=max{dp,dp+prices} dp=max{dp,dp-prices-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 ( n 2 ) O(n^2) 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 ( n log ( n ) ) O(n\log(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
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 , n u m s 1 [ i ] = n u m s 2 [ j ] m a x { d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] } , n u m s 1 [ i ] ≠ n u m s 2 [ 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}
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 , n u m s 1 [ i ] = n u m s 2 [ j ] ma x { d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ]} , n u m s 1 [ i ] = n u m s 2 [ j ]
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] 。
确定递推公式
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
d p ( i , j ) = { d p ( i , j − 1 ) + d p ( i − 1 , j − 1 ) , t [ i ] = s [ j ] d p ( i , j − 1 ) , 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}
d p ( i , j ) = { d p ( i , j − 1 ) + d p ( i − 1 , j − 1 ) , t [ i ] = s [ j ] d p ( i , j − 1 ) , t [ i ] = s [ j ]
解析:
当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 : @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) { 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 var numDistinct = function (s, t ) { 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 ( n 2 ) O(n^2) 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 ( n 2 ) O(n^2) 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 ()); 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 ; 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 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 ) { return 1 ; } if (n==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 = 1 0 9 + 7 m=10^9+7 m = 1 0 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 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 factors2
and 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 2
more 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
灵神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 cachedef 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]); }; return cdfs (w.size ()-1 ,capacity); }
本题需要对上述模板进行变形,变形方式如下图
解题
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 = 0 n n u m s [ i ] ) − t a r g e t s=(\sum\limits_{i=0}^nnums[i])-target s = ( i = 0 ∑ n n u m s [ i ]) − t a r g e t
本题等价于在nums
中任意取几个数相加,满足和等于s 2 \frac s 2 2 s ,求任意取几个数相加的方法总数
例如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]
表示只考虑nums
前i
个元素时,相加得到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)
(i ≥ 1 i\ge 1 i ≥ 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)): 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 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() 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) { 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链接
对于s t o n e s = [ a 1 , a 2 , ⋯ , a n ] stones=[a_1,a_2,\cdots,a_n] s t o n es = [ a 1 , a 2 , ⋯ , a n ]
可以找到一种分割子集的方法,使两个子集[ b 1 , b 2 , ⋯ , b i ] [b_1,b_2,\cdots,b_i] [ b 1 , b 2 , ⋯ , b i ] 和[ c 1 , c 2 , ⋯ , c j ] [c_1,c_2,\cdots,c_j] [ c 1 , c 2 , ⋯ , c j ] 的和相差最小,相差记为s s s
有b 1 + b 2 + ⋯ + b i = c 1 + c 2 + ⋯ + c j + s b_1+b_2+\cdots+b_i=c_1+c_2+\cdots+c_j+s b 1 + b 2 + ⋯ + b i = c 1 + c 2 + ⋯ + c j + s
(b 1 , b 2 , ⋯ , b i , c 1 , c 2 , ⋯ , c j ∈ s t o n e s b_1,b_2,\cdots,b_i,c_1,c_2,\cdots,c_j\in stones b 1 , b 2 , ⋯ , b i , c 1 , c 2 , ⋯ , c j ∈ s t o n es )
s s s 就是最后石头的最小可能重量
最后会把石头分成两堆,重量分别为add
和su-add
(add<=su-add
)
add
的理论最大值为su//add
(//是整除),只需让add
从su//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
二维数组:
d p ( i , c ) = { d p ( i − 1 , c ) , c < n u m s [ i ] d p ( i − 1 , c ) + d p ( n u m s . s i z e ( ) − 1 , c − n u m s [ i ] ) , c ≥ n u m s [ 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}
d p ( i , c ) = { d p ( i − 1 , c ) , c < n u m s [ i ] d p ( i − 1 , c ) + d p ( n u m s . s i ze ( ) − 1 , c − n u m s [ i ]) , c ≥ n u m s [ i ]
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) @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链接
可以得到递推式:
可以证明,简化后结果为
d f s ( i , j ) = { d f s ( i − 1 , j − 1 ) + 1 ( s [ i ] = t [ j ] ) m a x { d f s ( i − 1 , j ) , d f s ( i , j − 1 ) } ( 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}
df s ( i , j ) = { df s ( i − 1 , j − 1 ) + 1 ( s [ i ] = t [ j ]) ma x { df s ( i − 1 , j ) , df s ( i , j − 1 )} ( s [ i ] = t [ j ])
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链接
d p ( i , j ) = { m i n { d p ( i − 1 , j ) , d p ( i , j − 1 ) , d p ( i − 1 , j − 1 ) } + 1 , w o r d 1 [ i ] ≠ w o r d 2 [ j ] d p ( i − 1 , j − 1 ) , w o r d 1 [ i ] = w o r d 2 [ 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}
d p ( i , j ) = { min { d p ( i − 1 , j ) , d p ( i , j − 1 ) , d p ( i − 1 , j − 1 )} + 1 , w or d 1 [ i ] = w or d 2 [ j ] d p ( i − 1 , j − 1 ) , w or d 1 [ i ] = w or d 2 [ j ]
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链接
d p ( i , j ) = { d p ( i − 1 , j − 1 ) + a s c i i ( s 1 [ i ] ) , s 1 [ i ] = s 2 [ j ] m a x { d p ( i − 1 , j ) , d p ( i , j − 1 ) } , s 1 [ i ] ≠ s 2 [ 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}
d p ( i , j ) = { d p ( i − 1 , j − 1 ) + a sc ii ( s 1 [ i ]) , s 1 [ i ] = s 2 [ j ] ma x { d p ( i − 1 , j ) , d p ( i , j − 1 )} , s 1 [ i ] = s 2 [ i ]
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
d p ( i , j ) = m a x { d p ( i − 1 , j − 1 ) + n u m s 1 [ i ] ∗ n u m s 2 [ j ] , d p ( i − 1 , j ) , d p ( i , j − 1 ) } dp(i,j)=max\{dp(i-1,j-1)+nums1[i]*nums2[j],dp(i-1,j),dp(i,j-1)\}
d p ( i , j ) = ma x { d p ( i − 1 , j − 1 ) + n u m s 1 [ i ] ∗ n u m s 2 [ j ] , d p ( i − 1 , j ) , d p ( 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 : 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; } };