前端常见算法题(数组篇)--下

357 阅读7分钟

三、路径问题

2020.10.19

No.62 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?

例如,上图是一个7 x 3 的网格。有多少可能的路径?

 

示例 1:

输入: m = 3, n = 2 输出: 3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右 示例 2:

输入: m = 7, n = 3 输出: 28  

提示:

1 <= m, n <= 100 题目数据保证答案小于等于 2 * 10 ^ 9

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/un… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=62 lang=javascript
 *
 * [62] 不同路径
 */

// @lc code=start
/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    // 用矩阵表来记录所有路径数
    /**
     * 如对于 3 x 2 的矩阵可用如下表示:
     * 1 1 1
     * 1 2 3
     * 无剪枝,将所有信息录入矩阵表,最后一位数就是路径和
     */
    let dp = new Array(n);
    // 对于第一行所有算元素和第一列所有元素置为1
    for(let row=0;row<n;row++) {
        dp[row] = new Array(m);
        dp[row][0] = 1;
    }
    for(let col=0;col<m;col++) {
        dp[0][col] = 1;
    }
    // 动态规划
    for(let i=1;i<n;i++) {
        for(let j=1;j<m;j++) {
            dp[i][j] = dp[i-1][j] + dp[i][j-1]
        }
    }

    return dp[n-1][m-1];
};

方案二:

/*
 * @lc app=leetcode.cn id=62 lang=javascript
 *
 * [62] 不同路径
 */

// @lc code=start
/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    // 阶乘函数
    const fac = (n) => {
        if(n<=1) return 1;
        return fac(n-1)*n;
    }

    return fac(n-1+m-1) / ( fac(m-1) * fac(n-1) );
};

有两种解法:1、动态规划:求路径问题一般都会想到动态规划,利用矩阵表来记录路径的和,返回最后一个加和结果,递归循环不变式为 dp[i][j]=dp[i-1][j]+dp[i][j-1] ,这里也可以使用一维数组直接替换上一层的数据,但扩展性不好,仅适用于本体;2、排列组合:可以将问题转换为向右为1,向下为0,每次都有两种不同结果的选择,进而为在m-1+n-1个选择中选择1或0的排列组合,即Cm-1+n-1n-1=Cm-1+n-1m-1



2020.10.20

No.63 不同路径-ii

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 n 的值均不超过 100。

示例 1:

输入: [  [0,0,0],  [0,1,0],  [0,0,0] ] 输出: 2 解释: 3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径:

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/un… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案:

/*
 * @lc app=leetcode.cn id=63 lang=javascript
 *
 * [63] 不同路径 II
 */

// @lc code=start
/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    const r = obstacleGrid.length,
          c = obstacleGrid[0].length;

    if(obstacleGrid[0][0] == 1 || obstacleGrid[r-1][c-1] == 1) return 0;

    // 构建二维数组
    let dp = new Array(r);
    for(let row=0;row<r;row++) dp[row] = new Array(c);

    dp[0][0] = 1;
    // 第一列元素
    for(let row=1;row<r;row++) {
        if(obstacleGrid[row][0] == 1) {
            dp[row][0] = 0;
        } else if(obstacleGrid[row][0] == 0) {
            dp[row][0] = dp[row-1][0];
        }
    };
    // 第一行元素
    for(let col=1;col<c;col++) {
        if(obstacleGrid[0][col] == 1) {
            dp[0][col] = 0;
        } else if(obstacleGrid[0][col] == 0) {
            dp[0][col] = dp[0][col-1];
        }
    };

    // 动态规划
    for(let i=1;i<r;i++) {
        for(let j=1;j<c;j++) {
            if(obstacleGrid[i][j] == 1) {
                dp[i][j] = 0;
            } else if(obstacleGrid[i][j] == 0) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
            
        }
    };

    return dp[r-1][c-1];
};

动态规划常见问题,这里的状态转移方程需要对obstacleGrid(i,j)的值判断后进行过滤:当obstacleGrid(i,j)为1时,dp[i][j]=0;否则,dp[i][j]=dp[i-1][j]+dp[i][j-1]。边界条件同样也需要符合过滤条件,不能只是对0或1的填充进行判断,这一行或这一列填0或1会对下一行或下一列产生影响



2020.10.21

No.120 三角形最小路径和

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。

 

例如,给定三角形:

