时间复杂度小于O(n)的算法

快速幂:O(logn)

lintcode-140
( a + b ) % c = ( a % c + b % c ) % c (a+b)\%c=(a\%c+b\%c)\%c (a+b)%c=(a%c+b%c)%c
( a − b ) % c = ( a % c − b % c ) % c (a-b)\%c=(a\%c-b\%c)\%c (ab)%c=(a%cb%c)%c
( a × b ) % c = ( a % c × b % c ) % c (a\times b)\%c=(a\%c\times b\%c)\%c (a×b)%c=(a%c×b%c)%c
解法1:递归
a n % b = ( a n 2 × a n 2 ) % b a^n\%b=(a^{\frac{n}{2}}\times a^{\frac{n}{2}})\%b an%b=(a2n×a2n)%b

public class Solution {
    public int fastPower(int a, int b, int n) {
        if (n == 0) {
            return 1 % b;
        }
        long product = fastPower(a, b, n / 2);
        product = (product * product) % b;
        if (n % 2 == 1) {
            product = (product * a) % b;
        }
        return (int)product;
    }
}

解法2:非递归+二进制

public class Solution {
    public int fastPower(int a, int b, int n) {
        long product = 1, temp = a;
        while (n > 0) {
            if (n % 2 == 1) {
                product = (product * temp) % b;
            }
            temp = (temp * temp) % b;
            n /= 2;
        }
        return (int)product % b;
    }
}

辗转相除法(最大公约数):O(logn)

lintcode-845
辗转相除法, 又名欧几里德算法, 是求最大公约数的一种方法。它的具体做法是:用较大的数除以较小的数,再用除数除以出现的余数(第一余数),再用第一余数除以出现的余数(第二余数),如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。

public class Solution {
    public int gcd(int a, int b) {
        int big = Math.max(a, b), small = Math.min(a, b);
        if (small != 0) {
            return gcd(small, big % small);
        }
        return big;
    }
}

两个排序数组的中位数:O(logn)

lintcode-65
解法1:基于 FindKth
整体思想类似于 median of unsorted array 可以用 find kth from unsorted array 的解题思路

  1. 先将找中点问题转换为找第 k 小的问题,令 k=(n+m)/2:目标是在 logk=log((n+m)/2)=log(n+m) 的时间内找到 A 和 B 数组中从小到大第 k 个
  2. 比较 A 数组的第 k/2 小和 B 数组的第 k/2 小的数,谁小就扔掉谁的前 k/2 个数
  3. 将目标寻找第 k 小修改为寻找第 (k-k/2) 小
  4. 重复第 2 步,直到 k=1 或者 A 数组 B 数组里已经没有数了
public class Solution {
    public double findMedianSortedArrays(int[] a, int[] b) {
        int n = a.length + b.length;
        if (n % 2 == 1) {
            return findKthNum(a, 0, b, 0, n / 2 + 1);
        }
        return (findKthNum(a, 0, b, 0, n / 2 + 1) + findKthNum(a, 0, b, 0, n / 2)) / 2.0;
    }

    public int findKthNum(int[] a, int startA, int[] b, int startB, int k) {
        if (startA >= a.length) {
            return b[startB + k - 1];
        }
        if (startB >= b.length) {
            return a[startA + k - 1];
        }
        if (k == 1) {
            return Math.min(a[startA], b[startB]);
        }
        int halfKA = startA + k / 2 - 1 < a.length ? a[startA + k / 2 - 1] : Integer.MAX_VALUE;
        int halfKB = startB + k / 2 - 1 < b.length ? b[startB + k / 2 - 1] : Integer.MAX_VALUE;
        if (halfKA < halfKB) {
            return findKthNum(a, startA + k / 2, b, startB, k - k / 2);
        }
        return findKthNum(a, startA, b, startB + k / 2, k - k / 2);
    }
}

解法2:二分法
二分 median 的值,然后再用二分法看一下两个数组里有多少个数小于这个二分出来的值

  1. 我们需要先确定二分的上下界限,由于两个数组 A, B 均有序,所以下界为 min(A[0],B[0]),上界为 max(A[A.length-1],B[B.length-1])
  2. 判断当前上下界限下的 mid 是否为我们需要的答案:分别对两个数组进行二分来找到两个数组中小于等于当前 mid 的数的个数 cnt1 与 cnt2,sum=cnt1+cnt2 即为 A 跟 B 合并后小于等于当前 mid 的数的个数.
  3. 如果 sum< k,即中位数肯定不是 mid,应该大于 mid,更新 start 为 mid,否则更新 end 为 mid,之后再重复第二步
  4. 当不满足 start+1< end 这个条件退出二分循环时,再分别判断一下 start 和 end ,最终返回符合要求的那个数即可

