简单算法题

142 阅读6分钟

快速排序

/*
 ** 快速排序的思想
 ** 以一个元素为目标,找到这个元素所在的正确位置
 ** 将这个元素左右一分为二(partition),继续在左右找一个元素,对应它的正确位置,直到不能再分位置
 ** partition的过程就是左边大于val 右边小于val 两个值交换
 */

function fastSort(arr) {
  sort(arr, 0, arr.length - 1);
}

function sort(arr, l, r) {
  if (l >= r) return;
  // 找到某个元素的正确位置
  let p = partition(arr, l, r);
  sort(arr, l, p - 1);
  sort(arr, p + 1, r);
}

/*   找到一个元素l,左边找到大于l,右边找到小于l 交换,直到i>j,
 **  此时j就是小于l范围的最右边元素,将l与选定元素交换,此时j就是该元素的正确位置
 */
function partition(arr, l, r) {
  let rnd = Math.floor(Math.random() * (r - l)) + l;
  wrap(arr, rnd, l);
  let i = l + 1;
  let j = r;
  while (true) {
   /*
   ** i是找到大于val的元素
   */
    while (arr[i] < arr[l] && i <= j) {
      i++;
    }
    /*
    ** j是找到小于val的元素
    */
    while (arr[j] > arr[l] && i <= j) {
      j--;
    }
    if (i >= j) break;
    swap(arr, i, j);
    i++;
    j--;
  }
  wrap(arr, j, l);
  return j;
}

function swap(arr, i, j) {
  let val = arr[i];
  arr[i] = arr[j];
  arr[j] = val;
}

双路快速排序的主要思想 O(nlogn)

partition分割过程:随机抽取一个元素,找到该元素的正确排序位置p。

利用递归,在p元素的左边和右边,重复执行partition分割过程

直到不能分割为止。

问题1:为什么要使用随机抽取index的方法,不能直接取第一个元素为基准吗?

直接取第一个元素,对于已经排好序的数组来说,复杂度会变高 O(n*n)。

问题2:如果数组中相等的元素出现的频率很高,使用双路排序法

可以将相等的元素平均分散在左右两边的数组中。

三路快速排序算法

应用场景:当数组中元素重复度较高时,将相等的元素分散在左右两路中,会造成算法的重复执行。

对于完全相等的数组,三路快速排序复杂度是O(n)

所以,三路快速排序的partition是将[l,r]分成小于V、等于V和大于V三部分。


function fastSortThree(arr) {
  sort(arr, 0, arr.length - 1);
}

function sort(arr, l, r) {
  if (l >= r) return;
  // [p,q]之间的元素是已经排好序的
  let [p, q] = partition(arr, l, r);
  sort(arr, l, p - 1);
  sort(arr, q + 1, r);
}

function partition(arr, l, r) {
  let rnd = Math.floor(Math.random() * (r - l)) + l;
  swap(arr, rnd, l);
  // [l+1,lt] 小于val
  let lt = l;
  // [gt,r] 大于val
  let gt = r + 1;
  let i = l + 1;
  while (i < gt) {
    if (arr[i] < arr[l]) {
      swap(arr, lt + 1, i);
      lt++;
      i++;
    } else if (arr[i] === arr[l]) {
      i++;
    } else if (arr[i] > arr[l]) {
      swap(arr, gt - 1, i);
      gt--;
    }
  }
  swap(arr, l, lt);
  return [lt, gt - 1];
}

function swap(arr, i, j) {
  let val = arr[i];
  arr[i] = arr[j];
  arr[j] = val;
}

经典题目:找到第K个最大值 、sort colors

滑动窗口

滑动窗口类问题都有个关键点,就是连续的最长/最短子..

例题:209 寻找和大于等于target的最短连续子数组的长度。


/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    // [l,r] 表示滑动窗口的左右位置
    let l = 0;
    let r = -1;
    let sum = 0;
    let result = nums.length + 1;
    while(l<nums.length){
    // 这里要加条件 r+1 < nums.length 否则在没有解的情况下会出现死循环,因为l一直小于nums.length
        if(sum < target && r+1 < nums.length){
            r++;
            sum += nums[r];
        } else {
            sum -= nums[l];
            l++;
        }

        if(sum >= target){
            result = Math.min(result,r-l+1);
        }

    }
    if(result === nums.length + 1) return 0;
    return result;
};

