前端算法题笔记

157 阅读4分钟

字符串

【数学】大整数加法字符串相加

var addStrings = function(num1, num2) {
    let i = num1.length - 1, j = num2.length - 1, add = 0;
    const ans = [];
    while (i >= 0 || j >= 0 || add != 0) {
        const x = i >= 0 ? num1.charAt(i) - '0' : 0;
        const y = j >= 0 ? num2.charAt(j) - '0' : 0;
        const result = x + y + add;
        ans.push(result % 10);
        add = Math.floor(result / 10);
        i -= 1;
        j -= 1;
    }
    return ans.reverse().join('');
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/add-strings/solution/zi-fu-chuan-xiang-jia-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

678. 有效的括号字符串

var checkValidString = function(s) {
    const leftStack = [];
    const asteriskStack = [];
    const n = s.length;
    for (let i = 0; i < n; i++) {
        const c = s[i];
        if (c === '(') {
            leftStack.push(i);
        } else if (c === '*') {
            asteriskStack.push(i);
        } else {
            if (leftStack.length) {
                leftStack.pop();
            } else if (asteriskStack.length) {
                asteriskStack.pop();
            } else {
                return false;
            }
        }
    }
    while (leftStack.length && asteriskStack.length) {
        const leftIndex = leftStack.pop();
        const asteriskIndex = asteriskStack.pop();
        if (leftIndex > asteriskIndex) {
            return false;
        }
    }
    return leftStack.length === 0;
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/valid-parenthesis-string/solution/you-xiao-de-gua-hao-zi-fu-chuan-by-leetc-osi3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

数组

数组中的第K个最大元素

思路:快速排序

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    const length = nums.length;
    const targetIndex = k - 1;
    let left = 0;
    let right = length - 1;
    while (left < right) {
        let index = partition(left, right, nums);
        if (index === targetIndex) {
            return nums[index];
        }
       else if (index > targetIndex) {
           right = index - 1;
       } else {
           left = index + 1;
       }
    }
    return nums[left];
   
};

var partition = function (start, end, nums) {
    let povit = nums[start];
    while (start < end) {
        while (start < end && nums[end] <= povit) {
            end--
        }
        nums[start] = nums[end];
        while (start < end && nums[start] > povit) {
            start++;
        }
        nums[end] = nums[start];
    }
    nums[start] = povit;
    return start;
}

最大子数组和

/**
 * @param {number[]} nums
 * @return {number}
 */
let maxSubArray = function(nums) {
 let max =nums[0];
 let pre = 0;
 for (const num of nums) {
     if (pre > 0) {
         pre += num;
     } else {
         pre = num;
     }
    max = Math.max(max, pre);
 }
 return max;
}

FED5 全排列

// 补全代码
const _permute = string => {
    if(string.length === 1) {
        return [string]
    }
    const results = []
    for(let s of string){
        const arr = string.split('').filter(str =>str !== s)
        _permute(arr.join('')).forEach(item => {
            results.push(s + item)
        })
    }
    return results
}

2-sum(数组中查找两数之和为K的序列对)两数之和

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    let map = new Map();
    for (let i = 0; i < nums.length; i++) {
        if (map.has(target - nums[i])) {
            return [map.get(target - nums[i]), i];
        } else {
            map.set(nums[i], i)
        }
    }
    return [];
}


三数之和

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    let ans = [];
    const len = nums.length;
    if(nums == null || len < 3) return ans;
    nums.sort((a, b) => a - b); // 排序
    for (let i = 0; i < len ; i++) {
        if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
        if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
        let L = i+1;
        let R = len-1;
        while(L < R){
            const sum = nums[i] + nums[L] + nums[R];
            if(sum == 0){
                ans.push([nums[i],nums[L],nums[R]]);
                while (L<R && nums[L] == nums[L+1]) L++; // 去重
                while (L<R && nums[R] == nums[R-1]) R--; // 去重
                L++;
                R--;
            }
            else if (sum < 0) L++;
            else if (sum > 0) R--;
        }
    }        
    return ans;
};

作者:guanpengchn
链接:https://leetcode-cn.com/problems/3sum/solution/hua-jie-suan-fa-15-san-shu-zhi-he-by-guanpengchn/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

麦乐鸡块问题

问题:在一个平行宇宙中,麦当劳的麦乐鸡块分为 7 块装、13 块装和 29 块装。有一天,你的老板让你出去购买正好为 n 块(0 < n <= 10000)的麦乐鸡块回来,请提供一个算法判断是否可行。

const checkNuggets = (nuggets) => {
    let temp = []
    temp[7] = true
    temp[13]-= true
    temp[29] = true
    for (let i = 7; i < nuggets; i+= 1) {
        if (temp[i]) {
            temp[i + 7] = true
            temp[i + 13] = true
            temp[i + 29] = true
        } else continue
    }
    return !!temp[nuggets]
}

console.log(checkNuggets(25)) // false
console.log(checkNuggets(26)) // true
console.log(checkNuggets(27)) // true
console.log(checkNuggets(28)) // true
console.log(checkNuggets(29)) // true
console.log(checkNuggets(30)) // false

