xiedaxia1hao / leetcode_pattern

1 stars 0 forks source link

Top K Elements #4

Open xiedaxia1hao opened 3 years ago

xiedaxia1hao commented 3 years ago

经常会有问题让我们求某个list里面的top/smallest/frequent K elements。比如经典的 215. Kth Largest Element in an Array 就是一道常见的面试题。

这一类问题,因为要维护一个size为k的set,通常情况下我们可以用heap来帮助我们解题。

在针对一些特定的题目,比如求top k appeared frequency的题目,我们可以用bucket sort来进一步优化。

这样说有一些抽象,下面就直接看例题吧。

xiedaxia1hao commented 3 years ago

模版题:215. Kth Largest Element in an Array

Given an integer array nums and an integer k, return the kth largest element in the array.
Note that it is the kth largest element in the sorted order, not the kth distinct element.

学会如何从O(nlgn) -> O(nlgk) -> O(n)!!

这道题让我们求一个数组里面第k大的数字。

比如 [1, 3, 5, 2, 6, 4] 这个数组中,让我们求k=2大的数的话,最容易想到的方法是把整个数组先sort一下,然后 return nums[arr.length-k+1] 即可。

这种做法的复杂度是O(nlgn),可以优化一下吗?

我们可以尝试一下使用minHeap:

  1. 我们可以维护一个size=k的最小堆,不断把数组里的数字加进去,直到size=k
  2. 之后,每当我们加一个元素进去的时候,因为现在的size=k+1,我们需要把heap里多余的数字给poll出来。根据heap的原理,这个时候poll出来的就是当前k+1个数字中最小的数字。
  3. 当我们把所有数字都加进去之后,这个时候heap的peek就是当前数组最大的k个数字中,最小的一个,即kth largest element。

我们用数组 [1, 3, 5, 2, 6, 4],k=4 来举个例子:

  1. 首先我们将前4个元素直接放到heap中,当前我们heap就是:[1, 3, 5, 2]
  2. 然后我们尝试把6加入到heap中,此时我们的heap就是: [1, 3, 5, 2, 6]。但是因为我们当前heap的size是5,我们需要除掉一个多余的数字。因为是最小堆,我们要除掉的是1。此时我们的heap就是:[3, 5, 2, 6]
  3. 同理,我们把4加入到heap中,得到 [3, 5, 2, 6, 4],除掉当前heap中最小的元素,得到: [3, 5, 6, 4]
  4. 此时heap里保存的[3, 5, 6, 4]就是原数组[1, 3, 5, 2, 6, 4]里最大的4个数字,这个时候,heap的顶端元素 3就是最大的4哥数字中,最小的一个,即第4大的元素。

这样做的话,时间复杂度是多少呢?

当然了,这题用maxHeap也可以,我们把所有的元素放到heap里,我们需要poll出去k-1个元素,然后此时heap的root就是我们的kth largest element了。
但是这样的话,空间复杂度就大了,因为我们最大要保存n个元素,而minHeap可以保证我们的size是 <= k的。

代码如下:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        for(int num : nums) {
            minHeap.offer(num);
            if(minHeap.size() > k) {
                minHeap.poll();
            }
        }
        return minHeap.peek();
    }
}

这里介绍一个quick select的方法,可以把复杂度优化到O(n),虽然和这个专题里的pattern没什么关系,但是因为这题实在是经典,quick select的方法也是需要掌握的。

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        int left = 0, right = n-1;
        Random rand = new Random(0);

        while(left <= right) {
            int chosenPivotIndex = rand.nextInt(right-left+1) + left;
            int finalPivotIndedx = partition(nums, left, right, chosenPivotIndex);

            if(finalPivotIndedx == n-k) {
                return nums[finalPivotIndedx];
            } else if(finalPivotIndedx < n-k) {
                left = finalPivotIndedx+1;
            } else {
                right = finalPivotIndedx-1;
            }
        }

        return -1;
    }

    public int partition(int[] nums, int left, int right, int pivotIndex) {
        int pivotValue = nums[pivotIndex];
        swap(nums, right, pivotIndex);

        int i = left;
        for(int j = left; j < right; j++) {
            if(nums[j] < pivotValue) {
                swap(nums, i, j);
                i++;
            }
        }

        swap(nums, i, right);
        return i;
    }

    public void swap(int[] nums, int first, int second) {
        int temp = nums[first];
        nums[first] = nums[second];
        nums[second] = temp;
    }
}