例题2:给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。


/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let arr = s.split('');
    let l = 0;
    let r = -1;
    let result = 0;
    while(l<arr.length){
        if(!isRepeat(arr.slice(l,r+2)) && r+1 < arr.length){
            r++;
        } else {
            l++;
        }
        
        if(!isRepeat(arr.slice(l,r+1))) {
            result = Math.max(result,r-l+1);
        }
    }
    return result;
};

function isRepeat(arr){
    let arr1 = Array.from(new Set(arr));
    if(arr1.length === arr.length) return false;
    return true;
}

Set Map

Set是集合 Map是映射。

Set方法:add has clear size delete

Set遍历:forEach keys values

Set是类数组数据结构,Array.from(new Set([1,2,2,5]))数组去重

Set不接受相同的数据,原理是严格等于(===),所以对于对象/数组来说,总是不相等。

Map方法:set get has size delete clear

Map的键值如果是基础类型,则只要判断键值是否相等

如果是引用类型,则判断引用类型的地址是否相等。

let m = new Map([['1',2],['2',3]]);

[...m.keys()]

[...m.values()]

[...m.entries()]

例题1 :给定两个数组 `nums1` 和 `nums2` ,返回 它们的交集 。
输出结果中的每个元素一定是 **唯一** 的。我们可以不考虑输出结果的顺序 。
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersection = function(nums1, nums2) {
    let s1 = new Set();
    let s2 = new Set();
    nums1.forEach(item=>{
        s1.add(item);
    })
    nums2.forEach(item=>{
        if(s1.has(item)){
            s2.add(item);
        }
    })

    return Array.from(s2);

};

以上题目不需要考虑重复元素出现的次数,所以不需要记录key的value值,所以可以只用Set就完成。


例题2350):需要记录重复元素的出现频率 要用Map

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersect = function(nums1, nums2) {
    let m1 = new Map();
    let res = [];
    nums1.forEach(item=>{
        if(m1.has(item)){
            let count = m1.get(item);
            m1.set(item,count+1);
        } else {
            m1.set(item,1);
        }
        
    })

    nums2.forEach(item=>{
        if(m1.get(item)>0){
            res.push(item);
            let count = m1.get(item);
            m1.set(item,count-1);
        }
    })

    return res;
};

下面这道题的重点在于,是一个无序的数组。如果是有序的数组,我们可以用双指针解决问题。

例题31):无序数组,找到和等于target的两个下标。

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

输入: nums = [3,3], target = 6
输出: [0,1]

var twoSum = function (nums, target) {
  let m1 = new Map();
  // 注意 不要在foreach中返回数据
  // 如果是重复key 后面的会覆盖前面的 用一个循环简单的解决问题
  nums.forEach((item, index) => {
    if (m1.has(item)) {
      res = [m1.get(item), index];
    } else {
      let a = target - item;
      m1.set(a, index);
    }
  });

  return res;
};

15.3sum为0--- 双指针和一层循环
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。


/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    // 先对数组进行排序
    nums.sort((a,b)=>a-b);
    // 防止结果数组有重复 [0,0,0,0] [[0,0,0],[0,0,0]]是错误的
    let res = new Set();
    // 一个for循环 双指针
    for(let i=0;i<nums.length;i++){
        let left = i + 1;
        let right = nums.length - 1;
        // 剪枝
        if(right - i < 2) break;
        while(left<right){
            if(nums[i]+nums[left]+nums[right]>0){
                right--;
            } else if(nums[i]+nums[left]+nums[right]<0){
                left++;
            } else {
                res.add([nums[i],nums[left],nums[right]].join('/'));
                // 可能还会存在其他可能性 必须要移动指针 不移动的话就会产生相同的解
                left++;
                right--;
            }
        }
        
    }

    let result = [];
    res.forEach(item=>{
        result.push(item.split('/'));
    })

    return result;
};
#### [16. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest/)


