如何降低以下代码块的时间复杂度?

Posted

技术标签:

【中文标题】如何降低以下代码块的时间复杂度?【英文标题】:How can I reduce the time complexity of the following block of code? 【发布时间】:2019-02-03 15:47:05 【问题描述】:

我正在取 1 到 n 位数字并找到可被 a 或 b 整除但不能被两者整除的数字计数。 我想通过一些逻辑变化来降低这个块的时间复杂度。

cin >> n >> a >> b >> k;      
for(int i = 1; i <= n; i++) 
    if(i % a == 0 && i % b==0) 
        count++;
     else if(i % b == 0 && i % a != 0) 
        count++;
    

【问题讨论】:

我认为你的第一个条件应该是i%a != 0 &amp;&amp; i%b == 0 变量“k”是什么? 【参考方案1】:

计算可被 a 整除的数,将其与可被 b 整除的数相加,再减去可被 a、b 的 lcm(最小公倍数)整除的数的两倍。

时间复杂度:O(log(min(a,b)))

因为要计算最小公倍数,所以要计算 gcd(最大公约数),可以在 O(log(min(a,b))) 中计算

注意:如果包含bits/stdc++.h,可以使用内置函数计算gcd:__gcd(int , int)

int lcm(int a, int b) 
        return (a * b)/__gcd(a,b);
    

cin>>n>>a>>b>>k;

int divisible_by_a = n / a;
int divisible_by_b = n / b;
int divisible_by_both = n / lcm(a,b);

ans = divisible_by_a + divisible_by_b - 2*divisible_by_both;

【讨论】:

它不是“C++”版本。它是内部编译器特定的实现版本,有竞争力的程序员在包含 bits/ 标头时获得。 来自 C++17 lcm 可从 获得:en.cppreference.com/w/cpp/numeric/lcm【参考方案2】:

在我看来,您的代码不像您描述的那样工作:它对每个可被b 整除的数字都很重要。您应该检查 i 是否是 a 或 b 的倍数

if (i % a == 0 && i % b != 0) ...
 else if (i % a != 0 && i % b == 0) ...

我还向您建议一种不同的方法:找到 ab 的倍数,直到您达到 n 并计算这些数字。从总和中删除列表中的相同数字(最好在最终总和之前这样做)

【讨论】:

【参考方案3】:

在优化它之前,确保它首先工作。

现在,您正在检查一个数字是只能被b 整除还是被两者 ab 整除。要使其成为ab 而不是两者,您需要在第一个条件下将i % b==0 切换为i % b!=0

for(int i = 1; i <= n; i++) 
    if(i % a == 0 && i % b!=0) 
        count++;
     else if(i % b == 0 && i % a != 0) 
        count++;
    

为了加快速度,您可以做的一件小事是每次只进行一次可除性检查并保存结果而不是两次。然后,您可以对最终结果使用单个 XOR。

for(int i = 1; i <= n; i++) 
    int div_a = (i % a == 0);
    int div_b = (i % b == 0);

    if (a ^ b) 
        count++;
    

【讨论】:

【参考方案4】:

让我们从这个开始:

    temp = a;
    while(temp < n) 
        if(temp%b != 0) 
            count++;
        
        temp += a;
    
    temp = b;
    while(temp < n) 
        if(temp%a != 0) 
            count++;
        
        temp += b;
    

接下来,考虑一些作弊。如果a%b == 0 则任何能被a 整除的数也能被b 整除;和b%a == 0 类似。在这两种情况下,计数都必须为零。

如果a == 0 则没有数字可以被a 整除;和b == 0 类似;如果ab 都为零,则计数必须为零。

最后;不要忘记(在 C 中)x%0 的行为是未定义的,您需要注意这一点。

结合以上所有内容,您会得到如下结果:

    if( (a == 0) && (b == 0) ) 
        return 0;
    
    if( (a != 0) && (b != 0) ) 
        if( (a%b == 0) || (b%a == 0) ) 
            return 0;
        
    

    count = 0;
    if(a != 0) 
        temp = a;
        while(temp < n) 
            if(temp%b != 0) 
                count++;
            
            temp += a;
        
    
    if(b != 0) 
        temp = b;
        while(temp < n) 
            if(temp%a != 0) 
                count++;
            
            temp += b;
        
    
    return count;

下一轮作弊:

如果 n &lt;= 1 则计数必须为零。 如果a == 1a == -1 则所有数字都可以被a 整除。 如果b == 1b == -1 则所有数字都可以被b 整除。

为了解决这些问题,我会使用“嵌套开关”来尽量减少分支的数量,例如:

switch(a) 
    case 0:
        switch(b) 
            case 0:
                ...
                break;
            case -1:
            case 1:
                ...
                break;
            default:
                ...
                break;
        
        break;
    case -1:
    case 1:
        switch(b) 
            case 0:
                ...
                break;
            case -1:
            case 1:
                ...
                break;
            default:
                ...
                break;
        
        break;
    default:
        switch(b) 
            case 0:
                ...
                break;
            case -1:
            case 1:
                ...
                break;
            default:
                ...
                break;
        
        break;

【讨论】:

以上是关于如何降低以下代码块的时间复杂度?的主要内容,如果未能解决你的问题,请参考以下文章

如何降低排序的时间复杂度?

重构此方法以将其认知复杂度从 16 降低到允许的 15。如何重构和降低复杂度?

如何降低这个问题的时间复杂度

程序优化--降低复杂度

程序优化--降低复杂度

如何降低装桶程序的时间复杂度?