js解leetcode(33)-中等

本文探讨了四种不同的编程问题,包括无重叠区间、寻找右区间、字母异位词查找和数组中重复数据的处理。通过排序、贪心策略和动态规划等方法,实现了高效解决方案。此外,还涉及链表的两数相加,通过反转链表实现相加后再反转结果。
摘要由CSDN通过智能技术生成

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);
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值