Java&C++题解与拓展——leetcode728.自除数【跳出多层循环,vector at】

每日一题做题记录,参考官方和三叶的题解

目录

  • 题目要求
  • 思路一:模拟
    • Java
      • out
    • C++
    • Python3
  • 思路二:二分
    • Java
    • C++
    • vector
  • 思路三:哈希表
    • Java
    • C++
  • 总结

题目要求

Java&C++题解与拓展——leetcode728.自除数【跳出多层循环,vector at】_第1张图片

思路一:模拟

简单题亘古不变的模拟。

Java

class Solution {
    public List<Integer> selfDividingNumbers(int left, int right) {
        List<Integer> res = new ArrayList<>();
        out:for(int i = left; i <= right; i++) {
            int cur = i;
            while(cur != 0) {
                int t = cur % 10;
                if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
                    continue out;
                cur /= 10;
            }
            res.add(i);
        }
        return res;
    }
}
  • 时间复杂度: O ( n log ⁡ r i g h t ) O(n\log right) O(nlogright),其中 n n n为区间大小,即 n = r i g h t − l e f t + 1 n = right - left + 1 n=rightleft+1
  • 空间复杂度: O ( 1 ) O(1) O(1)

out

  • 这个out没见过,搜了一下发现是个标签,用来跳出循环,通常breakcontinue啥的都只能跳出当前循环,那加个out就可以跳出被其标记的那个循环;
  • 所以他只是个名字,叫别的也行。

C++

C++里面是没有java那个标签功能的,那想到三个替代思路:

  1. goto跳到指定位置(直接放弃、问就是因为爱情);
  2. 设置一个 f l a g flag flag判断(类似思路三里的ok);
  3. 写成一个单独函数return出去。【用这个写一下】
class Solution {
public:
    vector<int> selfDividingNumbers(int left, int right) {
        vector<int> res;
        for(int i = left; i <= right; i++)
            if(isDN(i))
                res.emplace_back(i);
        return res;
    }
    //for循环写成函数
    bool isDN(int i) {
        int cur = i;
        while(cur != 0) {
            int t = cur % 10;
            if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
                return false;
            cur /= 10;
        }
        return true;
    }
};
  • 时间复杂度: O ( n log ⁡ r i g h t ) O(n\log right) O(nlogright)
  • 空间复杂度: O ( 1 ) O(1) O(1)

Python3

可以一行解决,所以写一下,时空复杂度应该是同上了……吧

class Solution:
    def selfDividingNumbers(self, left: int, right: int) -> List[int]:
        return [num for num in range(left, right + 1) 
                        if all(i > 0 and num % i == 0
                            for i in map(int, str(num)))]
  • 时间复杂度: O ( n log ⁡ r i g h t ) O(n\log right) O(nlogright)
  • 空间复杂度: O ( 1 ) O(1) O(1)

思路二:二分

数据范围不大( [ 1 , 1 0 4 ] [1,10^4] [1,104]),所以可以先把所有自除数都找出来,然后再根据题目要求圈定范围。
采用二分找到范围内最小的自除数,然后依次输出就可了。

Java

【前面搞static是因为leetcode的判定机制,其实都写函数里面就ok,这样提交的时候就不会每个example都算一遍,提升判定效率,三叶姐的static大法。】

class Solution {
    //提前找到数据范围内所有的自除数
    static List<Integer> list = new ArrayList<>();
    static {
        out:for(int i = 1; i < 10000; i++) {
            int cur = i;
            while(cur != 0) {
                int t = cur % 10;
                if(t == 0 || i % t != 0)
                    continue out;
                cur /= 10;
            }
            list.add(i);
        }
    }

    //二分找在题目范围内最小的
    public List<Integer> selfDividingNumbers(int left, int right) {
        List<Integer> res = new ArrayList<>();
        int l = 0, r = list.size() - 1;
        while (l < r) {
            int m = l + r >> 1;
            if(list.get(m) >= left)
                r = m;
            else
                l = m + 1;
        }
        while(list.get(r) <= right)
            res.add(list.get(r++));
        return res;
    }
}
  • 时间复杂度: O ( n + log ⁡ C ) O(n+\log C) O(n+logC) n n n为区间大小, C C C为数据范围,本题中 C = 1 0 4 C=10^4 C=104
  • 空间复杂度: O ( C ) O(C) O(C)