输入: nums = [-1,2,1,-4], target = 1
输出: 2
解释: 与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var threeSumClosest = function(nums, target) {
    nums.sort((a,b)=>a-b);
    let res = nums[0]+nums[1]+nums[nums.length-1];
    for(let i=0;i<nums.length;i++){
        let left = i+ 1;
        let right = nums.length - 1;
        if(right - i < 2) break;
        while(left<right){
            let sum = nums[i]+nums[left]+nums[right];
            if(Math.abs(sum -target) < Math.abs(res -target) ){
                res = sum;
            } 
            if(sum > target ){
                right--;
            } else if(sum < target) {
                left++;
            } else {
                res = sum;
                break;
            }
        }
    }

    return res;
};

链表


问题1206--反转链表

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    // 使用三个指针帮助我们记住数据
    let prev = null;
    let cur = head;
    let next;
    while(cur !== null){
        // next指针后移问题放在这里 是为了避免取不到next而报错
        next = cur.next;
        // 这一步是真正的反转操作
        cur.next = prev;
        // 接下来是指针后移操作
        prev = cur;
        cur = next;
    }

    return prev;
};

问题2206--删除链表中节点,需要删除的节点只有一个

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var deleteNode = function(head, val) {
    let dummyHead = new ListNode(-1);
    dummyHead.next = head;
    let pre = dummyHead;
    let cur = head;
    while(cur.val !== val){
        pre = cur;
        cur = cur.next;  
    }
    
    pre.next = cur.next;
    return dummyHead.next;

};

问题3203--删除链表中所有等于val的节点

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function(head, val) {

    let dummyHead = new ListNode(-1);
    dummyHead.next = head;
    let pre = dummyHead;
    let cur = head;
    while(cur !== null){
        if(cur.val === val){
            pre.next = cur.next;
            cur = cur.next;
        } else {
            pre = cur;
            cur = cur.next;
        }
    }
    
    return dummyHead.next;

};

问题424. 两两交换链表中的节点

输入: head = [1,2,3,4]
输出: [2,1,4,3]

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    let dummyHead = new ListNode(null);
    dummyHead.next = head;
    let prev = dummyHead;
    while(prev.next && prev.next.next){
        
        let node1 = prev.next;
        let node2 = prev.next.next;
        let next = node2.next;
        

        prev.next = node2;
        node2.next = node1;
        node1.next = next;
        
        prev = node1;
    }

    return dummyHead.next;
};

问题5237. 删除链表中的节点(不知道head节点)

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
    let next = node.next;
    node.val = next.val;
    node.next = next.next;
};

问题619. 删除链表的倒数第 N 个结点

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let dummyHead = new ListNode(0);
    dummyHead.next = head;
    let cur = head;
    let count = 1;
    while(cur.next !== null){
        count++;
        cur = cur.next;
    }

    delNode();
    return dummyHead.next;

    function delNode(){
        
        let prev = dummyHead;
        let cur = head;
        for(let i=0;i<count-n;i++){
            console.log(prev.val,cur.val)
            prev = cur;
            cur = cur.next;
        }
        prev.next = cur.next;
    }

    
};

第二种解法:只遍历一遍链表,双指针

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let dummyHead = new ListNode(0);
    dummyHead.next = head;
    let p = dummyHead;
    let q = dummyHead;
    for(let i=0;i<=n;i++){
        q = q.next;
    }
    while(q !== null){
        p = p.next;
        q = q.next;
    }
    p.next = p.next.next;

    return dummyHead.next;
};



队列(解决广度优先遍历)

给你二叉树的根节点 `root` ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

输入: root = [3,9,20,null,null,15,7]
输出: [[3],[9,20],[15,7]]

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
    let res = [];
    // 存放每层节点 一次存放一层的节点
    let q = [];
    if(root === null){
        return res;
    }

    q.push(root);
    while(q.length!==0){
        res.push([]);
        let currLevelSize = q.length;
        for(let i=0;i<currLevelSize;i++){
            // 由于队列先进先出的结构 才能从左到右打印每层树节点
            let node = q.shift();
            res[res.length-1].push(node.val);
            node.left && q.push(node.left);
            node.right && q.push(node.right);
        }
    }

    return res;

};

