1.无重叠区间
题目:
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
思路:这题,很像最长上升子序列的那题,所以解法是一样的
根据起点和终点升序排序,然后维护一个数组,记录保留该区间时,当前区间的数量。所以需要双层遍历,找到前一个区间,当前位置的数量=前一个区间的数量+1
时间复杂度O(n2),空间复杂度O(n)
/**
* @param {number[][]} intervals
* @return {number}
*/
var eraseOverlapIntervals = function(intervals) {
if (intervals.length < 2) return 0;
const l = intervals.length;
intervals.sort((a, b) => {
if (a[0] !== b[0]) return a[0] - b[0];
return a[1] - b[1];
});
const list = new Array(l).fill(1);
for (let i = 1; i < l; i++) {
for (let j = 0; j < i; j++) {
if (intervals[j][1] <= intervals[i][0]) {
list[i] = Math.max(list[i], list[j] + 1);
}
}
}
return l - Math.max(...list);
};
看一下怎么优化。首先,根据起点,对区间进行清理。同一个起点的区间,保留终点最小的那个。
然后根据起点进行排序。利用贪心的思想,因为区间是排序的,所以只需要找到上一个满足条件的区间,如果当前区间的起点在上一个区间的终点之前,那么该区间需要删除,否则保留并更新终点。最后得到了保留的区间数量
时间复杂度O(nlogn),空间复杂度O(n)
/**
* @param {number[][]} intervals
* @return {number}
*/
var eraseOverlapIntervals = function(intervals) {
if (intervals.length < 2) return 0;
const map = {};
for (const [start, end] of intervals) {
if (map[start] !== undefined) {
map[start] = Math.min(map[start], end);
} else {
map[start] = end;
}
}
const keys = Object.keys(map).sort((a, b) => {
if (map[a] !== map[b]) return map[a] - map[b];
return a - b;
});
let pre = -Infinity;
let c = 0;
for (const n of keys) {
if (n >= pre) {
c++;
pre = map[n];
}
}
return intervals.length - c;
};
再观察可知,不需要清理区间,只需要排序,同一起点的区间,按照终点升序排序。这样也是用的贪心策略。这样其实是对第一种解法的dp压缩策略。
时间复杂度O(n),空间复杂度O(1)
/**
* @param {number[][]} intervals
* @return {number}
*/
var eraseOverlapIntervals = function(intervals) {
if (intervals.length < 2) return 0;
intervals.sort((a, b) => {
if (a[1] !== b[1]) return a[1] - b[1];
return a[0] - b[0];
});
let pre = -Infinity;
let c = 0;
for (const item of intervals) {
if (item[0] >= pre) {
c++;
pre = item[1];
}
}
return intervals.length - c;
};
其实,也可以按照终点排序,原理是一样的
2.寻找右区间
题目:
给定一组区间,对于每一个区间 i,检查是否存在一个区间 j,它的起始点大于或等于区间 i 的终点,这可以称为 j 在 i 的“右侧”。
对于任何区间,你需要存储的满足条件的区间 j 的最小索引,这意味着区间 j 有最小的起始点可以使其成为“右侧”区间。如果区间 j 不存在,则将区间 i 存储为 -1。最后,你需要输出一个值为存储的区间值的数组。
注意:
你可以假设区间的终点总是大于它的起始点。
你可以假定这些区间都不具有相同的起始点。
思路:记录每个区间的起点和它的下标,然后对原数组映射处理
时间复杂度O(n),空间复杂度O(1)
/**
* @param {number[][]} intervals
* @return {number[]}
*/
var findRightInterval = function(intervals) {
if(intervals.length<2)return intervals.map(()=>-1)
let max = -Infinity;
const map = intervals.reduce((map, item, index) => {
max = Math.max(max, item[0]);
map[item[0]] = index;
return map;
}, {});
return intervals.map((item) => {
for (let i = item[1]; i <= max; i++) {
if (map[i] !== undefined) {
return map[i];
}
}
return -1;
});
};
3.找到字符串中所有字母异位词
题目:
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
思路:字母异位词,其实就是长度相等,且所有字符出现的次数相等。所以先记录p的各个字符出现次数,然后对s进行遍历,在移动时,动态更新每个字符出现的次数,并和p的字符出现次数比较
时间复杂度O(mn),空间复杂度O(n),m是s的长度,n是p的长度
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function(s, p) {
const l1 = s.length;
const l2 = p.length;
if (l1 < l2) return [];
const map2 = {};
for (const n of p) {
map2[n] = (map2[n] || 0) + 1;
}
const keys = Object.keys(map2);
const map1 = {};
let res = [];
for (let i = 0; i < l2; i++) {
map1[s[i]] = (map1[s[i]] || 0) + 1;
}
const flag = keys.every((item) => map1[item] == map2[item]);
if (flag) res.push(0);
for (let i = l2; i < l1; i++) {
map1[s[i]] = (map1[s[i]] || 0) + 1;
map1[s[i - l2]]--;
const flag = keys.every((item) => map1[item] == map2[item]);
if (flag) res.push(i - l2 + 1);
}
return res;
};
4.数组中重复的数据
题目:
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
思路:这题和之前一题很像,原题忘了,为了节省空间,其实就是原地修改数组,对于nums[i],将该值对应的下标变为负数,遍历时遇到对应的下标是负数就说明该值已出现过
时间复杂度O(n),空间复杂度O(1)
/**
* @param {number[]} nums
* @return {number[]}
*/
var findDuplicates = function(nums) {
const res = [];
for (const n of nums) {
const a = Math.abs(n);
if (nums[a - 1] < 0) {
res.push(a);
} else {
nums[a - 1] *= -1;
}
}
return res;
};
5.两数相加
题目:
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转
思路:将链表反转,依次相加,然后再反转即可
时间复杂度O(n),空间复杂度O(1)
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
const reverse = (root, pre = null) => {
if (!root) return pre;
const next = root.next;
root.next = pre;
return reverse(next, root);
};
var addTwoNumbers = function (l1, l2) {
if (!l1) return l2;
if (!l2) return l1;
l1 = reverse(l1);
l2 = reverse(l2);
const res = new ListNode();
let head=res
let v=0;
while (l1 || l2) {
const temp = (l1?.val ?? 0) + (l2?.val ?? 0) + v;
v = ~~(temp / 10);
head.next=new ListNode(temp%10)
head=head.next
l1 = l1?.next ?? null;
l2 = l2?.next ?? null;
}
if(v){
head.next=new ListNode(v)
}
return reverse(res.next);
};