/js-algorithm

A personal algorithm problems collection about Data Structures and Leetcode questions. It contains all Q&A on labuladong website.个人Javascript, Typescript版 数据结构与算法的训练集.它包括了Js数据结构,剑指Offer的全部题目,以及labuladong网站的所有算法题和总结,配合labuladong网站内容练习食用效果更佳。持续更新中...

Primary LanguageJavaScript

TO DO LIST

  • Complete No.6 Collections and Organize relevant files.
  • No.7 is a supplement to No.6 Collections.

Recommend

The fastest way to master the algorithm framework

Labuladong Algorithm:

Gitbook: https://labuladong.gitbook.io/algo/

Github Repo: https://github.com/labuladong/fucking-algorithm

English Repo:https://github.com/labuladong/fucking-algorithm/tree/english

推荐

《Labuladong的算法小抄》

最快速入门算法框架的办法:

中文Gitbook: https://labuladong.gitbook.io/algo/

Introduction

This is a collection of personal algorithm training codes based on JavaScript, including:

  1. "Learning JavaScript Data Structures and Algorithms"
  2. All the questions of "Coding Interviews". (in LeetCode)
  3. All the questions of the labuladong algorithm(based on C++ / Java)

这是个人JavaScript 算法训练集,内容包括:

  1. 《学习Javascript数据结构与算法》
  2. 《剑指Offer》. (in LeetCode)
  3. labuladong的算法小抄 (基于 C++ / Java)

Features

  • Common algorithm questions(LeetCode) and framework

  • Most common data structure

  • Detailed classification

  • Update in time

  • 常见算法问题(LeetCode)和框架

  • 最常见的数据结构

  • 详细问题分类

  • 长期更新

Data Structures

In Data Structures

    1.基础_BigInt使用.js
    2.基础_JS全局方法.js
    3.基础_Map.js
    4.基础_Math.js
    5.基础_Number.js
    6.基础_Set.js
    7.基础_Symbol.js
    8.基础_对象Obj.js
    9.基础_数组Array.js
    10.基础_字符串方法.js
    11.基础_条件问题.js
    12.基础_正则表达式.js
    13.补充_算法方法知识点.js
    AVL.html
    AYL树_自平衡二叉树.js
    Node中Buffer对象.js
    temp.html
    temp.js
    二叉树 copy.html
    二叉树.html
    二叉树.js
    二叉树_红黑树.html
    二叉树_红黑树.js
    优先级队列_最小堆.ts
    双向链表.html
    双向链表.js
    双端队列.js
    双端队列_对象实现.html
    哈希表.js
    哈希表_分离链接.js
    哈希表_分离链表.html
    .js
    图_BFS遍历_最短路径问题.js
    图_DFS遍历_最短路径问题.js
    图_拓扑排序.js
    堆_大顶堆_and堆排序.js
    堆_小顶堆.html
    堆_小顶堆.js
    字典.html
    字典_Map类.js
    字典_Obj类.js
    循环链表.js
    有序列表.js
    .js
    矩阵.js
    递归.js
    链表.html
    链表.js
    链表_新实现.html
    链表_新实现.js
    队列.js
    集合_Obj类实现.js
    集合_Set类实现.js
    集合_Set结构直接实现.js

Main Categories

├─1.算法框架_labuladong
  ├─1.必备算法框架
  ├─10.回文系列
  ├─11.分治算法框架
  ├─12.贪心算法
  ├─13.滑动窗口_解决子串子数组问题
  ├─14.区间问题
  ├─15.差分算法_前缀和算法
  ├─16.
  ├─17.位运算
  ├─18.未分类题目
  ├─19.高频面试题
  ├─2.排序算法 合集
  ├─3.二叉树集锦
  ├─4.单调栈__单调队列
  ├─5.动态规划问题  合集
  ├─6.双指针_快慢指针
  ├─7.回溯算法问题合集
  ├─8.递归汇总
  ├─9.二分法_详解
  └─New Topics_after 2021-08
      ├─DFS 岛屿问题
      ├─Dijkstra 算法
      ├─图相关
      ├─时间调度问题
      ├─贪心算法
      └─链表
├─2.剑指Offer
  ├─BFS_广度遍历_√
  ├─DFS_深度遍历_√
  ├─二叉树_√
  ├─位运算_√
  ├─动态规划_√
  ├─哈希表_√
  ├─回溯法_试探法_√
  ├─数组+字符串_√
  ├─栈和队列_√
  ├─正则表达式_√
  ├─递归_√
  └─链表_√
├─3.Data Structure
├─4.Basic Challenge
└─5.Daily Challenge
    ├─BFS
    └─DFS

Main Algorithm Framework

