日积跬步之每日一算法

578 阅读14分钟

个人算法学习打卡笔记

递归

1. 十进制转其他进制

/**
 * @description: 进制转换
 * @param {number} n 十进制数
 * @param {number} d 进制
 * @return {string}
 */
function otherByDecimal_1(n, d, s = [], tpl = '0123456789abcdef') {
    if (n === 0) return;
    otherByDecimal_1(Math.trunc(n / d), d, s);
    s.push(tpl.charAt(n % d));
    return s.join('');
}

function otherByDecimal_2(n, d, s = '', tpl = '0123456789abcdef') {
    if (n === 0) return s;
    s = tpl.charAt(n % d) + s;
    return otherByDecimal_2(Math.trunc(n / d), d, s);
}

console.log(otherByDecimal_1(31, 16);
console.log(otherByDecimal_2(31, 16);

分治法

1. 求pow(x, y),即x的y次方 T(n) = O(logn)

function pow(x, y) {

    function f(half) {
        if (half === 0) return 1;
        if (half === 1) return x;
        if (half === -1) return 1 / x;
        return f(half / 2) * f(half / 2);
    }

    // 处理奇数项
    if (y >= 0) {
        return y % 2 === 1 ? f(y - 1) * x : f(y);
    } else {
        return y % 2 === -1 ? f(y + 1) * (1 / x) : f(y);
    }

}
console.log(pow(9, -9), Math.pow(9, -9))

2.求fibonacci数列

2.1 普通

超时

function fibonacci(n) {
    if (n <= 0) return 0
    if (n <= 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
2.2 记忆法

会爆栈

function cacheFibonacci(n) {
    const memo = [0, 1, 1];
    function fibonacci(n) {
        if (n < 0) return 0;
        if (memo[n] !== undefined) return memo[n];

        return memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
    }
    return fibonacci(n);
}
2.3 尾调优化
function fibonacci(n, ac1 = 1, ac2 = 1) {
    if (n <= 2) return ac2;
    return fibonacci(n-1, ac2, ac1 + ac2);
}
2.4 普通方法
function fibonacci(n){
    let n1 = 1, n2 = 1, n3;
    for(let i=n; i>2; i--){
        n3 = n1
        n1 = n2;
        n2 = n2 + n3;
    }
    return n2;
}

基于栈的快速用数组构建栈思想

1.对对碰

function pair(str) {
    const stack = [];
    const pairArr = {
        '{': "}",
        '[': ']',
        '(': ')'
    };
    for(let i of str){
        const peek = stack[stack.length - 1];
        if(pairArr[peek] === i){
            stack.pop()
        }else{
            stack.push(i)
        }
    }
    return stack.length === 0;
}
console.log(pair('[(()]'), pair('([][])'))// false true

2.十进制转二进制

function decimalToBinary(decimal){
    const stack = [];
    while(decimal > 0){
        stack.unshift(decimal % 2);
        decimal = Math.floor(decimal / 2);
    }
    return stack.join('')
}

console.log(decimalToBinary(3)) // 11

3.气温问题

栈顶递减法

根据每日气温列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

function dailyTemperatures(T){
    const stack = [], result = new Array(T.length).fill(0);
    for(let [key, val] of T.entries()){
        const len = stack.length;
        while(stack.length>0 && val > stack[stack.length-1].val){
            const cur = stack.pop();
            result[cur.key] = key-cur.key;
        }
        stack.push({key, val});
    }
    return result;
}

队列

用栈实现队列

class Queue{
    constructor(){
        this.stack1 = [];
        this.stack2 = [];
    }

    push(val){
        this.stack1.push(val);
    }

    pop(){
        while(this.stack2.length <= 0){
            while(this.stack1.length){
                this.stack2.push(this.stack1.pop());
            }
        }
        return this.stack2.pop()
    }

    peek(){
        while(this.stack2.length <= 0){
            while(this.stack1.length){
                this.stack2.push(this.stack1.pop());
            }
        }
        const len = this.stack2.length;
        return len ? this.stack2[len - 1] : null;
    }

    empty(){
        return !this.stack1.length && !this.stack2.length;
    }
}

双端队列滑窗问题

function slideWin(nums, k){
    const len = nums.length;
    const res = [];
    for(let i=0; i<len - 1; i++){
        const last = i+k;
        if(last > len) break;
        const max = Math.max.apply(null, nums.slice(i, last));
        res.push(max);
    }
    return res;
}


function slideWinDoubleQueue(nums, k){
    const len = nums.length,
    res = [], dequeue = [];
    for(let i = 0;i<len;i++){
        let dequeueLen =  dequeue.length;
        while(dequeueLen > 0 && nums[dequeue[dequeueLen - 1]] < nums[i]){
            dequeue.pop();//维持递减队列
            dequeueLen--;
        }
        dequeue.push(i); dequeueLen++;
        while(dequeueLen && dequeue[0] <= i - k){
            dequeue.shift();//去除队列首部不在滑块区间的值
            dequeueLen--;
        }
        if(i >= k-1){ //让第一次滑动到达要求距离
            res.push(nums[dequeue[0]]);
        }
    }
    return res;
}

链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。

输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4

function merge(l1, l2){
    const head = new ListNode();
    let cur = head;

    while(l1 && l2){
        if(l1.val > l2.val){
            cur.next = l2;
            l2 = l2.next;
        }else{
            cur.next = l1;
            l1 = l1.next;
        }
        cur = cur.next;
    }
    // 剩下的直接衔接在后面
    cur.next = l1 === null ? l2 : l1;
    return head.next;// 首节点值为undefined
}

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1: 输入: 1->1->2 输出: 1->2 示例 2: 输入: 1->1->2->3->3 输出: 1->2->3

function delRepeat(head){
    let cur = head;
    while(cur != null && cur.next != null){
        if(cur.val === cur.next.val){
            cur.next = cur.next.next;
        }else{
            cur = cur.next;
        }
    }
    return head;
}

虚拟指针

给定一个排序链表,删除所有含有重复数字的结点,只保留原始链表中 没有重复出现的数字。

示例 1: 输入: 1->2->3->3->4->4->5 输出: 1->2->5 示例 2: 输入: 1->1->1->2->3 输出: 2->3

function delDup(head){
    if(!head || !head.next) return head;
    const dummy = new ListNode();
    // 前面增加一个虚拟节点
    dummy.next = head;
    let cur = dummy;
    while(cur.next && cur.next.next){
        if(cur.next.val === cur.next.next.val){
            let val = cur.next.val;
            while(cur.next && cur.next.val === val){
                cur.next = cur.next.next;
                //无限循环删除重复的
            }
        }else{
            cur = cur.next;
        }
    }
    return dummy.next;
} 

快慢指针

给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个结点后,链表变为 1->2->3->5. 说明: 给定的 n 保证是有效的。

function removeNthFromEnd(head, n){
    const dummy = new ListNode();
    dummy.next = head;
    let slow = fast = dummy;
    while(n > 0){
        fast = fast.next;
        n--;
    }
    while(fast.next){
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return dummy.next;
}

三指针翻转链表

function reverseList(head){
    let pre = null;
    let cur = head;
    while(cur !== null){
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}

局部翻转链表输入: 1->2->3->4->5->NULL, m = 2, n = 4 输出: 1->4->3->2->5->NULL

function reverseRange(head, m, n){
    let pre,cur,leftHead;
    const dummy = new ListNode();
    dummy.next = head;
    let p = dummy;
    for(let i=0;i<m-1;i++){
        p=p.next;
    }
    leftHead = p;//储存前面的衔接点
    let start = leftHead.next;//开始翻转点
    pre = start;
    cur = pre.next;
    for(let i=m;i<n;i++){
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur=next;
    }
    leftHead.next = pre;// 衔接前面
    start.next = cur;//衔接后面
    return dummy.next;
}

环形链表

判断一个链表中是否有环

function isCycle(head){
    while(head){
        if(head.flag){
            return true;
        }else{
            head.flag = true;
            head = head.next;
        }
    }
    return false;
}

深度优先搜索

穷举其实就是数的先序遍历

function xxx(入参) {
  前期的变量定义、缓存等准备工作 
  
  // 定义路径栈
  const path = []
  
  // 进入 dfs
  dfs(起点) 
  
  // 定义 dfs
  dfs(递归参数) {
    if(到达了递归边界) {
      结合题意处理边界逻辑,往往和 path 内容有关
      return   
    }
    
    // 注意这里也可能不是 for,视题意决定
    for(遍历坑位的可选值) {
      path.push(当前选中值)
      处理坑位本身的相关逻辑
      path.pop()
    }
  }
}

1. 先序遍历

常规递归

function pre(root){
    const res = [];
    function preOrderTraversal(root){
        if(!root) return;
        res.push(root.val);
        if(root.left) preOrderTraversal(root.left);
        if(root.right) preOrderTraversal(root.right);
    }
    preOrderTraversal(root);
    return res;
}

栈方法

function preOrderTraversal(root){
    const res = [];
    if(!root){
        return res;
    }
    const stack = [root];
    while(stack.length){
        const cur = stack.pop();
        res.push(cur.val);
        if(cur.right) stack.push(cur.right);
        if(cur.left) stack.push(cur.left);
    }
    return res;
}

2. 中序遍历

常规打印

function mid(root){
    const res = [];
    function midOrderTraversal(root){
        if(!root) return;
        if(root.left) midOrderTraversal(root.left);
        res.push(root.val);
        if(root.right) midOrderTraversal(root.right);
    }
    midOrderTraversal(root);
    return res;
}

栈方式

function midOrderTraversal(root){
    const res = [],  stack = [];
    let cur = root;
    while(stack.length || cur){
        while(cur){
            stack.push(cur);
            cur = cur.left;
        }
        cur = stack.pop();
        res.push(cur.val);
        cur = cur.right;
    }
    return res;
}

3. 后序遍历

常规打印

function after(root){
    const res = [];
    function afterOrderTraversal(root){
        if(!root) return;
        if(root.left) afterOrderTraversal(root.left);
        if(root.right) afterOrderTraversal(root.right);
        res.push(root.val);
    }
    afterOrderTraversal(root);
    return res;
}

栈方法

function afterOrderTraversal(root){
    const res = [];
    if(!root) return res;
    const stack = [root];
    while(stack.length){
        const cur = stack.pop();
        res.unshift(cur.val);
        if(cur.left) stack.push(cur.left);
        if(cur.right) stack.push(cur.right);
    }
    return res;
}

4. 全排列问题

题目描述:给定一个没有重复数字的序列,返回其所有可能的全排列。示例:
输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

const permute = function(nums){
    const len = nums.length;
    const curr = [];
    const res = [];
    const visited = {};
     
    function dfs(nth){
        if(nth === len){
            res.push(curr.slice());
            return;
        }
        for(let i=0; i<len; i++){
            if(!visited[nums[i]]){
                visited[nums[i]] = 1;
                curr.push(nums[i]);
                dfs(nth + 1);
                curr.pop();
                visited[nums[i]] = 0;
            }
        }
    }

    dfs(0);
    return res;
}
console.log(permute([1,2,3]))

5. 全结果

题目描述:给定一组不含重复元素的整数数组nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]

const subsets = function(nums){
    const res = [], len = nums.length, subset = [];
    dfs(0);
    function dfs(index){
        res.push(subset.slice());
        for(let i=index;i<len;i++){
            subset.push(nums[i]);
            dfs(i+1);
            subset.pop();
        }
    }
    return res;
}
console.log(subsets([1,2,3]))

6.限定组合问题

题目描述:给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]

const combine = function(n, k){
    const res = [], subset = [];
    dfs(0);
    function dfs(index){
        if(subset.length === k){
            res.push(subset.slice());
            return;
        }
        for(let i=index;i<n;i++){
            subset.push(i);
            dfs(i+1);
            subset.pop();
        }
    }
    return res;
}
console.log(combine(4, 2))

6. 层序遍历问题

  1. 示例: 二叉树:[3,9,20,null,null,15,7],返回其层次遍历结果: [ [3], [9,20], [15,7] ]
function levelOrder(root){
    const res = [];
    if(!root) return res;
    const queue = [root];
    while(queue.length){
        const len = queue.length;//记录当前这层的长度
        const cur = [];
        for(let i=0; i<len; i++){//直接一次性把该层消耗光
            const head = queue.shift();
            cur.push(head.val);
            if(head.left) queue.push(head.left);
            if(head.right) queue.push(head.right);
        }
        res.push(cur);
    }
    return res;
}

7. 翻转二叉树

const invertTree = function(root){
    if(!root) return root;
    const {left, right} = root;
    if(left){
        root.right = left;
        invertTree(left);
    }

    if(right){
        root.left = right;
        invertTree(right);
    }
}

广度优先搜索

1.树的层序遍历

// 广度优先搜素
function BFS(root){
    const queue = [];
    // 根节点入队
    queue.push(root);
    // 当队列长度存在
    while(queue.length){
        const top = queue[0];
        console.log(top.val);
        // 左子树存在入队
        if(top.left){
            queue.push(top.left);
        }
        // 右子树存在入队
        if(top.right){
            queue.push(top.right);
        }
        //当前层出队
        queue.shift();
    }
}

回溯算法

回溯采用试错的方法解决问题。一旦发现当前步骤失败,回溯方法就返回上一个步骤,选择另一种方案继续试错。

全排列问题

function allOrder(arr, cur = [], res = []){
    const len = arr.length;
    if(len === 0) {
        return res.push(cur.slice())
    };
    for(let i=0; i< len; i++){
        allOrder([...arr.slice(0,i), ...arr.slice(i+1)], [...cur, arr[i]], res);
    }
    return res;
}

console.log(allOrder(['红', '黑', '黄']));

限定组合

function limitCombine(arr, n, cur = [], res = []){
    if(cur.length === n){
        res.push(cur.slice());
        return;
    }
    const len = arr.length;
    for(let i=0; i<len; i++){
        limitCombine([...arr.slice(0, i), ...arr.slice(i+1)], n, [...cur, arr[i]], res);
    }
    return res;
}

console.log(limitCombine(['红', '黑', '黄'], 2));

二维数组棋牌相关

1. 棋牌中是否有某个连续字母的单词
function helper(board, current, row, column) {
    if (current.length === 0) return true;
    // 确立边界
    if (row >= 0 && row < board.length && column >= 0 && column < board[0].length) {
        if (board[row][column] === current[0]) {
            board[row][column] = ''; //标记为已走过
            // 检查上边界有没有下一个字母
            if (helper(board, current.slice(1), row - 1, column)) return true;
            // 检查下边界有没有下一个字母
            if (helper(board, current.slice(1), row + 1, column)) return true;
            // 检查左边界有没有下一个字母
            if (helper(board, current.slice(1), row, column - 1)) return true;
            // 检查右边界有没有下一个字母
            if (helper(board, current.slice(1), row, column + 1)) return true;
            board[row][column] = current[0]; //复原标记已读的
        }
    }
    return false;
}

function searchWord(board, word) {
    const current = word.split(''),
        rowLen = board.length,
        columnLen = board[0].length;
    for (let i = 0; i < rowLen; i++) {
        for (let j = 0; j < columnLen; j++) {
            if (helper(board, current, i, j)) {
                return true;
            }
        }
    }
    return false;
}

const board = [
    ['a', 'c', 'r', 'y', 'l'],
    ['l', 'w', 'o', 'r', 'i'],
    ['a', 'f', 'd', 'l', 'c'],
    ['k', 'e', 'e', 'w', 'e'],
    ['o', 'd', 'r', 'o', 's']
]
console.log(searchWord(board, 'world'));
2.n皇后问题
// 传入记录的点 行 和 n皇后
function helper(columnPos, rowIndex, n) {
    if(rowIndex === n){
        console.log(columnPos)
        print(columnPos, n);
        return;
    }
    for(let column=0;column<n; column++){
        columnPos[rowIndex] = column;//当前行的第几个
        if(isVaild(columnPos, rowIndex)){
            helper(columnPos, rowIndex + 1, n);
        }
    }
}

function isVaild(columnPos, rowIndex){
    for(let row=0; row<rowIndex; row++){//循环行
        if(columnPos[row] === columnPos[rowIndex]) return false;//在同一列
        // 在同一对角线
        if(Math.abs(columnPos[row] - columnPos[rowIndex]) === rowIndex - row) return false;
    }
    return true;
}

function print(columnPos, n){
    for(let i=0; i<n; i++){
        const column = columnPos[i];
        const arr = new Array(n).fill('口');
        arr.splice(column, 1, '皇');
        console.log(arr.join(''));
    }
    console.log('\n')
}
helper([], 0, 8)
3.数独游戏
class Sudoku{
    constructor(board){
        this.board = board;
        this.n = board.length;
        this.len = Math.pow(this.maxRow, 2);
    }

    /**
     * @description: 
     * @param {number} index 第几个格子
     * @return: 
     */
    helper(row=0, column=0){
        if(row>= this.n-1 && column>=this.n-1){
            this.board.forEach(e => {
                console.log(e.join(' '))
            });
            console.log('\n');
            return;
        }
        if(!this.board[row][column]){
            for(let i=1; i<10; i++){
                if(this.isVaild(row, column, i)){
                    this.board[row][column] = i;
                    this.helper(...this.getRowColumn(row, column+1));
                    this.board[row][column] = '';
                }
            }
        }else{
            this.helper(...this.getRowColumn(row, column+1));
        }
    }

    getRowColumn(row, column){
        if(column >= this.n){
            return [row+1, 0]
        }
        return [row, column]
    }

    isVaild(row, column, i){
        for(let j=0; j<this.n; j++){
            if(j === column) continue;
            if(this.board[row][j] === i) return false;
        }
        
        for(let k=0; k<this.n; k++){
            if(k === row) continue;
            if(this.board[k][column] === i) return false;
        }

        let startRow, startColumn, endRow, endColumn;
        if(row%3 === 0){
            startRow = row;
            endRow = row + 2;
        }else if(row%3 === 1){
            startRow = row - 1;
            endRow = row + 1;
        }else{
            startRow = row - 2;
            endRow = row;
        }

        if(column%3 === 0){
            startColumn = column;
            endColumn = column + 2;
        }else if(column%3 === 1){
            startColumn = column - 1;
            endColumn = column + 1;
        }else{
            startColumn = column - 2;
            endColumn = column;
        }

        for(let j=startRow; j<=endRow; j++){
            for(let k=startColumn; k<=endColumn; k++){
                if(j === row && k === column) continue;
                if(this.board[j][k] === i) return false; 
            }
        }
        return true;
        
    }
}
const board = [
    [4, 1, 0, 0, 0, 7, 8, 5, 0],
    [8, 0, 6, 0, 0, 0, 0, 0, 9],
    [0, 2, 0, 0, 9, 0, 6, 0, 0],

    [0, 0, 4, 0, 0, 0, 0, 1, 2],
    [2, 0, 0, 5, 8, 0, 0, 7, 0],
    [0, 0, 0, 0, 0, 0, 5, 0, 0],

    [0, 0, 0, 7, 0, 2, 0, 0, 0],
    [0, 0, 8, 0, 1, 0, 0, 0, 0],
    [0, 7, 0, 0, 6, 0, 0, 0, 0],
]
const sudoku = new Sudoku(board);
sudoku.helper()

贪心算法

硬币找零问题

求出找零需要最少硬币的最优解

function getSmallestConis(money){
    const coins = [0.05, 0.1, 0.2, 0.5, 1, 2];
    const counts= [20, 15, 10, 5, 2, 1];
    const total = coins.reduce((total, coin, idx) => {
        return total += coin * counts[idx];
    }, 0)
    if(money > total) return '太大,找不开';
    let idx = coins.length - 1;
    while(idx >= 0 || money >= coins[0]){
        let n = Math.trunc(money/coins[idx]);  
        if(n>0){
            n = n <= counts[idx] ? n : counts[idx];
            console.log(`${coins[idx]}元硬币${n}枚`);
            money = sub(money, n * coins[idx]);
        }  
        idx--;
    }
    console.log(`最终剩余${money}元钱没法找`)
}
// 减法防止精度溢出
function sub(num1, num2){
    const n1 = String(num1).split('.')[1] || '';
    const n2 = String(num2).split('.')[1] || '';
    let n = Math.max(n1.length, n2.length);
    const level = Math.pow(10, n);
    return ((num1*level - num2*level)/level).toFixed(n1.length>n2.length ? n1.length : n2.length);
}
getSmallestConis(2.5512);

活动安排问题

给出每个会议的开始和结束时间,求当天安排最多的会议的最优解

function bubble(startTimeList, endTimeList) {
    //对结束时间进行排序
    const len = endTimeList.length;
    for (let i = 0; i < len; i++) {
        for (let j = 0; j < len - i - 1; j++) {
            if (endTimeList[j] > endTimeList[j+1]) {
                [endTimeList[j], endTimeList[j+1]] = [endTimeList[j+1], endTimeList[j]];
                [startTimeList[j], startTimeList[j+1]] = [startTimeList[j+1], startTimeList[j]];
            }
        }
    }
    return {
        startTimeList,
        endTimeList
    };
}

function greedy(startTimeList, endTimeList) {
    bubble(startTimeList, endTimeList); //进行排序
    const len = startTimeList.length;
    const tag = [true]; //第一个时间最早的活动加入进去
    let curActivityIndex = 0;
    for (let i = 1; i < len; i++) {
        if (endTimeList[curActivityIndex] <= startTimeList[i]) {
            tag.push(true);
            curActivityIndex = i;
        } else {
            tag.push(false);
        }
    }

    //打印结果
    tag.forEach((isChoose, idx) => {
        if (isChoose) {
            console.log([startTimeList[idx], endTimeList[idx]])
        }
    })

}

// 每个会议的开始时间
const startTimeList = [0, 2, 4, 6, 1, 1, 1, 3, 5, 5, 5];
// 每个会议的结束时间
const endTimeList =   [2, 4, 6, 8, 3, 3, 3, 5, 7, 7, 7];
greedy(startTimeList, endTimeList);

动态规划

工人挖金矿问题

找出某个矿必须挖的情况下最大可获取的黄金数

const personNum = 10;
//每个矿的黄金储量
const gold = [400, 500, 200, 300, 350];
//每个矿所需要的工人
const goldPerson = [5, 5, 3, 4, 3];

function getResult(personNum, gold, goldPerson){
    let res = [], 
    pre = [],
    n = gold.length; //金矿数
    for(let i=0; i<personNum; i++){//初始化
        if(i>=goldPerson[0]){//人数大于等于第一个金矿所需人数
            pre[i] = gold[0];//获得第一个矿金子
        }else{
            pre[i] = 0;
        }
    }

    for(let i=0; i<n; i++){//循环金矿
        for(let j=0; j<personNum; j++){//循环矿工
            if(j<goldPerson[i]){//矿工少于当前金矿所需人数则采矿数为上一次的当前最优
                res[j] = pre[j];
            }else{
                // 要么这个矿不采用上次当前人数最优解
                // 要么当前人数-当前所需最少的人 来获取上次最优解 并且加上这次的矿
                // 两个获取最大值
                res[j] = Math.max(pre[j], pre[j-goldPerson[i]] + gold[i]);
            }
        }
        pre = res;
    }
    return res;
}
console.log(getResult(personNum, gold.slice(0,1), goldPerson.slice(0,1)))

背包装石头问题

某个背包容量最大装石头量

const stoneWeight = [0,2,3,4,5,6];
const stoneValue = [0,3,4,5,6,7];
const bagCapacity = 10;

// 得到最大值
const getTable = function(stoneWeight, stoneValue, bagCapacity){
    const res = [new Array(bagCapacity+1).fill(0)];
    const stoneNum = stoneWeight.length;
    for(let stone = 1; stone<stoneNum; stone++){//循环石头编号
        const cur = [];
        const pre = res[res.length - 1]; //结果最后一项作为上次
        const w = stoneWeight[stone];//当前石头重量
        const v = stoneValue[stone];//当前石头的价值
        for(let bag=0; bag<=bagCapacity; bag++){//循环背包容量
            if(bag<w){//若背包容量小于当前石头所需容量则装不了,只能取上次
                cur[bag] = pre[bag];
            }else{//要么装现在的石头 要么不装
                //不装当前 和装当前价值对比(装当前剩余容量可以装上次剩余容量的最优值)
                cur[bag] = Math.max(pre[bag], v + pre[bag-w]);
            }
        }
        res.push(cur.slice());
    }
    return res;
}

const table = getTable(stoneWeight, stoneValue, bagCapacity);

function print(table, stoneWeight, stoneValue){
    let curRow = table.length -1;//记录当前行
    let curColumn = table[0].length - 1;//记录当前列
    let curVal = table[curRow][curColumn];//记录当前剩余值
    while(curRow){
        let preRow = table[curRow -1];//上一行
        if(curVal === preRow[[curColumn]]){//当前行和上一行相等则肯定没装当前编号石头
            console.log(`背包没有装${curRow}号石头`);
            curRow--;
        }else{//不想等则装了 然后减去石头的值 查找上一行的价值
            console.log(`背包装有${curRow}号石头,石头重量为${stoneWeight[curRow]},石头价值为${stoneValue[curRow]}`);
            curVal -= stoneValue[curRow];
            const curColumn = preRow.find(e => e === curVal);
            curRow--;
        }
    }
}
print(table, stoneWeight, stoneValue);

公共子序列

s1='abcd'和s2='becd'两个字符中同时出现的字符且字符的先后顺序一样,则为公共子序列。比如'bcd',其中最长的公共子序列被叫做最长公共子序列

假如字符串

A='a0,a1,a2...am';

B='b0,b1,b2...bn';

最长公共子序列为

Z='z0,z1,z2...zk';

如果am=bn=zk则可以推导出'a0,a1,a2...a(m-1)''b0,b1,b2...b(n-1)'的最长公共子序列为'z0,z1,z2...z(k-1)';

如果am!=bn,则zk!=am;'a0,a1,a2...a(m-1)''b0,b1,b2...bn'的最长公共子序列为'z0,z1,z2...zk';

如果am!=bn,则zk!=bn;'a0,a1,a2...am''b0,b1,b2...b(n-1)'的最长公共子序列为'z0,z1,z2...zk';

通过上面我们可以推导出求 C[i, j]的最长公共子序列

i为字符A的最后一项

j为字符B的最后一项

if(i===0 || j===0){ 最长子序列为零 }

if(i === j){ 最长子序列为 C[i-1, j-1] + 1; }

if(i !== j){ 最长公共子序列为 max(C[i-1, j], C[i, j-1]) }

const getTable = (s1, s2) => {
    const len1 = s1.length, len2 = s2.length;
    let max = 0;
    if(len1 === 0 || len2 === 0) return max;
    const table = [];

    for(let i=0; i<len1; i++){
        table.push([]);
        const cur = table[table.length - 1];
        for(let j=0; j<len2; j++){
            if(s1[i] === s2[j]){
                cur[j] = ((table[i - 1] || [])[j-1] || 0) + 1;//为了解决没有首部空行来垫
            }else{
                cur[j] = Math.max(((table[i - 1] || [])[j] || 0), ((table[i] || [])[j-1] || 0))
            }
        }
    }
    return table;
}
const s1 = '13456778', s2 = '357486782';
const table = getTable(s1, s2);

双指针法

有序数组联想双指针对撞指针

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例: 输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出: [1,2,2,3,5,6]

function f(arr1, n, arr2, m) {
    let i = n - 1,
        j = m - 1,
        k = n + m - 1;
    while (i >= 0 && j >= 0) {
        if (arr1[i] > arr2[j]) {
            arr1[k] = arr1[i];
            k--;
            i--;
        } else {
            arr1[k] = arr2[j]
            k--;
            j--;
        }
    }

    while (j >= 0) {
        arr1[k] = arr2[j];
        k--;
        j--;
    }
}

真题描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。

真题描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。

const nums = [-1, 0, 1, 2, -1, -4];

function threeSum(nums) {
    // 排序
    nums.sort((a, b) => {
        return a - b;
    })
    const result = [];
    const len = nums.length;
    for (let i = 0; i < len - 2; i++) {//最后两个元素不用去
        let j = i + 1,
            k = len - 1;

        if (i > 0 && nums[i] === nums[i - 1]) {
            continue; //去重
        }

        while (j < k) {
            const diff = nums[j] + nums[k] + nums[i];
            if (diff > 0) {
                k--;
                while (j < k && nums[k] === nums[k + 1]) {
                    k--; //去重
                }
            } else if (diff < 0) {
                j++;
                while (j < k && nums[j] === nums[j - 1]) {
                    j++; //去重
                }
            } else {
                result.push([nums[i], nums[j], nums[k]]);
                j++;
                k--;
                while (j < k && nums[k] === nums[k + 1]) {
                    k--; //去重
                }
                while (j < k && nums[j] === nums[j - 1]) {
                    j++; //去重
                }
            }
        }
    }

    return result;
}
threeSum(nums) 
//[ [ -1, -1, 2 ], [ -1, 0, 1 ] ]

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

function validPalindrome(s){
    s = s.replace(/\s+/g, '');
    const len = s.length;
    let [i, j] = [0, len-1];
    while(i<j && s[i] === s[j]){
        i++;
        j--;
    }

    if(isPalindrome(i+1, j)){
        return true;
    }
    if(isPalindrome(i, j-1)){
        return true;
    }
    function isPalindrome(st, sd){
        while(st<sd){
            if(s[st] !== s[sd]) return false;
            st++;
            sd--;
        }
        return true;
    }
    return false;
}

其他

1.求是否为素数 T(n) = O(logn)

function isPrime(n){
    if(n <= 1) return false;
    const N = Math.floor(Math.sqrt(n));
    for(let i = 2; i<=N; i++){
        if(n%i === 0){
            return false;
        }
    }
    return true;
}

2.求数组的连续子数组,和是否能被整除

function f(arr, N){
    let isPass = false;
    for(let i=0;i<=arr.length;i++){
        for(let j=i+1;j<=arr.length;j++){
            const remain = arr.slice(i, j).reduce((total, val) => {
                return total + val
            }) % N
            if(remain === 0) return true;
        }
    }
    return false;
}
console.log(f([1,2,3], 6)) //true

3.投射法

const students = [{
    score: 70,
    name: 'xiaoming',
}, {
    score: 80,
    name: 'xiaowang',
}, {
    score: 50,
    name: 'xiaohong',
}]
const studentsWithGrade = students.map(student => {
    return {
        ...student,
        grade: student.score >= 60 ? '合格' : '不合格'
    }
})

4.分组法

const students = [{
    group_id: 1,
    score: 70,
    name: 'xiaoming',
}, {
    group_id: 2,
    score: 80,
    name: 'xiaowang',
}, {
    group_id: 1,
    score: 50,
    name: 'xiaohong',
}]
const studentsWithGroup = students.reduce((groups, student) => {
    groups[student.group_id] = [...(groups[student.group_id] || []), student];
    return groups;
}, {})

5.联合法

const students = [{
    id: 1,
    color: 'red',
    parent_id: 2,
}, {
    id: 2,
    color: 'blue',
    parent_id: 3,
}, {
    id: 3,
    color: 'green',
    parent_id: 1,
}]
const studentsWithGroup = students.map(e => {
    const group = students.find(m => e.parent_id === m.id);
    return {
        ...e,
        parent_color: group.color
    }
})