Quick select的时间复杂度怎么算呢?

我们可以用递推式来想:

每一轮我们都会把我们的数组变成其中的一半,然后对于每一步来说,如果当前的数组长度为n,我们要做n-1次comparison来得到最终的答案,那我们最后的递推式就是:

T(n) = T(n/2) + (n-1)

具体的解法就是:

xiedaxia1hao commented 3 years ago

973. K Closest Points to Origin

这道题让我们求最接近原点的k个点

image

这题和模版题一样,其实对于每个点我们可以直接算出来其与原地的距离,所以这题的解法和模版题的三种解法一致。下面把两种解法都放出来,复杂度也是一致的。

class Solution {
    public int[][] kClosest(int[][] points, int k) {
        PriorityQueue<int[]> maxHeap = new PriorityQueue<>((a, b) -> (b[1] * b[1] + b[0] * b[0] - a[1] * a[1] - a[0] * a[0]));

        for(int[] point : points) {
            maxHeap.offer(point);
            if(maxHeap.size() > k) {
                maxHeap.poll();
            }
        }

        int[][] res = new int[k][2];
        for(int i = 0; i < res.length; i++) {
            res[i] = maxHeap.poll();
        }

        return res;
    }
}

Quick Select:

class Solution {
    public int[][] kClosest(int[][] points, int k) {
        int n = points.length;
        int[][] res = new int[k][2];
        int left = 0, right = n-1;
        Random rand = new Random(0);

        while(left <= right) {
            int chosenPivotIndex = rand.nextInt(right-left+1) + left;
            int finalPivotIndex = partition(points, left, right, chosenPivotIndex);
            if(finalPivotIndex == k-1) {
                for(int i = 0; i < k; i++) {
                    res[i] = points[i];
                }
                return res;
            } else if(finalPivotIndex < k-1) {
                left = finalPivotIndex + 1;
            } else {
                right = finalPivotIndex - 1;
            }
        }

        return new int[0][2];
    }

    public int partition(int[][] points, int left, int right, int chosenPivotIndex) {
        int[] pivotPoint = points[chosenPivotIndex];
        int pivotToOriginDist = calculateDistance(pivotPoint);

        swap(points, chosenPivotIndex, right);
        int i = left;
        for(int j = left; j < right; j++) {
            if(calculateDistance(points[j]) < pivotToOriginDist) {
                swap(points, i, j);
                i++;
            }
        }

        swap(points, i, right);
        return i;
    }

    public int calculateDistance(int[] point) {
        return point[0] * point[0] + point[1] * point[1];
    }

    public void swap(int[][] points, int first, int second) {
        int[] temp = points[first];
        points[first] = points[second];
        points[second] = temp;
    }
}
xiedaxia1hao commented 3 years ago

Grokking. Connect Ropes OR 1167. Minimum Cost to Connect Sticks

Given ‘N’ ropes with different lengths, we need to connect these ropes into one big rope with minimum cost. The cost of connecting two ropes is equal to the sum of their lengths.

image

这一题其实把上面几个example的case走一遍之后,就大概能理解怎么做了。

本质上就是一个贪心的思想,我们每次把长度最短的两根绳子相加,直到加到最后一根绳子。

既然每次我们都要把长度最短的两根绳子相加,我们可以维护一个minHeap:

代码如下:

class ConnectRopes {

  public static int minimumCostToConnectRopes(int[] ropeLengths) {
    int res = 0;
    PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    for(int length : ropeLengths) {
      minHeap.offer(length);
    }

    while(minHeap.size() > 1) {
      int newLength = minHeap.poll() + minHeap.poll();
      res += newLength;
      minHeap.offer(newLength);
    }

    return res;
  }
}