[     [2],    [3,4],   [6,5,7],  [4,1,8,3] ] 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

 

说明:

如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/tr… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=120 lang=javascript
 *
 * [120] 三角形最小路径和
 */

// @lc code=start
/**
 * @param {number[][]} triangle
 * @return {number}
 */
var minimumTotal = function(triangle) {
    const r = triangle.length,
          c = triangle[0].length;
    // 构造动态规划路径
    const dp = new Array(r);
    for(let row=0;row<r;row++) dp[row] = new Array(c);

    /**
     * 状态转移方程 dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j-1]) + triangle[i][j]
     */

    // 边界条件处理
    dp[0][0] = triangle[0][0];

    for(let i=1;i<r;i++) {
        // 最左侧 dp[i][0] = dp[i-1][0] + triangle[i][0]
        dp[i][0] = dp[i-1][0] + triangle[i][0];

        for(let j=1;j<i;j++) {
            dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j-1]) + triangle[i][j];
        }

        // 最右侧 dp[i][i] = dp[i-1][i-1] + triangle[i][i] 
        dp[i][i] = dp[i-1][i-1] + triangle[i][i];
    }

    return Math.min(...dp[r-1]);
};

方案二:

/*
 * @lc app=leetcode.cn id=120 lang=javascript
 *
 * [120] 三角形最小路径和
 */

// @lc code=start
/**
 * @param {number[][]} triangle
 * @return {number}
 */
const minimumTotal = (triangle) => {
  const height = triangle.length;
  const width = triangle[0].length;
  // 初始化dp数组
  const dp = new Array(height);
  for (let i = 0; i < height; i++) {
    dp[i] = new Array(width);
  }
  for (let i = height - 1; i >= 0; i--) {
    for (let j = triangle[i].length - 1; j >= 0; j--) {
      if (i == height - 1) {  
        // base case
        dp[i][j] = triangle[i][j];  
      } else {
        // 状态转移方程
        dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j];
      }
    }
  }
  return dp[0][0];
};

动态规划经典题目,有两种方案:1、自顶向下:利用 dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j-1]) + triangle[i][j] 的状态转移方程,注意边界的处理;2、自底向上:从最底层一层一层向上查找,最底层中的最小值是确定的,因而其走向基本是确定的,最终会归约到一个数上



2020.10.22

No.864 获取所有钥匙的最短路径

给定一个二维网格 grid。 "." 代表一个空房间, "#" 代表一堵墙, "@" 是起点,("a", "b", ...)代表钥匙,("A", "B", ...)代表锁。

我们从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间。我们不能在网格外面行走,也无法穿过一堵墙。如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。

假设 K 为钥匙/锁的个数,且满足 1 <= K <= 6,字母表中的前 K 个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。

返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 -1 。

 

示例 1:

输入:["@.a.#","###.#","b.A.B"] 输出:8 示例 2:

输入:["@..aA","..B#.","....b"] 输出:6  

提示:

1 <= grid.length <= 30 1 <= grid[0].length <= 30 grid[i][j] 只含有 '.', '#', '@', 'a'-'f' 以及 'A'-'F' 钥匙的数目范围是 [1, 6],每个钥匙都对应一个不同的字母,正好打开一个对应的锁。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/sh… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案:

/*
 * @lc app=leetcode.cn id=864 lang=javascript
 *
 * [864] 获取所有钥匙的最短路径
 */

// @lc code=start
/**
 * @param {string[]} grid
 * @return {number}
 */
