贪心算法和动态规划

贪心算法和动态规划

贪心算法

当遇到一个求解全局最优解问题时,如果可以将全局问题切分为小的局部问题,并寻求局部最优解,同时可以证明局部最优解累计的结果就是全局最优解,则可以使用贪心算法

  • 贪心算法无需回溯,效率高,但是可能不是最优解

面试题:找零问题

示例:假设你有一间小店,需要找给客户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);
}

你可能感兴趣的:(数据结构与算法)