二叉树和递归


 [104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/);
 
 /**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if(root === null) return 0;
    let leftDepth = maxDepth(root.left);
    let rightDepth = maxDepth(root.right);
    return Math.max(leftDepth,rightDepth) + 1; 
};
 [111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/)
 
 
 /**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {
    if(root === null) return 0;
    let leftDepth = minDepth(root.left);
    let rightDepth = minDepth(root.right);
    if(root.left === null && root.right === null){
        return 1;
    }
    if(root.left === null){
        return rightDepth + 1;
    }
    if(root.right === null){
        return leftDepth + 1;
    }
    
    return Math.min(leftDepth,rightDepth) + 1;
};

#### [226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)

给你一棵二叉树的根节点 `root` ,翻转这棵二叉树,并返回其根节点。

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if(root === null) return root;
    let left = invertTree(root.left);
    let right = invertTree(root.right);
    root.left = right;
    root.right = left;
    return root;
};

# 100 给你两棵二叉树的根节点 `p` 和 `q` ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。


/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {boolean}
 */
var isSameTree = function(p, q) {
    if(p === null && q === null) return true;
    if(p === null || q === null || p.val !== q.val) return false;
    if(p.val === q.val) {
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right); 
    }
    
};
# 101 #### [101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/)

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function(root) {
    if(root === null) return true;

    return loop(root.left,root.right);
};


function loop(left,right){
    if(left === null && right === null) return true;
    if(left === null || right === null) return false;

    if(left.val === right.val) {
        return loop(left.left,right.right) && loop(left.right,right.left) ;
    } else {
        return false;
    }
    
}
# 222 #### [222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/)

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var countNodes = function(root) {
    if(root === null) return 0;
    return loop(root);
};

function loop(root){
    let count = 0;
    if(!root) return 0;
    if(root) count += 1;
    if(root.left){
        count += loop(root.left);
    }
    if(root.right){
        count += loop(root.right);
    }
    return count;
}
# 110

递归和回溯

树形结构解决排列问题


17 #### [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/)

输入: digits = "23"
输出: ["ad","ae","af","bd","be","bf","cd","ce","cf"]


/**
 * @param {string} digits
 * @return {string[]}
 */
var letterCombinations = function(digits) {
    if(!digits) return [];
    let res = [];
    let s = '';
    let m = new Map([['2','abc'],['3','def'],['4','ghi'],['5','jkl'],['6','mno'],['7','pqrs'],['8','tuv'],['9','wxyz']]);
    loop(digits,0,s)
    // 计算第index位的digits,所计算得到的字符串
    function loop(digits,index,s){
        if(index === digits.length) {
            res.push(s);
            return;
        }
        m.get(digits[index]).split('').forEach(item=>{
            loop(digits,index+1,s+item);
        })
    }

    return res;
};


46,47

树形结构解决组合问题


#### [77. 组合](https://leetcode.cn/problems/combinations/)
给定两个整数 `n` 和 `k`,返回范围 `[1, n]` 中所有可能的 `k` 个数的组合。

你可以按 **任何顺序** 返回答案。


/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k) {
    if(n <=0 || k <= 0 || k>n) return [];
    let res = [];
    loop(n,k,1,[]);

    function loop(n,k,start,p){
        // 剪支
        if(k - p.length > n - start + 1 ) return;
        if(p.length === k){
            res.push(p.slice());
            return;
        }

        for(let i=start;i<=n;i++){
            p.push(i);
            loop(n,k,i+1,p);
            // 回溯算法
            p.pop();
        }
    }
    return res;
};

动态规划

1.斐波那契数列

方法一:动态规划(自上至下)

/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
    let memo = new Array(n+1);
    memo.fill(-1);
    memo[0] = 0;
    memo[1] = 1;
    for(let i=2;i<=n;i++){
        memo[i] = (memo[i-1]+memo[i-2])%1000000007;
    }
    return memo[n];
};


方法二:记忆化搜索法(递归+记忆化搜索)

