仅为记录,某些图片可以自行粘贴gitee网址查看,需要密码就…算了😂
一、 动态规划
原问题可以分解成规模较小的两个子问题,且子问题的解可以复用
2022.1.18 斐波那契
一、 剑指 Offer 10- I. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
/**
* @param {number} n
* @return {number}
*/
var fib = function(n) {
// 暴力
// const MOD = 1e9+7;
// if(n === 0) return 0;
// if(n === 1 || n === 2) return 1;
// return (fib (n-1) + fib (n-2))%MOD;
// // 带备忘录的DP,时间复杂度 O(n)
// if(n === 0) return 0;
// const arr = new Array(n+1);
// return dp(arr, n);
// dp数组的迭代
const MOD = 1e9+7;
if(n < 2) return n;
if(n==2) return 1;
const dp = new Array(n+1);
dp[1] = dp[2] = 1;
for(let i = 3; i <= n; i++){
dp[i] = (dp[i-1] +dp[i-2])%MOD;
}
return dp[n];
};
// const dp =(arr, n) =>{
// const MOD = 1e9+7;
// if(n == 1 || n == 2) return 1;
// if(arr[n] != 0) return arr[n];
// arr[n] = (dp(arr, n-1) + dp(arr, n-2))%MOD;
// return arr[n];
// }
// 题解时间复杂度O(1)
// 状态压缩,从N缩小为2
var fib = function(n) {
const MOD = 1000000007;
if (n < 2) {
return n;
}
let p = 0, q = 0, r = 1;
for (let i = 2; i <= n; ++i) {
p = q;
q = r;
r = (p + q) % MOD;
}
return r;
};
二、 509. 斐波那契数
斐波那契数,通常用
F(n)
表示,形成的序列称为 斐波那契数列 。该数列由0
和1
开始,后面的每一项数字都是前面两项数字的和。也就是:
```
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
```
给你 `n` ,请计算 `F(n)` 。
2022.1.19
一、 322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
cccccccccccccccccccccc
二、
2022.1.20
一、 674. 最长连续递增序列
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。
/**
* @param {number[]} nums
* @return {number}
*/
var findLengthOfLCIS = function(nums) {
let ans = 0;
let start = 0;
for(let i = 0 ; i < nums.length; i++){
if(i>0 && nums[i] <= nums[i-1])
start = i;
ans = Math.max(ans, i - start + 1);
}
return ans;
};
二、 300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
2-1 动态规划
/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function(nums) {
// 对dp数组初始化为1
const numsLen = nums.length;
const dp = new Array(numsLen).fill(1);
for(let i= 0;i < numsLen;i++){
for(let j =0;j<i;j++){
if(nums[i] > nums[j]){
// dp[i]存储的是以nums[i]为最大值的最长递增子序列的个数
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
}
let max = 1;
for(key in dp){
max = Math.max(dp[key],max);
}
return max;
};
2-2 二分搜索法
正常人想不到,更何况,我还是普通人
2022.1.24 二维递增子序列:信封嵌套问题
一、 354. 俄罗斯套娃信封问题
给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mFODkLA1-1645370197776)(https://gitee.com/hannah_bingo/yyy/raw/master/image-20220124111602343.png)]
/**
* @param {number[][]} envelopes
* @return {number}
*/
// 时间复杂度O(n方)
var maxEnvelopes = function(envelopes) {
const enLen = envelopes.length;
if(enLen === 0){
return 0;
}
// 二维数组arr[i][0]升序排列,arr[i][1]降序
envelopes.sort((arr1, arr2) =>{
return arr1[0] !== arr2[0] ? arr1[0] - arr2[0] : arr2[1] - arr1[1];
})
// 查找arr[i][1]的LIS
let max = 1;
// const dp = new Array[enLen].fill(1);
const dp = new Array(enLen).fill(1);
for(let i = 1; i<enLen; ++i){
for(let j = 0 ; j<i;++j){
if(envelopes[i][1] > envelopes[j][1]) {
dp[i] = Math.max(dp[i], dp[j]+1);
}
}
max = Math.max(max, dp[i]);
}
return max;
};
二、 673. 最长递增子序列的个数
细节问题
var findNumberOfLIS = function(nums) {
let n = nums.length, maxLen = 0, ans = 0;
const dp = new Array(n).fill(0);
const cnt = new Array(n).fill(0);
for (let i = 0; i < n; ++i) {
dp[i] = 1;
cnt[i] = 1;
for (let j = 0; j < i; ++j) {
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
cnt[i] = cnt[j]; // 重置计数
} else if (dp[j] + 1 === dp[i]) {
cnt[i] += cnt[j];
}
}
}
if (dp[i] > maxLen) {
maxLen = dp[i];
ans = cnt[i]; // 重置计数
} else if (dp[i] === maxLen) {
ans += cnt[i];
}
}
return ans;
};
2022.1.26
一、 53. 最大子数组和
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
const numsLen = nums.length;
const dp = new Array(numsLen);
let max = -Infinity;
for(let i =0; i< numsLen;i++){
if(i>0){
dp[i] = (dp[i-1]+nums[i]) > nums[i] ? (dp[i-1]+nums[i]) : nums[i];
}else dp[0] = nums[0];
max = Math.max(max, dp[i]);
}
return max;
};
2022.2.10
一、1143. 最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
/**
* @param {string} text1
* @param {string} text2
* @return {number}
*/
var longestCommonSubsequence = function(text1, text2) {
const t1L = text1.length,t2L = text2.length;
// base case
const dp = new Array(t1L + 1).fill(0).map(() => new Array(t2L + 1).fill(0));
// 状态转移
// 正向遍历
for(let i = 1; i <= t1L; i++){
for(let j = 1; j<=t2L; j++){
if(text1[i-1] === text2[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]);
}
}
}
return dp[t1L][t2L];
};
二、 72. 编辑距离
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
// 超时
// var minDistance = function(word1, word2) {
// const len1 = word1.length;
// const len2 = word2.length;
// const dp = (i,j) =>{
// // base case
// // word1先换到头
// if(i===-1) return j+1;
// // word2 先走到头,删除word1剩余的
// if(j===-1) return i+1;
// // 选择
// if(word1[i] === word2[j]){
// return dp(i-1,j-1);
// } else {
// return Math.min(
// dp(i,j-1) + 1, // 插入
// dp(i -1 ,j) + 1, // 删除
// dp(i-1,j-1) + 1 // 替换
// )
// }
// }
// return dp(len1-1,len2-1);
// };
// 备忘录优化
var minDistance = function(word1, word2) {
const w1L = word1.length;
const w2L = word2.length;
// 备忘录
let memo = new Array(w1L+1).fill(-1).map(() => new Array(w2L+1).fill(-1));
const dp = (i,j) => {
// base case
// w1,w2走完
if (i === -1) return j+1;
if (j === -1) return i+1;
// 先查备忘录
if(memo[i][j] !== -1) return memo[i][j];
if(word1[i] === word2[j]){
memo[i][j] = dp(i-1,j-1);
} else{
memo[i][j] = Math.min(
dp(i-1,j) + 1, // 删除
dp(i,j-1) + 1, // 插入
dp(i-1,j-1)+1
)
}
return memo[i][j];
}
return dp(w1L-1,w2L-1);
};
// DP Table
// var minDistance = function (word1, word2) {
// let dp = Array.from(Array(word1.length + 1), () =>
// Array(word2.length + 1).fill(0)
// );
// /* base case 是i走完word1或j走完word2,可以直接返回另一个字符串剩下的长度 */
// // j走完word2 如果i还没走完word1,那么只能用删除操作把word1缩短为word2,也就是直接返回另一个字符串剩下的长度
// for (let i = 1; i <= word1.length; i++) {
// dp[i][0] = i;
// }
// // 同理 如果i走完word1时j还没走完了word2,那就只能用插入操作把word2剩下的字符全部插入word1
// for (let j = 1; j <= word2.length; j++) {
// dp[0][j] = j;
// }
// for (let i = 1; i <= word1.length; i++) {
// for (let j = 1; j <= word2.length; j++) {
// if (word1[i - 1] === word2[j - 1]) {
// // 啥也不做
// dp[i][j] = dp[i - 1][j - 1];
// } else {
// // 三选一 哪个操作最后得到的编辑距离最小,就选谁
// dp[i][j] = Math.min(
// // 删除
// // 直接把word1[i]这个字符删掉,前移i继续跟j对比 操作数+1
// dp[i - 1][j] + 1,
// // 插入
// // 直接在word1[i]插入一个和word2[j]一样的字符,那么word2[j]就被匹配了,前移j继续跟i对比
// dp[i][j - 1] + 1,
// // 替换
// // 直接把word1[i]替换成word2[j] 这样它俩就匹配了,同时前移i,j继续对比
// dp[i - 1][j - 1] + 1
// );
// }
// }
// }
// return dp[word1.length][word2.length];
// };
2022.2.12
一、 516. 最长回文子序列
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
/**
* @param {string} s
* @return {number}
*/
var longestPalindromeSubseq = function(s) {
const sL = s.length;
const dp = new Array(sL+1).fill(0).map(() => new Array(sL+1).fill(0));
// base case
for(let i = 0; i < sL; i++){
dp[i][i] = 1;
}
// 状态转移,反向遍历,求dp[0][j]
for(let i = sL-2; i >= 0; i--){
for(let j = i+1;j<sL;j++){
if(s[i] === s[j]){
dp[i][j] = dp[i+1][j-1] + 2;
}else {
dp[i][j] = Math.max(dp[i][j-1],dp[i+1][j]);
}
}
}
return dp[0][sL -1];
};
CCCCCCCCC,调试时什么奇奇怪怪的问题
二、 最长回文字串
给你一个字符串
s
,找到s中最长回文子串
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
String res = "";
boolean[][] dp = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1]); //j - i 代表长度减去 1
if (dp[i][j] && j - i + 1 > res.length()) {
res = s.substring(i, j + 1);
}
}
}
return res;
}
}
三、 1312. 让字符串成为回文串的最少插入次数
给你一个字符串
s
,每一次操作你都可以在字符串的任意位置插入任意字符。请你返回让
s
成为回文串的 最少操作次数 。
/**
* @param {string} s
* @return {number}
*/
var minInsertions = function(s) {
const sL = s.length;
const dp = new Array(sL+1).fill(0).map(()=>new Array(sL+1).fill(0));
// base case i==j时dp[i][j]=0
// 状态转移,斜向遍历
for(let i = sL-2; i >= 0; i--){
for(let j = i+1; j <sL; j++){
if(s[i] === s[j]){
dp[i][j] = dp[i+1][j-1];
} else {
// 选择代价最小的插入1
dp[i][j] = Math.min(dp[i+1][j],dp[i][j-1])+1;
}
}
}
return dp[0][sL-1];
};
四、 10. 正则表达式匹配
面试题19. 正则表达式匹配
请实现一个函数用来匹配包含’. ‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
var isMatch = function(s, p) {
const sL = s.length;
const pL = p.length;
dp = (s,i,p,j) => {
// base case,s和p走完一个了
if(j === pL) {
return i === sL;
} else if (i === sL){
// 匹配规则没走完--->
// 剩余不是 a*
if((pL - j)%2 === 1 ){
return false;
}
// 剩余x*y*z*
while(j+1 < pL){
if(p[j+1] != '*'){
return false;
}
j += 2;
}
return true;
}
let res = false;
// 状态转移
// 匹配
if(s[i] === p[j] || p[j] === '.'){
if(j<pL-1 && p[j+1] === '*'){
// 匹配0或多次
res = dp(s,i,p,j+2) || dp(s,i+1,p,j);
} else {
// 正常匹配
res = dp(s,i+1,p,j+1);
}
}
else {
// 匹配0次
if(j < pL-1 && p[j+1] === '*'){
res = dp(s,i,p,j+2);
} else {
res = false;
}
}
return res;
}
dp(s,0,p,0);
};
- 如果我有罪,请不要让我检查了小十遍代码,undefined,undefined,undefined,undefined,undefined…
- wacaowocao---------------------------------------------------------哇哇哇哇哇哇哇
五、 887. 鸡蛋掉落
给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。
已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。
每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?
// /**
// * @param {number} k
// * @param {number} n
// * @return {number}
// */
// // 递归超时
// var superEggDrop = function(k, n) {
// const memo = new Map();
// function dp (k, n) {
// if (k === 1) return n; // 当k为1时,只能线性扫描所有楼层
// if (n === 0) return 0;
// if (memo.has(k + '' + n)) {
// return memo.get(k + '' + n)
// }
// let res = Infinity;
// for (let i = 1; i <= n; i++) {
// res = Math.min(
// res,
// Math.max(
// dp(k, n - i),
// dp(k - 1, i - 1)
// ) + 1
// )
// }
// memo.set(k + '' + n, res)
// return res;
// }
// return dp(k, n)
// };
var superEggDrop= function(k, n) {
const dp = new Array(k + 1).fill(0).map(() => new Array(n + 1).fill(0));
let m = 0;
while (dp[k][m] < n) {
m++;
for (let i = 1; i <= k; i++) {
dp[i][m] = dp[i][m - 1] + dp[i - 1][m - 1] + 1;
}
}
return m;
};
2022.2.15
第一章 背包问题
1-1 0-1背包问题
一、 474. 一和零
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
/**
* @param {string[]} strs
* @param {number} m
* @param {number} n
* @return {number}
*/
//0-1背包
var findMaxForm = function(strs, m, n) {
// m,n看作背包的W和N
// dp[i][j] 表示 对于 还剩i个0和j个1 ,这种情况下 strs 的最大子集 的长度
const dp = new Array(m+1).fill(0).map(() => new Array(n+1).fill(0));
// base case
// for(let i = 0; i < m;i++){
// dp[i][0] = 0;
// }
// for(let j = 0; j < n; j++){
// dp[0][j] = 0;
// }
for(let p= 0; p < strs.length; p++){
let zeroNum = 0,oneNum = 0;
const str = strs[p];
for(let s = 0; s <str.length;s++ ){
console.log(str[s]);
if(str[s] === '0') ++zeroNum;
else ++oneNum;
}
for(let i = m; i >= zeroNum; i--){
for(let j = n; j >= oneNum; j--){
dp[i][j] = Math.max(dp[i][j],dp[i-zeroNum][j-oneNum] + 1);
}
}
}
return dp[m][n];
};
二、 494. 目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
三、 416. 分割等和子集
给你一个 只包含正整数 的 非空 数组
nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
/**
* @param {number[]} nums
* @return {boolean}
*/
var canPartition = function(nums) {
//
let sum = 0;
nums.forEach(item => sum += item);
if(sum %2 !== 0) return false;
// dp[i][i] : 对于前i个物品,是否能够装满容量为j;
const dp = new Array(nums.length+1).fill(0).map(()=>new Array(sum / 2 +1) );
// base case
for(let i = 0; i< nums.length;i++){
dp[i][0] = true;
}
// 状态装一
for(let i = 1; i<= nums.length;i++){
for(let j =1 ;j <= sum /2;j++){
if(j-nums[i-1] <0){
dp[i][j] = dp[i-1][j];
}else {
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
}
}
}
// 求dp[nums.length-1][sum/2];
return dp[nums.length][sum/2];
};
心态有些炸
四、1049. 最后一块石头的重量 II
我不会!!这TM题解咋感觉是子集背包的题解???
1-2-14-3 完全背包 问题
一、[bug]
322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
// dp[i][j] : 对于前i个硬币,背包容量为j时,装满背包所需最少的硬币数量
const dp = new Array(coins.length +1).fill(0).map(()=>new Array(amount+1).fill(0));
// base case
// 状态转移
for(let i = 1; i <= coins.length;++i){
for(let j = 1; j<=amount;++j){
if(j-coins[i-1] < 0){
dp[i][j] = dp[i-1][j];
} else{
dp[i][j] = Math.min(dp[i-1][j-coins[i-1]]+1 ,dp[i][j-coins[i-1]]+1);
}
}
}
//求 dp[coins.length][amout]
return dp[coins.length][amount];
};
二、 518. 零钱兑换 II
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整
/**
* @param {number} amount
* @param {number[]} coins
* @return {number}
*/
var change = function(amount, coins) {
// dp[i][j] : 对于前i个硬币,背包容量为j时,有dp[i][j]种凑法
const dp = new Array(coins.length +1).fill(0).map(()=>new Array(amount+1).fill(0));
// base case
for(let i = 0; i <= coins.length;i++){
dp[i][0] = 1;
}
// 状态转移
for(let i = 1; i <= coins.length;++i){
for(let j = 1; j<=amount;++j){
if(j-coins[i-1] < 0){
dp[i][j] = dp[i-1][j];
} else{
dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]];
}
}
}
//求 dp[coins.length][amout]
return dp[coins.length][amount];
};