【菜鸟刷题笔记——持续更新】

前言
在读上海某211计算机专硕,把平时刷题的笔记整理了一下发布在这里,很多题目是照着代码随想录的刷题路线做的,包括解题思路等等。本人代码基础挺薄弱的,刷的很多题都比较简单,发在博客上督促自己进步,希望与大家共勉。

文章目录

    • 回溯
      • 回溯法解决的问题
      • 解题模板
        • 组合问题
        • 排列问题
        • 棋盘问题(n皇后、数独)
    • 动态规划
      • 解决模板
      • 动态规划基础
        • 343.整数拆分
      • 背包问题
        • 01背包⭐⭐
          • 416.分割等和子集
          • 1049.最后一块石头的重量
          • 494.目标和
      • 打家劫舍
          • 打家劫舍1
          • 打家劫舍2
      • 股票系列
      • 子序列系列
        • 300.最长递增子序列
        • 718.最长重复子数组
        • 1143.最长公共子序列
        • 53.最大子序和
        • 392.判断子序列
        • 72.编辑距离
        • 647.回文子串

回溯

回溯法解决的问题

  • 组合问题:N个数里面按一定规则找出k个数的集合
    一个集合求组合,需要startIndex;多个集合求组合,且各个集合之间互不影响,不需要startIndex
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

「回溯法解决的问题都可以抽象为树形结构」,是的,我指的是所有回溯法的问题都可以抽象为树形结构!

因为回溯法解决的都是在集合中递归查找子集,「集合的大小就构成了树的宽度,递归的深度,都构成的树的深度」

递归就要有终止条件,所以必然是一颗高度有限的树(N叉树)。

解题模板

void backtracking(参数){
	if (终止条件) {
    存放结果;
    return;	//只取叶子结点时,return,如果其他结点也要取,不加return
	}
	for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
	}
} 

组合问题

【77.组合】

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
【菜鸟刷题笔记——持续更新】_第1张图片
「图中可以发现n相当于树的宽度,k相当于树的深度」

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    vector<vector<int>> combine(int n, int k) {
        backtracking(1, k, n);
        return result;
    }
    void backtracking(int index, int k, int n){
        //终止条件
        if(path.size()==k){
            result.push_back(path);
            return;
        }
        //循环
        for(int i=index; i<=n; i++){
            path.push_back(i);
            backtracking(i+1, k, n);
            path.pop_back();
        }
    }
};

排列问题

【46.全排列】
【菜鸟刷题笔记——持续更新】_第2张图片
只取叶子结点!

需要used数组记录使用过的数字,不需要startIndex记录起始位置

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

used[i-1]==true 说明同一树枝使用过

used[i-1]==false 说明同一树层使用过
【菜鸟刷题笔记——持续更新】_第3张图片

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    vector<vector<int>> permute(vector<int>& nums) {
        int n = nums.size();
        vector<int> used(n, 0);
        backtracking(nums, used);
        return result;
    }
    //排列问题同样的数字顺序不同也是不同的结果,所以不需要index记录遍历到哪里
    void backtracking(vector<int>& nums, vector<int>& used){
        //终止条件
        if(path.size() == nums.size()){
            result.push_back(path);
            return;
        }
        //遍历
        for(int i=0; i<nums.size(); i++){  
            if(used[i] == 1){
                //说明path中已经有这个元素了
                continue;
            }
            used[i] = 1;
            path.push_back(nums[i]);
            backtracking(nums, used);
            used[i] = 0;
            path.pop_back();
        }
    }
};

棋盘问题(n皇后、数独)

n皇后

约束条件:不能同行、不能同列、不能同斜线

//递归函数:row记录遍历到了第几行
backtracking(int n, int row, vector& chessBoard)

终止条件:递归到最底层即 叶子结点 终止

if(row==n){
	result.push_back(chessBoard);
	return;
}

单层搜索逻辑

