hot100——第十一周
目录
目标和
零钱兑换
完全平方数
单词拆分
最长递增子序列
下一个排列
最长有效括号
不同路径
最小路径和
最大子数组和
乘积最大子数组
最长公共子序列
编辑距离
分割等和子集
目标和
- 假设,正数和 s1,总和 s
- 那么,负数和 s2 = s - s1
- 目标和 t = s1 - (s - s1)
- 转换 t + s = 2 * s1 -> s1 = (t + s) / 2
- 本质上也就是从nums选若干个数使得结果等于 (t + s) / 2
递归:从最后一个数开始递归 dfs(i,s)
选择当前数,共有:dfs(i - 1,s - nums[i])种方法(前提是:s >= nums[i])
不选择当前数,共有:dfs(i - 1,s)种方法
两种情况进行累加即可
由于递归出现很多重复,使用 python 的 cache 修饰器进行记忆化(也可以使用二维数组)
class Solution:# 正数和 s1 总和 s# 负数和 s2 = s - s1# 目标和 t = s1 - (s - s1) -> t + s = 2*s1 -> s1 = (t + s) / 2 -> 也就是从nums选若干个数使得结果等于 (t + s) / 2def findTargetSumWays(self, nums: List[int],target: int) -> int:target += sum(nums)if target < 0 or target % 2 != 0:return 0target //= 2n = len(nums)#记忆化@cachedef dfs(i,s) -> int:if i < 0:if s == 0:return 1return 0if s < nums[i]:return dfs(i - 1,s)return dfs(i - 1,s) + dfs(i - 1,s - nums[i])return dfs(n - 1,target)
一比一翻译成递推
class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:target += sum(nums)if target < 0 or target % 2 != 0:return 0target //= 2n = len(nums)dp = [[0] * (target + 1) for _ in range(n + 1)]# 初始化dp[0][0] = 1for i in range(1,n + 1):for j in range(target + 1):if(j > nums[i - 1]):dp[i][j] = dp[i - 1][j - nums[i - 1]]dp[i][j] += dp[i-1][j]return dp[n][target]
优化成两个数组
class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:target += sum(nums)if target < 0 or target % 2 != 0:return 0target //= 2n = len(nums)dp = [[0] * (target + 1) for _ in range(2)]# 初始化dp[0][0] = 1for i in range(1,n + 1):for j in range(target + 1):if(j < nums[i - 1]):dp[i % 2][j] = dp[(i - 1) % 2][j]else:dp[i % 2][j] = dp[(i - 1) % 2][j] + dp[(i - 1) % 2][j - nums[i - 1]]return dp[n % 2][target]
优化成一维数组(反向遍历:dp[i - 1][j - x] 要使用到当前行的左边值,如果正序会被覆盖)
class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:target += sum(nums)if target < 0 or target % 2 != 0:return 0target //= 2n = len(nums)dp = [0] * (target + 1)# 初始化dp[0] = 1# 倒着遍历for x in nums:for j in range(target,x - 1,-1):dp[j] = dp[j] + dp[j - x]return dp[target]
零钱兑换
背包问题的完全背包,因为物品可以无限次选,所以递归选的时候i不变
class Solution:def coinChange(self, coins: List[int], amount: int) -> int:n = len(coins)@cachedef dfs(i,at):if i < 0:if at == 0:#不用硬币了return 0 return infif at < coins[i]:return dfs(i - 1,at)return min(dfs(i - 1,at),dfs(i,at - coins[i]) + 1)ans = dfs(n - 1,amount)if(ans == inf):return -1return ans
翻译成递推
class Solution:def coinChange(self, coins: List[int], amount: int) -> int:n = len(coins)dp = [[inf] * (amount + 1) for _ in range(n + 1)]dp[0][0] = 0for i in range(1,n + 1):for j in range(0,amount + 1):if j < coins[i - 1]:dp[i][j] = dp[i - 1][j]else:dp[i][j] = min(dp[i - 1][j],dp[i][j - coins[i - 1]] + 1)return dp[n][amount] if dp[n][amount] != inf else -1
优化成一维数组(正向遍历:dp[i][j - coins[i - 1]] 时用到的是当前行的右边值)
class Solution:def coinChange(self, coins: List[int], amount: int) -> int:n = len(coins)dp = [inf] * (amount + 1)dp[0] = 0for coin in coins:for j in range(coin,amount + 1):dp[j] = min(dp[j],dp[j - coin] + 1)return dp[amount] if dp[amount] != inf else -1
完全平方数
解法:与上题一样,还是使用完全背包进行解答
class Solution {
public:// int cache[10001][101];// int dfs(int t,int x)// {// if(x==0)// {// if(t==0) return 0;// return INT_MAX;// }// if(t<x*x)// {// if(cache[t][x-1]!=-1) return cache[t][x-1];// cache[t][x-1]=dfs(t,x-1);// return cache[t][x-1];// //return dfs(t,x-1);// }// int a,b;// if(cache[t-(x*x)][x]==-1) cache[t-(x*x)][x]=dfs(t-(x*x),x);// a = cache[t-(x*x)][x];// if(cache[t][x-1]==-1) cache[t][x-1]=dfs(t,x-1);// b = cache[t][x-1];// int ret = min(a+1,b);// //int ret = min(dfs(t-(x*x),x)+1,dfs(t,x-1));// return ret;// }int dp[10001];int numSquares(int n) {memset(dp, 0x3f3f3f3f, sizeof(dp));dp[0] = 0;for (int i = 1; i * i <= n; i++){for (int j = i * i; j <= n; j++){dp[j] = min(dp[j - i * i] + 1, dp[j]);}}return dp[n];//memset(cache,-1,sizeof(cache));//return dfs(n,sqrt(n));}
};
单词拆分
解法:递归
dfs(n):表示s在【0,n-1】区间中的子串是否可以划分成若干份,使得刚好在 wordDict 中
如果枚举长度为1,那么就看看长度为1的子串是否在wordDict,且 dfs(n-1):s在【0,n-2】区间中的子串是否可以划分成若干份...
枚举长度不超过 wordDict 里字符串的最大值,超过后枚举一定不满足
递归出口:
dfs(0):s在【0,-1】区间中的子串不存在,返回true
class Solution {
public:unordered_set<std::string> hash;int max_len = 0;int memo[310];bool dfs(int i, std::string& s, vector<string>& wordDict){if (i == 0) return true;int& ret = memo[i];//引用使得下面的值修改在memo[i]中if (ret != -1) return ret;for (int j = i - 1; j >= max(i - max_len, 0); j--){if (hash.contains(s.substr(j, i - j)) && dfs(j, s, wordDict)){ret = true;return ret;}}ret = false;return ret;}bool wordBreak(string s, vector<string>& wordDict) {for (auto& word : wordDict){max_len = max(max_len, (int)word.size());hash.insert(word);}memset(memo, -1, sizeof(memo));return dfs(s.size(), s, wordDict);}
};
翻译成递推
class Solution {
public:bool dp[310];bool wordBreak(string s, vector<string>& wordDict) {unordered_set<std::string> hash;int max_len = 0;for (auto& word : wordDict){max_len = max(max_len, (int)word.size());hash.insert(word);}memset(dp, false, sizeof(dp));int n = s.size();dp[0] = true;for (int i = 1; i <= n; i++){int tmp = max(i - max_len, 0);for (int j = i - 1; j >= tmp; j--){// i-j+1 X i-j+1 把枚举子串为1的情况给去掉了if (hash.contains(s.substr(j, i - j)) && dp[j]) dp[i] = true;}}return dp[n];}
};
也可以把状态转移方程往前进1好理解
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<std::string> hash;for (auto& word : wordDict){hash.insert(word);}int n=s.size();vector<bool> dp(n+1,false);dp[0]=true;s =" " + s;// dp[i] -> 从[1,i]的子串能否划分为若干段for(int i=1;i<=n;i++){for(int j=i;j>=1;j--){//[j,i]子串在hash中且dp[j-1] -> [1,j-1]子串能否划分为若干段 if(hash.contains(s.substr(j,i-j+1))&&dp[j-1]) dp[i]=true;}}return dp[n];}
};
最长递增子序列
解法:递归
dfs(i):以i为结尾的最大递增子序列,不一定答案是它,而是dfs(i) ~ dfs(0)全部结果的最大值
class Solution {
public:int memo[2510];int dfs(int i, vector<int>& nums){if (memo[i] != -1) return memo[i];int ret1 = 1;for (int j = i - 1; j >= 0; j--){if (nums[j] < nums[i]){ret1 = max(ret1, dfs(j, nums) + 1);}}memo[i] = ret1;return memo[i];}int lengthOfLIS(vector<int>& nums) {memset(memo, -1, sizeof(memo));int n = nums.size();int ret = 0;for (int i = n - 1; i >= 0; i--){ret = max(dfs(i, nums), ret);}return ret;}
};
翻译成递推
class Solution {
public:int lengthOfLIS(vector<int>& nums) {int ret = 1;int n = nums.size();vector<int> dp(n, 1);for (int i = 0; i < n; i++){for (int j = 0; j < i; j++){if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1);}ret = max(ret, dp[i]);}return ret;}
};
解法2:贪心+二分
数组ret保存以i位置为结尾的最大递增子序列,数组ret为0的位置代表以ret[0]为结尾的最大递增子序列为1...
遍历数组nums,如果ret为空或者ret的结尾的值小于x(假设遍历过程为x),那么说明x可以放到它的后面称为更长的递增子序列,进行尾插;
如果大于x,就要寻找合适位置进行插入,插入在大于等于x的位置(右区间)上才能保证结果不干扰;由于数组是递增的,寻找插入位置就可以使用二分进行查找
class Solution {
public:int lengthOfLIS(vector<int>& nums) {int n = nums.size();vector<int> ret;for (int i = 0; i < n; i++){if (ret.empty() || ret.back() < nums[i]) ret.push_back(nums[i]);else{int left = 0, right = ret.size() - 1;int x = nums[i];// 插入到>=x的位置,也就是右半段区间while (left < right){int mid = left + (right - left) / 2;if (ret[mid] < x) left = mid + 1;else right = mid;}ret[left] = x;}}return ret.size();}
};
下一个排列
解法:规律
- 从右向左找第一次出现的数x:右边的其中一个数大于x,此时右边的数呈现递减状态(如果不是说明该排列是升序,此时第一次出现的数x是倒数第二个 这也归为一种情况)
- 为了找下一个排列,替换x位置的数不能太大,刚好大于x的第一个数交换(因为是递减,从右向左找第一个大于x就行了)
- 之后就反转 【i+1,n-1】的数,让它们从递减(x交换过来也满足递减吗? 一定满足,因为x前提是右边有一个数大于它,交换的数在它的右边)变成递增即可完成找到下一个排序
class Solution {
public:void nextPermutation(vector<int>& nums) {//[1,3,5,4,2]//[0,4,2,0,3,3,3,3]int n = nums.size();int i = n - 1;while (i - 1 >= 0 && nums[i - 1] >= nums[i]){i--;}//[5,3,1]if (i == 0){int e = n - 1;while (i < e){swap(nums[i++], nums[e--]);}}//[1,3,5,7]else if (i == n - 1){swap(nums[i - 1], nums[i]);}else{int k1 = i - 1;while (i < n && nums[k1] < nums[i]) i++;int k2 = i - 1;swap(nums[k1], nums[k2]);int b = k1 + 1, e = n - 1;while (b < e) swap(nums[b++], nums[e--]);}}
};
简洁写法
class Solution {
public:void nextPermutation(vector<int>& nums) {//[1,3,5,4,2]int n = nums.size();int i = n - 2;while (i >= 0 && nums[i] >= nums[i + 1]){i--;}//[1,2,3] [1,4,5,3,2]if (i >= 0){int j = n - 1;//从右向左找第一个大于nums[i]的位置while (nums[j] <= nums[i]){j--;}swap(nums[i], nums[j]);}//[7,5,3,1]int b = i + 1, e = n - 1;while (b < e) swap(nums[b++], nums[e--]);}
};
最长有效括号
解法1:栈
- 遍历数组:左括号入栈(保存的是下标)
- 右括号就说明:可能找到一对匹配的括号对,收集左右的下标后删除栈顶(最近)的左括号(前提是栈不为空,可能存在:s = ")" )
- 收集后将下标进行排序,从左到右枚举出最长的有效括号
class Solution {
public:int longestValidParentheses(string s) {//[(()]if (s.empty()) return 0;stack<int> st;vector<int> v;int n = s.size();for (int i = 0; i < n; i++){if (s[i] == '(') st.push(i);else{if (!st.empty()){v.push_back(st.top());st.pop();v.push_back(i);}}}sort(v.begin(), v.end());int ret = 0;int i = 0;while (i < v.size()){int j = i;while (j + 1 < v.size() && v[j] + 1 == v[j + 1]) j++;ret = max(ret, v[j] - v[i] + 1);i = j + 1;}return ret;}
};
排序时间 O(NlogN),太高了,可以优化成不排序来实现
- 先在栈中保留-1作为边界(s = ")")
- 遍历遇到左括号入栈(保存下标)
- 遍历到 i 时,遇到 s[i] == ')' 时,栈顶(可能是最近的左括号)出栈(如果此时栈为空,就要把当前右括号入栈,后面计算才不会越界,可能 s = "))") 此时当前有效括号的数量:i - s.top()
class Solution {
public:int longestValidParentheses(string s) {//[(()]if (s.empty()) return 0;stack<int> st;st.push(-1);int n = s.size(), ret = 0;for (int i = 0; i < n; i++){if (s[i] == '(') st.push(i);else{st.pop();//[))]if (st.empty()) st.push(i);ret = max(ret, i - st.top());}}return ret;}
};
解法2:动态规划
状态表示:dp[i] 表示以i位置为结尾的最长有效括号的长度
状态转移方程:
- 如果 s[i] 为 ‘(’ ,那么以它为结尾不可能是有效括号,dp[i] = 0
- 如果 s[i] 为 ‘)’,就要看看 s[i-1] 的情况:
- 如果 s[i-1] 为 ‘(’,刚好与 s[i] 组成一对有效括号对,dp[i] = 2,剩下的还要到 i-1 后面找最长的有效括号的长度与这一对组合组成最长有效括号对,刚好就在 dp[i-2] 存着,所以:dp[i] = dp[i-2] + 2
- 如果 s[i-1] 为 ')',此时看不出来以i结尾有效括号的长度,所以就要到 i-1-dp[i-1] 位置看看(为什么要减dp[i-1]? 目的是要找到与 s[i-1] 组成有效括号的前一个位置看看是否能组成一对有效括号,如果可能,就能与 dp[i-1] 一同组成最长的有效括号的长度)(s = "((...))" 其实就是找红色的位置)
- 如果 i-1-dp[i-1] 为 '(',那就说明不仅可以与 i 组成一对有效括号对,dp[i] = dp[i-1-dp[i-1]-1] +2还可以与 dp[i-1] 共同组成最长的有效括号的长度,dp[i] += dp[i-1]
初始化:第一个位置无论是 ‘(’ 还是 ')',都是无法组成有效括号,所有第一个位置初始化为0,从第二个位置开始枚举;注意找位置可以越界(特判或者 max)
返回值:每个位置的最大值
class Solution {
public:int longestValidParentheses(string s) {int n = s.size(), ret = 0;vector<int> dp(n);for (int i = 1; i < n; i++){if (s[i] == '(') dp[i] = 0;else{if (s[i - 1] == '('){dp[i] = dp[max(i - 1 - 1, 0)] + 2;}else{//这种格式 ((...)) 找前面的'('与s[i]匹配int i1 = i - 1 - dp[i - 1];if (i1 >= 0 && s[i1] == '('){dp[i] = dp[max(i1 - 1, 0)] + 2;//[i1,i-1]内的有效括号数也要统计 ((...))dp[i] += dp[i - 1];}}}ret = max(ret, dp[i]);}return ret;}
};
不同路径
解法1:递归 + 记忆化搜索
dfs(i,j)表示:从 [i,j] 位置出发,到达起点所走的路径之和
递归时如果走上面,则要知道上面有多少种路径,刚好在 dfs(i-1,j) 存着
递归时如果走左边,则要知道上面有多少种路径,刚好在 dfs(i,j-1) 存着
二者相加就是dfs(i,j)的路径之和
递归出口
如果坐标越界,也就是其中 i<0 || j<0 返回 0
如果 i==0 && j ==0 到终点了,此时有1种方案返回1
可能 dfs(i-1,j) 和 dfs(i,j-1) 都遍历过了,可以先使用 memo 数组进行记忆化
class Solution {
public:int uniquePaths(int m, int n) {int memo[110][110];memset(memo, -1, sizeof(memo));auto dfs = [&](this auto&& dfs, int i, int j)-> int{if (i < 0 || j < 0) return 0;if (i == 0 && j == 0) return 1;int& ret = memo[i][j];if (ret != -1) return ret;ret = dfs(i - 1, j) + dfs(i, j - 1);return ret;};return dfs(m - 1, n - 1);}
};
翻译成递推
class Solution {
public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m+1,vector<int>(n+1));dp[0][1]=1;for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){dp[i][j]=dp[i-1][j]+dp[i][j-1];}}return dp[m][n];}
};
空间优化
class Solution {
public:int uniquePaths(int m, int n) {vector<int> dp(n + 1);dp[1] = 1;for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){dp[j] = dp[j] + dp[j - 1];}}return dp[n];}
};
最小路径和
解法:递归 + 记忆化搜索
递归时如果走上面,则要知道上面的最小路径和,刚好在 dfs(i-1,j) 存着
递归时如果走左边,则要知道上面的最小路径和,刚好在 dfs(i,j-1) 存着
二者取消再加上当前位置grid[i][j] 就是dfs(i,j)的最小路径和
递归出口
如果坐标越界,也就是其中 i<0 || j<0 返回 0
如果 i==0 && j ==0 到终点了,返回自己
class Solution {
public:int minPathSum(vector<vector<int>>& grid) {int m = grid.size(), n = grid[0].size();int memo[m][n];memset(memo, -1, sizeof(memo));auto dfs = [&](this auto&& dfs, int i, int j)-> int{if (i < 0 || j < 0) return INT_MAX;if (i == 0 && j == 0) return grid[0][0];int& ret = memo[i][j];if (ret != -1) return ret;ret = min(dfs(i - 1, j), dfs(i, j - 1)) + grid[i][j];return ret;};return dfs(m - 1, n - 1);}
};
一比一翻译成递推
class Solution {
public:int minPathSum(vector<vector<int>>& grid) {int m = grid.size(), n = grid[0].size();vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));dp[0][1] = 0;for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];}}return dp[m][n];}
};
空间优化成一维数组,注意初始化的位置
class Solution {
public:int minPathSum(vector<vector<int>>& grid) {int m=grid.size(),n=grid[0].size();vector<int> dp(n+1,INT_MAX);//dp[0]=0;//或者是第二个初始化为0就不会干扰dp[1]=0;for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){dp[j]=min(dp[j-1],dp[j])+grid[i-1][j-1];}//第二次进行标记,防止干扰第二个的dp//dp[0]=INT_MAX;}return dp[n];}
};
原地数组修改
class Solution {
public:int minPathSum(vector<vector<int>>& grid) {int m = grid.size(), n = grid[0].size();for (int i = 0; i < m; i++){for (int j = 0; j < n; j++){if (i == 0 && j == 0) continue;if (i == 0) grid[i][j] = grid[i][j - 1] + grid[i][j];else if (j == 0) grid[i][j] = grid[i - 1][j] + grid[i][j];else grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];}}return grid[m - 1][n - 1];}
};
最大子数组和
解法:动态规划
dp[i] 表示以i位置为结尾的子数组之和的最大值
遍历到i位置时,既可以长度为1:选择nums[i] 作为单独子数组,也可以与前面最大子数组和一起组成最大子数组(如果不是负数的话),dp[i-1] + nums[i],取二者的最大值即可
class Solution:def maxSubArray(self, nums: List[int]) -> int:sum = min_sum = 0ret = -inffor x in nums:sum += xret = max(ret, sum - min_sum)min_sum = min(min_sum, sum)return ret
进行空间优化(滚动数组)
class Solution:def maxSubArray(self, nums: List[int]) -> int:dp = [0] * (len(nums) + 1)ret = -inffor i in range(1, len(nums) + 1):dp[i] = max(dp[i - 1], 0) + nums[i - 1]ret = max(ret, dp[i])return ret
乘积最大子数组
解法:动态规划
与上题类似,但要注意的是,要维护min_dp表示乘积最小子数组(max_dp表示乘积最大子数组),因为nums[i]可能为正,可能为负,与后面组成子数组的是不确定的:
自己组成子数组
与乘积最大子数组组成子数组
与乘积最小子数组组成子数组
不管为正为负,取三者的最大值即可
class Solution:def maxProduct(self, nums: List[int]) -> int:max_dp = [0] * len(nums)min_dp = [0] * len(nums)max_dp[0] = min_dp[0] = nums[0]for i in range(1, len(nums)):max_dp[i] = max(max_dp[i - 1] * nums[i], max(min_dp[i - 1] * nums[i], nums[i]))min_dp[i] = min(max_dp[i - 1] * nums[i], min(min_dp[i - 1] * nums[i], nums[i]))return max(max_dp)
空间优化,注意:要先保存max_dp前一个值给min_dp初始化使用
class Solution:def maxProduct(self, nums: List[int]) -> int:max_dp = min_dp = 1ret = -inffor x in nums:tmp = max_dp # min_dp 要使用更新之前的 max_dp 进行更新max_dp = max(max_dp * x, max(min_dp * x, x))min_dp = min(tmp * x, min(min_dp * x, x))ret = max(ret, max_dp)return ret
最长公共子序列
解法:递归
从最后一个位置进行判断(假设两个字符串分别为s,t)
dfs(i,j)表示 s以 i 位置为结尾,t以j位置为结尾时最长公共子序列之和
如果 s[i] == s[j],那么就需要 s 以 i-1 位置为结尾找,t 以 j-1 位置为结尾找最长公共子序列,也就是 dfs(i-1,j-1)再加上相同的字符(+1)
如果不相等,就要考虑选与不选的问题:
如果选s[i],那么就要 s 从 i-1 找,t 从 j 找,也就是 dfs(i - 1,j)
如果选t[j],那么就要 s 从 i 找,t 从 j-1 找,也就是 dfs(i,j - 1)
取二者的最大值就是答案;可以都不选,从 dfs(i-1,j-1)中找吗?
可以但没必要,二选一时包含了,比如 这次选 s[i],也就是 dfs(i-1,j),下次还是不相等就选 s[j],也就是 dfs(i-1,j-1)
class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m, n = len(text1), len(text2)@cachedef dfs(i, j):if i < 0 or j < 0:return 0if text1[i] == text2[j]:return dfs(i - 1, j - 1) + 1else:return max(dfs(i - 1, j), dfs(i, j - 1))return dfs(m - 1, n - 1)
翻译成递推,为了防止越界各自+1(也可以从1位置开始遍历,数组下标就-1应对映射)
class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m, n = len(text1), len(text2)dp = [[0] * (n + 1) for _ in range(m + 1)]for i, x in enumerate(text1):for j, y in enumerate(text2):if x == y:dp[i + 1][j + 1] = dp[i][j] + 1else:dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1])return dp[m][n]
优化成单个数组时,要注意先保留dp[i-1][j-1],也就是左上角的值,因为字符相等时要用到,如果不保存使用前面 dp[j-1],就会导致拿到的是前面更新过的值
# 两个数组
class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m, n = len(text1), len(text2)dp = [[0] * (n + 1) for _ in range(2)]for i, x in enumerate(text1):for j, y in enumerate(text2):if x == y:dp[(i + 1) % 2][j + 1] = dp[i % 2][j] + 1else:dp[(i + 1) % 2][j + 1] = max(dp[i % 2][j + 1], dp[(i + 1) % 2][j])return dp[m % 2][n]# 单个数组
class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m, n = len(text1), len(text2)dp = [0] * (n + 1)for i, x in enumerate(text1):prev = 0for j, y in enumerate(text2):tmp = dp[j + 1]if x == y:dp[j + 1] = prev + 1else:dp[j + 1] = max(dp[j + 1], dp[j])prev = tmpreturn dp[n]
编辑距离
思路与上题类似
dfs(i,j)表示 s以 i 位置为结尾,t 以 j 位置为结尾时的最小编辑距离
如果 s[i] == t[j],则最小编辑距离在 dfs(i-1,j-1)中,不需要进行编辑距离的操作
否则就需要在三种编辑距离中找最小(再加上1)
s新增 t[j],相当于要把 t[j] 抵消,此时就要到 dfs(i,j-1)找
s删除 s[i],此时就要到 dfs(i-1,j)找
s[i] 替换成 t[j],那么就是 s[i] = t[j] 的情况,那么就从 dfs(i-1,j-1)中找
递归出口
如果 i<0,那吗s就没有字符进行比较,就要新增 j+1 个字符才能与 t相等
j<0也是同理
class Solution:def minDistance(self, word1: str, word2: str) -> int:m, n = len(word1), len(word2)@cachedef dfs(i, j):# word1后面没单词,需要新增[0,j]个与word2相同的单词if i < 0:return j + 1# word2后面没单词,需要删除[0,i]个单词才能与word1相同if j < 0:return i + 1if word1[i] == word2[j]:return dfs(i - 1, j - 1)else:return (min(dfs(i, j - 1), dfs(i - 1, j), dfs(i - 1, j - 1)) + 1) # 新增,删除,替换时的最小操作次数return dfs(m - 1, n - 1)
翻译成递推
class Solution:def minDistance(self, word1: str, word2: str) -> int:m, n = len(word1), len(word2)dp = [[0] * (n + 1) for _ in range(m + 1)]# 初始化第一行(word1为空),第一列(word2为空)dp[0] = list(range(n + 1))for i in range(m + 1):dp[i][0] = ifor i, x in enumerate(word1):for j, y in enumerate(word2):if x == y:dp[i + 1][j + 1] = dp[i][j]else:dp[i + 1][j + 1] = min(dp[i + 1][j], dp[i][j + 1], dp[i][j]) + 1return dp[m][n]
优化成一维数组
class Solution:def minDistance(self, word1: str, word2: str) -> int:m, n = len(word1), len(word2)dp = [i for i in range(n + 1)]for i, x in enumerate(word1):prev = dp[0]dp[0] = i + 1for j, y in enumerate(word2):tmp = dp[j + 1]if x == y:dp[j + 1] = prevelse:dp[j + 1] = min(dp[j], dp[j + 1], prev) + 1prev = tmpreturn dp[n]
分割等和子集
解法:递归
把问题转换成:从数组中选取n个数使得总和为数组总和的一半,也就是变成了0 1背包
class Solution:def canPartition(self, nums: List[int]) -> bool:sum = 0for x in nums:sum += xif sum % 2 != 0:return Falsesum //= 2n = len(nums)@cachedef dfs(i, j):if i == 0:return j == 0if j < 0:return Falsereturn dfs(i - 1, j) or dfs(i - 1, j - nums[i])return dfs(n - 1, sum)
翻译为递归
class Solution:def canPartition(self, nums: List[int]) -> bool:sum = 0for x in nums:sum += xif sum % 2 != 0:return Falsesum //= 2n = len(nums)dp = [[False] * (sum + 1) for _ in range(n + 1)]for i, x in enumerate(nums):dp[i][0] = Truefor j in range(1, sum + 1):dp[i + 1][j] = dp[i][j]if j - x >= 0:dp[i + 1][j] = dp[i + 1][j] or dp[i][j - x]return dp[n][sum]
优化为一维数组
class Solution:def canPartition(self, nums: List[int]) -> bool:sum = 0for x in nums:sum += xif sum % 2 != 0:return Falsesum //= 2dp = [False] * (sum + 1)dp[0] = Truefor i, x in enumerate(nums):for j in range(sum, x - 1, -1):dp[j] = dp[j] or dp[j - x]return dp[sum]
以上便是全部内容,有问题欢迎在评论区指正,感谢观看!