var shortestPathAllKeys = function(grid) {
    // 构造一个结构体满足 {i,j,keybit,step}
    /**
     * i 横坐标 行坐标
     * j 纵坐标 列坐标
     * keybit 已经拿到的钥匙信息
     * step 起始节点的最短距离
     */
    function Step(i,j,keybit,step) {
        this.i = i;
        this.j = j;
        this.keybit = keybit;
        this.step = step;
    };

    const ROW = grid.length,
          COL = grid[0].length,
          LOWER_A = 'a'.charCodeAt(), // 97
          UPPER_A = 'A'.charCodeAt(); // 68

    let KEY_BIT = 0;

    // 获取所有钥匙信息
    let start = null;

    for(let r = 0; r < ROW; r++) {
        for(let c = 0; c < COL; c++) {
            if(grid[r][c] == '@') {
                // 起始位置
                start = [r,c];
            } else if (grid[r][c] >= 'a' && grid[r][c] <= 'f') {
                // 标志判断位的移位异或比较,这是ACL的一种常见的方法
                KEY_BIT = KEY_BIT | ( 1 << ( grid[r][c].charCodeAt() - LOWER_A ) )
            }
        }
    }

    

    // 构造已经遍历节点结构
    const visited = new Array(ROW);
    for(let i = 0; i < ROW; i++) {
        visited[i] = new Array(COL);

        for( let j = 0; j < COL; j++) {
            visited[i][j] = {}
        }
    };
    visited[start[0]][start[1]][0] = true;

    // 移动方向
    const directions = [
        [-1, 0], // 左
        [1, 0], // 右
        [0, 1], // 上
        [0, -1] // 下
    ];

    // 构造队列结构 做缓存用
    const queue = [new Step(start[0], start[1], 0 ,0)];

    // 循环队列进行判断
    while ( queue.length != 0) {
        const current = queue.shift();

        if(current.keybit == KEY_BIT) {
            return current.step;
        }

        for(let d = 0; d < directions.length; d++) {
            // 下一步的坐标
            const ni = current.i + directions[d][0],
                  nj = current.j + directions[d][1];
            // 对四个方向进行不同的判断
            if( ( ni >= 0 && ni < ROW ) && ( nj >= 0 && nj < COL ) && !visited[ni][nj][current.keybit] ) {
                // 是钥匙
                if( grid[ni][nj] >= 'a' && grid[ni][nj] <= 'f' ) {
                    const _keybit = current.keybit | ( 1 << (grid[ni][nj].charCodeAt() - LOWER_A) );

                    queue.push(new Step(ni, nj, _keybit, current.step+1));
                    // 都已经访问过了
                    visited[ni][nj][current.keybit] = true;
                    visited[ni][nj][_keybit] = true;
                } else if ( // 是空房间、起始点以及是没有被访问过状态的锁
                    ( grid[ni][nj] == '.' ) || 
                    ( grid[ni][nj] == '@' ) ||
                    ( grid[ni][nj] >= 'A' && grid[ni][nj] <= 'F' && ( current.keybit & ( 1 << (grid[ni][nj].charCodeAt() - UPPER_A) ) ) ) 
                ) {
                    queue.push(new Step(ni, nj, current.keybit, current.step+1));
                    // 对这个路径状态置为访问过了
                    visited[ni][nj][current.keybit] = true;
                }

            }
        }
    }
    return -1;
};

Dijkstra最短路径算法:对路径是否访问过进行状态校验,这里利用位运算进行校验位的判断,对关键节点进行不同方向尝试最后获取最短路径



2020.10.23

No.1138 字母板上的路径

我们从一块字母板上的位置 (0, 0) 出发,该坐标对应的字符为 board[0][0]。

在本题里,字母板为board = ["abcde", "fghij", "klmno", "pqrst", "uvwxy", "z"],如下所示。

azboard.png

我们可以按下面的指令规则行动:

如果方格存在,'U' 意味着将我们的位置上移一行; 如果方格存在,'D' 意味着将我们的位置下移一行; 如果方格存在,'L' 意味着将我们的位置左移一列; 如果方格存在,'R' 意味着将我们的位置右移一列; '!' 会把在我们当前位置 (r, c) 的字符 board[r][c] 添加到答案中。 (注意,字母板上只存在有字母的位置。)

返回指令序列,用最小的行动次数让答案和目标 target 相同。你可以返回任何达成目标的路径。

 

示例 1:

输入:target = "leet" 输出:"DDR!UURRR!!DDD!" 示例 2:

输入:target = "code" 输出:"RR!DDRR!UUL!R!"  

提示:

1 <= target.length <= 100 target 仅含有小写英文字母。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/al… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=1138 lang=javascript
 *
 * [1138] 字母板上的路径
 */

// @lc code=start
/**
 * @param {string} target
 * @return {string}
 */
