1.砖墙
题目:
你的面前有一堵矩形的、由多行砖块组成的砖墙。 这些砖块高度相同但是宽度不同。你现在要画一条自顶向下的、穿过最少砖块的垂线。
砖墙由行的列表表示。 每一行都是一个代表从左至右每块砖的宽度的整数列表。
如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你需要找出怎样画才能使这条线穿过的砖块数量最少,并且返回穿过的砖块数量。
你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。
思路:遍历每一行,记录缝隙所在的位置及出现的次数,穿过的砖块最少,就是缝隙最多。所以找到最多的缝隙即可
时间复杂度O(n),空间复杂度O(m),n是砖块总数,m是宽度
/**
* @param {number[][]} wall
* @return {number}
*/
var leastBricks = function(wall) {
const map = new Map();
let rowSum = 0;
for (const row of wall) {
let sum = 0;
for (const item of row) {
sum += item;
map.set(sum, (map.get(sum) || 0) + 1);
}
rowSum = sum;
}
map.delete(rowSum);
if (!map.size) return wall.length;
return wall.length - Math.max(...map.values());
};
2.下一个更大元素
题目:给定一个32位正整数 n,你需要找到最小的32位整数,其与 n 中存在的位数完全相同,并且其值大于n。如果不存在这样的32位整数,则返回-1。
思路:这个和之前某题很像。下一个更大的元素,就是找到最后的升序部分,然后将大于该数字的第一个数字交换,剩下的数字降序排列
/**
* @param {number} n
* @return {number}
*/
var nextGreaterElement = function(n) {
let s = `${n}`.split("");
let i = s.length - 2;
while (s[i] >= s[i + 1]) i--; // 从右开始,找第一个严格降序的数字
if (i < 0) return -1; // 不存在,返回-1
let j = s.length - 1;
while (s[j] <= s[i]) j--; // 从右开始,找到第一个比上一步找到数字大的
[s[i], s[j]] = [s[j], s[i]]; // 换位
let res = parseInt(
s.slice(0, i + 1).join("") +
s
.slice(i + 1)
.reverse()
.join("")
); // 反转右边
return res >= 2 ** 31 - 1 ? -1 : res;
};
3.和为K的子数组
题目:给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
思路:注意条件,数组的元素可能是负数,所以不能用双指针。
可以利用前缀和的思路,记录每一个元素对应的前缀和及出现次数。对于当前的元素,如果前缀和是sum,且sum-k存在,那么说明之前有和为k的连续子数组
时间复杂度O(n),空间复杂度O(n)
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var subarraySum = function(nums, k) {
const l = nums.length;
let v = 0;
let c = 0;
const map = new Map();
map.set(0, 1);
for (let i = 0; i < l; i++) {
v += nums[i];
c += map.get(v - k) || 0;
map.set(v, (map.get(v) || 0) + 1);
}
return c;
};
4.数组嵌套
题目:
索引从0开始长度为N的数组A,包含0到N - 1的所有整数。找到最大的集合S并返回其大小,其中 S[i] = {A[i], A[A[i]], A[A[A[i]]], ... }且遵守以下的规则。
假设选择索引为i的元素A[i]为S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]... 以此类推,不断添加直到S出现重复的元素。
思路:把数字看成为若干个环,我们要找到长度最长的环。因为对于一个环,任意节点进入该环都是一样的,所以一个dfs,遍历每个元素,然后进入dfs,记录遍历过的节点,如果遇到遍历过的,就退出当前循环,更新环的长度
时间复杂度O(n),空间复杂度O(n)
/**
* @param {number[]} nums
* @return {number}
*/
var arrayNesting = function(nums) {
const l = nums.length;
const dp = new Array(l).fill(false);
let res = 0;
for (let i = 0; i < l; i++) {
let c = 0;
let v = i;
while (!dp[v]) {
c++;
dp[v]=true
v = nums[v];
}
res = Math.max(res, c);
}
return res;
};
5.字符串的排列
思路:
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
思路:因为是排列,所以记录当前字符串的每个字符的出现次数,然后依次比较每个字符的出现次数是否相等。为了节约空间,在遍历s2的时候,只修改首尾两端的字符的出现次数
时间复杂度O(mn),空间复杂度O(m),m是s1的长度,n是s2的长度
/**
* @param {string} s1
* @param {string} s2
* @return {boolean}
*/
var checkInclusion = function(s1, s2) {
const map = new Map();
const l = s1.length;
for (const s of s1) {
map.set(s, (map.get(s) || 0) + 1);
}
const map2 = new Map();
for (let i = 0; i < l; i++) {
map2.set(s2[i], (map2.get(s2[i]) || 0) + 1);
}
let right = l - 1;
const l2 = s2.length;
const keys = map.keys();
let flag = true;
for (const key of keys) {
if (map.get(key) !== map2.get(key)) {
flag = false;
break;
}
}
if (flag) return true;
while (right < l2) {
right++;
const keyLeft = s2[right - l];
const keyRight = s2[right];
map2.set(keyLeft, (map2.get(keyLeft) || 0) - 1);
map2.set(keyRight, (map2.get(keyRight) || 0) + 1);
let flag = true;
const keys = map.keys();
for (const key of keys) {
if (map.get(key) !== map2.get(key)) {
flag = false;
break;
}
}
if (flag) return true;
}
return false;
};