一、数组
1、数组处理技巧
1、1取余使数字变小
1015. 可被 K 整除的最小整数
给定正整数 k ,你需要找出可以被 k 整除的、仅包含数字 1 的最 小 正整数 n 的长度。
返回 n 的长度。如果不存在这样的 n ,就返回-1。
注意: n 不符合 64 位带符号整数。
class Solution {
public:
int smallestRepunitDivByK(int k) {
int max = 1e9;
long long n = 1;
int result=1;
if(k%2==0||k%5==0) return -1;
for(int i=0;i<max;i++){
n %=k;
if(n==0) return result;
n = 10*n+1;
result++;
}
return -1;
}
};
在这道题目中,需要判断某个数是否可以被都是1的元素整除。判断整除时,防止数字过大,可以先取余降维。因为取余只会取出来不整除的部分,只要整体不会被整除,则取出来的部分可以代表整体的性质。
2、数组排序
1054、距离相等的条形码
在一个仓库里,有一排条形码,其中第 i 个条形码为 barcodes[i]。
请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。 你可以返回任何满足该要求的答案,此题保证存在答案。
class Solution {
public:
vector<int> rearrangeBarcodes(vector<int>& barcodes) {
int n = barcodes.size();
for(int i = 1;i<n;i++){
if(barcodes[i]==barcodes[i-1]){
int j = i+1;
while(j<n&&barcodes[j]==barcodes[i]) j++;
if(j<n) swap(barcodes[i],barcodes[j]);
}
}
for(int i = n-2;i>0;i--){
if(barcodes[i]==barcodes[i+1]){
int j = i-1;
while(j>=0&&barcodes[j]==barcodes[i]) j--;
if(j>=0) swap(barcodes[i],barcodes[j]);
}
}
return barcodes;
}
};
在处理排序问题时,如果暴力解法,导致最有一个数字无法通过,可以反向再遍历一次。
3、数组模拟
1072. 按列翻转得到最大值等行数
给定 m x n 矩阵 matrix 。
你可以从中选出任意数量的列并翻转其上的 每个 单元格。(即翻转后,单元格的值从 0 变成 1,或者从 1 变为 0 。)
返回 经过一些翻转后,行与行之间所有值都相等的最大行数 。
class Solution {
public:
int maxEqualRowsAfterFlips(vector<vector<int>>& matrix) {
unordered_map<string,int> m;
int result=0;
for(auto& row : matrix){
string s;
for(auto c:row){
s.push_back('0'+(row[0]==0? c:c^1));
}
m[s]++;
result = max(m[s],result);
}
return result;
}
};
通过观察发现,行之间存在等价行,然后把问题转化为找有多少个相同的等价行
4、滑动窗口
53. 最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int ans =0;
int pre =INT_MIN;
int i=0;
for(int j=0;j<nums.size();j++){
ans+=nums[j];
pre = ans>pre? ans:pre;
1、当pre数组小于0时,缩小滑动窗口到正为止
while(ans<0){
ans-=nums[i];
i++;
}
}
return pre;
}
};
动态规划解法
class Solution {
public:
vector<int> memo;
int ans=INT_MIN;
int maxSubArray(vector<int>& nums) {
int n = nums.size();
memo = vector<int>(n+1,nums[0]);
dp(nums,n-1);
for(auto c:memo){
ans = max(ans,c);
}
return ans;
}
int dp(vector<int>& nums,int i){
if(i==0) return nums[0];
if(memo[i]!=nums[0]) return memo[i];
int res;
连续子数组的状态公式为max(dp(i-1)+num[i],num[i])
res = max(dp(nums,i-1)+nums[i],nums[i]);
memo[i]=res;
return res;
}
};
5、前缀和
剑指 Offer 42. 连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> presum(nums.size()+1,0);
for(int i=1;i<=nums.size();i++){
presum[i] =nums[i-1]+presum[i-1];
}
int tmp = INT_MAX;
int res = INT_MIN;
1、nums[i]为以i为尾的数组和,要求最大子数组和,可以将问题转换为求最小的presum[i],并依次保存最大的presum[i+1]-最小的presum[i]
for(int i=0;i<presum.size()-1;i++){
tmp = min(tmp,presum[i]);
res = max(res,presum[i+1]-tmp);
}
return res;
}
};
二、栈
栈善于处理消消乐这类配对问题。并且可以同时string和queue来模拟栈
1003. 检查替换后的词是否有效
给你一个字符串 s ,请你判断它是否 有效 。
字符串 s 有效 需要满足:假设开始有一个空字符串 t = “” ,你可以执行 任意次 下述操作将 t 转换为 s :
将字符串 “abc” 插入到 t 中的任意位置。形式上,t 变为 tleft + “abc” + tright,其中 t == tleft + tright 。注意,tleft 和 tright 可能为 空 。
如果字符串 s 有效,则返回 true;否则,返回 false。
class Solution {
public:
bool isValid(string s) {
int n = s.size();
if(n%3!=0) return false;
string st;
for(auto c:s){
st.push_back(c);
if(st.size()>=3){
string str = st.substr(st.size()-3,3);
if(str=="abc") st.erase(st.end()-3,st.end());
}
}
return st.empty();
}
};
这道题目中,通过string来模拟栈,但是需要注意substr的参数为int,不是迭代器。
三、字符串
string.substr(i,j)代表包括i开始的j个数字,共有i+j-1个,所以回溯传递i+j给子树。并且subbstr的后索引为i+j-1
子串
1016. 子串能表示从 1 到 N 数字的二进制串
给定一个二进制字符串 s 和一个正整数 n,如果对于 [1, n] 范围内的每个整数,其二进制表示都是 s 的 子字符串 ,就返回 true,否则返回 false 。
子字符串 是字符串中连续的字符序列。
class Solution {
public:
bool queryString(string s, int n) {
for(int i =1;i<=n;i++){
string str;
int tmp = i;
while(tmp/2){
if(tmp%2) str.push_back('1');
else str.push_back('0');
tmp /=2;
}
if(tmp%2) str.push_back('1');
else str.push_back('0');
reverse(str.begin(),str.end());
if(s.find(str)==-1) return false;
}
return true;
}
};
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
1、子串是连续元素
int n = s.size();
unordered_map<char,int> map;
int slow = 0;
int fast = 0;
int ans = 0;
2、每次遍历,都将慢指针前进到无重复的位置
while(fast<n){
map[s[fast]]++;
while(map[s[fast]]>=2){
map[s[slow]]--;
slow++;
}
int tmp = fast-slow+1;
ans = ans<tmp? tmp:ans;
fast++;
}
return ans;
}
};
四、bfs
1、双端队列
1263 推箱子
「推箱子」是一款风靡全球的益智小游戏,玩家需要将箱子推到仓库中的目标位置。
游戏地图用大小为 m x n 的网格 grid 表示,其中每个元素可以是墙、地板或者是箱子。
现在你将作为玩家参与游戏,按规则将箱子 ‘B’ 移动到目标位置 ‘T’ :
玩家用字符 ‘S’ 表示,只要他在地板上,就可以在网格中向上、下、左、右四个方向移动。
地板用字符 ‘.’ 表示,意味着可以自由行走。
墙用字符 ‘#’ 表示,意味着障碍物,不能通行。
箱子仅有一个,用字符 ‘B’ 表示。相应地,网格上有一个目标位置 ‘T’。
玩家需要站在箱子旁边,然后沿着箱子的方向进行移动,此时箱子会被移动到相邻的地板单元格。记作一次「推动」。
玩家无法越过箱子。
返回将箱子推到目标位置的最小 推动 次数,如果无法做到,请返回 -1。
class Solution {
public:
int dx[4] = {1,0,-1,0};
int dy[4] = {0,1,0,-1};
int dirs[5] = {-1, 0, 1, 0, -1};
int minPushBox(vector<vector<char>>& grid) {
int m = grid.size();
int n = grid[0].size();
int si=0,sj=0,bi=0,bj=0;
for(int i = 0;i<m;i++){
for(int j = 0;j<n;j++){
char tmp = grid[i][j];
if(tmp=='B'){
bi = i;
bj = j;
}
else if(tmp=='S'){
si = i;
sj = j;
}
}
}
auto f = [&](int i,int j ){
return i*n+j;
};
auto check = [&](int i, int j) {
return i >= 0 && i < m && j >= 0 && j < n && grid[i][j] != '#';
};
vector<vector<bool>> vis(m*n+m,vector<bool>(m*n+m,false));
deque<tuple<int,int,int>> q;
q.emplace_back(f(si,sj),f(bi,bj),0);
vis[f(si,sj)][f(bi,bj)] = true;
while(!q.empty()){
auto [s,b,d] = q.front();
q.pop_front();
si = s/n, sj = s%n;
bi = b/n,bj = b%n;
if(grid[bi][bj]=='T'){
return d;
}
for(int k =0;k<4;++k){
int sx = si+dirs[k];
int sy = sj+dirs[k+1];
if(!check(sx,sy)) continue;
if(sx==bi&&sy==bj){
int bx = bi+dirs[k];
int by = bj+dirs[k+1];
if(!check(bx,by)||vis[f(sx,sy)][f(bx,by)]){
continue;
}
q.emplace_back(f(sx,sy),f(bx,by),d+1);
vis[f(sx,sy)][f(bx,by)]=true;
}else if(!vis[f(sx,sy)][f(bi,bj)]){
q.emplace_front(f(sx,sy),f(bi,bj),d);
vis[f(sx,sy)][f(bi,bj)]=true;
}
}
}
return -1;
}
};
五、动态规划
动态规划要求题目具有最优子结构,且存在重叠子问题现象。本质上是通过暴力遍历和dp数组优化递归。
1、基本套路
最少的硬币数目
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
- 暴力递归
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> vec(amount+1,INT_MAX);
return dp(coins,amount);
}
int dp(vector<int>& coins, int amount){
if(amount==0) return 0;
if(amount<0) return -1;
int res = INT_MAX;
for(auto c:coins){
int tmp = dp(coins,amount-c);
if(tmp== -1 )continue;
res = min(res,tmp+1);
}
return res==INT_MAX? -1:res;
}
};
- 通过数组解决重叠子问题
class Solution { public: int coinChange(vector<int>& coins, int amount) { vector<int> vec(amount+1,INT_MAX); return dp(coins,amount,vec); } int dp(vector<int>& coins, int amount,vector<int>& vec){ if(amount==0) return 0; if(amount<0) return -1; if(vec[amount]!=INT_MAX) return vec[amount]; int res =INT_MAX; for(auto coin:coins){ int tmp = dp(coins,amount-coin,vec); if(tmp==-1) continue; res = min(res,tmp+1); } vec[amount]= res==INT_MAX? -1:res; return vec[amount]; } };
300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
https://leetcode.cn/problems/longest-increasing-subsequence/description/
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
//定义dp为以当前索引为尾的最长递增序列数目,所以dp大小为size
vector<int>dp(nums.size(),1);
for(int i=1;i<nums.size();i++){
//遍历每一个子问题
for(int j=0;j<i;j++){
//求子问题的最优解
if(nums[j]<nums[i]) dp[i] = max(dp[i],dp[j]+1);
}
}
//从子问题最优解中,找出最终解
int res=0;
for(auto c:dp){
res = max(res,c);
}
return res;
}
};
354. 俄罗斯套娃信封问题
https://leetcode.cn/problems/russian-doll-envelopes/
给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
注意:不允许旋转信封。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
// 定义:dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度
vector<int> dp(nums.size(), 1);
// base case:dp 数组全都初始化为 1
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
}
}
//先将二维数组俺第一位数升序,保证宽度可以套娃,然后由于题目不允许相同大小套娃,所以将第一位相同的,按第二位降序。保证递增序列额中不包括第一位相同的,也进不了套娃案例
int res = 0;
for (int i = 0; i < dp.size(); i++) {
res = max(res, dp[i]);
}
return res;
}
int maxEnvelopes(vector<vector<int>>& envelopes) {
int n = envelopes.size();
// 按宽度升序排列,如果宽度一样,则按高度降序排列
sort(envelopes.begin(), envelopes.end(),
[](vector<int>& a, vector<int>& b) {
return a[0]<b[0] ||a[0]==b[0]&&a[1]>b[1];
});
// 对高度数组寻找 LIS
vector<int> height(n);
for (int i = 0; i < n; i++)
height[i] = envelopes[i][1];
return lengthOfLIS(height);
}
};
2、自顶向下递归
(1)序列性质变化
- 连续子数组的状态公式为max(dp(i-1)+num[i],num[i])
- 连续子序列状态公式为max(dp(i-1)+num[i],dp(i-1))
- 01背包问题状态公式为dp[i-1][j]+dp[i-1][j-coins[i-1]
- 完全背包状态公式为dp[i-1][j]+dp[i][j-coins[i-1]
(2)base case变化
- 当求最少操作时,为if(i==s.size()) return 0;
- 当求回文子序列长度时,if(1==j) return 1;
- 当求最少使得两串相等的删除和时,if(i==s.size()) return 剩余另一个串总和
(3) 子序列删除条件变化
- 一次只能删除一步 res = min(dp(word1,word2,i+1,j),dp(word1,word2,i,j+1))+1;
- 一次可以删除两部 res = min(dp(s1,s2,i+1,j)+s1[i],min(dp(s1,s2,i,j+1)+s2[j],dp(s1,s2,i+1,j+1)+s1[i]+s2[j]));
- 可以插入删除替换 memo[i][j] = min(dp(word1,word2,i,j-1)+1,min(dp(word1,word2,i-1,j-1)+1,dp(word1,word2,i-1,j)+1));
1、数组
剑指 Offer II 099. 最小路径之和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:一个机器人每次只能向下或者向右移动一步。
class Solution {
public:
vector<vector<int>> memo;
//1、将dp数组定义在外部,然后函数内初始化
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
memo = vector<vector<int>>(m,vector<int>(n,-1));
return dp(grid,m-1,n-1);
}
int dp(vector<vector<int>>& grid,int i,int j){
//3、递归实现的base case
if(i==0&&j==0) return grid[0][0];
//4、处理边界条件
if(i<0||j<0) return INT_MAX;
if(memo[i][j]!=-1) return memo[i][j];
int a = dp(grid,i-1,j);
int b = dp(grid,i,j-1);
int tmp = 0;
//2、写出子问题的状态转移公示
tmp = min(a,b)+grid[i][j];
memo[i][j] = tmp;
return tmp;
}
};
1312. 让字符串成为回文串的最少插入次数
https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/description/
给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。
「回文串」是正读和反读都相同的字符串。
class Solution {
public:
vector<vector<int>> memo;
int minInsertions(string s) {
int n = s.size()-1;
memo = vector<vector<int>>(n+1,vector<int>(n+1,-1));
if(n<1) return 0;
return dp(s,0,n);
}
int dp(string& s,int i,int j){
if(i==j) return 0;
if(i>j) return 0;
if(memo[i][j]!=-1) return memo[i][j];
if(s[i]==s[j]){
memo[i][j] = dp(s,i+1,j-1);
}
else {
memo[i][j] = min(dp(s,i+1,j)+1,dp(s,i,j-1)+1);
}
return memo[i][j];
}
};
迭代法需要倒序遍历:
class Solution {
public:
int minInsertions(string s) {
int n = s.size()-1;
vector<vector<int>> dp(n+1,vector<int>(n+1,0));
for(int i=0;i<=n;i++) dp[i][i] = 0;
for(int i = n;i>=0;i--){
for(int j = i+1;j<=n;j++){
if(s[i]==s[j]) dp[i][j] = dp[i+1][j-1];
else {
dp[i][j] = min(dp[i][j-1],dp[i+1][j])+1;
}
}
}
return dp[0][n];
}
};
2384. 最大回文数字
给你一个仅由数字(0 - 9)组成的字符串 num 。
请你找出能够使用 num 中数字形成的 最大回文 整数,并以字符串形式返回。该整数不含 前导零 。
注意:
你 无需 使用 num 中的所有数字,但你必须使用 至少 一个数字。
数字可以重新排序。
class Solution {
public:
1、这道题目中,测试用例可以改变字符串除了回文以外的顺序,相当于自己排列组合凑出回文,不能使用动态规划,只能统计个数
string largestPalindromic(string num) {
int cnt[10]={0};
for(auto c:num) cnt[c-'0']++;
string ans,ans2;
for(int i=9;i>=0;i--){
if(i==0&&ans.empty()) break;
while(cnt[i]/2){
ans.push_back(i+'0');
cnt[i]-=2;
}
}
ans2 = ans;
reverse(ans2.begin(),ans2.end());
for(int i=9;i>=0;i--){
if(cnt[i]>0){
ans.push_back(i+'0');
break;
}
}
return ans+ans2;
}
};
516. 最长回文子序列
https://leetcode.cn/problems/longest-palindromic-subsequence/description/
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
class Solution {
public:
1、这道题求子序列,即不连续的子集,但是不能充足这些子集的顺序,相当于遮挡组成,但是不能重排
vector<vector<int>> memo;
int longestPalindromeSubseq(string s) {
int n = s.size()-1;
//由于在递归判断边界时,需要进入到i=s.size()的情况,所以dp需要+1
memo = vector<vector<int>>(n+1,vector<int>(n+1,-1));
return dp(s,0,n);
}
int dp(string& s,int i,int j){
if(i==j) return 1;
if(i>j||i>=s.size()||j<0) return 0;
if(memo[i][j]!=-1) return memo[i][j];
int res = 0;
if(s[i]==s[j]){
res =dp(s,i+1,j-1)+2;
// return dp(s,i+1,j-1)+2;
}
else{
res =max(dp(s,i,j-1),dp(s,i+1,j));
// return max(dp(s,i,j-1),dp(s,i+1,j));
}
memo[i][j]=res;
return res;
}
};
最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
class Solution {
public:
string part(string s,int i,int j){
while(i>=0&&j<s.size()&&s[i]==s[j]){
i--;
j++;
}
return s.substr(i+1,j-1-i);
}
string longestPalindrome(string s) {
string ans;
for(int i=0;i<s.size();i++){
string s1 = part(s,i,i);
string s2 = part(s,i,i+1);
string tmp = s1.size()<s2.size()? s2:s1;
ans = ans.size()<tmp.size()? tmp:ans;
}
return ans;
}
};
931. 下降路径最小和
给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。
1、在读题的时候要分析清楚最优子结构,这道题如果用迭代法需要倒序遍历
class Solution {
public:
// vector<vector<int>> memo;
int minFallingPathSum(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> memo = vector<vector<int>>(n+1,vector<int>(n+1,6666));
int result = INT_MAX;
for(int j=0;j<n;j++){
result = min(result,dp(matrix,n-1,j,memo));
}
return result;
}
int dp(vector<vector<int>>& matrix,int i,int j,vector<vector<int>>& memo){
int m1 = matrix.size();
int n1 = matrix[0].size();
if(i<0||j<0||i>=m1||j>=n1) return INT_MAX;
if(i==0) return matrix[0][j];
if(memo[i][j]!=6666) return memo[i][j];
int res;
res = min(dp(matrix,i-1,j,memo),min(dp(matrix,i-1,j-1,memo),dp(matrix,i-1,j+1,memo)))+matrix[i][j];
2、如果排查不出问题,很可能memo或matrix函数错误
memo[i][j] = res;
return res;
}
};
// 100 -42 -46 -41
// 31 97 10 -10
// -58 -51 82 89
// 51 81 69 -51
63. 不同路径 II
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
class Solution {
public:
vector<vector<int>> memo;
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
1、由于题目设定dp值不包含负数,所以memo初始为-1,不会被选到
return dp(obstacleGrid,m-1,n-1);
}
int dp(vector<vector<int>>& obstacleGrid,int i,int j){
2、由于dp为路径条数,应该选一个不可能的值,所以边界返回0
if(i<0||j<0||obstacleGrid[i][j]==1) return 0;
if(i==0&&j==0) return 1;
if(memo[i][j]!=-1) return memo[i][j];
int res;
res = dp(obstacleGrid,i,j-1)+dp(obstacleGrid,i-1,j);
memo[i][j]=res;
return res;
}
};
115. 不同的子序列
https://leetcode.cn/problems/distinct-subsequences/description/
给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。
题目数据保证答案符合 32 位带符号整数范围。
class Solution {
public:
vector<vector<int>> memo;
int numDistinct(string s, string t) {
int m = s.size();
int n = t.size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
return dp(s,t,0,0);
}
int dp(string& s,string& t,int i,int j){
if(j==t.size()) return 1;
if(s.size()-i<t.size()-j) return 0;
if(memo[i][j]!=-1) return memo[i][j];
int res =0;
1、最优子结构由每一个字符的结果组成
2、子结构最优通过每个字符的重复递归完成
3、for循环实现最优子结构
for(int k=i;k<s.size();k++){
if(s[k]==t[j]){
res+=dp(s,t,k+1,j+1);
}
}
memo[i][j]=res;
return res;
}
};
自顶向下解法:
class Solution {
public:
vector<vector<int>> memo;
int numDistinct(string s, string t) {
int m = s.size();
int n = t.size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
return dp(s,t,m-1,n-1);
}
int dp(string& s,string& t,int i,int j){
if(j<0) return 1;
if(i<0||(i<j)) return 0;
if(memo[i][j]!=-1) return memo[i][j];
int res = 0;
for(int k =i;k>=0;k--){
if(s[k]==t[j]){
res+=dp(s,t,k-1,j-1);
}
}
memo[i][j]=res;
return res;
}
};
球视角遍历最优解:
class Solution {
public:
vector<vector<int>> memo;
int numDistinct(string s, string t) {
int m = s.size();
int n = t.size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
return dp(s,t,0,0);
}
1、以i索引以后的s子串中包含j索引以后t子串个数得到dp定义
2、以球的视角遍历,通过选与不选两个状态,避免for循环遍历
int dp(string& s,string& t,int i,int j){
if(j==t.size()) return 1;
if(i==s.size()&&j!=t.size()) return 0;
if(memo[i][j]!=-1) return memo[i][j];
int res =0;
if(s[i]!=t[j]){
res = dp(s,t,i+1,j);
}
else{
res = dp(s,t,i+1,j+1)+dp(s,t,i+1,j);
}
memo[i][j]=res;
return res;
}
};
剑指 Offer II 091. 粉刷房子
假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。
例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。
请计算出粉刷完所有房子最少的花费成本。
class Solution {
public:
vector<vector<int>> memo;
int minCost(vector<vector<int>>& costs) {
int n = costs.size();
memo = vector<vector<int>>(n+1,vector<int>(3,-1));
return min(dp(costs,n-1,0),min(dp(costs,n-1,1),dp(costs,n-1,2)));
}
int dp(vector<vector<int>>& costs,int i,int j){
if(i==0) return costs[i][j];
if(memo[i][j]!=-1) return memo[i][j];
int res = INT_MAX;
for(int k=0;k<3;k++){
1、定义dp为以i为尾时,颜色为j的情况的最小花费
if(k!=j) res = min(res,dp(costs,i-1,k)+costs[i][j]);
}
memo[i][j] = res;
return res;
}
};
1143. 最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
自底向上的解法
class Solution {
public:
vector<vector<int>> memo;
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size();
int n = text2.size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
return dp(text1,text2,0,0);
}
int dp(string& text1, string& text2,int i,int j){
if(i==text1.size()||j==text2.size()) return 0;
if(memo[i][j]!=-1) return memo[i][j];
int res =0;
if(text1[i]==text2[j]){
res = dp(text1,text2,i+1,j+1)+1;
}
else {
res = max(dp(text1,text2,i+1,j),max(dp(text1,text2,i+1,j+1),dp(text1,text2,i,j+1)));
}
memo[i][j] = res;
return res;
}
};
自顶向下
class Solution {
public:
vector<vector<int>> memo;
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size();
int n = text2.size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
return dp(text1,text2,m-1,n-1);
}
int dp(string& text1, string& text2,int i,int j){
if(i<0||j<0) return 0;
if(memo[i][j]!=-1) return memo[i][j];
int res =0;
if(text1[i]==text2[j]){
res = dp(text1,text2,i-1,j-1)+1;
}
else {
res = max(dp(text1,text2,i-1,j),max(dp(text1,text2,i-1,j-1),dp(text1,text2,i,j-1)));
}
memo[i][j] = res;
return res;
}
};
2、字符串
72. 编辑距离
https://leetcode.cn/problems/edit-distance/description/
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
class Solution {
public:
vector<vector<int>> memo;
int minDistance(string word1, string word2) {
int m = word1.size();
int n = word2.size();
memo = vector<vector<int>>(m,vector<int>(n,-1));
return dp(word1,word2,m-1,n-1);
}
int dp(string word1, string word2,int i,int j){
//1、base case 边界位置,分别返回另一个字符的剩余字符,全部删除
if(i<0) return j+1;
if(j<0) return i+1;
if(memo[i][j]!=-1) return memo[i][j];
//2、当字符相同时,迭代获取索引都-1的结果
if(word1[i]==word2[j]){
memo[i][j] = dp(word1,word2,i-1,j-1);
}else memo[i][j] = min(dp(word1,word2,i,j-1)+1,min(dp(word1,word2,i-1,j-1)+1,dp(word1,word2,i-1,j)+1));
return memo[i][j];
}
};
97. 交错字符串
https://leetcode.cn/problems/interleaving-string/description/?show=1
给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。
两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + … + sn
t = t1 + t2 + … + tm
|n - m| <= 1
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int m = s1.size();
int n = s2.size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
if(m+n!=s3.size()) return false;
//1、通过正序遍历,避免边界条件
return dp(s1, 0, s2, 0, s3);
}
bool dp(string& s1, int i, string& s2, int j, string& s3){
int k = i+j;
if(k==s3.size()) return true;
//memo数组设-1,0,1三种状态,只要非-1,说明遍历过结果,直接查表
if(memo[i][j]!=-1) return memo[i][j]==1? true:false;
bool res = false;
if(i<s1.size()&&s3[k]==s1[i]) res = dp(s1, i + 1, s2, j, s3);
if(j<s2.size()&&s3[k]==s2[j]) res =res|| dp(s1, i, s2, j + 1, s3);
memo[i][j]=res==true? 1:0;
return res;
}
private:
vector<vector<int>> memo;
};
712. 两个字符串的最小ASCII删除和
给定两个字符串s1 和 s2,返回 使两个字符串相等所需删除字符的 ASCII 值的最小和 。
class Solution {
public:
vector<vector<int>> memo;
int minimumDeleteSum(string s1, string s2) {
int m = s1.size();
int n = s2.size();
memo = vector<vector<int>>(m+1,vector<int>(n+1,-1));
return dp(s1,s2,0,0);
}
int dp(string& s1,string& s2,int i,int j){
1、base case中,如果s1到头,需要把另外一个全部删掉,所以需要求和
if(i==s1.size()){
int a = 0;
for(;j<s2.size();j++) a+=s2[j];
return a;
}
if(j==s2.size()){
int a = 0;
for(;i<s1.size();i++) a+=s1[i];
return a;
}
if(memo[i][j]!=-1) return memo[i][j];
int res = INT_MAX;
if(s1[i]==s2[j]){
res = dp(s1,s2,i+1,j+1);
}
else{
res = min(dp(s1,s2,i+1,j)+s1[i],min(dp(s1,s2,i,j+1)+s2[j],dp(s1,s2,i+1,j+1)+s1[i]+s2[j]));
}
memo[i][j]=res;
return res;
}
};
3、背包问题
1、01背包
剑指 Offer II 101. 分割等和子集
https://leetcode.cn/problems/NUPfPr/?show=1
给定一个非空的正整数数组 nums ,请判断能否将这些数字分成元素和相等的两部分。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int total =0;
for(auto c:nums) total+=c;
1、由于/号为自动取舍,所以如果和为奇数,需要手动判断
if(total%2) return false;
total = total/2;
int n = nums.size();
vector<vector<int>> dp(n+1,vector<int>(total+1,0));
for(int i=0;i<n;i++) dp[i][0] = 1;
for(int i =1;i<n;i++){
for(int j = 1;j<=total;j++){
2、在下面状态转移中,j-nums[i]需要进行判断,否则会数组越界
if(j-nums[i]<0){
dp[i][j] = dp[i-1][j];
}
else{
dp[i][j] = dp[i-1][j]||dp[i-1][j-nums[i]];
}
}
}
return dp[n-1][total]== 1? true:false;
}
};
494. 目标和
https://leetcode.cn/problems/target-sum/description/
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int n = nums.size();
int total =0;
for(auto c:nums) total+=c;
1、看上去像是背包问题,所以通过数学运算简化背包
int a_target = (total+target)/2;
2、由于可能有负数,所以需要abs
if(total<abs(target)||(total+target)%2==1) return 0;
vector<vector<int>>dp(n+1,vector<int>(a_target+1,0));
3、当dp定义为子集个数时,如果元素存在0,则初始化dp[..][0]不能全部为1,可能有大量含0解
dp[0][0]=1;
4、由于没有完全初始化,所以将背包0情况加入dp计算
for(int i=0;i<=a_target;i++){
for(int j=1;j<=n;j++){
if((i-nums[j-1])<0) dp[j][i]=dp[j-1][i];
else dp[j][i]=dp[j-1][i]+dp[j-1][i-nums[j-1]];
}
}
return dp[n][a_target];
}
};
2、完全背包
518. 零钱兑换 II
https://leetcode.cn/problems/coin-change-ii/description/
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
class Solution {
public:
int change(int amount, vector<int>& coins) {
int n = coins.size();
1、在背包问题中,dp定义为第i个物品和背包容量j,装满的数量。i从1开始计数,因为把i放到i,则base case从0开始,初始化直接全为0.避免了完全背包在物品上的重复初始化。
vector<vector<int>> dp(n+1,vector<int>(amount+1,0));
for(int i=0;i<=n;i++) dp[i][0] = 1;
for(int i = 1;i<=n;i++){
for(int j = 1;j<=amount;j++){
if(j<coins[i-1]) dp[i][j] = dp[i-1][j];
2、完全背包中,选择当前物品dp[i][j-coins[i-1]
else dp[i][j] = dp[i-1][j]+dp[i][j-coins[i-1]];
}
}
return dp[n][amount];
}
};
377. 组合总和 Ⅳ
https://leetcode.cn/problems/combination-sum-iv/description/
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1,0);
dp[0]=1;
1、求组合不需要管先后顺序,所以先遍历物品。如果求排列先遍历背包
for(int i=1;i<=target;i++){
for(int j=1;j<=nums.size();j++){
2、如果宝贝包容力特别大,物品特别小,dp选择相加可能超过INT_MAX
if(i-nums[j-1]<0||(dp[i])>INT_MAX-dp[i-nums[j-1]]) dp[i] = dp[i];
else dp[i] = dp[i]+dp[i-nums[j-1]];
}
}
return dp[target];
}
};
4、递归+备忘录
给定一个正整数数组 nums 和一个整数 target 。
https://leetcode.cn/problems/YaVDxD/description/
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
class Solution {
public:
unordered_map<string,int> memo;
1、本题没有最优子结构,就是单纯的递归+备忘录,思路接近回溯
int findTargetSumWays(vector<int>& nums, int target) {
int n = nums.size();
return dp(nums,target,0,0);
}
int dp(vector<int>& nums, int target,int i,int total){
if(i==nums.size()){
if(total==target) return 1;
2、需要判断异常,并犯规base case
return 0;
}
int res =0;
string key = to_string(i)+','+to_string(total);
if(memo.count(key)) return memo[key];
res = dp(nums,target,i+1,total+nums[i])+dp(nums,target,i+1,total-nums[i]);
memo[key] = res;
return res;
}
};
174. 地下城游戏
恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快解救公主,骑士决定每次只 向右 或 向下 移动一步。
返回确保骑士能够拯救到公主所需的最低初始健康点数。
注意:任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
class Solution {
public:
unordered_map<string,int> memo;
int calculateMinimumHP(vector<vector<int>>& dungeon) {
return dp(dungeon,0,0);
}
1、dp数组的定义,最好是直接通过i,j的状态,从base case直接获取答案值,然后层层返回
int dp(vector<vector<int>>& dungeon,int i,int j){
int m = dungeon.size();
int n = dungeon[0].size();
if(i==m-1&&j==n-1) return dungeon[i][j]>=0? 1:-dungeon[i][j]+1;
if(i>=m||j>=n) return INT_MAX;
string key = to_string(i)+','+to_string(j);
if(memo.count(key)) return memo[key];
2、由于这里定义为i,j时刻所需最小生命,所以需要减去正值
int res = min(dp(dungeon,i+1,j),dp(dungeon,i,j+1))-dungeon[i][j];
memo[key] = res<=0? 1:res;
return memo[key];
}
};
514. 自由之路
https://leetcode.cn/problems/freedom-trail/description/
电子游戏“辐射4”中,任务 “通向自由” 要求玩家到达名为 “Freedom Trail Ring” 的金属表盘,并使用表盘拼写特定关键词才能开门。
给定一个字符串 ring ,表示刻在外环上的编码;给定另一个字符串 key ,表示需要拼写的关键词。您需要算出能够拼写关键词中所有字符的最少步数。
最初,ring 的第一个字符与 12:00 方向对齐。您需要顺时针或逆时针旋转 ring 以使 key 的一个字符在 12:00 方向对齐,然后按下中心按钮,以此逐个拼写完 key 中的所有字符。
旋转 ring 拼出 key 字符 key[i] 的阶段中:
您可以将 ring 顺时针或逆时针旋转 一个位置 ,计为1步。旋转的最终目的是将字符串 ring 的一个字符与 12:00 方向对齐,并且这个字符必须等于字符 key[i] 。
如果字符 key[i] 已经对齐到12:00方向,您需要按下中心按钮进行拼写,这也将算作 1 步。按完之后,您可以开始拼写 key 的下一个字符(下一阶段), 直至完成所有拼写。
class Solution {
public:
unordered_map<char,vector<int>> dict;
unordered_map<string,int> memo;
int findRotateSteps(string ring, string key) {
1、由于ring中可能有重复的数字,所以在哈希表中存入vector
for(int i=0;i<ring.size();i++){
dict[ring[i]].push_back(i);
}
return dp(ring,key,0,0);
}
int dp(string ring, string key,int i,int j){
int n = ring.size();
2、base case为j溢出,在i位置需要移动的距离为0
if(j==key.size()) return 0;
int res = INT_MAX;
string key1 = to_string(i)+','+to_string(j);
if(memo[key1]) return memo[key1];
3、遍历每一个重复情况,取最优解
for(auto c:dict[key[j]]){
int pos = c;
int forw = abs(pos-i);
int step = min(forw,n-forw);
int sub = dp(ring,key,pos,j+1);
res =min(res,step+sub+1) ;
}
memo[key1] = res;
return res;
}
};
787. K 站中转内最便宜的航班
https://leetcode.cn/problems/cheapest-flights-within-k-stops/description/
有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei] ,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。
dfs递归+备忘录解法
class Solution {
public:
unordered_map<string,int> memo;
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
int tmp = dp(n,flights,src,dst,k);
return tmp==1e5? -1:tmp;
}
1、变化的状态只有起始点,和中转次数
int dp(int n, vector<vector<int>>& flights, int src, int dst, int k){
int res = 1e5;
int price = 1e5;
// if(k<0) return 1e5;
2、dfs判断base case
if(src!=dst&&k<0) return 1e5;
if(src==dst&&k>=-1) return 0;
string key = to_string(src)+','+to_string(k);
if(memo.count(key)) return memo[key];
for(int i=0;i<flights.size();i++){
if(flights[i][0]==src){
price = flights[i][2];
int tmp = dp(n,flights,flights[i][1],dst,k-1);
if (tmp>=0) price+=tmp;
}
res = min(res,price);
}
memo[key] = res;
return res;
}
};
建图+dp分解问题
class Solution {
public:
unordered_map<int,vector<vector<int>>> graph;
unordered_map<string,int> memo;
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
for(int i=0;i<flights.size();i++){
int from = flights[i][0];
int to = flights[i][1];
int price = flights[i][2];
graph[to].push_back({from,price});
}
return dp(flights,src,dst,k);
}
int dp(vector<vector<int>>& flights,int src,int dst,int k){
if(src==dst) return 0;
if(k==-1) return -1;
int res = INT_MAX;
string key = to_string(dst)+'k'+to_string(k);
if(memo.count(key)) return memo[key];
if(graph.count(dst)){
for(auto c:graph[dst]){
int from = c[0];
int price = c[1];
int sub = dp(flights,src,from,k-1);
if(sub!=-1){
res = min(res,price+sub);
}
}
}
memo[key] = res==INT_MAX? -1:res;
return memo[key];
}
};
六、回溯
139. 单词拆分
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
class Solution {
public:
bool backtracking(string s, vector<string>& wordDict,int index){
if(index==s.size()) return true;
if(index>s.size()) return false;
bool flag=false;
for(int i=0;i<wordDict.size();i++){
int n = wordDict[i].size();
if(s.substr(index,n)==wordDict[i]){
flag = flag||backtracking(s,wordDict,index+n);
}
}
return flag;
}
bool wordBreak(string s, vector<string>& wordDict) {
return backtracking(s,wordDict,0);
}
};
动态规划解法:
class Solution {
public:
vector<int> memo;
unordered_set<string> set;
bool backtracking(string& s,int index){
if(index==s.size()) return true;
// if(index>s.size()) return false;
if(memo[index]!=-1) return memo[index]==1? true:false;
for(int i=1;i+index<=s.size();i++){
string str = s.substr(index,i);
if(set.count(str)){
if(backtracking(s,index+i)){
memo[index]=1;
return true;
}
}
}
memo[index] = 0;
return false;
}
bool wordBreak(string& s, vector<string>& wordDict) {
memo = vector<int>(s.size()+1,-1);
for(auto s:wordDict) set.insert(s);
return backtracking(s,0);
}
};
140. 单词拆分 II
给定一个字符串 s 和一个字符串字典 wordDict ,在字符串 s 中增加空格来构建一个句子,使得句子中所有的单词都在词典中。以任意顺序 返回所有这些可能的句子。
注意:词典中的同一个单词可能在分段中被重复使用多次。
class Solution {
public:
unordered_set<string> set;
1、不好初始化的多维数组,使用vector a[1000]定义数组
vector<string> memo[1001];
vector<string> wordBreak(string s, vector<string>& wordDict) {
for(auto s:wordDict) set.insert(s);
return dp(s,0);
}
vector<string> dp(string& s,int i){
vector<string> res;
if(i==s.size()){
res.push_back("");
return res;
}
if(!memo[i].empty()) return memo[i];
for(int len = 1;i+len-1<s.size();len++){
string str = s.substr(i,len);
if(set.count(str)){
vector<string> sub = dp(s,i+len);
for(auto c:sub){
if(c.empty()){
res.push_back(str);
}
else{
res.push_back(str + " " + c);
}
}
}
}
memo[i] = res;
return res;
}
};
七、链表
21. 合并两个有序链表
https://leetcode.cn/problems/merge-two-sorted-lists/description/
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
1、由于需要返回一个初始节点,所以先创建一个虚拟结点,并留一个副本指针
ListNode* root = new ListNode(),*p = root;
2、当拷贝链表不为nullptr时,分别取结点
while(list1!=nullptr&&list2!=nullptr){
int a = list1->val;
int b = list2->val;
if(a>b){
root->next = list2;
list2 = list2->next;
}else{
root->next = list1;
list1 = list1->next;
}
root = root->next;
}
while(list1!=nullptr){
root->next = list1;
list1 = list1->next;
root = root->next;
}
while(list2!=nullptr){
root->next = list2;
list2 = list2->next;
root = root->next;
}
return p->next;
}
};
86. 分隔链表
https://leetcode.cn/problems/partition-list/description/
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode* cur = head;
ListNode* left = new ListNode(),*l = left;
ListNode* right = new ListNode();
ListNode* r = right;
while(cur!=nullptr){
if(cur->val<x){
l->next = cur;
l = l->next;
}
else if(cur->val>=x){
r->next = cur;
r = r->next;
}
cur = cur->next;
}
1、在新建链表中加入其他节点时,需要对尾部节点next进行清空,否则会成环
r->next = nullptr;
l->next = right->next;
return left->next;
}
};
剑指 Offer 25. 合并两个排序的链表
https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/description/
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* dumy = new ListNode(),*p = dumy;
while(l1!=nullptr&&l2!=nullptr){
if(l1->val<l2->val){
p->next = l1;
l1 = l1->next;
}else{
p->next = l2;
l2 = l2->next;
}
1、每次更新完新节点之后,都要进行后移
p = p->next;
}
if(l1==nullptr){
while(l2!=nullptr){
p->next = l2;
l2 = l2->next;
p = p->next;
}
}
if(l2==nullptr){
while(l1!=nullptr){
p->next = l1;
l1 = l1->next;
2、每次更新完新节点之后,都要进行后移
p = p->next;
}
}
p->next = nullptr;
return dumy->next;
}
};
23. 合并 K 个升序链表
https://leetcode.cn/problems/merge-k-sorted-lists/description/
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
ListNode* mergeKLists(vector<ListNode*>& lists) {
auto cmp = [](ListNode* a,ListNode* b){
return a->val>b->val;
};
1、优先级队列参数为元素、元素容器、比较函数的类型,需要使用decltype,并传入函数名
priority_queue<ListNode*,vector<ListNode*>,decltype(cmp)> que(cmp);
for(auto c:lists){
if(c) que.push(c);
}
ListNode* dumy = new ListNode(),*p = dumy;
while(!que.empty()){
ListNode* tmp = que.top();
que.pop();
p->next = tmp;
p = p->next;
if(tmp->next) que.push(tmp->next);
}
return dumy->next;
}