leetcode
1155. 掷骰子的N种方法
这里有 d
个一样的骰子,每个骰子上都有 f
个面,分别标号为 1, 2, ..., f
。
我们约定:掷骰子的得到总点数为各骰子面朝上的数字的总和。
如果需要掷出的总点数为 target
,请你计算出有多少种不同的组合情况(所有的组合情况总共有 f^d
种),模 10^9 + 7
后返回。
示例 1:
输入:d = 1, f = 6, target = 3 输出:1
示例 2:
输入:d = 2, f = 6, target = 7 输出:6
示例 3:
输入:d = 2, f = 5, target = 10 输出:1
示例 4:
输入:d = 1, f = 2, target = 3 输出:0
示例 5:
输入:d = 30, f = 30, target = 500 输出:222616187
提示:
1 <= d, f <= 30
1 <= target <= 1000
var numRollsToTarget = function(d, f, target) { const cache = {} return ways(d, target) function ways(d, target) { const key = d + ',' + target if (cache[key] !== undefined) return cache[key] if (d <= 1) { if (target > 0 && target <= f) return (cache[key] = 1) return (cache[key] = 0) } let sum = 0 for (let i = 1; i <= f; i++) { if (target <= i) continue sum += ways(d - 1, target - i) } return (cache[key] = sum % (10 ** 9 + 7)) } // 不行,超时间限制 // return ways(d, target); // function ways(d, target){ // if(d<=1){ // if(target >0 && target<=f) return 1; // return 0 // } // let sum = 0; // for(let i=1; i<=f; i++){ // if(target <=i ) continue // sum += ways(d-1, target-i) // } // return sum % (10**9 + 7) // } };
// 像这种出现类似操作的肯定是可以用递归解决的,low点的办法就是拼接字符串,eval
5130. 等价多米诺骨牌对的数量
给你一个由一些多米诺骨牌组成的列表 dominoes
。
如果其中某一张多米诺骨牌可以通过旋转 0
度或 180
度得到另一张多米诺骨牌,我们就认为这两张牌是等价的。
形式上,dominoes[i] = [a, b]
和 dominoes[j] = [c, d]
等价的前提是 a==c
且 b==d
,或是 a==d
且 b==c
。
在 0 <= i < j < dominoes.length
的前提下,找出满足 dominoes[i]
和 dominoes[j]
等价的骨牌对 (i, j)
的数量。
示例:
输入:dominoes = [[1,2],[2,1],[3,4],[5,6]] 输出:1
提示:
1 <= dominoes.length <= 40000
1 <= dominoes[i][j] <= 9
/**
* @param {number[][]} dominoes
* @return {number}
*/
var numEquivDominoPairs = function(dominoes) {
let hash = new Map();
let ans = 0;
for(let dom of dominoes){
let key=dom.sort((a,b)=>a-b).join(":");
let val = hash.get(key)||0
if(val>0){
ans+=val;
}
hash.set(key,val+1);
}
return ans;
};
let hash={}
let memo={}
for(let i=0;i<dominoes.length;i++){
let [f,e]=dominoes[i]
let min=Math.min(f,e)
let max=Math.max(f,e)
if(hash[min+' '+max]==null)hash[min+' '+max]=1
else hash[min+' '+max]++
}
let res=0
for(let k in hash){
res+=add(hash[k]-1)
}
return res
function add(n){
let res=0
for(let i=1;i<=n;i++){
res+=i
}
return res
}
反思:
悲哀,自己只会用双层for循环,导致测试用例总是超出时间限制,所以存在效率性问题可以考虑下set、map数据结构。
5128. 最深叶节点的最近公共祖先(二叉树)
给你一个有根节点的二叉树,找到它最深的叶节点的最近公共祖先。
回想一下:
- 叶节点 是二叉树中没有子节点的节点
- 树的根节点的 深度 为
0
,如果某一节点的深度为d
,那它的子节点的深度就是d+1
- 如果我们假定
A
是一组节点S
的 最近公共祖先,s
中的每个节点都在以A
为根节点的子树中,且A
的深度达到此条件下可能的最大值。
示例 1:
输入:root = [1,2,3] 输出:[1,2,3]
示例 2:
输入:root = [1,2,3,4] 输出:[4]
示例 3:
输入:root = [1,2,3,4,5] 输出:[2,4,5]
提示:
- 给你的树中将有 1 到 1000 个节点。
- 树中每个节点的值都在 1 到 1000 之间。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var lcaDeepestLeaves = function(root) {
let parent=null
let maxLevel=0
deep(root,0)
return parent
function deep(root,level){
if(!root)return 0
let left=deep(root.left,level+1)
let right=deep(root.right,level+1)
let max=Math.max(left,right)+1
maxLevel=Math.max(maxLevel,level)
if(left===right){
if(level+left===maxLevel || max===maxLevel){
parent=root
}
}
return max
}
};
解析:
有必要深入探讨
1094. 拼车
假设你是一位顺风车司机,车上最初有 capacity
个空座位可以用来载客。由于道路的限制,车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向,你可以将其想象为一个向量)。
这儿有一份行程计划表 trips[][]
,其中 trips[i] = [num_passengers, start_location, end_location]
包含了你的第 i
次行程信息:
- 必须接送的乘客数量;
- 乘客的上车地点;
- 以及乘客的下车地点。
这些给出的地点位置是从你的 初始 出发位置向前行驶到这些地点所需的距离(它们一定在你的行驶方向上)。
请你根据给出的行程计划表和车子的座位数,来判断你的车是否可以顺利完成接送所用乘客的任务(当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true
,否则请返回 false
)。
示例 1:
输入:trips = [[2,1,5],[3,3,7]], capacity = 4 输出:false
示例 2:
输入:trips = [[2,1,5],[3,3,7]], capacity = 5 输出:true
示例 3:
输入:trips = [[2,1,5],[3,5,7]], capacity = 3 输出:true
示例 4:
输入:trips = [[3,2,7],[3,7,9],[8,3,9]], capacity = 11 输出:true
提示:
- 你可以假设乘客会自觉遵守 “先下后上” 的良好素质
trips.length <= 1000
trips[i].length == 3
1 <= trips[i][0] <= 100
0 <= trips[i][1] < trips[i][2] <= 1000
1 <= capacity <= 100000
/**
* @param {number[][]} trips
* @param {number} capacity
* @return {boolean}
*/
function carPooling(trips, capacity) {
let a =new Array(1000).fill(0);
for(let i = 0; i < trips.length; i++){
for(let j = trips[i][1]; j < trips[i][2]; j++){
a[j] += trips[i][0];
if(a[j] > capacity){
return false;
}
}
}
return true;
}
// 这个题之前见过类似的。
/*
var carPooling = function(trips, capacity) {
let passengers = [],
locations = [];
trips.forEach((trip)=>{
passengers.push(trip[0]);
locations.push([trip[1], trip[2]]);
})
for(let i=0; i<locations.length; i++){
let pas = passengers[i];
for(let j=1; j<locations.length; j++){
let loc = (locations[i][0]<locations[j][0] && locations[i][1] >locations[j][0]) || (locations[i][0]>locations[j][0] && locations[j][1]>locations[i][0])
if(loc){
pas+= passengers[j];
if(pas>capacity) return false;
}
}
}
return true
};
*/
这种类型的题这么多,今天还是没做出来
1109. 航班预订统计(数组)
这里有 n
个航班,它们分别从 1
到 n
进行编号。
我们这儿有一份航班预订表,表中第 i
条预订记录 bookings[i] = [i, j, k]
意味着我们在从 i
到 j
的每个航班上预订了 k
个座位。
请你返回一个长度为 n
的数组 answer
,按航班编号顺序返回每个航班上预订的座位数。
示例:
输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5 输出:[10,55,45,25,25]
提示:
1 <= bookings.length <= 20000
1 <= bookings[i][0] <= bookings[i][1] <= n <= 20000
1 <= bookings[i][2] <= 10000
/**
* @param {number[][]} bookings
* @param {number} n
* @return {number[]}
*/
/*
// 20000个航班就不行了
var corpFlightBookings = function(bookings, n) {
let res =[];
let sum = 0;
for(let i=1; i<=n; i++){
sum = 0;
for(let j=0; j<bookings.length; j++){
if(i>=bookings[j][0] && i<=bookings[j][1]){
sum += bookings[j][2];
}
}
res.push(sum);
}
return res;
};
*/
var corpFlightBookings = function(bookings, n) {
const res = Array(n).fill(0)
bookings.forEach(v => {
// v[1]-v[0] 必然是小于等于20000的
for (let i = v[0]; i <= v[1]; i++) {
res[i - 1] += v[2]
}
})
return res
};
个人理解:
之前的写法会内存溢出,可以考虑每个条记录的前两个元素正是某个区域的航班,必然小于n个航班;由原来的push操作,直接变为赋值操作。
5129. 表现良好的最长时间段(数组)
给你一份工作时间表 hours
,上面记录着某一位员工每天的工作小时数。
我们认为当员工一天中的工作小时数大于 8
小时的时候,那么这一天就是「劳累的一天」。
所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。
请你返回「表现良好时间段」的最大长度。
示例 1:
输入:hours = [9,9,6,0,6,6,9] 输出:3 解释:最长的表现良好时间段是 [9,9,6]。
提示:
1 <= hours.length <= 10000
0 <= hours[i] <= 16
题目链接:
https://leetcode-cn.com/contest/weekly-contest-145/problems/longest-well-performing-interval/
/**
* @param {number[]} hours
* @return {number}
*/
var longestWPI = function(hours) {
let arr = [];
for (var i = 0; i < hours.length; i++) {
for (var j = i+1; j <= hours.length; j++) {
let lao=0, xiu=0, subArr = hours.slice(i, j);
subArr.forEach(s=>{
if(s>8){
lao++
}else {
xiu++
}
})
if(lao>xiu){
pushArr(arr, subArr);
}
}
}
if(!arr.length) return 0;
let maxLenArr = arr[0];
for (var i = 1; i < arr.length; i++) {
if(arr[i].length>maxLenArr.length){
maxLenArr = arr[i]
}
}
return maxLenArr.length;
};
let pushArr =function(arr, subArr){
arr.push({
subArr: subArr,
length: subArr.length
});
}
/**
* @param {number[]} hours
* @return {number}
*/
var longestWPI = function(hours) {
let n = hours.length;
let array = new Array(n);
for(let i = 0; i < n; i++){
if(hours[i] > 8)
array[i] = 1;
else
array[i] = -1;
}
let ans = 0;
for(let i = 0; i < n; i++){
let temp = 0;
for(let j = i; j < n; j++){
temp += array[j];
if(temp > 0)
ans = Math.max(ans, j-i+1);
}
}
return ans;
};
个人理解:
这种双层for循环对子数组进行处理的问题。
像这种求最值,多层循环又会出现内存溢出的问题。
总是会出现额外的数组,还有Math.max之类的。
1005. K 次取反后最大化的数组和 VS 1002.查找常用字符
给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个个索引 i
并将 A[i]
替换为 -A[i]
,然后总共重复这个过程 K
次。(我们可以多次选择同一个索引 i
。)
以这种方式修改数组后,返回数组可能的最大和。
示例 1:
输入:A = [4,2,3], K = 1 输出:5 解释:选择索引 (1,) ,然后 A 变为 [4,-2,3]。
示例 2:
输入:A = [3,-1,0,2], K = 3 输出:6 解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。
示例 3:
输入:A = [2,-3,-1,5,-4], K = 2 输出:13 解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。
提示:
1 <= A.length <= 10000
1 <= K <= 10000
-100 <= A[i] <= 100
/**
* @param {number[]} A
* @param {number} K
* @return {number}
*/
var largestSumAfterKNegations = function(A, K) {
A.sort((a, b)=> a-b);
let a=0;
A.forEach(item=>{
if(item<=0) a++;
})
if(a<=K){
A = A.map(a=>Math.abs(a));
if((K-a)%2){
A.sort((a, b)=> a-b);
A[0] = -A[0];
}
return A.reduce((init, val)=>init+val)
} else {
for(let i=0; i<K; i++){
A[i] = -A[i]
}
return A.reduce((init, val)=>init+val, 0);
}
};
想法:
开始想的是要在[0, A.length-1]里面取K次,把所有的可能遍历一遍,然后Math.max();其实也想到排一下序也可以ok(也许排下序列,真的会有新的想法)。还想了,要不也拼一下字符串然后eval。
994. 腐烂的橘子
994. 腐烂的橘子
在给定的网格中,每个单元格可以有以下三个值之一:
- 值
0
代表空单元格; - 值
1
代表新鲜橘子; - 值
2
代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1
。
输入:[[2,1,1],[1,1,0],[0,1,1]] 输出:4
示例 2:
输入:[[2,1,1],[0,1,1],[1,0,1]] 输出:-1 解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个正向上。
示例 3:
输入:[[0,2]] 输出:0 解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。
提示:
1 <= grid.length <= 10
1 <= grid[0].length <= 10
grid[i][j]
仅为0
、1
或2
var orangesRotting = function(grid) {
if(grid.length === 0) return 0;
let enumOrange = [[1,0],[0,1],[-1,0],[0,-1]],
freshNum = 0,
rots = [],
x = grid.length,
y = grid[0].length,
step=0;
for(let i=0; i<x; i++){
for(let j=0; j<y; j++){
if(grid[i][j] === 1){
freshNum++;
}else if(grid[i][j]===2){
rots.push([i, j])
}
}
}
if(freshNum === 0) return 0;
while(rots.length){
if(freshNum === 0) return step;
step++;
let num = rots.length;
for (let i = 0; i < num; i++) {
let curr = rots.shift();
for(let k=0; k<enumOrange.length; k++){
let m = enumOrange[k][0],
n = enumOrange[k][1];
let next = grid[curr[0]+m] && grid[curr[0]+m][curr[1]+n];
if(next===1){
grid[curr[0]+m][curr[1]+n] = 2;
rots.push([curr[0]+m, curr[1]+n]);
freshNum--;
}
}
};
}
return -1
}
复盘:
枚举类型和for of的应用
1002.查找常用字符
给定仅有小写字母组成的字符串数组 A
,返回列表中的每个字符串中都显示的全部字符(包括重复字符)组成的列表。例如,如果一个字符在每个字符串中出现 3 次,但不是 4 次,则需要在最终答案中包含该字符 3 次。
你可以按任意顺序返回答案。
示例 1:
输入:["bella","label","roller"] 输出:["e","l","l"]
示例 2:
输入:["cool","lock","cook"] 输出:["c","o"]
提示:
1 <= A.length <= 100
1 <= A[i].length <= 100
A[i][j]
是小写字母
解答:
/**
* @param {string[]} A
* @return {string[]}
*/
var commonChars = function(A) {
let res = [];
let arr1 = A.map(a=>{
return a.split('')
});
for(let j=0; j<arr1[0].length; j++){
let a1 = arr1[0][j],
count = 0;
for(let i=1; i<arr1.length; i++){
let subArr = arr1[i],
index = subArr.indexOf(a1);
if(index<0){
break;
} else {
subArr.splice(index, 1)
count++;
}
}
if(count === arr1.length-1) {
res.push(a1)
}
}
return res;
};
复盘:
当时没考虑数组的长度,以为都是3,所有3个循环然后splice;如果不是3个是n个呢,像这种最low的方法就是拼接字符串eval。
像这种拿第一个数组的元素分别循环剩下的元素可以用一个计算器count,判断count是否等于除第一个外,剩下数组的个数。
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
仅含有小写英文字母。
/** * @param {string} target * @return {string} */ // var alphabetBoardPath = function(target) { // let board = ["abcde", "fghij", "klmno", "pqrst", "uvwxy", "z"]; // let map = new Map(); // for(let i=0; i<board.length; i++){ // for(let j=0; j<board[i].length; j++){ // map.set(board[i][j],[i, j]) // } // } // let arr = target.split(''); // let sum = ''; // for (var i = 0; i < arr.length; i++) { // let x, y; // if(i===0){ // [x, y] = map.get(arr[i]); // } else { // let [x1, y1] = map.get(arr[i-1]); // let [x2, y2] = map.get(arr[i]); // x = x2-x1; // y = y2-y1; // } // let absX = Math.abs(x), // absY = Math.abs(y); // if(x === 0 && y ===0){ // sum+='!' // } else { // if(x>0){ // sum+=numsOfChar(absX, 'D', map); // }else { // sum+=numsOfChar(absX, 'U', map); // } // if(y>0){ // sum+=numsOfChar(absY, 'R', map); // } else { // sum+=numsOfChar(absY, 'L', map); // } // sum+='!' // } // } // return sum; // }; // let numsOfChar= function(num, char, map){ // let map1 = { // 'D': [1, 0], // 'U': [-1, 0], // 'R': [0, 1], // 'L': [-1, 0] // } // let res = ''; // for (var i = 0; i < num; i++) { // res+=char // } // return res; // } var alphabetBoardPath = function(target) { const board = {} for (let i = 0; i < 26; i++) { board[String.fromCharCode(97 + i)] = [Math.floor(i / 5), i % 5] } let curCode = 'a' let res = [] for (let i = 0, len = target.length; i < len; i++) { const cur = board[curCode] const newcur = board[target[i]] let flag = curCode === 'z' ? false : true if (flag) { if (newcur[1] >= cur[1]) res.push('R'.repeat(newcur[1] - cur[1])) else res.push('L'.repeat(cur[1] - newcur[1])) if (newcur[0] >= cur[0]) res.push('D'.repeat(newcur[0] - cur[0])) else res.push('U'.repeat(cur[0] - newcur[0])) } else { if (newcur[0] >= cur[0]) res.push('D'.repeat(newcur[0] - cur[0])) else res.push('U'.repeat(cur[0] - newcur[0])) if (newcur[1] >= cur[1]) res.push('R'.repeat(newcur[1] - cur[1])) else res.push('L'.repeat(cur[1] - newcur[1])) } curCode = target[i] res.push('!') } return res.join('') }
复盘:
它走路径如果超出边界会拐弯走,这个题,先走左右,后走上下就能避免走'z'的时候越界
5182. 删除一次得到子数组最大和
给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。
换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。
注意,删除一个元素后,子数组 不能为空。
请看示例:
示例 1:
输入:arr = [1,-2,0,3] 输出:4 解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。
示例 2:
输入:arr = [1,-2,-2,3] 输出:3 解释:我们直接选出 [3],这就是最大和。
示例 3:
输入:arr = [-1,-1,-1,-1] 输出:-1 解释:最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。 我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。
提示:
1 <= arr.length <= 10^5
-10^4 <= arr[i] <= 10^4
var maximumSum = function(arr) { const r = [[null, null], [arr[0], null]] const len = arr.length let max = arr[0] for (let i = 1; i < len; i++) { const val = arr[i] // 计算第一种 let res1 = val if (r[i][0] !== null && r[i][0] > 0) res1 = r[i][0] + val if (res1 > max) max = res1 // 计算第二种 let res2 = null if (r[i][1] !== null) res2 = r[i][1] + val if (r[i - 1][0] !== null) { if (res2 === null || r[i - 1][0] + val > res2) res2 = r[i - 1][0] + val } if (res2 !== null && res2 > max) max = res2 r.push([res1, res2]) } return max }
// 动态规划,数组保存了所有可能的值,求最大值
K 次串联后最大子数组之和
给你一个整数数组 arr
和一个整数 k
。
首先,我们要对该数组进行修改,即把原数组 arr
重复 k
次。
举个例子,如果
arr = [1, 2]
且k = 3
,那么修改后的数组就是[1, 2, 1, 2, 1, 2]
。
然后,请你返回修改后的数组中的最大的子数组之和。
注意,子数组长度可以是 0
,在这种情况下它的总和也是 0
。
由于 结果可能会很大,所以需要 模(mod) 10^9 + 7
后再返回。
示例 1:
输入:arr = [1,2], k = 3 输出:9
示例 2:
输入:arr = [1,-2,1], k = 5 输出:2
示例 3:
输入:arr = [-1,-2], k = 7 输出:0
提示:
1 <= arr.length <= 10^5
1 <= k <= 10^5
-10^4 <= arr[i] <= 10^4
const p = 1e9 + 7 // 数组和的最大值 function work(arr) { let ans = 0 let temp = 0 for(let i = 0; i < arr.length; i++) { temp += arr[i] ans = Math.max(ans, temp) if (temp < 0) { temp = 0 } } return ans } var kConcatenationMaxSum = function(arr, k) { let flag = true for(let i = 0; i < arr.length; i++) { if (arr[i] < 0) flag = false } const sum = arr.reduce((pre, item) => pre + item , 0) if (flag) { return (sum * k) % p } if (k === 1) { return work(arr) % p } if (k === 2) { return work([...arr, ...arr]) % p } if (sum > 0) { return (work([...arr, ...arr]) + ( k - 2) * sum ) % p } else { return work([...arr, ...arr]) % p } };
反思: 如何求数组子数组的最大值,难道要把所有子数组列出来,求最大值吗?
重复两个的规律?