/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
    let memo = new Array(n+1);
    memo.fill(-1);
    return loop(n);

    
    function loop(n){
        if(n===0) return 0;
        if(n===1) return 1;
        if(memo[n]!==-1){
            return memo[n];
        } else {
            memo[n] = (loop(n-1)+loop(n-2))%1000000007;
            return memo[n];
        }
    }
};
#### [120. 三角形最小路径和](https://leetcode.cn/problems/triangle/)

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
   2
  3 4
 6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

方法1:递归和搜索记忆法

/**
 * @param {number[][]} triangle
 * @return {number}
 */
var minimumTotal = function(triangle) {
    let memo = [];
    for(let i=0;i<triangle.length;i++){
        memo[i] = new Array(triangle[i].length);
        memo[i].fill(undefined);
    }
    return f(0,0);
    // f(i,j)表示triangle[0][0]到triangle[i][j]的最小距离
    function f(i,j){
        if(i === triangle.length) return 0;
        if(memo[i][j] !== undefined){
            return memo[i][j];
        } else {
            memo[i][j] = Math.min(f(i+1,j),f(i+1,j+1)) + triangle[i][j];
            return memo[i][j];
        }
        
    }
};


方法2:动态规划

/**
 * @param {number[][]} triangle
 * @return {number}
 */
var minimumTotal = function(triangle) {
    let n = triangle.length - 1;
    let memo = [];
    for(let i=0;i<triangle.length;i++){
        memo[i] = new Array(triangle[i].length);
        memo[i].fill(undefined);
    }
    memo[n] = triangle[n];
    for(let i=n-1;i>=0;i--){
        for(let j=0;j<triangle[i].length;j++){
            memo[i][j] = Math.min(memo[i+1][j],memo[i+1][j+1]) + triangle[i][j];
            
        }
    }

    return memo[0,0];
};
#### [64. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/)

输入: grid = [[1,3,1],[1,5,1],[4,2,1]]
输出: 7
解释: 因为路径 13111 的总和最小。


/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    let m = grid.length;
    let n = grid[0].length;
    let memo = new Array(m);
    for(let i=0;i<m;i++){
        memo[i] = (new Array(n)).fill(undefined);
    }
    memo[0][0] = grid[0][0];
    return f(m-1,n-1);

    function f(i,j){
        if(i<0 || j<0) return 0;
        if(i===0 && j===0) return grid[0][0];
        if(i===0) return f(0,j-1) + grid[0][j];
        if(j===0) return f(i-1,0) + grid[i][0];
        if(memo[i][j] !==undefined){
            return memo[i][j];
        } else {
            memo[i][j] = Math.min(f(i-1,j),f(i,j-1)) + grid[i][j];
            return memo[i][j];
        }
        
    }
    
};
#### [343. 整数拆分](https://leetcode.cn/problems/integer-break/)

方法1:递归和搜索记忆法

/**
 * @param {number} n
 * @return {number}
 */
var integerBreak = function(n) {
    // 需要计算f(n) 所以要创建n+1个数
    let memo = new Array(n+1);
    memo.fill(-1);
    console.log(memo)

    return f(n);
    // f(n)将n至少分成2部分 返回每个部分的乘积的最大值
    function f(n){
        if(n === 1) return 1;
        if(memo[n]!==-1){
            return memo[n];
        } else {
            let max = -1;
            for(let i=1;i<=n-1;i++){
                // (n-i)*i表示不再分割 也是一种答案
                max = max3(max,(n-i)*i,f(n-i)*i);
               
            }
            memo[n] = max;
            return memo[n];
            
        }
    }

    function max3(a,b,c){
        return Math.max(Math.max(a,b),c);
    }
};



方法2:动态规划 

/**
 * @param {number} n
 * @return {number}
 */
var integerBreak = function(n) {
    // 需要计算f(n) 所以要创建n+1个数
    let memo = new Array(n+1);
    memo.fill(-1);

    memo[1] = 1;
    for(let i=2;i<=n;i++){
        let res = -1;
        for(let j=1;j<=i-1;j++){
            res = max3(j*(i-j),memo[i-j]*j,res)
        }
        memo[i] = res;
    }

    return memo[n];

    

    function max3(a,b,c){
        return Math.max(Math.max(a,b),c);
    }
};