for (int col = 0; col < n; col++) {
    if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
        chessboard[row][col] = 'Q'; // 放置皇后
        backtracking(n, row + 1, chessboard);
        chessboard[row][col] = '.'; // 回溯,撤销皇后
    }
}
//isValid 合法判断
bool isValid(int row, int col, vector<string>& chessboard, int n) {
    int count = 0;
    // 检查列
    for (int i = 0; i < row; i++) { // 这是一个剪枝
        if (chessboard[i][col] == 'Q') {
            return false;
        }
    }
    // 检查 45度角是否有皇后
    for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
        if (chessboard[i][j] == 'Q') {
            return false;
        }
    }
    // 检查 135度角是否有皇后
    for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
        if (chessboard[i][j] == 'Q') {
            return false;
        }
    }
    return true;
}

动态规划

解决模板

  1. 确定dp数组以及下标的含义
  2. 确定递推公式
  3. dp数组初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

动态规划基础

343.整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。返回你可以获得的最大乘积。

示例 1: 输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。

【解题】

dp[i]含义:正整数i的最大乘积

递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});
max中要有dp[i],因为保存了二层循环内的最大值

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1);
        //赋初值  dp[i]含义:正整数i的最大乘积
        dp[0] = 0, dp[1] = 1;
        if(n<=1) return dp[n];
        for(int i=2; i<=n; i++){
            //递推
            for(int j=1; j<i; j++){
                dp[i] = max(dp[i], max(dp[i-j]*j, (i-j)*j));
            }
        }
        return dp[n];
    }
};

背包问题

01背包⭐⭐

【问题描述】:有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

【例题】背包最大重量为4

重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

dp含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。本题要求i=2,j=4

递推公式:

  • 由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]
  • 由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

初始化:dp[i][0]=0;

// dp[0][j] 要倒叙遍历 存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 如果背包重量<0号物品重量,该单元=0
for (int j = bagWeight; j >= weight[0]; j--) {
    dp[0][j] = dp[0][j - weight[0]] + value[0]; // 初始化i为0时候的情况
}

确定遍历顺序:先遍历物品,然后遍历背包重量

// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
    for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 
        if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化
        else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    }
}

滚动数组】可以将二维数组优化为一维数组

对于二维数组的递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

如果把dp[i - 1]那一层拷贝到dp[i]上,表达式可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);

  1. 确定dp数组的定义
    在一维dp数组中,dp[j]表示:容量为j的背包,所背的最大物品价值

  2. 递推公式

    dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    
  3. 初始化
    dp[0]=0

  4. 遍历顺序

    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    
    416.分割等和子集

题目描述】给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200

示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int i:nums){
            sum+=i;
        }
        //sum/2是背包的容量
        if(sum%2!=0) return false;
        //volume:背包容量
        int volume = sum/2;
        //dp[i]:背包总容量为i,最大可以凑成i的子集总和为dp[i]
        int[] dp = new int[volume+1];
        dp[0] = 0;
        //遍历每个物品
        for(int i=0; i<nums.length; i++){
            for(int j=volume; j>=nums[i]; j--){
                //从后往前 递推
                dp[j] = Math.max(dp[j], dp[j-nums[i]]+nums[i]);
            }
        }
        if(dp[volume]==volume){
            return true;
        }else{
            return false;
        }
    }
}

二维数组版本

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }

        if (sum % 2 == 1)
            return false;
        int target = sum / 2;

        //dp[i][j]代表可装物品为0-i,背包容量为j的情况下,背包内容量的最大价值
        int[][] dp = new int[nums.length][target + 1];

        //初始化,dp[0][j]的最大价值nums[0](if j > weight[i])
        //dp[i][0]均为0,不用初始化
        for (int j = nums[0]; j <= target; j++) {
            dp[0][j] = nums[0];
        }

        //遍历物品,遍历背包
        //递推公式:
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j <= target; j++) {
                //背包容量可以容纳nums[i]
                if (j >= nums[i]) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        return dp[nums.length - 1][target] == target;
    }
}
1049.最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