注意

  • 这一题如果用二分法来做,其实就是一个二分答案的过程
  • 首先我们已经得到了上下界限,那么答案必定是在这个上下界限中的,需要实现的就是从这个歌上下界限中找出答案
  • 我们每次取的 mid,其实就是我们每次在假设答案为 mid,二分的过程就是不断的推翻这个假设,然后再假设新的答案
  • 需要满足的条件为:上面算法描述中的 sum 需要等于 k,这里的 k=(A.length+B.length)/2;如果 sum< k,很明显当前的 mid 偏小,需要增大,否则就说明当前的 mid 偏大,需要缩小
  • 最终在 start 与 end 相邻的时候退出循环,判断 start 跟 end 哪个符合条件即可得到最终结果
public class Solution {
    public double findMedianSortedArrays(int[] a, int[] b) {
        int n = a.length + b.length;
        if (n % 2 == 1) {
            return findKthNum(a, b, n / 2 + 1);
        }
        return (findKthNum(a, b, n / 2) + findKthNum(a, b, n / 2 + 1)) / 2.0;
    }

    public int findKthNum(int[] a, int[] b, int index) {
        if (a.length == 0) {
            return b[index - 1];
        }
        if (b.length == 0) {
            return a[index - 1];
        }
        int start = Math.min(a[0], b[0]), end = Math.max(a[a.length - 1], b[b.length - 1]);
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (countLessEqual(a, mid) + countLessEqual(b, mid) < index) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (countLessEqual(a, start) + countLessEqual(b, start) >= index) {
            return start;
        }
        return end;
    }

    public int countLessEqual(int[] a, int target) {
        int start = 0, end = a.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (a[mid] <= target) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (a[start] > target) {
            return start;
        }
        if (a[end] > target) {
            return end;
        }
        return a.length;
    }
}

相似题目:K个有序数组的中位数
lintcode-931

public class Solution {
    public double findMedian(int[][] nums) {
        int n = getTotalLen(nums);
        if (n == 0) {
            return 0;
        }
        if (n % 2 == 1) {
            return findKthNum(nums, n / 2 + 1);
        }
        return findKthNum(nums, n / 2) / 2.0 + findKthNum(nums, n / 2 + 1) / 2.0;
    }

    public int getTotalLen(int[][] a) {
        int len = 0;
        for (int[] arr : a) {
            len += arr.length;
        }
        return len;
    }

    public int findKthNum(int[][] a, int index) {
        int start = Integer.MAX_VALUE, end = Integer.MIN_VALUE;
        for (int i = 0; i < a.length; ++i) {
            if (a[i].length == 0) {
                continue;
            }
            start = Math.min(start, a[i][0]);
            end = Math.max(end, a[i][a[i].length - 1]);
        }
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (countLessEqual2D(a, mid) < index) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (countLessEqual2D(a, start) >= index) {
            return start;
        }
        return end;
    }

    public int countLessEqual2D(int[][] a, int target) {
        int cnt = 0;
        for (int i = 0; i < a.length; ++i) {
            if (a[i].length == 0) {
                continue;
            }
            cnt += countLessEqual1D(a[i], target);
        }
        return cnt;
    }

    public int countLessEqual1D(int[] a, int target) {
        int start = 0, end = a.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (a[mid] <= target) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (a[start] > target) {
            return start;
        }
        if (a[end] > target) {
            return end;
        }
        return a.length;
    }
}

分解质因数:O(sqrt(n))

lintcode-235
步骤

  1. 记 up=sqrt(n),作为质因数 k 的上界,初始化 k=2
  2. 当 k<=up 且 n 不为 1 时,执行步骤 3,否则执行步骤 4
  3. 当 n 被 k 整除时,不断整除并覆盖 n,同时结果中记录 k,直到 n 不能整出k为止;之后 k 自增,执行步骤 2
  4. 当 n 不为 1 时,把 n 也加入结果当中,算法结束

