LeetCode Weekly Contest 39解题思路

LeetCode Weekly Contest 39解题思路

详细代码可以fork下Github上leetcode项目,不定期更新。

赛题

本次周赛主要分为以下4道题:

Leetcode 633. Sum of Square Numbers

Problem:

Given a non-negative integer c, your task is to decide whether there’re two integers a and b such that a2 + b2 = c.

Example 1:

Input: 5
Output: True
Explanation: 1 * 1 + 2 * 2 = 5

Example 2:

Input: 3
Output: False

很简单,提供两种做法。

左右指针法:

    public boolean judgeSquareSum(int c) {
        int n = (int) Math.sqrt(c) + 1;
        int lf = 0, rt = n;
        while (lf <= rt){
            if (rt * rt + lf * lf > c){
                rt --;
            }
            else if (lf * lf + rt * rt < c){
                lf ++;
            }
            else return true;
        }
        return false;
    }

核心都是减少搜索量,当 rt2+lf2>c 的情况下,lf不可能再增大去搜索,同理, lf2+rt2<c 的情况下,只能rt++

直接求解法:

    public boolean judgeSquareSum(int c) {
        for (int i = 0; (long) i * i <= c; ++i) {
            int x = (int) Math.sqrt(c - i * i);
            if (x * x + i * i == c) return true;
        }
        return false;
    }

开始没有加long,导致在求大数时,超时了。在实际比赛中,没有找到超时的原因,耽误了不少时间,经验不足。

for循环中的代码一步判断,已经是最优的了,所以没有优化空间,看到很多答案有判断x+1和x-1的情况,其实不需要,因为如果存在整数解,必然return true,而那些强转成int型的x显然不可能是答案。

超时的原因,i * i 溢出了!造成死循环,呵呵哒,所以用long解决。

Leetcode 635. Design Log Storage System

Problem:

You are given several logs that each log contains a unique id and timestamp. Timestamp is a string that has the following format: Year:Month:Day:Hour:Minute:Second, for example, 2017:01:01:23:59:59. All domains are zero-padded decimal numbers.

Design a log storage system to implement the following functions:

void Put(int id, string timestamp): Given a log’s unique id and timestamp, store the log in your storage system.

int[] Retrieve(String start, String end, String granularity): Return the id of logs whose timestamps are within the range from start to end. Start and end all have the same format as timestamp. However, granularity means the time level for consideration. For example, start = “2017:01:01:23:59:59”, end = “2017:01:02:23:59:59”, granularity = “Day”, it means that we need to find the logs within the range from Jan.

Example:

put(1, “2017:01:01:23:59:59”);
put(2, “2017:01:01:22:59:59”);
put(3, “2016:01:01:00:00:00”);
retrieve(“2016:01:01:01:01:01”,”2017:01:01:23:00:00”,”Year”); // return [1,2,3], because you need to return all logs within 2016 and 2017.
retrieve(“2016:01:01:01:01:01”,”2017:01:01:23:00:00”,”Hour”); // return [1,2], because you need to return all logs start from 2016:01:01:01 to 2017:01:01:23, where log 3 is left outside the range.

Note:

  • There will be at most 300 operations of Put or Retrieve.
  • Year ranges from [2000,2017]. Hour ranges from [00,23].
  • Output for Retrieve has no order required.

好吧,来个简单粗暴,冗长的做法。核心思路:不同单元的字符串比较。