【思路分析】

尽可能的把石头分成重量相同的两堆,这样碰撞后得到的石头最小

weight:石头重量

value:石头重量

1.确定dp含义
dp[i]:表示容量为i的背包,最多可以背dp[i]重量的石头

2.递推公式

//	dp[j - stones[i]]为 容量为j - stones[i]的背包最大所背重量。
dp[j] = max(dp[j], dp[j - stone[i]] + stone[i]);

3.dp初始化(初值,确定dp数组大小)

既然 dp[j]中的j表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和。

因为提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。

而我们要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了。

当然也可以把石头遍历一遍,计算出石头总重量 然后除2,得到dp数组的大小。

target = totalWeight/2

4.确定遍历顺序

for(int i=0; i<n; i++){
    for(int j=totalWeight/2; j>=stones[i]; j--){
        //dp递推
        dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
    }
}
//相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。
494.目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入:nums: [1, 1, 1, 1, 1], S: 3 输出:5 解释:

-1+1+1+1+1 = 3 +1-1+1+1+1 = 3 +1+1-1+1+1 = 3 +1+1+1-1+1 = 3 +1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

思路分析

sum = nums数组累加;做加法的数总和为x,那么做减法的为sum-x;要让x-(sum-x)=S,则x=(sum+S)/2

问题变为:装满x=(sum+S)/2的背包有几种方法,要注意判断x为奇数的情况

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int n = nums.length;
        int result = 0, sum = 0;
        for(int i:nums){
            sum+=i;
        }
        if(target>sum){
            return 0;
        }
        int volume = (target+sum)/2;
        //dp[i]:运算结果为i的方法有dp[i]种
        int[] dp = new int[volume+1];
        dp[0] = 1;
        //遍历
        for(int i=0; i<n; i++){
            for(int j=volume; j>=nums[i]; j--){
                //递推
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[volume];
    }
}

打家劫舍

打家劫舍1

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        if(n==1) return nums[0];
        if(n==2) return Math.max(nums[0], nums[1]);
        int[] dp = new int[n+1];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for(int i=2; i<n; i++){
            dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
        }
        return dp[n-1];
    }
}
打家劫舍2

房屋围成一个环,即首元素和尾元素是相连的,其余条件不变

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        //要加条件判断 不然会有指针错误
        if(n==0) return 0;
        if(n==1) return nums[0];
        int[] dp = new int[n+1];
        //情况1:含首不含尾
        int rob1 = robRange(nums, 0, n-2);
        //情况2:含尾不含首
        int rob2 = robRange(nums, 1, n-1);
        return Math.max(rob1, rob2);
    }
    //单独把循环遍历写成一个函数
    int robRange(int[] nums, int start, int end){
        if(start==end) return nums[start];
        int[] dp = new int[nums.length];
        dp[start] = nums[start];
        dp[start+1] = Math.max(nums[start], nums[start+1]);
        for(int i=start+2; i<=end; i++){
            dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
        }
        return dp[end];
    }
}

股票系列

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

解题思路

//dp[i]:第i天能获得的最大利润
//递推公式:dp[i] = max(dp[i-1], prices[i]-minCost); 要么之前的利润最大,要么当日股价-最低股价最大
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n<=1) return 0;
        vector<int> dp(n);
        dp[0] = 0;
        int result = 0;
        int minCost = prices[0];
        for(int i=1; i<n; i++){
            minCost = min(minCost, prices[i]);
            dp[i] = max(dp[i-1], prices[i]-minCost);
        }
        return dp[n-1];
    }
};

子序列系列

300.最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

解题思路

1.dp[i]:0~i区间的最长递增子序列

2.递推公式:

//位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。 需要双层循环
if (nums[i] > nums[j]){
    dp[i] = max(dp[i], dp[j] + 1);
} 