注意

  • 不需要判定 k 是否为质数,如果 k 不为质数,且能整除 n 时,n 早被 k 的因数所除,故能整除 n 的 k 必是质数
  • 为何引入 up?为了优化性能:当 k 大于 up 时,k 已不可能整除 n,除非 k 是 n 自身;也即为何步骤 4 判断 n 是否为 1,n 不为 1 时必是比 up 大的质数
  • 步骤 2 中也判定 n 是否为1,也是为了性能:当 n 已为 1 时可早停
public class Solution {
    public List<Integer> primeFactorization(int num) {
        List<Integer> factors = new ArrayList<Integer>();
        for (int i = 2; i * i <= num; ++i) {
            if (num <= 1) {
                break;
            }
            while (num % i == 0) {
                factors.add(i);
                num /= i;
            }
        }
        if (num > 1) {
            factors.add(num);
        }
        return factors;
    }
}

复杂度分析

  • 最坏时间复杂度 O(sqrt(n)):当 n 为质数时,取到其最坏时间复杂度
  • 空间复杂度 O(log(n)):当 n 质因数很多时,需要空间大,但不会多于 O(log(n)) 个

分块检索算法:O(sqrt(n))

统计前面比自己小的数的个数
lintcode-249

public class Solution {
    class Block {
        private int total;
        private int[] counts;

        public Block(int size) {
            this.total = 0;
            this.counts = new int[size];
        }
    }

    class BlockArray {
        private Block[] blocks;
        private int blockSize;

        public BlockArray(int capacity) {
            this.blockSize = (int) Math.sqrt(capacity);
            int blockNum = capacity / this.blockSize + 1;
            this.blocks = new Block[blockNum];
            for (int i = 0; i < blockNum; ++i) {
                this.blocks[i] = new Block(blockSize);
            }
        }

        public int countLess(int target) {
            int cnt = 0;
            int index = target / this.blockSize;
            for (int i = 0; i < index; ++i) {
                cnt += this.blocks[i].total;
            }
            for (int i = 0; i < target - index * this.blockSize; ++i) {
                cnt += this.blocks[index].counts[i];
            }
            return cnt;
        }

        public void insert(int target) {
            int index = target / this.blockSize;
            this.blocks[index].total++;
            this.blocks[index].counts[target - index * this.blockSize]++;
        }
    }

    public List<Integer> countOfSmallerNumberII(int[] a) {
        List<Integer> counts = new ArrayList<Integer>();
        BlockArray blocks = new BlockArray(10000);
        for (int num : a) {
            counts.add(blocks.countLess(num));
            blocks.insert(num);
        }
        return counts;
    }
}

相似题目:统计比给定整数小的数的个数
lintcode-248
解法1:分块检索法

public class Solution {
    class Block {
        private int total;
        private int[] counts;

        public Block(int size) {
            this.total = 0;
            this.counts = new int[size];
        }
    }

    class BlockArray {
        private Block[] blocks;
        private int blockSize;

        public BlockArray(int capacity) {
            this.blockSize = (int) Math.sqrt(capacity);
            int blockNum = capacity / this.blockSize + 1;
            this.blocks = new Block[blockNum];
            for (int i = 0; i < blockNum; ++i) {
                this.blocks[i] = new Block(blockSize);
            }
        }

        public int countLess(int target) {
            int cnt = 0;
            int index = target / this.blockSize;
            for (int i = 0; i < index; ++i) {
                cnt += blocks[i].total;
            }
            for (int i = 0; i < target - index * this.blockSize; ++i) {
                cnt += blocks[index].counts[i];
            }
            return cnt;
        }

        public void insert(int target) {
            int index = target / this.blockSize;
            blocks[index].total++;
            blocks[index].counts[target - index * this.blockSize]++;
        }
    }

    public List<Integer> countOfSmallerNumber(int[] a, int[] queries) {
        List<Integer> counts = new ArrayList<Integer>();
        BlockArray blocks = new BlockArray(10000);
        for (int num : a) {
            blocks.insert(num);
        }
        for (int query : queries) {
            counts.add(blocks.countLess(query));
        }
        return counts;
    }
}

解法2:二分法

public class Solution {
    public List<Integer> countOfSmallerNumber(int[] a, int[] queries) {
        List<Integer> counts = new ArrayList<Integer>();
        Arrays.sort(a);
        for (int query : queries) {
            counts.add(countLess(a, query));
        }
        return counts;
    }

    public int countLess(int[] a, int target) {
        if (a.length == 0) {
            return 0;
        }
        int start = 0, end = a.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (a[mid] < target) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (a[start] >= target) {
            return start;
        }
        if (a[end] >= target) {
            return end;
        }
        return a.length;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值