var alphabetBoardPath = function(target) {
    // 构造字母的hash表
    let hashTable = {},
        stack = 'abcdefghijklmnopqrstuvwxyz'.split('').reverse();
    for( let i = 0; i <= 5; i++ ) {
        for( let j = 0; j <= 4; j++ ) {
            let key = stack.pop();
            if( key ) {
                hashTable[key] = [i, j];
            }
        }
    };

    // 构造target的链表结构
    let list = [[0, 0]];
    target.split('').forEach(l => {
        list.push(hashTable[l]);
    });

    console.log(list);

    const mapPos = ( pos1, pos2 ) => {
        // 解构数组位置
        const [ x1, y1 ] = pos1,
              [ x2, y2 ] = pos2,
              xn = Math.abs(x1 - x2), // 恒坐标相差单位的绝对值
              yn = Math.abs(y1 - y2); // 纵坐标相差单位的绝对值

        let r = '';

        // 无需兼容z的情况是,只要限制z的方向,其他的可以随意,因而只要满足z的要求就可以了,对于z来说其方向是有先后顺序的,即:z是起点 => 先上后右;z是终点 => 先左后下
        if ( x2 - x1 < 0 ) {
            r += 'U'.repeat(xn)
        };      

        if ( y2 - y1 > 0 ) {
            r += 'R'.repeat(yn)
        };
        
        if ( y2 - y1 < 0 ) {
            r += 'L'.repeat(yn)
        };

        if( x2 - x1 > 0 ) {
            r += 'D'.repeat(xn)
        }; 
            
            
        

        return r + '!';
    };

    // 遍历list,获取相邻间距的值,返回最终的result
    let result = '';

    for(let p1=0,p2=1;p2<list.length;p1++,p2++) {
        result += mapPos(list[p1], list[p2]);
    };

    return result;
};

方案二:

/*
 * @lc app=leetcode.cn id=1138 lang=javascript
 *
 * [1138] 字母板上的路径
 */

// @lc code=start
/**
 * @param {string} target
 * @return {string}
 */
var alphabetBoardPath = function (target) {
  const mapManu = (p1, p2) => {
    // 离'a'对比的距离
    const LETTER_A = 'a'.charCodeAt();
    p1 -= LETTER_A;
    p2 -= LETTER_A;
    let r = '';
    const dy = Math.floor(p1 / 5) - Math.floor(p2 / 5),
        dx = p1 % 5 - p2 % 5;
    if (dy > 0) r += 'U'.repeat(dy)
    if (dx < 0) r += 'R'.repeat(-dx)
    if (dx > 0) r += 'L'.repeat(dx)
    if (dy < 0) r += 'D'.repeat(-dy)
    return r + '!';
  };
  

  let res = '',
      p1 = 'a'.charCodeAt();

  
  for (let i = 0; i < target.length; i++) {
    res += mapManu(p1, target[i].charCodeAt());
    p1 = target[i].charCodeAt();
  }
  return res;
};

主要有两种解法:1、构建hash表,对target中的字母进行查找,获取位置顺序坐标,对坐标的位置进行映射后输出答案,需要注意对最后一行的'z'的处理方向顺序;2、不构建hash表,利用除法和求余处理target中的每个字母与'a'的距离,输出最后的答案



总结:

  1. 路径问题主要用到的通解方法是动态规划,需要考虑边界问题以及对每个题目的状态转移方程的归纳;
  2. 对于某些特殊场景的题目,可以考虑排列组合、Dijkstra算法、hash表等数据结构与算法的变形引申

四、区间问题

2020.10.28

No.56 合并区间

给出一个区间的集合,请合并所有重叠的区间。

 

示例 1:

输入: intervals = [[1,3],[2,6],[8,10],[15,18]] 输出: [[1,6],[8,10],[15,18]] 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. 示例 2:

输入: intervals = [[1,4],[4,5]] 输出: [[1,5]] 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 注意:输入类型已于2019年4月15日更改。 请重置默认代码定义以获取新方法签名。

 

提示:

intervals[i][0] <= intervals[i][1]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/me… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=56 lang=javascript
 *
 * [56] 合并区间
 */