C++

用刚才的2.写,设置一个stop的flag。
然后,没想到C++怎么搞类似上面那种自动执行的static函数,就直接写里面了。

class Solution {
public:
    vector<int> selfDividingNumbers(int left, int right) {
        //提前找到数据范围内所有的自除数
        vector<int> list;
        for(int i = 1; i < 10000; i++) {
            int cur = i;
            bool stop = false;
            while(cur != 0 && !stop) {
                int t = cur % 10;
                if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
                    stop = true;
                cur /= 10;
            }
            if(!stop)
                list.emplace_back(i);
        }

        //二分找在题目范围内最小的      
        vector<int> res;
        int l = 0, r = list.size() - 1;
        while(l < r) {
            int m = l + r >> 1;
            if(list.at(m) >= left)
                r = m;
            else
                l = m + 1;
        }
        while(list.at(r) <= right)
            res.emplace_back(list.at(r++));
        return res;
    }
};
  • 时间复杂度: O ( n + log ⁡ C ) O(n+\log C) O(n+logC)
  • 空间复杂度: O ( C ) O(C) O(C)

vector

  • 学习参考链接
  • 就只是提一下直接访问元素list[i]和用list.at(i)的区别,简单讲后者更安全,它会自动检查 i i i是否在合法范围内,不会产生越界等问题。

思路三:哈希表

避免二分,把索引也预处理出来(不会改变空间复杂度)。
i d x [ i ] idx[i] idx[i]:表示不超过 i i i的最大自除数在 l i s t list list中的编号。
【判定不超过 l e f t left left的最大自除数是否等于自己,等就恰好从此开始,不等就跳到下一个开始】

Java

class Solution {
    //提前找到数据范围内所有的自除数
    static List<Integer> list = new ArrayList<>();
    static int[] idx = new int[100001]; //list编号
    static {
        for(int i = 1; i < 10000; i++) {
            int cur = i;
            boolean ok = true;
            while(cur != 0 && ok) {
                int t = cur % 10;
                if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
                    ok = false;
                cur /= 10;
            }
            if(ok)
                list.add(i);
            idx[i] = list.size() - 1; //当前情况下最大的自除数编号
        }
    }

    //找在题目范围内最小的
    public List<Integer> selfDividingNumbers(int left, int right) {
        List<Integer> res = new ArrayList<>();
        int i = list.get(idx[left]) == left ? idx[left] : idx[left] + 1; //判断从left还是下一个开始
        while(list.get(i) <= right)
            res.add(list.get(i++));
        return res;
    }
}
  • 时间复杂度: O ( n ) O(n) O(n) n n n为区间大小
  • 空间复杂度: O ( C ) O(C) O(C)

C++

class Solution {
public:
    vector<int> selfDividingNumbers(int left, int right) {
        //提前找到数据范围内所有的自除数
        vector<int> list;
        int idx[100001]; //list编号
        for(int i = 1; i < 10000; i++) {
            int cur = i;
            bool stop = false;
            while(cur != 0 && !stop) {
                int t = cur % 10;
                if(t == 0 || i % t != 0) //某一位为0 or 不能被整除
                    stop = true;
                cur /= 10;
            }
            if(!stop)
                list.emplace_back(i);
            idx[i] = list.size() - 1; //当前情况下最大的自除数编号
        }

        //找在题目范围内最小的      
        vector<int> res;
        int i = list.at(idx[left]) == left ? idx[left] : idx[left] + 1; //判断从left还是下一个开始
        while(list.at(i) <= right)
            res.emplace_back(list.at(i++));
        return res;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( C ) O(C) O(C)

总结

简单题本来模拟就结了,基于数据范围和判定模式,还引申出来一种二分和哈希表避免二分的方法,挺有意思的。
【三月徽章get√,四月加油】


欢迎指正与讨论!

你可能感兴趣的:(leetcode,java,c++,leetcode)