当遇到一个求解全局最优解问题时,如果可以将全局问题切分为小的局部问题,并寻求局部最优解,同时可以证明局部最优解累计的结果就是全局最优解,则可以使用贪心算法
面试题:找零问题
示例:假设你有一间小店,需要找给客户46分钱的硬币,你的货柜里只有面额为25分、10分、5分、1分的硬币,如何找零才能保证数额正确并且硬币数最小
/**
* 得到一个找零的结果: [25, 10, 10, 1]
* @param {*} total 要找零的总额
* @param {*} denos 拥有的面额
*/
function exchange(total, denos) {
num++;
if (total <= 0) {
return []; //不用找零
}
// 寻找最大的面额,同时要保证面额小于等于total
var max = 0;
for (var i = 0; i < denos.length; i++) {
var deno = denos[i];
if (deno > max && deno <= total) {
max = deno;
}
}
// max记录这一次的解(局部最优解)
var result = [max];
var next = exchange(total - max, denos); //得到后续的局部最优解
result = result.concat(next); //拼接之后,就是整体最优解
return result;
}
var total = 51;
var denos = [25, 10, 5, 1];
var result = exchange(total, denos);
console.log(result, num);
以上只看局部,不回溯,会导致结果可能不是最优解,以下为找零问题的精确算法
只需要看,要不要找第一个面值
1.看total是不是0,如果是。不需要找零
2.total等于第一张面值,如果等于,用第一张面值作为找零
3.total小于第一张面值,第一张面值无法用于找零,看后续面值
4.total比第一个面值大,分为找和不找。分别求解,比较解的结果,得到最终结果
var num = 0;
/**
* 根据总的找零金额,和拥有的面值,精确的计算最终结果
* 如果无解,返回false
* @param {*} total 总的找零金额
* @param {*} denos 拥有的面试
*/
function exchange(total, denos) {
var cache = []; // {total:xx, index:xxx, result:xxx}
function _exchange(total, index) {
// 只需要看,要不要找第i个面值
if (total === 0) {
return []; //不用找零
} else if (total < 0) {
return false; //无解
}
if (index >= denos.length) {
// index这个位置没有任何面值
return false; //无解
}
//查看是否命中缓存
for (var i = 0; i < cache.length; i++) {
var c = cache[i];
if (c.total === total && c.index === index) {
return c.result;
}
}
num++;
var result; //缓存结果
// 1. 看total是不是等于第index个面值,如果等于,用第index张面值作为找零
var deno = denos[index]; //第index张面值
if (total === deno) {
result = [deno]; //deno就是最优解
}
// 2. total比第index个面值小,第index张面值无法用于找零,看后续面值
else if (total < deno) {
return _exchange(total, index + 1);
}
// 3. total比第index个面值大,分为找和不找,分别求解,比较解的结果,得到最终结果
else if (total > deno) {
//找
var result1 = _exchange(total - deno, index); //找了这张面值后,剩下的最优解
//不找
var result2 = _exchange(total, index + 1); //不找这个面值,剩下的最优解
//对比两种结果
if (result1 === false && result2 === false) {
result = false;
} else if (result1 === false && result2 !== false) {
result = result2;
} else if (result1 !== false && result2 === false) {
result = [deno].concat(result1);
} else {
//都有解
result1 = [deno].concat(result1); // 让找自己的情况加入自己这张面额
result = result1.length < result2.length ? result1 : result2;
}
}
cache.push({
total: total,
index: index,
result: result
});
return result;
}
console.log(cache);
return _exchange(total, 0);
}
var result = exchange(51, [30, 25, 10, 5, 1]);
console.log(result, "运行了" + num + "次"); //预期: 25 25 1
// var result = exchange(51, [11, 10, 5, 2]);
// console.log(result); //预期: false
分治法有一个问题,就是容易重复计算已经计算过的值,使用动态规划,可以讲每一次分治时算出的值记录下来,防止重复计算,从而提高效率。(空间换时间)
通用写法:
var cache = {}/[] 缓存已经计算过的结果
面试题1:青蛙跳台阶问题
有N级台阶,一只青蛙每次可以跳1级或两级,一共有多少种跳法可以跳完台阶?
/**
* @description: 青蛙跳台阶问题(没有用动态规划)
* @param {type} total:一共多少台阶
* @return:
*/
var num1 = 0;
function count1(total){
num1 ++;
if(total === 0) return 0;
if(total === 1) return 1;
if(total === 2) return 2;
return count1(total-1)+count1(total-2)
}
var num2 = 0;
function count2(total){
var cache = {};
function _count2(total){
if(cache[total] != undefined){
return cache[total];
}
num2 ++;
var result;
if(total === 0) result = 0;
else if(total === 1) result = 1;
else if(total === 2) result = 2;
else{
result = _count2(total -1) + _count2(total -2);
}
cache[total]=result;
return result;
}
return _count2(total);
}
面试题2:最长公共子序列问题(LCS)
有的时候,我们需要比较两个字符串的相似程度,通常就是比较两个字符串有多少相同的公共子序列
例如有两个字符串
以上两个字符串的最长公共子序列为:??? 邓哥是渡一的
情况1:第一位相同,第一位一定进入最长公共子序列,只需要搞定后续的即可
情况2:第一位不同
1.去点第一个字符串的首字母,和第二个字符串相比,LCS1
2.去点第二个人字符串的首字母,和第一个字符串相比,LCS2
3.比较LCS1和lCS2谁更长
/**
* 得到两个字符串的最长公共子序列
* @param {*} str1
* @param {*} str2
*/
function LCS(str1, str2) {
var cache = []; // {str1:xxx, str2:xxxx, result:xxxx}
function _LCS(str1, str2) {
//特殊情况去掉
if (str1 === "" || str2 === "") {
return "";
}
//查看缓存
for (var i = 0; i < cache.length; i++) {
if (cache[i].str1 === str1 && cache[i].str2 === str2) {
return cache[i].result; //命中缓存
}
}
num++;
var result;
//两个字符串都有值
if (str1[0] === str2[0]) {
result = str1[0] + _LCS(str1.substr(1), str2.substr(1));
} else {
// 去掉第一个字符串的首字母
var lcs1 = _LCS(str1.substr(1), str2);
// 去掉第二个字符串的首字母
var lcs2 = _LCS(str1, str2.substr(1));
if (lcs1.length > lcs2.length) {
result = lcs1;
} else {
result = lcs2;
}
}
cache.push({
str1: str1,
str2: str2,
result: result
});
return result;
}
console.log(cache);
return _LCS(str1, str2);
}