// @lc code=start
/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
    // 按左边界升序排列
    intervals.sort((a,b) => a[0] - b[0]);
    // 获取满足要求的子数组位置
    let rIndex = [];
    for( let p = 0; p < intervals.length; p++ ) {
        if ( isOverlap(intervals[p], intervals[p+1]) ) {
            intervals.splice(p+1, 1, mergeOverlap(intervals[p], intervals[p+1]));
        } else {
            rIndex.push(p)
        }
    };

    // 返回符合要求的区间
    let r = [];

    for(let i=0;i<rIndex.length;i++) {
        r.push(intervals[rIndex[i]])
    }

    return r;

    // 判断两个数组是否重叠
    function isOverlap( arr1, arr2 ) {
        if(!arr1 || !arr2) return false;
        
        const [ a1, b1 ] = arr1,
              [ a2, b2 ] = arr2;
        
        if( ( a1 <= a2 && a2 <= b1 && b1 <= b2 ) || 
            ( a1 <= a2 && b2 <= b1 ) || 
            ( a2 <= a1 && b1 <= b2 ) || 
            ( a2 <= a1 && a1 <= b2 && b2 <= b1 ) 
        ) {
            return true;
        }
        
        return false;
    };

    // 重叠两数组进行合并
    function mergeOverlap(arr1, arr2) {
        const a = [...arr1, ...arr2].sort((a,b) => a-b);
        return [ a[0], a[ a.length - 1 ] ];
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=56 lang=javascript
 *
 * [56] 合并区间
 */
// @lc code=start
/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
    intervals.sort((a, b) => a[0] - b[0]);
    let res = [];
    let idx = -1;
    for (let interval of intervals) {
        if (idx == -1 || interval[0] > res[idx][1]) {
            res.push(interval);
            idx++;
        } else {
            res[idx][1] = Math.max(res[idx][1], interval[1]);
        }
    }
    return res;
};

有两种解法:1、逐个判断,记录位置,对于重叠区间会替换到最后一个合并的位置,会改变原数组;2、优化方案一,对于按区间左边界递增排序的数组,只需要判断后一位的右边界和前一位的做边界的大小即可,对于满足重叠规则的右边界进行取最大值,对于不满足规则的只需将区间存入结果数组即可



2020.10.29

No.57 插入区间

给出一个无重叠的 ,按照区间起始端点排序的区间列表。

在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。

 

示例 1:

输入:intervals = [[1,3],[6,9]], newInterval = [2,5] 输出:[[1,5],[6,9]] 示例 2:

输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8] 输出:[[1,2],[3,10],[12,16]] 解释:这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。  

注意:输入类型已在 2019 年 4 月 15 日更改。请重置为默认代码定义以获取新的方法签名。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/in… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=57 lang=javascript
 *
 * [57] 插入区间
 */

// @lc code=start
/**
 * @param {number[][]} intervals
 * @param {number[]} newInterval
 * @return {number[][]}
 */