代码如下:

    class Log{
        String year;
        String month;
        String day;
        String hour;
        String minute;
        String second;
        int id;
    }

    List<Log> logs;
    public LogSystem() {
        logs = new ArrayList<>();
    }

    public void put(int id, String timestamp) {
        Log log = new Log();
        log.id = id;
        String[] times = timestamp.split(":");
        log.year = times[0];
        log.month = times[0] + times[1];
        log.day = times[0] + times[1] + times[2];
        log.hour = times[0] + times[1] + times[2] + times[3];
        log.minute = times[0] + times[1] + times[2] + times[3] + times[4];
        log.second = times[0] + times[1] + times[2] + times[3] + times[4] + times[5];
        logs.add(log);
    }

    public List<Integer> retrieve(String s, String e, String gra) {
        String[] ss = s.split(":");
        String[] es = e.split(":");
        StringBuilder stcmp = new StringBuilder();
        StringBuilder etcmp = new StringBuilder();
        List<Integer> ans = new ArrayList<>();
        if (gra.equals("Year")){
            stcmp.append(ss[0]);
            etcmp.append(es[0]);
            for (int i = 0; i < logs.size(); ++i){
                if (logs.get(i).year.compareTo(stcmp.toString()) >= 0 && logs.get(i).year.compareTo(etcmp.toString()) <= 0){
                    ans.add(logs.get(i).id);
                }
            }
        }
        if (gra.equals("Month")){
            stcmp.append(ss[0] + ss[1]);
            etcmp.append(es[0] + es[1]);
            for (int i = 0; i < logs.size(); ++i){
                if (logs.get(i).month.compareTo(stcmp.toString()) >= 0 && logs.get(i).month.compareTo(etcmp.toString()) <= 0){
                    ans.add(logs.get(i).id);
                }
            }
        }
        if (gra.equals("Day")){
            stcmp.append(ss[0] + ss[1] + ss[2]);
            etcmp.append(es[0] + es[1] + es[2]);
            for (int i = 0; i < logs.size(); ++i){
                if (logs.get(i).day.compareTo(stcmp.toString()) >= 0 && logs.get(i).day.compareTo(etcmp.toString()) <= 0){
                    ans.add(logs.get(i).id);
                }
            }
        }
        if (gra.equals("Hour")){
            stcmp.append(ss[0] + ss[1] + ss[2] + ss[3]);
            etcmp.append(es[0] + es[1] + es[2] + es[3]);
            for (int i = 0; i < logs.size(); ++i){
                if (logs.get(i).hour.compareTo(stcmp.toString()) >= 0 && logs.get(i).hour.compareTo(etcmp.toString()) <= 0){
                    ans.add(logs.get(i).id);
                }
            }
        }
        if (gra.equals("Minute")){
            stcmp.append(ss[0] + ss[1] + ss[2] + ss[3] + ss[4]);
            etcmp.append(es[0] + es[1] + es[2] + es[3] + es[4]);
            for (int i = 0; i < logs.size(); ++i){
                if (logs.get(i).minute.compareTo(stcmp.toString()) >= 0 && logs.get(i).minute.compareTo(etcmp.toString()) <= 0){
                    ans.add(logs.get(i).id);
                }
            }
        }
        if (gra.equals("Second")){
            stcmp.append(ss[0] + ss[1] + ss[2] + ss[3] + ss[4] + ss[5]);
            etcmp.append(es[0] + es[1] + es[2] + es[3] + es[4] + es[5]);
            for (int i = 0; i < logs.size(); ++i){
                if (logs.get(i).second.compareTo(stcmp.toString()) >= 0 && logs.get(i).second.compareTo(etcmp.toString()) <= 0){
                    ans.add(logs.get(i).id);
                }
            }
        }
        return ans;
    }

其实String的比较可以完全把”:”放进去一起比较,所以根据不同的gra,只需要切分字符串就好了,可以省去很多代码。

public class LogSystem {

    List<String[]> logs;
    public LogSystem() {
        logs = new ArrayList<>();
    }

    public void put(int id, String timestamp) {
        logs.add(new String[]{id + "", timestamp});
    }

    List<String> units = Arrays.asList("Year", "Month", "Day", "Hour", "Minute", "Second");
    int[] idices = {4, 7, 10, 13, 16, 19};
    public List<Integer> retrieve(String s, String e, String gra) {
        List<Integer> ans = new ArrayList<>();
        int idx = idices[units.indexOf(gra)];
        for (String[] log : logs){
            if (log[1].substring(0, idx).compareTo(s.substring(0, idx)) >= 0 &&
                    log[1].substring(0, idx).compareTo(e.substring(0,idx)) <= 0) ans.add(Integer.parseInt(log[0]));
        }
        return ans;
    }
}

Leetcode 634. Find the Derangement of An Array

Problem:

In combinatorial mathematics, a derangement is a permutation of the elements of a set, such that no element appears in its original position.

There’s originally an array consisting of n integers from 1 to n in ascending order, you need to find the number of derangement it can generate.

Also, since the answer may be very large, you should return the output mod 109 + 7.

Example 1:

Example 1:
Input: 3
Output: 2
Explanation: The original array is [1,2,3]. The two derangements are [2,3,1] and [3,1,2].

Note:

n is in the range of [1, 106].

动态规划+排列组合题,关键看能不能一眼看出递归式了。

递归如下:

f(x)=(x1)[f(x1)+f(x2)]

举例:

nums = 1 2 3 4 5

a. 当1的位置被[2,5]中的任何一个替代时,总共用n-1种情况,此时有:
4 2 3 1 5
所以当前2 3 5的位置需要交换,有了子问题f(n-2)
所以有:f(n-2) * (n-1)

b.当1的位置未被[2,5]中的任何一个替代,此时有:
1 2 3 4 5
那么怎样让它变成合法的?
f(2,3,4,5),所以先把2,3,4,5的问题求解出来,有f(n-1)种,此时任何一个位置都可以和1发生互换。

证明:
所有的(2,3,4,5)的idx都不等于1,且1小于所有(2,3,4,5)的idx,所以和1交换位置一定合法。

b中的情况会和a中的情况重复么?
因为b是先对(2,3,4,5)的位置进行互换,所以f(2,3,4,5)之后,它们一定不会在原来的位置上,这就意味着再和1进行位置交换时,位置1所指向的那个值的值一定不是1.

代码如下:

    static final int MOD = 1000000000 + 7;
    public int findDerangement(int n) {
        long[] dp = new long[n + 1];
        if (n == 1) return 0;
        if (n == 2) return 1;
        dp[1] = 0;
        dp[2] = 1;
        for (int i = 3; i <= n; ++i){
            dp[i] = (dp[i - 1] + dp[i - 2]) % MOD;
            dp[i] = (dp[i] * (i - 1)) % MOD;
        }
        return (int)dp[n];
    }