703. Kth Largest Element in a Stream

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

class KthLargest {
    PriorityQueue<Integer> minHeap;
    int k;
    public KthLargest(int k, int[] nums) {
        this.minHeap = new PriorityQueue<>();
        this.k = k;
        for(int num : nums) {
            minHeap.offer(num);
            if(minHeap.size() > k) {
                minHeap.poll();
            }
        }
    }

    public int add(int val) {
        minHeap.offer(val);
        while(minHeap.size() > k) {
            minHeap.poll();
        }
        return minHeap.peek();
    }
}
xiedaxia1hao commented 3 years ago

Top K Elements 和 Bucket Sort

对于一些特定的top k的题目,使用heap的效率可能并不一定是最快的。有些题目由于会有一些隐藏的限制,可以进行进一步的优化。

比如让我们求top k frequency appeared characters,我们可以知道一个character最大的frequency就是nums.length,最小的就为1,在这样的constraints下,我们可以想到用bucket sort来进一步优化。

347. Top K Frequent Elements

Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.
Constraints: 1 <= nums.length <= 10^5 k is in the range [1, the number of unique elements in the array]. It is guaranteed that the answer is unique.
Follow up: Your algorithm's time complexity must be better than O(n log n), where n is the array's size.

这题的思想和kth largest elements的思想是一样的,我们可以先用一个hashmap来保存所有的element和它们分别出现的次数,然后通过heap来处理找top k frequent的元素。这样的话,时间复杂度是O(nlgk),符合题目的要求 (O(nlgn))。

代码如下:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int num : nums) {
            map.put(num, map.getOrDefault(num, 0)+1);
        }

        PriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>((a, b) -> a.getValue()-b.getValue());
        for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
            minHeap.offer(entry);
            if(minHeap.size() > k) {
                minHeap.poll();
            }
        }

        int[] res = new int[k];
        for(int i = 0; i < res.length; i++) {
            res[i] = minHeap.poll().getKey();
        }
        return res;
    }
}

Time Complexity: O(N + N * logk) Space Complexity: O(N)

这题如果用quickselect也能做,但是实现上可能没有那么方便。

仔细读题后我们可以知道,一个数字的最大的出现频率无非就是array.length,所以我们可以尝试一下用bucket sort来求解,这样的话时间复杂度就是O(n):

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int num : nums) {
            map.put(num, map.getOrDefault(num, 0)+1);
        }

        List<Integer>[] bucket = new List[nums.length+1];
        for(int key : map.keySet()) {
            int frequency = map.get(key);
            if(bucket[frequency] == null) {
                bucket[frequency] = new ArrayList<>();
            }
            bucket[frequency].add(key);
        }

        // add result to an array
        int[] res = new int[k];
        int j = 0;
        for(int i = bucket.length-1; i >= 0; i--) {
            if(bucket[i] != null) {
                List<Integer> numWithFrequencyI = bucket[i];
                for(int num : numWithFrequencyI) {
                    res[j++] = num;
                }
                if(j == k) break;
            }
        }
        return res;
    }
}

451. Sort Characters By Frequency

Given a string s, sort it in decreasing order based on the frequency of characters, and return the sorted string.

这题和上面一题本质上是一样的,只是上面一题我们sort的是数字,这一题改成character了。

我们可以用heap和bucket sort分别解一下:

class Solution {
    public String frequencySort(String s) {
        Map<Character, Integer> map = new HashMap<>();
        for(char chr : s.toCharArray()) {
            map.put(chr, map.getOrDefault(chr, 0)+1);
        }

        PriorityQueue<Map.Entry<Character, Integer>> maxHeap = new PriorityQueue<>((a, b) -> (b.getValue() - a.getValue()));

        for(Map.Entry<Character, Integer> entry : map.entrySet()) {
            maxHeap.offer(entry);
        }

        StringBuilder res = new StringBuilder();
        while(!maxHeap.isEmpty()) {
            Map.Entry<Character, Integer> charFrequencyPair = maxHeap.poll();
            char chr = charFrequencyPair.getKey();
            int frequency =  charFrequencyPair.getValue();
            for(int i = 0; i < frequency; i++) {
                res.append(chr);
            }
        }

        return new String(res);
    }
}