var insert = function(intervals, newInterval) {
    // 如果原数组为空,直接push新数组
    if(!intervals.length) return [newInterval];

    const [ left, right ] = newInterval;

    // 设立左右指针
    let p1 = 0,
        p2 = intervals.length - 1;

    if( getPos(right, intervals[0]) == 'l' ) {
        return [ newInterval, ...intervals ]
    } else if ( getPos(left, intervals[intervals.length-1]) == 'r' ) {
        return [ ...intervals, newInterval ]
    }

    while( p1 <= p2 ) {
        if ( getPos( left, intervals[p1] ) == 'r' ) {
            p1++;
        } else if ( getPos( right, intervals[p2] ) == 'l' ) {
            p2--;
        } else {
            intervals.splice(p1, p2-p1+1, [Math.min(left, intervals[p1][0]),Math.max(right, intervals[p2][1])]);
            break;
        }
    };

    if( p1 > p2 ) intervals.splice(p1,0,newInterval)

    return intervals;

    function getPos( num, arr ) {
        if( num < arr[0] ) {
            return 'l';
        } else if ( num > arr[1] ) {
            return 'r';
        } else if ( num >= arr[0] && num <= arr[1] ) {
            return 'm';
        }
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=57 lang=javascript
 *
 * [57] 插入区间
 */

// @lc code=start
/**
 * @param {number[][]} intervals
 * @param {number[]} newInterval
 * @return {number[][]}
 */
var insert = function(intervals, newInterval) {
    intervals.push(newInterval);
    // 按左边界升序排列
    intervals.sort((a,b) => a[0] - b[0]);
    // 获取满足要求的子数组位置
    let rIndex = [];
    for( let p = 0; p < intervals.length; p++ ) {
        if ( isOverlap(intervals[p], intervals[p+1]) ) {
            intervals.splice(p+1, 1, mergeOverlap(intervals[p], intervals[p+1]));
        } else {
            rIndex.push(p)
        }
    };

    // 返回符合要求的区间
    let r = [];

    for(let i=0;i<rIndex.length;i++) {
        r.push(intervals[rIndex[i]])
    }

    return r;

    // 判断两个数组是否重叠
    function isOverlap( arr1, arr2 ) {
        if(!arr1 || !arr2) return false;
        
        const [ a1, b1 ] = arr1,
              [ a2, b2 ] = arr2;
        
        if( ( a1 <= a2 && a2 <= b1 && b1 <= b2 ) || 
            ( a1 <= a2 && b2 <= b1 ) || 
            ( a2 <= a1 && b1 <= b2 ) || 
            ( a2 <= a1 && a1 <= b2 && b2 <= b1 ) 
        ) {
            return true;
        }
        
        return false;
    };

    // 重叠两数组进行合并
    function mergeOverlap(arr1, arr2) {
        const a = [...arr1, ...arr2].sort((a,b) => a-b);
        return [ a[0], a[ a.length - 1 ] ];
    }
};

有两种方案:1、首尾指针:将新数组左右边界和原数组的每个区间进行位置比较,对在区间左侧、区间中、区间右侧三种情况进行分别处理,最大限度的使用原数组进行合并或增加操作;2、先插入后排序再次合并,利用56题的解法,只需要将新数组插入后按区间左侧进行升序排列,然后对有重叠的区间进行合并即可,思路比较容易想到,但效率不高



2020.10.30

No.228 汇总区间

给定一个无重复元素的有序整数数组 nums 。

返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。

列表中的每个区间范围 [a,b] 应该按如下格式输出:

"a->b" ,如果 a != b "a" ,如果 a == b  

示例 1:

输入:nums = [0,1,2,4,5,7] 输出:["0->2","4->5","7"] 解释:区间范围是: [0,2] --> "0->2" [4,5] --> "4->5" [7,7] --> "7" 示例 2:

输入:nums = [0,2,3,4,6,8,9] 输出:["0","2->4","6","8->9"] 解释:区间范围是: [0,0] --> "0" [2,4] --> "2->4" [6,6] --> "6" [8,9] --> "8->9" 示例 3:

输入:nums = [] 输出:[] 示例 4:

输入:nums = [-1] 输出:["-1"] 示例 5:

输入:nums = [0] 输出:["0"]  

提示:

0 <= nums.length <= 20 -231 <= nums[i] <= 231 - 1 nums 中的所有值都 互不相同

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/su… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=228 lang=javascript
 *
 * [228] 汇总区间
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {string[]}
 */
var summaryRanges = function(nums) {
    if( nums.length == 0 ) {
        return [];
    } else if ( nums.length == 1 ) {
        return [ nums[0] + '' ]
    } else {
        let rIndex = [],
            r = [],
            // 双指针
            p1 = 0, 
            p2 = 1;
        while ( p2 < nums.length ) {
            if( nums[p2] - nums[p1] == ( p2 - p1 ) ) {
                p2++
            } else {
                rIndex.push(p2);
                p1 = p2;
                p2++;
            }
        }
        rIndex.unshift(0);
        rIndex.push(nums.length);
        // 构造r数组
        for(let t1=0,t2=1;t2<rIndex.length;t1++,t2++) {
            r.push(nums.slice(rIndex[t1],rIndex[t2]))
        }

        r = r.map(item => {if(item.length==1){ return item[0] + ''}else{ return item[0] + '->' + item[item.length-1]}});

        return r;
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=228 lang=javascript
 *
 * [228] 汇总区间
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {string[]}
 */
var summaryRanges = function(nums) {
    const res = [];
    nums.push(Infinity);
    for (let i = 0, j = 1; j < nums.length; j++) {
        if (nums[j] - 1 !== nums[j - 1]) {
            res.push(i === j - 1 ? '' + nums[i] : `${nums[i]}->${nums[j - 1]}`);
            i = j;
        }
    }
    return res;
};

双指针移动窗口,有两种写法:1、获取切割位点,对切割出来的数组进行格式转换;2、



总结:

  1. 区间问题常见做法主要是通过双指针判断区间的左右位置,需要注意一些边界问题的处理;
  2. 对于某些特殊场景的题目,可以考虑动态规划以及通项公式的总结归纳