In 3 算法框架_套路_labuladong

    0_学习刷题的框架思维.js
    0_详解最长公共子序列问题,秒杀三道动态规划题目.js
    1_动态规划_框架.js
    2_DFS_算法_框架.js
    3_BFS算法_框架.js
    3_DFS与BFS使用范围.js
    4_分治框架_归并排序.js
    4_分治算法_框架.js
    5_回溯算法_团灭子集-排列-组合问题.js
    5_递归框架.js
    6_二分搜索_本质为左右指针.js
    6_二分法实战_887.高楼扔鸡蛋.js
    6_如何运用二分查找算法.js
    7_二叉树常见操作.js
    7_二叉树遍历 框架.js
    8_单调栈.js
    8_单调队列.js
    9_差分数组.js
    10_双指针技巧总结.js
    11_滑动窗口.js
    12_贪心算法-区间调度问题.js
    12_贪心算法.js
    13_前缀和  Ksum套路.js
    14.LRU缓存淘汰算法.js
    15.LFU使用次数最少缓存淘汰算法.js
    javascript常见位操作.js
    两个栈实现队列.js
    优先级队列.js
    团灭nSum问题.js
    团灭区间问题.js
    团灭打家劫舍问题.js
    团灭股票买卖问题.js
    如何计算编辑距离.js
    子集背包问题.js
    完全背包问题.js
    洗牌算法_打乱数组.js
    补充_208. 实现 Trie (前缀树).js
    队列实现栈.js
    集合_Set结构直接实现.js
    高效进行模幂运算.js
/* 
第一题是只进行一次交易,相当于 k = 1;
第二题是不限交易次数,相当于 k = +infinity(正无穷);
第三题是只进行 2 次交易,相当于 k = 2;
剩下两道也是不限次数,但是加了交易「冷冻期」和「手续费」的额外条件,其实就是第二题的变种,都很容易处理。

*/
/* 
每天都有三种「选择」:买入、卖出、无操作,
我们用 buy, sell, rest 表示这三种选择。
>比如说 dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。
>再比如 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。
>我们想求的最终答案是 dp[n - 1][K][0],即最后一天,最多允许 K 次交易,最多获得多少利润。

dp[i][k][0 or 1]
0 <= i <= n-1, 1 <= k <= K
n 为天数,大 K 为最多交易数
此问题共 n × K × 2 种状态,全部穷举就能搞定。

for 0 <= i < n:
    for 1 <= k <= K:
        for s in {0, 1}:
            dp[i][k][s] = max(buy, sell, rest)

*/


/* 
! 状态转移方程

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
              max(   选择 rest  ,             选择 sell      )

解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
              max(   选择 rest  ,           选择 buy         )

解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。

>如果 buy,就要从利润中减去 prices[i],如果 sell,就要给利润增加 prices[i]。

*/


/* 
! base case分析

dp[-1][k][0] = 0
# 解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。
dp[-1][k][1] = -infinity
# 解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
# 解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = -infinity
# 解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。

*/

/* 
! 整理后:

base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity

状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

*/

// > 题目1: 最多进行一次交易,也就是K=1;

var maxProfit = function(prices) {
  let n = prices.length;
  if(prices.length===0) return 0;
  // k = 0 的 base case,所以 dp[i-1][0][0] = 0。
  // 现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。
  // 也就可以去掉所有的K;
  let dp=new Array(n).fill(0).map(v=>new Array(2).fill(0));
  for (let i = 0; i < n; i++) {
    // 显然 i = 0 时 dp[i-1] 是不合法的。这是因为我们没有对 i 的 base case 进行处理。
      if (i===0) {
        dp[i][0] = 0;
        dp[i][1] = -prices[i];
        continue;
    }
      dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
      dp[i][1] = Math.max(dp[i-1][1], -prices[i]);
  }
  return dp[n - 1][0];  //左下角

};


// > 降维后  第一题就解决了,但是这样处理 base case 很麻烦,而且注意一下状态转移方程,
// > 新状态只和相邻的一个状态有关,其实不用整个 dp 数组,只需要一个变量储存相邻的那个状态就足够了,这样可以把空间复杂度降到 O(1):
/* 
      今天没股票,可能使今天卖了,或者昨天就没有
      今天有股票,可能使今天买的,或者昨天就有
*/
// k == 1
function maxProfit_k_1(prices) {
  let n = prices.length;
  // base case: dp[-1][0] = 0, dp[-1][1] = -infinity
  let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER;
  for (let i = 0; i < n; i++) {
      dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 
      dp_i_1 = Math.max(dp_i_1, -prices[i]); 
  }
  return dp_i_0;
}

