三、路径问题
2020.10.19
No.62 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
示例 1:
输入: m = 3, n = 2 输出: 3 解释: 从左上角开始,总共有 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 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
来源:力扣(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"],如下所示。
我们可以按下面的指令规则行动:
如果方格存在,'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'的距离,输出最后的答案
总结:
- 路径问题主要用到的通解方法是动态规划,需要考虑边界问题以及对每个题目的状态转移方程的归纳;
- 对于某些特殊场景的题目,可以考虑排列组合、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、
总结:
- 区间问题常见做法主要是通过双指针判断区间的左右位置,需要注意一些边界问题的处理;
- 对于某些特殊场景的题目,可以考虑动态规划以及通项公式的总结归纳