数据结构刷题总结

一、数组

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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值