3.初始化:每个dp[i]都至少为1

4.确定遍历顺序

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if(n<2) return n;
        int[] dp = new int[n];
        int result = 0;
        //一定要全部赋初值
        for(int i=0; i<n; i++){
            dp[i]=1;
        }
        for(int i=1; i<n; i++){
            for(int j=0; j<i; j++){
                if(nums[i]>nums[j]){
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}

718.最长重复子数组

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3

解题思路

题目中的子数组,其实就是连续子序列。这种问题动规最拿手

1.dp[i][j]:以下标i-1结尾的A,以下标j-1结尾的B,最长公共子数组长度为dp[i][j]

2.递推公式:

if(A[i-1]==B[j-1]){
	dp[i][j] = dp[i-1][j-1]+1;
}

3.初始化:从下标为1开始循环遍历,dp[0][0] = 0;首行和首列也都为0

4.确定循环顺序:外层循环A,内层循环B

5.举例

【菜鸟刷题笔记——持续更新】_第4张图片

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        int[][] dp = new int[m+1][n+1];
        dp[0][0] = 0;
        int result = 0;
        for(int i=1; i<=m; i++){
            dp[i][0] = 0;
        }
        for(int j=1; j<=n; j++){
            dp[0][j] = 0;
        }
        //
        for(int i=1; i<=m; i++){
            for(int j=1; j<=n; j++){
                if(nums1[i-1]==nums2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }
                result = Math.max(result,dp[i][j]);
            }
        }
        return result;
    }
}

1143.最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

解题思路

1.dp[i][j]:以下标i-1结尾的text1,以下标j-1结尾的text2,最长公共子数组长度为dp[i][j]

2.递推公式:

//如果text1[i-1]和text2[j-1]相同
if(text1[i-1]==text2[j-1]){
	dp[i][j] = dp[i-1][j-1]+1;
} else{
    //如果不相同 取dp[i][j-1]和dp[i-1][j]较大的
    dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
}

3.初始化:从下标为1开始循环遍历,dp[0][0] = 0;首行和首列也都为0

4.确定循环顺序:外层循环text1,内层循环text2

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int n1 = text1.length();
        int n2 = text2.length();
        int result = 0;
        int[][] dp = new int[n1+1][n2+1];
        for(int k=1; k<=n1; k++){
            dp[k][0] = 0;
        }
        for(int k=1; k<=n2; k++){
            dp[0][k] = 0;
        }
        dp[0][0] = 0;
        for(int i=1; i<=n1; i++){
            for(int j=1; j<=n2; j++){
                if(text1.charAt(i-1)==text2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1]+1;
                } else{
                    dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
                }
                result = Math.max(result, dp[i][j]);
            }
        }
        return result;
    }
}

53.最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

解题思路

【贪心】

例[-2,1,4,-3,2,-3,5]

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。

全局最优:选取最大“连续和”

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result = INT32_MIN;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) {
            count += nums[i];
            if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
                result = count;
            }
            if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
        }
        return result;
    }
};

【动态规划】

1.dp[i]:下标为i之前的最大连续子序和为dp[i]

2.递推公式:

