每日算法28_2020-12-04

来源:题库 - 力扣 (LeetCode) (leetcode-cn.com)。侵删。

题目一

659. 分割数组为连续子序列

给你一个按升序排序的整数数组 num(可能包含重复数字),请你将它们分割成一个或多个子序列,其中每个子序列都由连续整数组成且长度至少为 3 。

如果可以完成上述分割,则返回 true ;否则,返回 false 。

示例 1:

输入: [1,2,3,3,4,5]
输出: True
解释:
你可以分割出这样两个连续子序列 :
1, 2, 3
3, 4, 5

示例 2:

输入: [1,2,3,3,4,4,5,5]
输出: True
解释:
你可以分割出这样两个连续子序列 :
1, 2, 3, 4, 5
3, 4, 5

示例 3:

输入: [1,2,3,4,4,5]
输出: False

提示:

输入的数组长度范围为 [1, 10000]

我没做出来T T…

官方方法一:哈希表 + 最小堆

class Solution {
    public boolean isPossible(int[] nums) {
        Map<Integer, PriorityQueue<Integer>> map = new HashMap<Integer, PriorityQueue<Integer>>();
        for (int x : nums) {
            if (!map.containsKey(x)) {
                map.put(x, new PriorityQueue<Integer>());
            }
            if (map.containsKey(x - 1)) {
                int prevLength = map.get(x - 1).poll();
                if (map.get(x - 1).isEmpty()) {
                    map.remove(x - 1);
                }
                map.get(x).offer(prevLength + 1);
            } else {
                map.get(x).offer(1);
            }
        }
        Set<Map.Entry<Integer, PriorityQueue<Integer>>> entrySet = map.entrySet();
        for (Map.Entry<Integer, PriorityQueue<Integer>> entry : entrySet) {
            PriorityQueue<Integer> queue = entry.getValue();
            if (queue.peek() < 3) {
                return false;
            }
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度:O(nlogn),其中 n 是数组的长度。
    需要遍历数组,对于数组中的每个数,都要对哈希表和最小堆进行更新。每个数对应的最小堆的长度不超过 n,因此每次对最小堆的操作的时间复杂度是 O(logn),数组长度为 n,因此时间复杂度是 O(nlogn)。
    然后需要遍历哈希表中的每一条记录,判断是否满足每个子序列的长度都不小于 3,子序列的数量不会超过 n,因此时间复杂度是 O(n)。
    因此总时间复杂度是 O(nlogn)。

  • 空间复杂度:O(n),其中 n 是数组的长度。需要使用哈希表和最小堆存储以每个数结尾的各个子序列的长度,哈希表和最小堆中的元素数量不会超过数组的长度。

官方方法二:贪心

class Solution {
    public boolean isPossible(int[] nums) {
        Map<Integer, Integer> countMap = new HashMap<Integer, Integer>();
        Map<Integer, Integer> endMap = new HashMap<Integer, Integer>();
        for (int x : nums) {
            int count = countMap.getOrDefault(x, 0) + 1;
            countMap.put(x, count);
        }
        for (int x : nums) {
            int count = countMap.getOrDefault(x, 0);
            if (count > 0) {
                int prevEndCount = endMap.getOrDefault(x - 1, 0);
                if (prevEndCount > 0) {
                    countMap.put(x, count - 1);
                    endMap.put(x - 1, prevEndCount - 1);
                    endMap.put(x, endMap.getOrDefault(x, 0) + 1);
                } else {
                    int count1 = countMap.getOrDefault(x + 1, 0);
                    int count2 = countMap.getOrDefault(x + 2, 0);
                    if (count1 > 0 && count2 > 0) {
                        countMap.put(x, count - 1);
                        countMap.put(x + 1, count1 - 1);
                        countMap.put(x + 2, count2 - 1);
                        endMap.put(x + 2, endMap.getOrDefault(x + 2, 0) + 1);
                    } else {
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

复杂度分析

时间复杂度:O(n),其中 n 是数组的长度。需要遍历数组两次,对于数组中的每个元素,更新哈希表的时间复杂度是常数。

空间复杂度:O(n),其中 n 是数组的长度。需要使用两个哈希表,每个哈希表的大小都不会超过 n。

最优贪心

class Solution {
    public boolean isPossible(int[] nums) {
        int n = nums.length;
        int dp1 = 0;    // 长度为1的子序列数目
        int dp2 = 0;    // 长度为2的子序列数目
        int dp3 = 0;    // 长度>=3的子序列数目
        int idx = 0;
        int start = 0;  // 起始位置

        while(idx < n){
            start = idx;                        // 重新将起始位置赋值
            int x = nums[idx];
            while(idx < n && nums[idx] == x){   // 去掉所有和x重复的元素
                idx++;
            }
            int cnt = idx - start;              

            if(start > 0 && x != nums[start - 1] + 1){  // 对于nums[idx] != nums[idx - 1] + 1
                if(dp1 + dp2 > 0){                      // 如果 dp1+dp2>0,说明有些长度≤2的序列无法被满足,因此不存在相应的分割方案。
                    return false;
                }else{                                  // 否则,此前的序列全部作废
                    dp1 = cnt;
                    dp2 = dp3 = 0;
                }
            }else{                                      // 对于nums[idx] == nums[idx - 1] + 1
                                                        // 说明当前整数可以加入到所有以nums[idx-1]为结尾的序列中。假设数组中x的数目为cnt:
                if(dp1 + dp2 > cnt){                    // 首先,根据贪心的策略,我们要尽可能地先把 xx 添加到长度≤2 的子序列中,从而尽可能地保证子序列的长度都≥3。如果x的数量不够,说明不存在相应的分割方案。
                    return false;
                }
                int left = cnt - dp1 - dp2;             // 此时 还剩下left = cnt -dp1 -dp2个 nums[idx-1](x)
                int keep = Math.min(dp3,left);          // 尽量将余下的left个整数添加到长度≥3的子序列中
                // 最后,我们更新 dp1,dp2,dp3的取值
                dp3 = keep + dp2;
                dp2 = dp1;
                dp1 = left - keep;                      // 开启 新序列的数目等于left−keep。
            }
        }

        return dp1 == 0 && dp2 == 0;
    }
}

最优贪心解法

  • 时间复杂度是 O(N)。
  • 空间复杂度是 O(1)。

详细大佬思路链接:https://leetcode-cn.com/problems/split-array-into-consecutive-subsequences/solution/tan-xin-o1-kong-jian-fu-za-du-de-zui-you-jie-fa-by/ (C++)

1ms不错的答案

class Solution {
    public boolean isPossible(int[] nums) {
        int n = nums.length, i = 0;
        if (n < 3) return false;
        // 当前长度为1,2的序列的数量,当前能用的多于2的序列数量
        int one = 0, two = 0, more = 0;
        // 初始化one
        while (i < n && nums[i] == nums[0]) {
            one++;
            i++;
        }
        while (i < n) {
            int pre = nums[i - 1];
            // 下一个数与上一个数断了的情况,即count为0的特殊情况
            if (nums[i] - pre > 1) {
                // 有one或two则直接返回false
                if (one > 0 || two > 0) {
                    return false;
                }
                // more全部断
                more = 0;
                int start = nums[i];
                // 初始化one
                while (i < n && nums[i] == start) {
                    one++;
                    i++;
                }
            } else {
                int count = 0;
                // 查找比上一个数大1的数的数量
                while (i < n && nums[i] == pre + 1) {
                    count++;
                    i++;
                }
                int remain = count - one - two, temp;
                if (remain < 0) {
                    // 长度1和长度为2的是一定需要被满足的,所以说如果count不够,返回false
                    return false;
                } else {
                    // 满足长度1和2后,贪心算法,如果有more没必要另起一个one,所以remain尽可能给more
                    // 原来的two也变成more,remain如果小于more,那么有一部分more断了不再讨论
                    temp = remain - more;
                    more = Math.min(remain, more) + two;
                }
                // 原来的one变成two
                two = one;
                // 如果remain大于more,则多的另起one,否则没有one
                one = Math.max(0, temp);
            }
        }
        return one + two == 0;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值