用heap的解法的话,时间复杂度:O(DlgD), D代表string中的distinct character。最差的情况下,时间复杂度则为O(NlgN), N代表string的character的数量。

那如果用bucket sort的解法来解呢?

class Solution {
    public String frequencySort(String s) {
        Map<Character, Integer> map = new HashMap<>();
        for(char chr : s.toCharArray()) {
            map.put(chr, map.getOrDefault(chr, 0)+1);
        }

        List<Character>[] bucket = new List[s.length()+1];
        for(char key : map.keySet()) {
            int frequency = map.get(key);
            if(bucket[frequency] == null) {
                bucket[frequency] = new ArrayList<>();
            }
            bucket[frequency].add(key);
        }

        StringBuilder res = new StringBuilder();
        for(int freq = bucket.length-1; freq >= 0; freq--) {
            if(bucket[freq] != null) {
                for(char key : bucket[freq]) {
                    for(int i = 0; i < freq; i++) {
                        res.append(key);
                    }
                }
            }
        }

        return new String(res);
    }
}

时间复杂度就变为:O(n)

xiedaxia1hao commented 3 years ago

658. Find K Closest Elements

Given a sorted integer array arr, two integers k and x, return the k closest integers to x in the array. The result should also be sorted in ascending order.
An integer a is closer to x than an integer b if:

  • |a - x| < |b - x|, or
  • |a - x| == |b - x| and a < b

这题其实可以把所有的arr里的数放到一个minHeap里,排序的规则就是离点到x的距离越小,对应的优先级越高,如果有两个点到x的距离相等,那么index越小的优先级越高。

这题难点就是该怎么样写这个lambda表达式,仔细琢磨一下可以得到:

PriorityQueue<Integer> minHeap = 
         new PriorityQueue<>((a, b) -> (Math.abs(a-x) != Math.abs(b-x) ? Math.abs(a-x) - Math.abs(b-x) : a-b));

最后再sort一下就行了。

class Solution {
    public List<Integer> findClosestElements(int[] arr, int k, int x) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> (Math.abs(a-x) != Math.abs(b-x) ? Math.abs(a-x) - Math.abs(b-x) : a-b));

        for(int num : arr) {
            minHeap.offer(num);
        }

        List<Integer> res = new ArrayList<>();
        for(int i = 0; i < k; i++) {
            res.add(minHeap.poll());
        }

        Collections.sort(res);
        return res; 
    }
}

如果我们这样做的话,上面一题的时间复杂度是O(nlgn + klgk)。

我们可以稍微优化一下,因为我们把所有的数都放到heap里面来了。如果我们先用binary search,找到该x原本应该在的位置,然后向左向右k个距离的一个区间就是我们答案出现的可能的区间了。我们可以对剩下的2k长度进行heap操作,这样可以做到一定程度上的优化。

优化后的时间复杂度:O(lgn + klgk)

class Solution {
    public List<Integer> findClosestElements(int[] arr, int k, int x) {

        int index = binarySearch(arr, x);
        int left = Math.max(0, index-k);
        int right = Math.min(index+k, arr.length-1);

        PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> (Math.abs(a-x) != Math.abs(b-x) ? Math.abs(a-x) - Math.abs(b-x) : a-b));

        for(int i = left; i <= right; i++) {
            minHeap.offer(arr[i]);
        }

        List<Integer> res = new ArrayList<>();
        for(int i = 0; i < k; i++) {
            res.add(minHeap.poll());
        }

        Collections.sort(res);
        return res; 
    } 

    public int binarySearch(int[] arr, int target) {
        int lo = 0, hi = arr.length-1;
        while(lo <= hi) {
            int mid = lo + (hi-lo)/2;
            if(arr[mid] == target) {
                return mid;
            } else if(arr[mid] > target) {
                hi = mid-1;
            } else {
                lo = mid+1;
            }
        }

        return hi;
    }
}