//要么从当前位置重新开始
dp[i] = max(nums[i-1], dp[i-1]+nums[i]);
class Solution {
    //动态规划
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        if(n<1) return 0;
        if(n==1) return nums[0];
        int[] dp = new int[n];
        dp[0] = nums[0];
        //result不能设成0 或者设成min_max
        int result = dp[0];
        //
        for(int i=1; i<n; i++){
            dp[i] = Math.max(nums[i], dp[i-1]+nums[i]);
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}

392.判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

双指针法

class Solution {
public:
    bool isSubsequence(string s, string t) {
        bool flag = false;
        int m = s.size();
        int n = t.size();
        int count = 0;
        if(m>n) return false;
        if(m==0) return true;
        //range
        int i=0, j=0;
        //注意:双指针用while循环较方便
        while(i<m&&j<n){
            if(s[i]==t[j]){
                i++;
                j++;
                count++;
            } else{
                j++;
            }
            if(count==m)  flag=true;
        }
        return flag;
    }
};

动态规划

1.dp[i][j]:表示以下标i-1结尾的字符串s,和以下标j-1结尾的字符串t,相同子序列的长度为dp[i][j]

2.递推公式:

if(s[i-1]==t[j-1]){
	dp[i][j] = dp[i-1][j-1]+1;
} else{
	//不一样 则相当于t要删除掉当前指向的元素
	dp[i][j] = dp[i][j-1];
}

3.初始化

【菜鸟刷题笔记——持续更新】_第5张图片

class Solution {
	public:
		bool isSubsequence(string s, string t) {
			vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
            for (int i = 1; i <= s.size(); i++) {
                for (int j = 1; j <= t.size(); j++) {
                if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = dp[i][j - 1];
                }
            }
            if (dp[s.size()][t.size()] == s.size()) return true;
            return false;
        }
};

72.编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

解题思路

1.dp[i][j]: 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j],编辑距离就是最少操作数。

2.递推公式:

操作一(增):word1增加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 加上一个增加元素的操作。

即 dp[i][j] = dp[i - 1][j] + 1;

操作二(删):word2添加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。

即 dp[i][j] = dp[i][j - 1] + 1;

这里有同学发现了,怎么都是添加元素,删除元素去哪了。

word2添加一个元素,相当于word1删除一个元素,例如 word1 = “ad” ,word2 = “a”,word2添加一个元素d,也就是相当于word1删除一个元素d,操作数是一样!

操作三(换):替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个替换元素的操作。

即 dp[i][j] = dp[i - 1][j - 1] + 1;

综合三种操作:

dp[i][j] = min(dp[i-1][j]+1, min(dp[i-1][j-1]+1, dp[i][j-1]+1));

3.初始化:dp[0][0] = 0;dp[i][0] = i;dp[0][j] = j

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size();
        int len2 = word2.size();
        int result=0;
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
        //初始化
        for(int k=0; k<=len1; k++){
            dp[k][0] = k;
        }
        for(int k=0; k<=len2; k++){
            dp[0][k] = k;
        }
        //range
        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                if(word1[i-1]==word2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                } else{
                    dp[i][j] = min(dp[i-1][j]+1, min(dp[i-1][j-1]+1, dp[i][j-1]+1));
                }
            }
        }
        result = dp[len1][len2];
        return result;
    }
};

647.回文子串

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

双指针——中心扩展

例如字符串:ababa

以中间的a为中心点,两个指针left和right分别向两边扩散,每次扩散时判断两指针所指字符是否一致

两个字符的中心点ba,分别向两边扩散

中心点如何确定? len个单字符中心点和len-1个双字符中心点,共有2len-1个中心点

  • aba 有5个中心点,分别是 a、b、a、ab、ba

  • abba 有7个中心点,分别是 a、b、b、a、ab、bb、ba|
    【菜鸟刷题笔记——持续更新】_第6张图片

    left = i/2;right=left+i%2

class Solution6472 {
    public int countSubstrings(String s) {
        // 中心扩展法
        int ans = 0;
        for (int center = 0; center < 2 * s.length() - 1; center++) {
            // left和right指针和中心点的关系是?
            // 首先是left,有一个很明显的2倍关系的存在,其次是right,可能和left指向同一个(偶数时),也可能往后移动一个(奇数)
            // 大致的关系出来了,可以选择带两个特殊例子进去看看是否满足。
            int left = center / 2;
            int right = left + center % 2;

            while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
                ans++;
                left--;
                right++;
            }
        }
        return ans;
    }
}

你可能感兴趣的:(刷题,算法,c++,数据结构)