链表

剑指 Offer 24. 反转链表

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let prev = null;
    let curr = head;
    while(curr) {
        let next = curr.next;
        curr.next = prev;
        pre = curr;
        curr = next;
    }
    return prev;
};

链表求和

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let carry = 0;
    let node = new ListNode(null);
    let p = node;
    while(true) {
        var val = (l1 ? l1.val : 0) + carry + (l2 ? l2.val : 0);
        carry = Math.floor(val / 10);
        val = val % 10;
        p.next = new ListNode(val);
        p = p.next;
        if (l1) l1 = l1.next;
        if (l2) l2 = l2.next;
        if (!l1 && !l2 && !carry) break

    }
    return node.next

};

传送门:链表求和

剑指 Offer 22. 链表中倒数第k个节点

var getKthFromEnd = function(head, k) {
    let node = head, n = 0;
    while (node) {
        node = node.next;
        n++;
    }
    node = head;
    for (let i = 0; i < n - k; i++) {
        node = node.next;
    }
    return node; 
};

21. 合并两个有序链表

var mergeTwoLists = function(l1, l2) {
    const prehead = new ListNode(-1);

    let prev = prehead;
    while (l1 != null && l2 != null) {
        if (l1.val <= l2.val) {
            prev.next = l1;
            l1 = l1.next;
        } else {
            prev.next = l2;
            l2 = l2.next;
        }
        prev = prev.next;
    }

    // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
    prev.next = l1 === null ? l2 : l1;

    return prehead.next;
};

二叉树

236. 二叉树的最近公共祖先

var lowestCommonAncestor = function(root, p, q) {
    let ans;
    const dfs = (root, p, q) => {
        if (root === null) return false;
        const lson = dfs(root.left, p, q);
        const rson = dfs(root.right, p, q);
        if ((lson && rson) || ((root.val === p.val || root.val === q.val) && (lson || rson))) {
            ans = root;
        } 
        return lson || rson || (root.val === p.val || root.val === q.val);
    }
    dfs(root, p, q);
    return ans;
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/solution/er-cha-shu-de-zui-jin-gong-gong-zu-xian-by-leetc-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

剑指 Offer 34. 二叉树中和为某一值的路径

/**
 * 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
 * @param {number} target
 * @return {number[][]}
 */

var pathSum = function(root, target) {
    let res = [];
    var backtrack = function (root, path, sum) {
        if (root === null) return null;
        path.push(root.val);
        sum += root.val;
        if (root.left === null && root.right === null && sum === target) res.push(path.slice());
        backtrack(root.left, path, sum);
        backtrack(root.right, path, sum);
        path.pop();
        
    }
    backtrack(root, [], 0);
    return res;
};

112. 路径总和

const hasPathSum = (root, sum) => {
  if (root == null) { // 遍历到null节点
    return false;
  }                
  if (root.left == null && root.right == null) { // 遍历到叶子节点
    return sum - root.val == 0;                  // 如果满足这个就返回true。否则返回false
  }
  // 不是上面的情况,则拆成两个子树的问题,其中一个true了就行
  return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); 
}

剑指 Offer 39. 数组中出现次数超过一半的数字

  1. 哈希表
  2. 排序取众数 时间复杂度:O(n\log n)O(nlogn)空间复杂度:O(\log n)O(logn)
  3. 摩尔投票法(最优)
/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function(nums) {
    let count = 0;
    let num = 0;
    for (let i = 0; i < nums.length; i++) {
        if (!count) {
            num = nums[i];
            count++;
        } else {
            count += num === nums[i] ? 1 : -1;
        }
    }
    return num
};

剑指 Offer 55 - I. 二叉树的深度

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if (root === null) return 0;
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};

时间复杂度 O(N)O(N) : NN 为树的节点数量,计算树的深度需要遍历所有节点。 空间复杂度 O(N)O(N) : 最差情况下(当树退化为链表时),递归深度可达到 NN 。

124. 二叉树中的最大路径和