当然,这题不用heap做可以更快。因为arr本身是sorted的,而本质上我们要找的是一个固定大小的区间,所以我们可以用collide pointer的方法。

分别初始化一个lohi指针,指向arr的头和尾,哪个离x的距离越远,就把那根指针缩回来,直到hi-lo+1=k

时间复杂度就是O(n)

class Solution {
    public List<Integer> findClosestElements(int[] arr, int k, int x) {

        int lo = 0, hi = arr.length-1;
        while(hi-lo+1 > k) {
            if(Math.abs(arr[lo]-x) <= Math.abs(arr[hi]-x)) {
                hi--;
            } else {
                lo++;
            }
        }

        List<Integer> res = new ArrayList<>();
        for(int i = lo; i <= hi; i++) {
            res.add(arr[i]);
        }

        return res;
    } 
}
xiedaxia1hao commented 3 years ago

Grokking. Maximum Distinct Elements

Given an array of numbers and a number ‘K’, we need to remove ‘K’ numbers from the array such that we are left with maximum distinct numbers.

这题和之前的套路类似:

时间复杂度分析:

我们需要把所有的number加到hashmap和minheap中,这一步会花费nlgn的时间。然后我们要把一些数字从minheap中取出来。最极端的情况是我们的数组里所有的数字都只出现了2次,这样的话最多需要把n/2个数取出来。取一个数的操作要消耗lgn的时间,那取n/2个数需要(n/2)(lgn)的时间。

所以最后的时间复杂度是: O(nglgn + n/2logn) = O(lgn) 空间复杂度:O(n)

public static int findMaximumDistinctElements(int[] nums, int k) {
  Map<Integer, Integer> map = new HashMap<>();
  for(int num : nums) {
    map.put(num, map.getOrDefault(num, 0)+1);
  }

  PriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>((a, b) -> a.getValue()-b.getValue());
  int count = 0;
  for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
    if(entry.getValue() == 1) {
      count++;
    } else {
      minHeap.offer(entry);
    }
  }

  // following a greedy approach, try removing the least frequent numbers first from the min-heap
  while(k > 0 && minHeap.size() > 0) {
    Map.Entry<Integer, Integer> entry = minHeap.poll();
    k -= entry.getValue()-1;
    // to make an element distinct, we need to remove all of its occurrences except one
    if(k >= 0) {
      count++;
    }
  }

  // if k > 0, this means we have to remove some distinct numbers
  if(k > 0) {
    count -= k;
  }

  return count;
}

Grokking. Sum of Elements

Given an array, find the sum of all numbers between the K1’th and K2’th smallest elements of that array.

这题就很直白了,直接把所有的数字放到minheap里,把前k1个数字都poll出去,把接下来的k2-k1-1个数相加,返回即可。

时间复杂度:O(nlgn)

public static int findSumOfElements(int[] nums, int k1, int k2) {
  PriorityQueue<Integer> minHeap = new PriorityQueue<>();
  for(int num : nums) {
    minHeap.offer(num);
  }

  for(int i = 0; i < k1; i++) {
    minHeap.poll();
  }

  int res = 0;
  for(int i = 0; i < k2-k1-1; i++) {
    res += minHeap.poll();
  }

  return res;
}
xiedaxia1hao commented 3 years ago

767. Reorganize String

Given a string s, rearrange the characters of s so that any two adjacent characters are not the same. Return any possible rearrangement of s or return "" if not possible.
Example 1: Input: s = "aab" Output: "aba"
Example 2: Input: s = "aaab" Output: ""

