作者的话
当前进度:【简单 12】【中等 6】【困难 2】。
- 建议在 PC 端阅读该文章,以获得最佳的阅读体验。
- 该文章中的题解,解法不一定最优,但注释尽量详细。
- 该文章已迁移至Github,望各位看官多多支持。
1.两数之和
// 题目:https://leetcode-cn.com/problems/two-sum/
// 难度:简单
// 标签:数组、哈希表
// 执行用时:64 ms, 击败了 95.98% 的用户
// 内存消耗:36.8 MB, 击败了 6.78% 的用户
// 解题思路:
// 符合直觉的做法是,通过两层循环找到符合要求的两个数字,并返回他们的下标
// 然而其时间复杂度为O(n^2),效率低
// 故考虑利用哈希表来降低时间复杂度:
// 1.遍历nums,建立key和val分别为数字及其在nums中的下标的哈希表
// 2.再次遍历nums,根据当前数字找到目标数字,再根据哈希表找到目标数字的下标
// 3.返回当前数字的下标和目标数字的下标
// 其中,因为利用哈希表查找数字的下标的时间复杂度为O(1)
// 所以整个流程(仅有单层循环)的时间复杂度仅为O(n)
// 而且实际解题时,前两步可以整合为一步
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
// 1.定义numsMap,其key和val分别表示数字和数字在nums中的下标
// 如numsMap.set(2,3)表示:收集数字2及其在nums中的下标3到numsMap中
const numsMap = new Map();
// 2.遍历nums
for (let i = 0; i < nums.length; i++) {
// 2.1 num1和num2加起来等于target,为符合要求的两个数字
const num1 = nums[i],
num2 = target - num1;
// 2.2 在numsMap中寻找num2在nums中的下标j
// 如果存在,则返回下标i和下标j
if (numsMap.has(num2)) {
const j = numsMap.get(num2);
return [i, j];
}
// 2.3 否则,将num1及其在nums中的下标收集到numsMap中
numsMap.set(num1, i);
}
};
2.两数相加
// 题目:https://leetcode-cn.com/problems/add-two-numbers/
// 难度:中等
// 标签:链表、数学
// 执行用时:124 ms, 击败了 88.68% 的用户
// 内存消耗:41.6 MB, 击败了 13.23% 的用户
// 解题思路:
// 以node1=[2,4,3,9],node2=[2,4,8]为例
// 两个节点分别表示数字9342和842,其相加的运算过程:
// (格式:node1的值+node2的值+进位值)
// 1.个位数相加:2+2+0=4
// 2.十位数相加:4+4+0=8
// 3.百位数相加:3+8+0=11,进1位
// 4.千位数相加:9+1+1=10,进1位
// 5.万位数相加:0+0+1=1
// 相加结果为10184,故应返回节点[4,8,1,0,1]
// 由此得到解题思路:
// (nodeRet为结果节点)
// 1.个位数相加:nodeRet.val=node1.val+node2.val+进位值
// 2.十位数相加:nodeRet.next.val=node1.next.val+node2.next.val+进位值
// 3.百位数相加:nodeRet.next.next.val=node1.next.next.val+node2.next.next.val+进位值
// ...
// 重复以上步骤,直到node1和node2的next抵达null,无法再递进下去为止
/**
* @param {ListNode} node1
* @param {ListNode} node2
* @return {ListNode}
*/
var addTwoNumbers = function (node1, node2) {
// 1.定义nodeRet和nodeCur
// nodeRet表示结果节点
// nodeCur表示结果节点的递进处,初始指向nodeRet
const nodeRet = new ListNode(0);
let nodeCur = nodeRet;
// 2.定义forward,用于表示进位值,初始为0
let forward = 0;
// 3.遍历node1和node2
while (node1 || node2) {
// 3.1 sum表示当前位的和,初始值为forward
let sum = forward;
// 3.2 如果node1存在,sum加上node1.val,同时node1递进一步
// node2同理
if (node1) {
sum += node1.val;
node1 = node1.next;
}
if (node2) {
sum += node2.val;
node2 = node2.next;
}
// 3.3 将sum的个位数赋值给nodeCur.val
nodeCur.val = sum % 10;
// 3.4 将sum的十位数赋值给forward
forward = (sum - nodeCur.val) / 10;
// 3.5 node1和node2递进后,如果其任一存在,则说明递进尚未结束
// 故设置nodeCur.next指向一个新节点,同时nodeCur递进一步
if (node1 || node2) {
nodeCur.next = new ListNode(0);
nodeCur = nodeCur.next;
}
}
// 4.递进结束后,如果有进位未处理,则补充一个val为forward的新节点
if (forward > 0) nodeCur.next = new ListNode(forward);
// 5.返回结果节点
return nodeRet;
};
3.无重复字符的最长子串
// 题目:https://leetcode-cn.com/problems/two-sum/
// 难度:中等
// 标签:哈希表、双指针、字符串、Sliding Window
// 执行用时:92 ms, 击败了 92.35% 的用户
// 内存消耗:38.3 MB, 击败了 71.23% 的用户
// 解题思路:
// 以s="abcdadc"为例
// 对其进行遍历,当前下标用i表示
// 当前无重复字符子串用sub表示,当前sub的长度用len表示
// 迄今sub的最大长度用lenMax表示
// i=0时,sub="a",故len=1,lenMax=1
// i=1时,sub="ab",len=2,lenMax=2
// i=2时,sub="abc",len=3,lenMax=3
// i=3时,sub="abcd",len=4,lenMax=4
// i=4时,出现了重复字符"a",sub="bcda",len=4,lenMax=4
// i=5时,出现了重复字符"d",sub="ad",len=2,lenMax=4
// i=6时,sub="adc",len=3,lenMax=4
// 以此为思路,从左向右遍历s,其间判断是否出现重复字符
// 如果未出现,len加1;如果出现,根据重复字符下标和当前下标更新len
// 每次更新len时,需要判断len是否大于lenMax,如果是,则也更新lenMax
// 所以解题重点在于,如何判断是否出现重复字符和获取重复字符下标
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function (s) {
// 1.定义ret,用于表示lenMax
let ret = 0;
// 2.定义charMap,其key和val分别为字符和字符在s中的下标
// 其是用于判断是否出现重复字符和获取重复字符下标的关键
const charMap = new Map();
// 3.定义left和right,分别用于表示重复字符下标和当前下标
// left初始为-1,right初始为0
let left = -1,
right = 0;
// 4.从左向右遍历s
while (right < s.length) {
// 4.1 获取当前下标的字符char
const char = s[right];
// 4.2 如果charMap中有char,说明出现了重复字符
// 获取该重复字符的下标left_,当left_大于left时才更新left
// 如果不判断两者的大小关系就直接更新left,会导致如下情况:
// 对于"abcba",right=3时,left=left_=1,sub="cb",没问题
// 而right=4时,left=left_=0,sub="abcba",明显不正确
// 这是因为left只能向右单向移动,如果往回走,中途可能遇到重复字符
if (charMap.has(char)) {
const left_ = charMap.get(char);
left = Math.max(left, left_);
}
// 4.3 收集char及其在s中的下标到charMap中
charMap.set(char, right);
// 4.4 此时left为重复字符下标,right为当前字符下标
// 如"abc"遍历到"c"时,left=-1,right=2
// right减去left即为当前无重复字符子串长度len
ret = Math.max(ret, right - left);
// 4.5 right自增
right++;
}
// 5.返回结果
return ret;
};
7.整数反转
// 题目:https://leetcode-cn.com/problems/reverse-integer/
// 难度:简单
// 标签:数学
// 执行用时:84 ms, 击败了 87.75% 的用户
// 内存消耗:35.7 MB, 击败了 93.91% 的用户
// 解题思路:
// 以x=-3456为例,考虑如何对其进行反转
// 计算3456%10,可以获取3456的个位数6
// 计算(3456-6)/10,可以将其转换为规模更小的数345
// 而计算345%10,可以获取3456的十位数5
// 累积下来,可以依次获取6、5、4、3这四个数
// 声明一个变量ret,用于表示反转后的数,初始为0
// 每获取到一个数,就对ret进行一次如下形式的更新
// 1.获取到6时,ret=ret*10+6=0+6=6
// 2.获取到5时,ret=ret*10+5=60+5=65
// 3.获取到4时,ret=ret*10+4=65*10+4=654
// 4.获取到3时,ret=ret*10+3=654*10+3=6543
// 最后需要给ret加上负号,并对ret做溢出判断
/**
* @param {number} x
* @return {number}
*/
var reverse = function (x) {
// 1.定义ret,用于表示反转后的整数
let ret = 0;
// 2.定义sign,用于表示x的正负
// 如果x为正数,sign等于1
// 如果x为负数,sign等于-1
const sign = Math.sign(x);
// 3.将x转换为正数,以便于后面的计算
x *= sign;
// 4.累积获取x各位上的数字并更新ret
// 这里以x=3456为例
while (x > 0) {
// 4.1 获取x个位上的数6
const n = x % 10;
// 4.2 x减去6、再除以10后,x=345
x = (x - n) / 10;
// 4.3 ret乘以10、再加上6后,num=6
ret = ret * 10 + n;
// 4.4 第1轮,n=6,x=345,ret=6
// 第2轮,n=5,x=34,ret=60+5=65
// 第3轮,n=4,x=3,ret=650+4=654
// 第4轮,n=3,x=0,ret=6540+3=6543
// 其后,由于x=0,循环中断
}
// 5.判断x是否溢出,如果溢出返回0
if (ret >= Math.pow(2, 31)) return 0;
// 6.否则,将num带上符号并返回
return ret * sign;
};
9.回文数
// 题目:https://leetcode-cn.com/problems/palindrome-number/
// 难度:简单
// 标签:数学
// 执行用时:200 ms, 击败了 96.07% 的用户
// 内存消耗:44.5 MB, 击败了 98.00% 的用户
// 解题思路:
// 如果x为负数,那么其必然不是回文数
// 如果x为正数,可以基于第7题,求得反转后的x
// 并根据反转后的x是否和x相等来判断其是否为回文数
/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function (x) {
// 1.如果x为负数,其必然不是回文数
if (x < 0) return false;
// 2.否则,需判断反转后的x是否与x相等
// 如果相等,则x是回文数;如果不相等,则x不是回文数
return reverse(x) === x;
// 3.定义函数reverse,用于反转数字
// (该函数借由第7题包装而来,不再详述)
function reverse(x) {
let y = 0;
while (x > 0) {
const n = x % 10;
x = (x - n) / 10;
y = y * 10 + n;
}
return y;
}
};
51.N 皇后
// 题目:https://leetcode-cn.com/problems/n-queens/
// 难度:困难
// 标签:回溯算法
// 执行用时:68 ms, 击败了 99.44% 的用户
// 内存消耗:38.2 MB, 击败了 100.00% 的用户
// 解题思路:
// 根据摆放规则,每行仅能且必须摆放一个皇后,所以可以将摆放情况用数组来表示
// 如[2,4,1,3]表示第1~4行的皇后分别摆放在第2、4、1、3列上
// 基于此,采用递归+回溯的方式来摆放皇后
/**
* @param {number} n
* @return {string[][]}
*/
var solveNQueens = function (n) {
// 1.定义rets,用于存放所有的摆放情况ret1、ret2、...
const rets = [];
// 2.定义用于摆放皇后的函数arrange,其参数ret为当前的摆放情况
// 如arrange([3,1])表示,第1~2行的皇后已经分别摆放在第3、1列上了
// 将基于当前摆放情况,进行下一行(第3行)的摆放
function arrange(ret) {
// 2.1 第1~ret.length行的皇后都已经被摆放好了
// 那么即将进行摆放的行数row为ret.length+1
const row = ret.length + 1;
// 2.2 如果row大于n,表示摆放已经结束了
// 此时将ret添加到rets中,并结束摆放
if (row > n) {
rets.push(ret);
return;
}
// 2.3 反之,摆放未结束,需执行摆放
// 2.3.1 第row行的棋子可在1~n列内摆放
for (let col = 1; col <= n; col++) {
// 2.3.2 定义flag,用于表示第row行的皇后摆放在第col列是否可行
let flag = true;
// 2.3.3 遍历ret,即遍历已经摆放过的皇后(第1~row_行)
// 因为row_是从1开始计数的,而ret下标是从0开始计数的
// 所以第row_行的皇后被摆放在的列用ret[row_-1]表示
for (let row_ = 1; row_ < row; row_++) {
// 2.3.3.1 用col_表示第row_行的皇后被摆放在的列数
const col_ = ret[row_ - 1];
// 2.3.3.2 因为ret的格式确保了每行只摆放一个皇后
// 所以无需判断是否有同行的情况
// 2.3.3.3 判断是否有同列的情况
if (col === col_) {
flag = false;
break;
}
// 2.3.3.4 判断是否有同对角线的情况
if (row - row_ === Math.abs(col - col_)) {
flag = false;
break;
}
}
// 2.3.4 当遍历完已经摆放过的皇后时,如果flag仍为true
// 表示将第row行皇后摆放在第col列是可行的
if (flag) {
// 2.3.4.1 既然可行,那么进行摆放
// ret[row-1]表示第row行的摆放情况
ret[row - 1] = col;
// 2.3.4.2 第row行已经摆放好了,继续下一行的摆放
// 为了防止不同分支情况互相影响,传递的参数为ret的复制品
arrange([...ret]);
}
}
}
// 3.进行初始摆放,即在摆放完第一行的情况下进行第二行的摆放
for (let col = 1; col <= n; col++) {
arrange([col]);
}
// 4.返回结果前要对rets格式进行转换
// 如觉得不好理解,可按步骤编号从内向外看
// 4.2 返回经过拼接的ret们
return rets.map((ret) => {
// 4.1 遍历ret,col表示当前行的皇后摆放在第col列上
return ret.map((col) => {
// 4.1.1 建立数组,长度为棋盘尺寸,元素均填充为"."
const charArr = new Array(n).fill(".");
// 4.1.2 将col-1下标处的元素修改为"Q",表示皇后
charArr[col - 1] = "Q";
// 4.1.3 拼接为字符串并返回
return charArr.join("");
});
});
};
52.N 皇后 II
// 题目:https://leetcode-cn.com/problems/n-queens-ii/
// 难度:困难
// 标签:回溯算法
// 执行用时:72 ms, 击败了 78.99% 的用户
// 内存消耗:37.5 MB, 击败了 100.00% 的用户
// 解题思路:
// 该题需求出N皇后的解的个数,而第51题需求N皇后的所有解
// 两题大同小异,可以基于第51题来解答该题
/**
* @param {number} n
* @return {string[][]}
*/
var totalNQueens = function (n) {
// 以下只对不同于第51题的部分做说明
// 1.定义rets,用于表示解的个数,初始为0
let rets = 0;
function arrange(ret) {
const row = ret.length + 1;
// 2.结束摆放,解的个数加1
if (row > n) {
rets++;
return;
}
for (let col = 1; col <= n; col++) {
let flag = true;
for (let row_ = 1; row_ < row; row_++) {
const col_ = ret[row_ - 1];
if (col === col_) {
flag = false;
break;
}
if (row - row_ === Math.abs(col - col_)) {
flag = false;
break;
}
}
if (flag) {
ret[row - 1] = col;
arrange([...ret]);
}
}
}
for (let col = 1; col <= n; col++) {
arrange([col]);
}
// 3.直接返回rets
return rets;
};
58.最后一个单词的长度
// 题目:https://leetcode-cn.com/problems/length-of-last-word/
// 难度:简单
// 标签:字符串
// 执行用时:60 ms, 击败了 93.05% 的用户
// 内存消耗:32.3 MB, 击败了 100.00% 的用户
/**
* @param {string} s
* @return {number}
*/
var lengthOfLastWord = function (s) {
// 1.定义ret,表示最后一个单词的长度
let ret = 0;
// 2.去除s尾端的空格
s = s.trimEnd();
// 3.从后向前遍历s
for (let i = s.length - 1; i >= 0; i--) {
// 3.1 如遇到空格,就说明单词结束了,应中断循环
if (s[i] === " ") break;
// 3.2 否则,ret加1
ret++;
}
// 4.返回结果
return ret;
};
66.加一
// 题目:https://leetcode-cn.com/problems/plus-one/
// 难度:简单
// 标签:数组
// 执行用时:56 ms, 击败了 98.83% 的用户
// 内存消耗:32.7 MB, 击败了 100.00% 的用户
/**
* @param {number[]} digits
* @return {number[]}
*/
var plusOne = function (digits) {
// 1.在digits首端插入数字0
// 以应对可能出现的进位情况
digits.unshift(0);
// 2.定义index,用于从后向前遍历digits
let index = digits.length - 1;
// 3.末尾数字加1
digits[index] += 1;
// 4.遍历digits
while (index > 0) {
// 4.1 如果当前数为10,则进位
if (digits[index] === 10) {
// 4.2 当前数字从10变为0
digits[index] = 0;
// 4.3 前一个数字加1
digits[index - 1] += 1;
}
// 4.4 index减1
index--;
}
// 5.如果digits首端仍为0,则将其删除
if (digits[0] === 0) digits.shift();
// 6.返回结果
return digits;
};
292.Nim 游戏
// 题目:https://leetcode-cn.com/problems/nim-game/
// 难度:简单
// 标签:脑筋急转弯、极小化极大
// 执行用时:68 ms, 击败了 57.99% 的用户
// 内存消耗:32.3 MB, 击败了 100.00% 的用户
// 解题思路:
// 首先,分情况进行讨论:
// 1.当n=1,2,3时【我方赢】
// 2.当n=4时,拿1剩3,拿2剩2,拿3剩1。对方面临情况1【我方输】
// 3.当n=5,6,7时,有5拿1剩4,有6拿2剩4,有7拿3剩4。我方能使对方面临情况2【我方赢】
// 4.当n=8时,拿1剩7,拿2剩6,拿3剩5。对方面临情况3,故对方能使我方面临情况2【我方输】
// 5.当n=9,10,11时,同理,我方能使对方面临情况4【我方赢】
// 6.当n=12时,同理,对方能使我方面临情况4【我方输】
// ...
// 发现规律:
// 当n为4的倍数时,最终对方会面临着n=1,2,3的情况,对方赢
// 否则,最终我方会面临着n=1,2,3的情况,我方赢
// 证明:
// 1.设我方初始面临n=4x的情况,拿完可能剩4x-1,4x-2,4x-3
// 2.对方再拿,能使我方面临4x-4,即n=4(x-1)的情况
// 3.再一个来回,我方面临n=4(x-2)的情况
// 4.最终,我方将面临x-2=1,即n=4的情况,我方输
// (否则,对方最终将面临n=4的情况,我方赢)
/**
* @param {number} n
* @return {boolean}
*/
var canWinNim = function (n) {
return n % 4;
};
319.灯泡开关
// 题目:https://leetcode-cn.com/problems/bulb-switcher/submissions/
// 难度:中等
// 标签:脑筋急转弯、数学
// 执行用时:64 ms, 击败了 79.25% 的用户
// 内存消耗:32.3 MB, 击败了 100.00% 的用户
// 解题思路:
// 据题意,相当于在第i轮时,每第i个灯泡就切换状态
// 第1轮亦是,每1个灯泡就切换状态,即每个灯泡都从关切换成开
// 试分析灯泡x在整个过程中的切换次数为P(x),1<=x<=n
// 1.首先,对于灯泡x,i=1,x时,是必切换的
// 比如,对于灯泡8,i=1,8时,是必切换的,即2次切换
// 2.推之,如果存在数k能被x整除,那么x/k也一定能被x整除
// 即对于灯泡x,i=k,x/k时,灯泡x会切换
// 2.1 一般情况下,k和x/k总是成对出现,所以总共会有偶数次切换
// 比如,对于灯泡15,存在k=1,3,5,15,1与3、5与15成对出现
// 2.2 但是有一类特殊情况,当x为完全平方数时,会有奇数次切换
// 比如,对于灯泡16,存在k=1,2,4,8,16,1与16、2与8成对出现
// 这是因为k=4时,x/4=4,与k重合,4不像1和2一样,有它自己的小伙伴
// 总而言之,对于灯泡x,如x为完全平方数,则有奇数次切换,否之有偶数次切换
// 而且,若一灯泡若历经偶数次切换,那么它最终保持初始状态,为“关”;
// 若一灯泡若历经奇数次切换,那么它最终不同于初始状态,为“开”
// 所以在1~n范围内,若有m个完全平方数,最终就会有m个灯泡为“开”
// m的值易求,为“n开方后向下取整”
/**
* @param {number} n
* @return {number}
*/
var bulbSwitch = function (n) {
return Math.floor(Math.sqrt(n));
};
415.字符串相加
// 题目:https://leetcode-cn.com/problems/add-strings/
// 难度:简单
// 标签:字符串
// 执行用时:72 ms, 击败了 94.59% 的用户
// 内存消耗:37.2 MB, 击败了 25.00% 的用户
/**
* @param {string} num1
* @param {string} num2
* @return {string}
*/
var addStrings = function (num1, num2) {
// 1.定义ret,用于表示结果字符串
// index1和index2分别表示遍历num1和num2时的下标
// 因为要末尾对齐,所以index1指向num1末尾,index2同理
// next表示是否进位,比如计算9+9=18时需将next设为true
let ret = "",
index1 = num1.length - 1,
index2 = num2.length - 1,
next = false;
// 2.以num1="35",num2="9"为例
// 从末尾向开头方向遍历num1和num2
while (index1 >= 0 || index2 >= 0) {
// 2.1 第一轮中,index1为1,index2为0,故n1为5,n2为9
// 同时,index1和index2都减1
const n1 = Number(num1[index1--] || "0"),
n2 = Number(num2[index2--] || "0");
// 2.2 计算n1和n2的和,如果next为true,表示要进位
// 刚好Number(false)等于0,Number(true)等于1
// 此时next为false,不用进位,故sum等于14
const sum = Number(next) + n1 + n2;
// 2.3 sum大于等于10,表示下一轮时要进位了,故将其设为true
next = sum >= 10;
// 2.4 连接"4"和"",并赋值给ret
ret = String(sum % 10).concat(ret);
// 2.5 第二轮过程:
// 2.5.1 index1为0,index2为-1(越界),故n1为3,n2为0
// 2.5.2 next为true,表示进位,故sum=1+3+0=4
// 2.5.3 sum小于10,下一轮不进位,将next设为false
// 2.5.4 连接"4"和"4",并赋值给ret
// 2.6 第二轮结束后,因为index2不再大于等于0,所以会结束循环
}
// 3.如果next为true,得在ret前面加上"1"
if (next) ret = String("1").concat(ret);
// 4.返回结果
return ret;
};
441.排列硬币
// 题目:https://leetcode-cn.com/problems/arranging-coins/
// 难度:简单
// 标签:数学、二分查找
// 执行用时:92 ms, 击败了 92.75% 的用户
// 内存消耗:37.1 MB, 击败了 100.00% 的用户
// 解题思路:
// 要达到第k行,需要1+2+...+k-1+k个硬币
// 即需要求f(k)=1/2*k(1+k)=n的根
// 根据一元二次方程求根公式,k=(-1+(1+8n)^(1/2))/2
// 将k向下取整,即为本题结果
/**
* @param {number} n
* @return {number}
*/
var arrangeCoins = function (n) {
return Math.floor((Math.sqrt(1 + 8 * n) - 1) / 2);
};
539.最小时间差
// 题目:https://leetcode-cn.com/problems/minimum-time-difference/
// 难度:中等
// 标签:字符串
// 执行用时:92 ms, 击败了 87.69% 的用户
// 内存消耗:38.6 MB, 击败了 100.00% 的用户
/**
* @param {string[]} timePoints
* @return {number}
*/
var findMinDifference = function (timePoints) {
// 1.为便于比较,先遍历timePoints,将时间转换为分钟
// 并存放在timeMinutes中,如2:15=>135,12:30=>750
const timeMinutes = timePoints.map((timePoint) => {
// 1.1 获取hour和minute,注意,这里两者均为String类型
const [hour, minute] = timePoint.split(":");
// 1.2 先将hour和minute转换为Number类型,再计算时间
return Number(hour * 60) + Number(minute);
});
// 2.对timeMinutes从小到大进行排序
timeMinutes.sort((a, b) => a - b);
// 3.在timeMinutes末尾添加“最小时间+一圈”
// 这是为了应对23:59和0:00这种情况
timeMinutes.push(timeMinutes[0] + 24 * 60);
// 4.找出timeMinutes中最小的相邻时间间隔
// 4.1 定义dMin,用于表示最小时间差
let dMin = 24 * 60;
// 4.2 从下标1开始,遍历timeMinutes
for (let i = 1; i < timeMinutes.length; i++) {
// 4.2.1 计算时间差
const d = timeMinutes[i] - timeMinutes[i - 1];
// 4.2.2 如果d小于dMin,则将d赋值给dMin
dMin = Math.min(dMin, d);
}
// 5.返回结果
return dMin;
};
557.反转字符串中的单词 III
// 题目:https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/
// 难度:简单
// 标签:字符串
// 执行用时:60 ms, 击败了 100.00% 的用户
// 内存消耗:36.6 MB, 击败了 100.00% 的用户
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function (s) {
// (这里为了分步骤讲解,未采用链式调用)
// 1.以"Let's take LeetCode contest"为例,对其进行分割
// 得到单词数组["Let's","take","LeetCode","contest"]
let words = s.split(" ");
// 2.遍历words
words = words.map((word) => {
// 2.1 以"take"为例,其被分割为["t","a","k","e"]
const letters = word.split("");
// 2.2 颠倒["t","a","k","e"]为["e","k","a","t"]
letters.reverse();
// 2.3 将["e","k","a","t"]合并为"ekat"
return letters.join("");
});
// 3.合并颠倒的word们,并返回结果
return words.join(" ");
};
709.转换成小写字母
// 题目:https://leetcode-cn.com/problems/to-lower-case/
// 难度:简单
// 标签:字符串
// 执行用时:48 ms, 击败了 99.64% 的用户
// 内存消耗:32.4 MB, 击败了 100.00% 的用户
/**
* @param {string} str
* @return {string}
*/
var toLowerCase = function (str) {
// 1.定义ret,表示结果字符串
let ret = "";
// 2.遍历str
for (let i = 0; i < str.length; i++) {
// 2.1 获取下标i字符的ASCII码
let code = str.charCodeAt(i);
// 2.2 如果code在[65,90]区间内,说明其是大写字符
// 大小写字符的编码差为32,所以加32将其转为小写字符
if (code >= 65 && code <= 90) code += 32;
// 2.3 将code转回字符,并添加到ret末端
ret += String.fromCharCode(code);
}
// 3.返回结果
return ret;
};
777.在 LR 字符串中交换相邻字符
// 题目:https://leetcode-cn.com/problems/swap-adjacent-in-lr-string/
// 难度:中等
// 标签:脑筋急转弯
// 执行用时:68 ms, 击败了 100.00% 的用户
// 内存消耗:36.6 MB, 击败了 100.00% 的用户
// 解题思路:
// 以一符合要求的情况为例:
// (X相当于空地,L向左走,R向右走,L和R不能穿越彼此)
// start: RXXLRXRXL => R--LR-R-L => RLRRL
// end: XRLXXRRLX => -RL--RRL- => RLRRL
// 发现,要符合要求,应符合以下规律:
// 1.start和end中,L的数量相等
// 2.start和end中,R的数量相等
// 3.start和end中,L和R的排序相同,即有一一对应关系
// 3.1 start中的L的位置要比end中的对应的L的位置要后(或相同)
// 3.2 start中的R的位置要比end中的对应的R的位置要前(或相同)
/**
* @param {string} start
* @param {string} end
* @return {boolean}
*/
var canTransform = function (start, end) {
// 1.声明countL和countR
// countL: end比start多出的L的数量
// countR: start比end多出的R的数量
// countL和countR的值能用于判断是否符合规律1和2
let countL = 0,
countR = 0;
// 2.遍历字符串
for (let i = 0; i < start.length; i++) {
// 2.1 因为L是向左移动的,end中L的位置比start中的要前(或相同)
// 所以先判断end[i],再判断start[i]
// 2.1.1 如果end[i]为L,countL加1
if (end[i] === "L") countL++;
// 2.1.2 如果start[i]为L,countL减1
if (start[i] === "L") {
countL--;
// 2.1.3 R!==0这个条件,是用于判断L和R是否穿越了彼此
// countR<0这个条件,是用于判断start中L的数量是否超额
// 如果这两个条件中任一为真,则中断循环
// 且中断循环时,countL和countR其一不为0
if (countL < 0 || countR !== 0) break;
}
// 2.2 同理,对R进行一系列判断
if (start[i] === "R") countR++;
if (end[i] === "R") {
countR--;
if (countR < 0 || countL !== 0) break;
}
}
// 3.经过上面一系列的判断后,如果countL和countR均为0
// 则表示符合规律1、2、3,即符合要求
return countL === 0 && countR === 0;
};
1033.移动石子直到连续
// 题目:https://leetcode-cn.com/problems/moving-stones-until-consecutive/
// 难度:简单
// 标签:脑筋急转弯
// 执行用时:68 ms, 击败了 77.78% 的用户
// 内存消耗:33.1 MB, 击败了 100.00% 的用户
/**
* @param {number} a
* @param {number} b
* @param {number} c
* @return {number[]}
*/
var numMovesStones = function (a, b, c) {
// 1.对石子位置进行排序:左记为x,中间记为y,右记为z
const [x, y, z] = [a, b, c].sort((a, b) => a - b);
// 2.区分可能出现的3种情况
// 2.1 当x、y、z连续时,无需移动
if (z - x === 2) {
return [0, 0];
}
// 2.2 当x和y间隔或y和z间隔小于等于2时
// 最少只需移动1步,如[1,3,5]、[2,3,5]
// 最多需移动(z-y-1)+(y-x-1)=z-x-2步
else if (y - x <= 2 || z - y <= 2) {
return [1, z - x - 2];
}
// 2.3 其他情况下,最少需移动2步,如[1,4,7]
// 最多需移动的步数同2.2
else {
return [2, z - x - 2];
}
};
1078.Bigram 分词
// 题目:https://leetcode-cn.com/problems/occurrences-after-bigram/
// 难度:简单
// 标签:哈希表
// 执行用时:68 ms, 击败了 60.00% 的用户
// 内存消耗:32.3 MB, 击败了 100.00% 的用户
/**
* @param {string} text
* @param {string} first
* @param {string} second
* @return {string[]}
*/
var findOcurrences = function (text, first, second) {
// 1.以空格为隔板,对text进行分割,将其转换为单词数组
const words = text.split(" ");
// 2.遍历words,筛选出所有的符合要求的词,并返回结果
return words.filter((word, index) => {
// 2.1 wordIndex为0或1时,前面的词数不够两个
if (index < 2) return false;
// 2.2 判断前两个词是否分别为first和second
if (words[index - 2] === first && words[index - 1] === second) return true;
});
};
1227.飞机座位分配概率
// 题目:https://leetcode-cn.com/problems/airplane-seat-assignment-probability/
// 难度:中等
// 标签:脑筋急转弯、数学、动态规划
// 执行用时:64 ms, 击败了 78.57% 的用户
// 内存消耗:32.1 MB, 击败了 100.00% 的用户
// 解题思路:
// 参考:https://leetcode-cn.com/problems/airplane-seat-assignment-probability/solution/ju-ti-fen-xi-lai-yi-bo-by-jobhunter-4/
// 易知,P(1)=1,P(2)=0.5
// 当n>=3时,设有乘客1~n,对应座位1~n
// 1.分析乘客1
// 1.1 坐到位置1的概率为1/n,此后乘客n能坐到位置n
// 1.2 坐到位置n的概率为1/n,此后乘客n不能坐到位置n
// 1.3 坐到位置k∈[2,n-1]的概率为(n-2)/n
// 此后乘客2~k-1能坐到正确位置上,乘客k待分析
// k.如果出现了情况1.3,则分析乘客k
// 此时剩下的位置数量为n-k+1,记为a
// k.1 坐到位置1的概率为1/a,此后乘客n能坐到位置n
// k.2 坐到位置n的概率为1/a,此后乘客n不能坐到位置n
// k.3 坐到位置m∈[k+1,n-1]的概率为(a-2)/a
// 此后乘客k+1~n-1能坐到正确位置上,乘客m待分析
// 会发现,乘客1和乘客k面临的选择是一样的
// 只是选择规模不一样,前者为n,后者为a=n-k+1
// 因k在[2,n-1]内均匀分布,故a在[2,n-1]内均匀分布
// 由此推出:
// 1.P(n) = 1/n + 1/n * (P(2) + ... + P(n-1))
// = 1/n * (1 + P(2) + ... + P(n-1))
// 2.P(n+1) = 1/(n+1) * (1 + P(2) + ... + p(n-1) + p(n))
// 3.(n+1) * p(n+1) - n * P(n) = P(n)
// 4.P(n+1) = P(n)
// 因为P(3) = 1/3 * (1 + 0.5) = 0.5
// 所以n>=3时,P(n) = P(3) = 0.5
// 整理上述式子,得:
// n=1时,P(n) = 1
// n>1时,P(n) = 0.5
/**
* @param {number} n
* @return {number}
*/
var nthPersonGetsNthSeat = function (n) {
return n === 1 ? 1 : 0.5;
};