/**
 * 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 maxPathSum = function(root) {
    let maxSum = Number.MIN_SAFE_INTEGER;
    const dfs = function (root) {
        if (root == null) return 0
        let left = dfs(root.left);
        let right = dfs(root.right);
        let innerMaxSum = left + root.val + right;
        maxSum = Math.max(innerMaxSum, maxSum);
        let outputMaxSum = root.val + Math.max(0, left, right);
        return outputMaxSum > 0 ? outputMaxSum : 0;

    }
    dfs(root);
    return maxSum;
};

传送门:「手画图解」别纠结递归的细节 | 124.二叉树中的最大路径和

蛇形打印二叉树 103. 二叉树的锯齿形层序遍历

var zigzagLevelOrder = function(root) {
    if (!root) {
        return [];
    }

    const ans = [];
    const nodeQueue = [root];

    let isOrderLeft = true;

    while (nodeQueue.length) {
        let levelList = [];
        const size = nodeQueue.length;
        for (let i = 0; i < size; ++i) {
            const node = nodeQueue.shift();
            if (isOrderLeft) {
                levelList.push(node.val);
            } else {
                levelList.unshift(node.val);
            }
            if (node.left !== null) {
                nodeQueue.push(node.left);
            }
            if (node.right !== null) {
                nodeQueue.push(node.right);
            }
        }            
        ans.push(levelList);
        isOrderLeft = !isOrderLeft;
    }

    return ans;
};

时间复杂度:O(N)O(N),其中 NN 为二叉树的节点数。每个节点会且仅会被遍历一次。

空间复杂度:O(N)O(N)。我们需要维护存储节点的队列和存储节点值的双端队列,空间复杂度为 O(N)O(N)。

作者:LeetCode-Solution 链接:leetcode-cn.com/problems/bi… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二叉树的右视图

DFS

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var rightSideView = function(root) {
  if(!root) return []
  let arr = []
  dfs(root, 0, arr)
  return arr
};
function dfs (root, step, res) {
  if(root){
    if(res.length === step){
      res.push(root.val)           // 当数组长度等于当前 深度 时, 把当前的值加入数组
    }
    // console.log(step, '-------', res)
    dfs(root.right, step + 1, res) // 先从右边开始, 当右边没了, 再轮到左边
    dfs(root.left, step + 1, res)
  }
}

作者:shetia
链接:https://leetcode-cn.com/problems/binary-tree-right-side-view/solution/shen-du-you-xian-sou-suo-by-shetia-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

BFS

var rightSideView = function(root) {
  if(!root) return []
  let queue = [root]                        // 队列 把树顶加入队列
  let arr = []                              // 用来存储每层最后个元素值
  while(queue.length > 0){
    let len = queue.length
    while (len) {
      let node = queue.shift()               // 取出队列第一个元素
      if(len === 1) arr.push(node.val)       // 当是 当前一层的最后一个元素时,把值加入arr
      if(node.left) queue.push(node.left)    // 继续往队列添加元素
      if(node.right) queue.push(node.right)
      len--
    }
  }
  return arr
};

作者:shetia
链接:https://leetcode-cn.com/problems/binary-tree-right-side-view/solution/shen-du-you-xian-sou-suo-by-shetia-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

动态规划

爬楼梯

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    let p = 0;
    let q = 0;
    let r = 1;
    for (let i = 1; i <= n; i++) {
        p = q;
        q = r;
        r = p + q;
    }
    return r
};

进制

剑指 Offer 15. 二进制中1的个数

var hammingWeight = function(n) {
    let ret = 0;
    for (let i = 0; i < 32; i++) {
        if ((n & (1 << i)) !== 0) {
            ret++;
        }
    }
    return ret;
};

时间复杂度:O(k)O(k),其中 kk 是 \texttt{int}int 型的二进制位数,k=32k=32。我们需要检查 nn 的二进制位的每一位,一共需要检查 3232 位。

空间复杂度:O(1)O(1),我们只需要常数的空间保存若干变量。

作者:LeetCode-Solution 链接:leetcode-cn.com/problems/er… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

var hammingWeight = function(n) {
    let ret = 0;
    while (n) {
        n &= n - 1;
        ret++;
    }
    return ret;
};

时间复杂度:O(\log n)O(logn)。循环次数等于 nn 的二进制位中 11 的个数,最坏情况下 nn 的二进制位全部为 11。我们需要循环 \log nlogn 次。

空间复杂度:O(1)O(1),我们只需要常数的空间保存若干变量。

最大交换

leetcode-cn.com/problems/ma…

矩阵

54. 螺旋矩阵

var spiralOrder = function(matrix) {
    if (!matrix.length || !matrix[0].length) {
        return [];
    }

    const rows = matrix.length, columns = matrix[0].length;
    const order = [];
    let left = 0, right = columns - 1, top = 0, bottom = rows - 1;
    while (left <= right && top <= bottom) {
        for (let column = left; column <= right; column++) {
            order.push(matrix[top][column]);
        }
        for (let row = top + 1; row <= bottom; row++) {
            order.push(matrix[row][right]);
        }
        if (left < right && top < bottom) {
            for (let column = right - 1; column > left; column--) {
                order.push(matrix[bottom][column]);
            }
            for (let row = bottom; row > top; row--) {
                order.push(matrix[row][left]);
            }
        }
        [left, right, top, bottom] = [left + 1, right - 1, top + 1, bottom - 1];
    }
    return order;
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/spiral-matrix/solution/luo-xuan-ju-zhen-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析

时间复杂度:O(mn)O(mn),其中 mm 和 nn 分别是输入矩阵的行数和列数。矩阵中的每个元素都要被访问一次。

空间复杂度:O(1)O(1)。除了输出数组以外,空间复杂度是常数。

509. 斐波那契数


var fib = function(n) {
    if (n < 2) {
        return n;
    }
    let p = 0, q = 0, r = 1;
    for (let i = 2; i <= n; i++) {
        p = q;
        q = r;
        r = p + q;
    }
    return r;
};