这题和上一题类似,也是要用到贪心的思想。

  1. 首先我们需要将这个string中所有字符以及它们出现的频率保存在一个map里。
  2. 同样的,我们需要用到贪心的思想。因为我们在越前面加出现频率最大的character,这样我们有最大的可能性让所有字符不相同。所以我们要把map里的所有entry加到一个maxheap。
  3. 我们先把heap里出现频率最大的character加到string里,但是我们不要马上把这个entry加回到heap里,因为我们要保证下一个元素不和当前元素相同。
  4. 再下一步中,我们同样的要把第二个frequent appeared的character加到string里,并且我们要把上一个entry加回到heap(只要frequency还是大于0的)。
  5. 这样循环之后,如果我们能把input string里所有的character加到output string,那我们就可以成功的把string排列起来了。

时间复杂度:O(nlgn) 空间复杂度: O(n)

class Solution {
    public String reorganizeString(String s) {
        Map<Character, Integer> map = new HashMap<>();
        for(char chr : s.toCharArray()) {
            map.put(chr, map.getOrDefault(chr, 0)+1);
        }

        PriorityQueue<Map.Entry<Character, Integer>> maxHeap = new PriorityQueue<>((a, b) -> (b.getValue() - a.getValue()));
        maxHeap.addAll(map.entrySet());

        StringBuilder res = new StringBuilder();
        Map.Entry<Character, Integer> prev = null;

        while(!maxHeap.isEmpty()) {
            Map.Entry<Character, Integer> curr = maxHeap.poll();
            if(prev != null && prev.getValue() > 0) {
                maxHeap.offer(prev);
            }

            res.append(curr.getKey());
            curr.setValue(curr.getValue()-1);
            prev = curr;
        }

        return res.length() == s.length() ? new String(res) : "";
    }
}

Grokking. Rearrange String K Distance Apart OR 358. Rearrange String K Distance Apart

Given a string and a number ‘K’, find if the string can be rearranged such that the same characters are at least ‘K’ distance apart from each other.

这题可以说是上一题的follow up,上一题是这一题的一种特殊情况k=2罢了。

本质上思路还是一样的,上一题我们用prev这个变量来保存,但是这题因为k可以取到很大,所以我们可以用一个queue来保存之前已经遍历过的、但是还不能放回heap里的entry。

时间复杂度:O(nlgn) 空间复杂度: O(n)

public static String reorganizeString(String str, int k) {
    if(k <= 1) {
        return str;
    }

    Map<Character, Integer> map = new HashMap<>();
    for(char chr : str.toCharArray()) {
        map.put(chr, map.getOrDefault(chr, 0)+1);
    }

    PriorityQueue<Map.Entry<Character, Integer>> maxHeap = new PriorityQueue<>((a, b)->(b.getValue()-a.getValue()));
    maxHeap.addAll(map.entrySet());
    Queue<Map.Entry<Character, Integer>> queue = new LinkedList<>();
    StringBuilder res = new StringBuilder();

    while(!maxHeap.isEmpty()) {
        Map.Entry<Character, Integer> curr = maxHeap.poll();
        res.append(curr.getKey());
        curr.setValue(curr.getValue()-1);
        queue.offer(curr);
        if(queue.size() == k) {
            Map.Entry<Character, Integer> entry = queue.poll();
            if(entry.getValue() > 0) {
                maxHeap.offer(entry);
            }
        }
    }

    return res.length() == str.length() ? new String(res) : "";
  }
xiedaxia1hao commented 3 years ago

621. Task Scheduler

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间 。

这题和之前的题的思路也类似:

  1. 我们先要用maxheap来保存所有的task和它们出现的频率。
  2. 每当我们执行完一个task,我们把它们的频率减1之后,加入到一个waitlist。
  3. 在每一轮iteration中,我们尽可能execute k+1 tasks。
  4. 在下一轮iteration中,我们把所有的waitlist中的entries加回到maxheap。
  5. 如果在某一轮iteration中我们没办法执行k+1个tasks,我们要把CPU idle的时间加上。
  6. 持续循环,直到maxheap变成empty。

时间复杂度:O(nlgn) 空间复杂度: O(n)

代码如下:

