// 贪心算法和动态规划
// 当遇到一个求解全局最优解问题时,如果可以将全局问题切分为小的局部问题,
// 并寻求局部最优解,同时可以证明局部最优解累计的结果就是全局最优解,则可以使用贪心算法
// 找零问题
// 示例:假设你有一间小店,需要找给客户46分钱的硬币,
// 你的货柜里只有面额为25分、10分、5分、1分的硬币,如何找零才能保证数额正确并且硬币数最小
/**
* 找零问题 使用贪心算法
* @param total {Number} 总金额
* @param coinArr {Array} 面值的结合
* @returns {*[]|[...[number], ...[]|[number]]}
*/
function getCoinCount(total = 0, coinArr = []) {
if (total <= 0 || coinArr.length === 0) return [];
// 取出最大的面额
let max = 0;
for (let i = 0, l = coinArr.length; i < l; i++) {
if (coinArr[i] <= total && coinArr[i] >= max) {
max = coinArr[i];
}
}
// 通过上面就可以找出来一个最大的面额
let res = [max];
// 获取下一个局部最大的解
let nextRes = getCoinCount(total - max, coinArr);
// 合并结果
res = [...res, ...nextRes];
return res;
}
// console.log(getCoinCount(155, [50, 25, 10, 5, 1])); [ 50, 50, 50, 5 ]
// console.log(getCoinCount(51, [30, 25, 10, 5, 1])); [ 30, 10, 10, 1 ]
// 但是找零问题,使用贪心算法是有缺陷的,如 getCoinCount(51, [30, 25, 10, 5, 1])
// 得出的结果是 [ 30, 10, 10, 1 ] 但是实际上还有更优解 [25,25,1]
// 动态规划
// 分治法有一个问题,就是容易重复计算已经计算过的值,使用动态规划,
// 可以讲每一次分治时算出的值记录下来,防止重复计算,从而提高效率。
// 青蛙跳台阶 有N级台阶,一只青蛙每次可以跳1级或两级,一共有多少种跳法可以跳完台阶?
// 分析:
// 台阶 1 级 跳法 1 种
// 台阶 2 级 跳法 2 种
// 台阶 3 级 跳法 3 种
// 台阶 4 级 跳法 5 种
// .... 发现有点像斐波那契数列
// 使用分治法
let num = 0;
/**
* 分治法 求青蛙跳台阶
* @param n {Number} 台阶数量
* @returns {number|*}
*/
function frogJumpSteps(n = 0) {
if (n <= 2) return n;
num += 1;
return frogJumpSteps(n - 1) + frogJumpSteps(n - 2);
}
// console.log(frogJumpSteps(10), num); // 结果 89 54
let num2 = 0;
/**
* 动态规划来计算 通过牺牲空间来换取时间
* @param num {Number} 跳的台阶数量
* @returns { Number} 返回跳的方式数量
*/
function frogJumpStepsDynamic(num = 0) {
let cache = {};
function _frogJumpStepsDynamic(num) {
if (num <= 2) return num;
else if (cache[num]) return cache[num];
else {
num2++;
let res = _frogJumpStepsDynamic(num - 2) + _frogJumpStepsDynamic(num - 1);
cache[num] = res;
return res;
}
}
return _frogJumpStepsDynamic(num)
}
// console.log(frogJumpStepsDynamic(10), num2); // 结果 89 8
// 最长公共子序列问题(LCS)
// 有的时候,我们需要比较两个字符串的相似程度,通常就是比较两个字符串有多少相同的公共子序列
// 例如有两个字符串
//
// abc1223de
// bc987de,但是他的女朋友们不喜欢
// 以上两个字符串的最长公共子序列为:bcde
// 分析:
// 情况1: 第一位的字符串相同 那继续比较后面的字符串
// 情况2: 第一位字符串不一样,会产生两种情况
// 1. 第一个字符串去除掉首位
// 2. 第二个字符串去掉首位
//
let num3 = 0;
/**
* 使用分治法 求最长子序列
* @param str1 {String}
* @param str2 {String}
* @returns {string|*}
*/
function getLCS(str1, str2) {
if (str1.length === 0 || str2.length === 0) return '';
num3++;
// 情况1
if (str1[0] === str2[0]) return str1[0] + getLCS(str1.substr(1), str2.substr(1));
else {
let str1Res = getLCS(str1.substr(1), str2);
let str2Res = getLCS(str1, str2.substr(1));
return str1Res.length > str2Res.length ? str1Res : str2Res;
}
}
console.log(getLCS('测试分治法特有的', '分治法,测试他的'),num3); // 分治法的 6863
let num4 = 0;
/**
* 使用动态规划做缓存,实现求最长子序列
* @param str1 {String}
* @param str2 {String}
* @returns {string}
*/
function getLCSDynamic(str1, str2) {
let cache = [];
let _getLCSDynamic = (str1, str2) => {
if (str1.length === 0 || str2.length === 0) return '';
num4 ++;
for (let i = 0, l = cache.length; i < l; i++) {
if (cache[i].str1 === str1 && cache[i].str2 === str2) return cache[i].res;
}
if (str1[0] === str2[0]) {
let res = str1[0] + _getLCSDynamic(str1.substr(1), str2.substr(1));
cache.push({
str1: str1.substr(1),
str2: str2.substr(1),
res: res
})
return res;
} else {
let str1Res = _getLCSDynamic(str1.substr(1), str2);
cache.push({
str1: str1.substr(1),
str2: str2,
res: str1Res
});
let str2Res = _getLCSDynamic(str1, str2.substr(1));
cache.push({
str1: str1,
str2: str2.substr(1),
res: str2Res
});
return str1Res.length > str2Res.length ? str1Res : str2Res;
}
}
return _getLCSDynamic(str1, str2);
}
console.log(getLCSDynamic('测试分治法特有的', '分治法,测试他的'), num4); //分治法的 79
js 实现 贪心算法和动态规划 贪心找零问题, 动态规划 青蛙跳台阶问题
最新推荐文章于 2024-07-16 14:53:37 发布