//>  第二题,k = +infinity  

/* 
* 如果 k 为正无穷,那么就可以认为 k 和 k - 1 是一样的。可以这样改写框架:

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
            = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i])

我们发现数组中的 k 已经不会改变了,也就是说不需要记录 k 这个状态了:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])

*/
function maxProfit_k_inf(prices) {
  let n = prices.length;
  let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER;
  for (let i = 0; i < n; i++) {
      let temp = dp_i_0;
      dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
      dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
  }
  return dp_i_0;
}


//> 第三题,k = +infinity with cooldown
// 
/* 
* 每次 sell 之后要等一天才能继续交易。只要把这个特点融入上一题的状态转移方程即可:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])
解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 。

*/
function maxProfit_with_cool(prices) {
  let n = prices.length;
  let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER;
  let dp_pre_0 = 0; // 代表 dp[i-2][0]
  for (let i = 0; i < n; i++) {
      let temp = dp_i_0;
      dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);//今天没股票,可能有股票然后卖了,或者本来就没有股票
      dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]);//今天有股票,可能本来就有,或者昨天买的
      dp_pre_0 = temp;
    // 第一天pre是0;第二天也是0,第三天才开始是pre=dp_i_0; 
  }
  return dp_i_0;
}



// > 第四题,k = +infinity with fee

/* 
* 每次交易要支付手续费,只要把手续费从利润中减去即可。改写方程:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i] - fee)
解释:相当于买入股票的价格升高了。
在第一个式子里减也是一样的,相当于卖出股票的价格减小了。
! 不限制交易次数,K可以认为对交易没有影响
*/

function maxProfit_with_fee(prices,fee) {
  let n = prices.length;
  let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER;
  for (let i = 0; i < n; i++) {
      let temp = dp_i_0;
      dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
      dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee);
  }
  return dp_i_0;
}

// > 第五题,k = 2
/* 
k = 2 和前面题目的情况稍微不同,因为上面的情况都和 k 的关系不太大。
要么 k 是正无穷,状态转移和 k 没关系了;要么 k = 1,跟 k = 0 这个 base case 挨得近,
最后也没有存在感。
这道题 k = 2 和后面要讲的 k 是任意正整数的情况中,对 k 的处理就凸显出来了。
我们直接写代码,边写边分析原因。

原始的动态转移方程,没有可化简的地方
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

! 这道题由于没有消掉 k 的影响,所以必须要对 k 进行穷举

let max_k = 2;
int[][][] dp = new int[n][max_k + 1][2];
for (int i = 0; i < n; i++) {
    for (int k = max_k; k >= 1; k--) {
        if (i - 1 == -1) { 处理 base case  }
        dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);
        dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]);
    }
}
穷举了 n × max_k × 2 个状态,正确。
return dp[n - 1][max_k][0];
*/

// 两种情况全考虑,肯定是交易越多钱越多,所以最后结果是dp_i20
function maxProfit_k_2(prices) {
  let dp_i10 = 0, dp_i11 =Number.MIN_SAFE_INTEGER;
  let dp_i20 = 0, dp_i21 = Number.MIN_SAFE_INTEGER;
  for (let price of prices) {
      dp_i20 = Math.max(dp_i20, dp_i21 + price);
      dp_i21 = Math.max(dp_i21, dp_i10 - price);
      dp_i10 = Math.max(dp_i10, dp_i11 + price);
      dp_i11 = Math.max(dp_i11, -price);
  }
  return dp_i20;
}


// > 第六题,k = any integer

/* 
一次交易由买入和卖出构成,至少需要两天。所以说有效的限制 k 应该不超过 n/2,
如果超过,就没有约束作用了,相当于 k = +infinity。这种情况是之前解决过的。

*/

function maxProfit_k_any( max_k,prices) {
  let n = prices.length;
  // 交易次数判断是否超过n/2。
  if (max_k > n / 2) return maxProfit_k_inf(prices);
  let dp=new Array(n).fill(0).map(v=>new Array(max_k+1).fill(0).map(v=>{
   return new Array(2).fill(0)
  }));

  for (let i = 0; i < n; i++) 
      for (let k = max_k; k >= 1; k--) {
          if (i===0) {
            dp[i][k][0] = 0;
            dp[i][k][1] = -prices[i];
            continue;
           }
          dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);
          dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]);     
      }
  return dp[n - 1][max_k][0];
}
// 备选项,如果K 大于一半,那说明就是infinity,随便交易
function maxProfit_k_inf(prices) {
  let n = prices.length;
  let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER;
  for (let i = 0; i < n; i++) {
      let temp = dp_i_0;
      dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
      dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
  }
  return dp_i_0;
}