利用空间的做法:

    public int findDerangement(int n) {
            int mod = 1000000007;
            if(n == 1)return 0;
            if(n == 2)return 1;
            long a = 0, b = 1;
            for(int i = 3;i <= n;i++){
                long c = (b+a)*(i-1)%mod;
                a = b; b = c;
            }
            return (int)b;
        }

Leetcode 632. Smallest Range

Problem:

You have k lists of sorted integers in ascending order. Find the smallest range that includes at least one number from each of the k lists.

We define the range [a,b] is smaller than range [c,d] if b-a < d-c or a < c if b-a == d-c.

Example:

Input:[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
Output: [20,24]
Explanation:
List 1: [4, 10, 15, 24,26], 24 is in range [20,24].
List 2: [0, 9, 12, 20], 20 is in range [20,24].
List 3: [5, 18, 22, 30], 22 is in range [20,24].

Note:

  • The given list may contain duplicates, so ascending order means >= here.
  • 1 <= k <= 3500
    -105 <= value of elements <= 105.

这道题应该也算特殊遍历的一种吧,毕竟我们没有直接的办法去求解答案,所以最有效的手段还是遍历,既然是遍历就讲究遍历的效率。

新的认识:我总是以为找到问题的性质,可以帮助解决问题,但这类题对问题的性质好像没那么看重,反而是遍历的技巧。我在纠结什么样的情况下可以得到符合[a,b]的情况,但反而把自己的思路给限制住了。之前遇到的尺取法同样如从,所以说:

问题本身性质挖的越深,或许算法越高效,但随之带来的各种边界情况越复杂,代码量会飞跃一个量级,得不偿失。而更加优秀的代码是解决问题的同时,尽量通俗易懂,在一个时间复杂度内就够了。

既然如此,我们就可以从【高效遍历】的角度来看待此题,首先最基本的性质是:

给定一个范围[a,b],使得每个list至少有一个值能够在此范围内,所以说,遍历过程中首先得满足这个条件,如何做到?

最简单的暴力做法:

还可以挖出一个有用的性质,[a,b]一定是所有的list中的某两个值,所以可以遍历所有pair对,怎么符合条件?有了[a,b]的区间,自然可以遍历k个list,存在一个值设一个flag,所有的k满足,更新候选[a,b]

代码参考:https://leetcode.com/articles/smallest-range

再来看看真正的遍历吧,与其在确定[a,b]之后判断是否符合更新规则,还不如直接构造出符合更新规则的情况,在这些情况下不断更新[a,b],此技巧算得上非常高级了,是一种求解问题思维的转变。

所以我们的目标变成:构造符合更新规则的情况(所有的list至少有一个元素在[a,b]范围内),那么我们从头开始考虑:

[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
此处有3个list,且均从小到大排列

第一个有效的遍历范围是?
很明显,从小的开始,因为题目要求range相同的情况下,a尽可能的小

所以符合要求的元素应该是[4,0,5]
它的范围很容易求解:max{4,0,5} - min{4,0,5} = 5 - 0 = 5;

继续第二个:
[4,0,5] + 10?
[4,0,5] + 9?
[4,0,5] + 18?

其实都可以,但需要注意新加的值均比此前的max大,所以没必要把它放在有效集合内,因为10 - 0 > 5, 9 - 0 > 5, 18 - 0 > 5;

此处是真正把复杂度降低的原因,需细细体会。

既然[4,0,5]情况下的后续所有遍历均无用,那唯一的办法就是删除[4,0,5]中的一个了,为了让range尽可能的小,很显然需要删除0.

所以[4,0,5] -> [4,5]
从有效集合变成无效集合了!!!再构建有效集合,必然加入0所在list的下一个元素!

所以[4,5] -> [4,5,9]
好了,更新区间吧,因为min变了,有:
max{4,5,9} - min{4,5,9} = 9 - 4 = 5

又是一个候选区间,更不更新看题目具体要求,此处不需要更新。

后面的更新自己推吧,最终就能得到答案[20,24]了

代码如下:

    class Node{
        int idx;
        int row;
        int val;

        public Node(int idx, int row, int val){
            this.idx = idx;
            this.row = row;
            this.val = val;
        }
    }

    public int[] smallestRange(List<List<Integer>> nums) {
        int max = Integer.MIN_VALUE;

        PriorityQueue<Node> queue = new PriorityQueue<>((a, b) -> (a.val - b.val));
        for (int i = 0; i < nums.size(); ++i){
            Node node = new Node(i, 0, nums.get(i).get(0));
            queue.offer(node);
            max = Math.max(max, node.val);
        }

        int range = Integer.MAX_VALUE;
        int start = -1, end = -1;

        while (queue.size() == nums.size()){
            Node curr = queue.poll();

            if (max - curr.val < range){
                range = max - curr.val;
                start = curr.val;
                end = max;
            }
            if (curr.row + 1 < nums.get(curr.idx).size()){
                curr.val = nums.get(curr.idx).get(curr.row + 1);
                curr.row = curr.row + 1;
                queue.offer(curr);
                max = Math.max(max, curr.val);
            }
        }
        return new int[]{start, end};
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值