LeetCode Weekly Contest 176 解题报告

卖大米 2020-2-17 1099


No.1 Count Negative Numbers in a Sorted Matrix ★解题思路

数据范围很小, 所以我们直接遍历整个矩阵, 统计负数的数量是完全可行的. 时间复杂度: o(nm)

代码展示


class Solution { 
   public int countNegatives(int[][] grid) { 
       int res = 0; 
       for (int[] line : grid) { 
           for (int num : line) { 
               res += num < 0 ? 1 : 0; 
           } 
       } 
       return res; 
   } 
}

解题思路

不过题目又说明: 每一行的元素单调不增, 那么我们就可以二分查找第一个负数(或最后一个非负数)的位置从而确定这一行内负数的个数, 时间复杂度 o(mlogn)

代码展示


class Solution { 
   public int countNegatives(int[][] grid) { 
       int res = 0; 
       for (int[] line : grid) { 
           res += line.length - firstNeg(line); 
       } 
       return res; 
   } 
   private int firstNeg(int[] line) { 
       // 二分查找 line 第一个负数的位置, 返回下标 
       // 若不存在则返回 line.length 
       int l = 0, r = line.length; 
       while (l + 1 < r) { 
           int mid = (l + r) / 2; 
           if (line[mid] >= 0) { 
               l = mid; 
           } else { 
               r = mid; 
           } 
       } 
       return line[l] < 0 ? l : r; 
   } 
}

No.2 Product of the Last K Numbers解题思路

使用前缀积数组可以做到o(1)的插入与查询.

不过如果添加的数字中有 0, 那么直接使用前缀积可能会有除零的风险.

所以当我们遇见 0 的时候, 就抛弃之前的前缀积数组. 对应地, 在查询的时候, 如果 k 比当前的前缀积数组要长, 说明倒数 k 个数字中一定会包含 0, 直接返回 0 即可.

代码展示


class ProductOfNumbers { 

   private List<Integer> list; 

   public ProductOfNumbers() { 
       list = new ArrayList<Integer>(){{add(1);}}; 
   } 

   public void add(int num) { 
       if (num == 0) { 
           list = new ArrayList<Integer>(){{add(1);}};             
       } else { 
           list.add(num * list.get(list.size() - 1)); 
       } 
   } 

   public int getProduct(int k) { 
       if (list.size() > k) { 
           return list.get(list.size() - 1) / list.get(list.size() - k - 1); 
       } else { 
           return 0; 
       } 
   } 
}

No.3 Maximun Number of Events That Can Be Attended解题思路

核心思路: 贪心法, 在每一天能参加的并且还未参加过的会议中, 选取那个结束最早的会议就可以了.

具体的实现, 首先给所有的会议按照开始时间排序, 然后定义一个堆来维护当前时间能参加的会议, 堆按照会议的结束时间排序.

我们枚举时间, 一天一天地枚举, 如果这一天有会议开始, 就把这些会议添加到堆中. 这一天要参加的会议就是堆顶的会议, 就是当前能参加的会议中结束最早的.

代码展示


class Event { 
   public int start, end; 

   public Event(int start, int end) { 
       this.start = start; 
       this.end = end; 
   } 
} 

class Solution { 
   public int maxEvents(int[][] events) { 
       Arrays.sort(events, (int[] o1, int[] o2) -> { 
           return o1[0] == o2[0] ? o1[1] - o2[1] : o1[0] - o2[0]; 
       }); 
       PriorityQueue<Event> heap = new PriorityQueue<>((Event a, Event b) -> { 
           return a.end == b.end ? a.start - b.start : a.end - b.end; 
       }); 
       int res = 0; 
       // now 表示当前时间点, 每次循环递增 
       // 循环结束的条件是: 即没有还没开始的会议, 也没有还没结束的会议 
       // 即 i 枚举完了, heap 也为空的时候 
       for (int i = 0, now = 0; i < events.length || !heap.isEmpty(); now++) { 
           // 把已经开始的会议添加到堆中 
           while (i < events.length && now >= events[i][0]) { 
               heap.add(new Event(events[i][0], events[i][1])); 
               i++; 
           } 
           // 从堆中把已经结束的会议移除 
           while (!heap.isEmpty() && heap.peek().end < now) { 
               heap.poll(); 
           } 
           // 若堆不为空, 则参加堆顶的会议 
           if (!heap.isEmpty()) { 
               heap.poll(); 
               res++; 
           } 
       } 
       return res; 
   } 
}

No.4 Construct Target Array Witn Multiple Sums

解题思路

这道题目很有趣, 其实并不难. 能想到: 总和一定是单调递增的, 也就是说更大的数字一定更晚被构造出来.

正着去构造可能并不容易, 但是我们可以倒着把数组还原成全 1.

我们拿出序列中最大的元素 max, 假设当前的总和是 sum. 可以知道之前的序列元素之和就是 max, 那么可以计算出这个位置的元素被设定为 max 之前是 max - (sum - max). 那么我们就完成了一次还原.

重复这样的过程直到数组全为 1 即可. 而非法情况就是推导当前的 max 的上一个值得到的是小于 1 的数, 或者 max 没有任何变化, 即 max - (sum - max) < 1 || max == sum, 此时应该返回 false.

具体的实现我们可以使用堆, 快速得到序列中最大的值.

代码展示


class Solution { 
   public boolean isPossible(int[] target) { 
       PriorityQueue<Integer> heap = new PriorityQueue<>(Collections.reverseOrder()); 
       int sum = 0; 
       for (int num : target) { 
           // 堆中储存的是大于 1 的元素, 表示还需要继续还原 
           if (num > 1) {   
               heap.add(num); 
           } 
           sum += num; 
       } 
       // 当堆不为空时, 表示还有元素需要还原 
       while (!heap.isEmpty()) { 
           int max = heap.poll();          // 取出最大 
           int last = max * 2 - sum;       // 计算该元素的上一个值 
           if (last < 1 || last == max) {  // 小于 1 或无变化, 非法 
               return false; 
           } 
           sum += last - max;              // 维护 sum 
           if (last != 1) {                // 非 1, 还需要继续还原, 加入堆 
               heap.add(last); 
           } 
       } 
       return true; 
   } 
}

解题思路

不过其实我们可以不必将 max 还原一次得到的 last 立即插入堆中, 因为此时 last 可能仍然比堆中任何一个元素大, 那么我们就白白进行了一次 add 和 poll 浪费时间.

所以下面是优化之后的写法, 取出 max 之后将其还原直到它不比堆顶大, 然后再把它插入堆. 下面是优化之后的代码 (12ms=>9ms 整体速度并不明显):

代码展示


class Solution { 
   public boolean isPossible(int[] target) { 
       PriorityQueue<Integer> heap = new PriorityQueue<>(Collections.reverseOrder()); 
       int sum = 0; 
       for (int num : target) { 
           if (num > 1) { 
               heap.add(num); 
           } 
           sum += num; 
       } 
       while (!heap.isEmpty()) { 
           int max = heap.poll(); 
           while (max > 1 && (heap.isEmpty() || max >= heap.peek())) { 
               int last = max * 2 - sum; 
               if (last <= 0 || last == max) { 
                   return false; 
               } 
               sum += last - max; 
               max = last; 
           } 
           if (max != 1) { 
               heap.add(max); 
           } 
       } 
       return true; 
   }

}

最新回复 (3)
  • Coco 2020-2-20
    1 引用 2
  • 卖大米 2020-2-21
    0 引用 3

    更新海报群二维码

  • Ppc1121 2020-3-12
    0 引用 4
    群二维码更新一个吧
返回