class Solution {
    public int leastInterval(char[] tasks, int k) {
        int intervalCount = 0;
        Map<Character, Integer> map = new HashMap<>();
        for(char task : tasks) {
            map.put(task, map.getOrDefault(task, 0)+1);
        }

        PriorityQueue<Map.Entry<Character, Integer>> maxHeap = new PriorityQueue<>((a, b) -> (b.getValue()-a.getValue()));
        maxHeap.addAll(map.entrySet());

        while(!maxHeap.isEmpty()) {
            List<Map.Entry<Character, Integer>> waitlist = new ArrayList<>(); 
            int n = k+1;
            for(; n > 0 && !maxHeap.isEmpty(); n--) {
                intervalCount++;
                Map.Entry<Character, Integer> curr = maxHeap.poll();
                if(curr.getValue() > 1) {
                    curr.setValue(curr.getValue()-1);
                    waitlist.add(curr);
                }
            }

            maxHeap.addAll(waitlist);
            if(!maxHeap.isEmpty()) {
                intervalCount += n;
            }
        }

        return intervalCount;
    }
}

这题还有一个O(n)的解法,因为用上面的方法,本质上我们可以得到最后的implemented的task的顺序,但是事实上我们不需要得到这个顺序,我们只care我们需要多少个intervals。

那能不能用数学的角度来解决呢?

可以参考这个回答:

https://leetcode.com/problems/task-scheduler/discuss/104496/concise-Java-Solution-O(N)-time-O(26)-space/407995

为什么最后要用tasks.length呢?

因为对于AAABBB 0来说, n there is 0, it means same task can put together, we just need to perform all the tasks. Therefore, our return result must be no less than the tasks' length, which is the reason to compare and choose a larger value.

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] charMap = new int[26];
        for(char task : tasks) {
            charMap[task-'A']++;
        }

        Arrays.sort(charMap);

        int numberOfTaskWithHighestFrequency = 0;
        for(int i = 25; i >= 0; i--) {
            if(charMap[i] == charMap[25]) {
                numberOfTaskWithHighestFrequency++;
            } else {
                break;
            }
        }

        return Math.max(tasks.length, (charMap[25]-1) * (n+1) + numberOfTaskWithHighestFrequency);
    }
}
xiedaxia1hao commented 3 years ago

895. Maximum Frequency Stack

Design a class that simulates a Stack data structure, implementing the following two operations:

  • push(int num): Pushes the number ‘num’ on the stack.
  • pop(): Returns the most frequent number in the stack. If there is a tie, return the number which was pushed later.

这题的解题思路和top k frequent numbers很像。

我们可以用maxheap来保存这些numbers,我们以它们出现的frequency为优先级,如果frequency相同,我们就比较它们被push进来的时间先后顺序。

但是我们需要考虑两点:

所以,除了数字本身,我们还需要记录每个数字相应的sequence number和frequency。我们可以建一个object来保存这些信息,然后push进heap。

class Element {
    int num;
    int frequency;
    int sequentialNum;
}

时间复杂度:push和pop都是O(lgn) 空间复杂度: O(n) for the heap

代码如下:

class FreqStack {

    class Element {
        int num;
        int frequency;
        int sequentialNum;

        public Element(int num, int frequency, int sequentialNum) {
            this.num = num;
            this.frequency = frequency;
            this.sequentialNum = sequentialNum;
        }
    }

    int sequentialNum;
    PriorityQueue<Element> maxHeap;
    Map<Integer, Integer> map;
    public FreqStack() {
        maxHeap = new PriorityQueue<>((a, b) -> (a.frequency == b.frequency ? b.sequentialNum-a.sequentialNum : b.frequency-a.frequency));
        map = new HashMap<>();
        sequentialNum = 0;
    }

    public void push(int val) {
        map.put(val, map.getOrDefault(val, 0)+1);
        maxHeap.offer(new Element(val, map.get(val), sequentialNum++));
    }

    public int pop() {
        int num = maxHeap.poll().num;

        if(map.get(num) > 1) {
            map.put(num, map.get(num)-1);
        } else {
            map.remove(num);
        }

        return num;
    }
}