LeetCode 974 - Subarray Sums Divisible by K


https://leetcode.com/problems/subarray-sums-divisible-by-k/
Given an array A of integers, return the number of (contiguous, non-empty) subarrays that have a sum divisible by K.

Example 1:
Input: A = [4,5,0,-2,-3,1], K = 5
Output: 7
Explanation: There are 7 subarrays with a sum divisible by K = 5:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

Note:
  1. 1 <= A.length <= 30000
  2. -10000 <= A[i] <= 10000
  3. 2 <= K <= 10000

Approach 1: Prefix Sums and Counting
As is typical with problems involving subarrays, we use prefix sums to add each subarray. Let P[i+1] = A[0] + A[1] + ... + A[i]. Then, each subarray can be written as P[j] - P[i] (for j > i). Thus, we have P[j] - P[i] equal to 0 modulo K, or equivalently P[i] and P[j] are the same value modulo K.
Algorithm
Count all the P[i]'s modulo K. Let's say there are C_x values P[i] \equiv x \pmod{K}. Then, there are \sum_x \binom{C_x}{2} possible subarrays.
For example, take A = [4,5,0,-2,-3,1]. Then P = [0,4,9,9,7,4,5], and C_0 = 2, C_2 = 1, C_4 = 4:
  • With C_0 = 2 (at P[0]P[6]), it indicates \binom{2}{2} = 1 subarray with sum divisible by K, namely A[0:6] = [4, 5, 0, -2, -3, 1].
  • With C_4 = 4 (at P[1]P[2]P[3]P[5]), it indicates \binom{4}{2} = 6 subarrays with sum divisible by K, namely A[1:2]A[1:3]A[1:5]A[2:3]A[2:5]A[3:5].
  • Time Complexity: O(N), where N is the length of A.
  • Space Complexity: O(N). (However, the solution can be modified to use O(K) space by storing only count.)
  public int subarraysDivByK(int[] A, int K) {
    int N = A.length;
    int[] P = new int[N + 1];
    for (int i = 0; i < N; ++i)
      P[i + 1] = P[i] + A[i];

    int[] count = new int[K];
    for (int x : P)
      count[(x % K + K) % K]++;

    int ans = 0;
    for (int v : count)
      ans += v * (v - 1) / 2;
    return ans;

  }
https://www.geeksforgeeks.org/count-sub-arrays-sum-divisible-k/
    static int subCount(int arr[], int n, int k)
    {
  
        // create auxiliary hash array to
        // count frequency of remainders
        int mod[] = new int[k];
        Arrays.fill(mod, 0);
  
        // Traverse original array and compute cumulative
        // sum take remainder of this current cumulative
        // sum and increase count by 1 for this remainder
        // in mod[] array
        int cumSum = 0;
        for (int i = 0; i < n; i++) {
            cumSum += arr[i];
  
            // as the sum can be negative, taking modulo twice
            mod[((cumSum % k) + k) % k]++;
        }
  
        // Initialize result
        int result = 0;
  
        // Traverse mod[]
        for (int i = 0; i < k; i++)
  
            // If there are more than one prefix subarrays
            // with a particular mod value.
            if (mod[i] > 1)
                result += (mod[i] * (mod[i] - 1)) / 2;
  
        // add the elements which are divisible by k itself
        // i.e., the elements whose sum = 0
        result += mod[0];
  
        return result;
    }

https://www.jianshu.com/p/7d9479ffcd5a
其实上面的解法已经很接近了,但是思考一下为什么会超时?比如K=5,当sum[0]=4时,后面的sum[i]=5*n + 4都符合要求,即不需要判断每个和,只需要判断sum[i]%5的值就好了。
假设:
sum[i]=p*K+ri
sum[j]=q*K+rj
则:
sum[j]-sum[i]=(q-p)*K+rj-ri
若想sum[j]-sum[i]被K整除,只需要rj-ri能够被K整除即可,因为rj和ri都是对K的模,所以只要rj=ri即可。
所以这道题就变成了求对K的每个模的个数,然后对于每个0~i,分别两两组合,假设mod[i]=n,那么其i的个数为n*(n-1)/2.
时间复杂度:O(n)
空间复杂度:O(K)
public int subarraysDivByK(int[] A, int K) {
    int mod[] = new int[K];

    int cumSum = 0;
    for (int i = 0; i < A.length; i++) {
        cumSum += A[i];
        // countSum % K 可能是负数,需要 +K
        mod[((cumSum % K) + K) % K]++;
    }

    int ans = 0;
    for (int i = 0; i < K; i++) {
        if (mod[i] > 1) {
            ans += (mod[i] * (mod[i] - 1)) / 2;
        }
    }

    ans += mod[0];
    return ans;
}
https://blog.csdn.net/dong_beijing/article/details/86444647
大致思路:比如数组A = [1,2,1], K=2,那么1%2 =1,(1+2)%2=1,所以 {2}是符合条件的子数组,而{1}和{1,2}的特点是有共同的余数1。类似的{}和{1,2,1}有共同的余数0,所以{1,2,1}是满足条件的数组。

详细思路:

(1)统计0到A.size()累计和的余数;

(2)统计相同余数的数量;

(3)相同余数的数量累加;

补充,由于题目A为整数,那么要处理负数的情况,负数的余数是A[i]%K+K。

    int subarraysDivByK(vector<int>& a, int k) {
        int as =a.size();
        vector<int> remain(as+1,0);
        int sum=0;
        for(int i=0;i<as;i++)
        {
            sum+=a[i];//statistac for remainer
            remain[i]=(sum%k+k)%k;//+k for neg
        }
        vector<int> stat(k,0);
        int ret=0;
        for(int i=0;i<=as;i++)
        {
            ret+=stat[remain[i]];//get same remainer counts
            stat[remain[i]]++;
        }
        return ret;
    }
X. https://leetcode.com/problems/subarray-sums-divisible-by-k/discuss/217985/JavaC%2B%2B-Count-the-Remainder
Calculate the prefix sum and count it.
Java, use HashMap
    public int subarraysDivByK(int[] A, int K) {
        Map<Integer, Integer> count = new HashMap<>();
        count.put(0, 1);
        int prefix = 0, res = 0;
        for (int a : A) {
            prefix = (prefix + a % K + K) % K;
            res += count.getOrDefault(prefix, 0);
            count.put(prefix, count.getOrDefault(prefix, 0) + 1);
        }
        return res;
    }
Java, Use Array
    public int subarraysDivByK(int[] A, int K) {
        int[] count = new int[K];
        count[0] = 1;
        int prefix = 0, res = 0;
        for (int a : A) {
            prefix = (prefix + a % K + K) % K;
            res += count[prefix]++;
        }
        return res;
    }
https://leetcode.com/problems/subarray-sums-divisible-by-k/discuss/217980/Java-O(N)-with-HashMap-and-prefix-Sum
About the problems - sum of contiguous subarray , prefix sum is a common technique.
Another thing is if sum[0, i] % K == sum[0, j] % K, sum[i + 1, j] is divisible by by K.
So for current index j, we need to find out how many index i (i < j) exit that has the same mod of K.
Now it easy to come up with HashMap <mod, frequency>


Time Complexity: O(N)
Space Complexity: O(K)
Very clever solution with map.put(0,1) to deal with "remainder = 0" situation
    public int subarraysDivByK(int[] A, int K) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);//\\
        int count = 0, sum = 0;
        for(int a : A) {
            sum = (sum + a) % K;
            if(sum < 0) sum += K;  // Because -1 % 5 = -1, but we need the positive mod 4
            count += map.getOrDefault(sum, 0);
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return count;
    }
we can just use array, which is faster than HashMap.
    public int subarraysDivByK(int[] A, int K) {
        int[] map = new int[K];
  map[0] = 1;
        int count = 0, sum = 0;
        for(int a : A) {
            sum = (sum + a) % K;
            if(sum < 0) sum += K;  // Because -1 % 5 = -1, but we need the positive mod 4
            count += map[sum];
            map[sum]++;
        }
        return count;
    }
https://leetcode.com/problems/subarray-sums-divisible-by-k/discuss/217962/Java-Clean-O(n)-Number-Theory-%2B-Prefix-Sums
I am already going to assume that you know about prefix sums before you read this.
We can all agree that for an array int[] A, where N = len(A), that there are N prefix sums.
prefix[0] = A[0], prefix[1] = A[0] + A[1], ... prefix[i] = prefix[0] + ... + prefix[i].
Then to calculate how many subarrays are divisible by K is logically equivalent to saying, how many ways can we pair up all prefix sum pairs (i,j) where i < j
such that (prefix[j] - prefix[i]) % K == 0.
Just from that information alone we easily get a O(n^2) solution.
Compute all prefix sums, then check all pair to see if k divides the difference between them.
However, if we just exploit some information w.r.t to the remainder of each prefix sum we can manipulate this into a linear algorithm. Here's how.
Number Theory Part
I noted above that we need to find all prefix sum pairs (i,j) such tha (p[j] - p[i]) % K == 0.
But this is only true, if and only if p[j] % K == p[i] % K
Why is this?
According the the division algorithm we can express p[j] and p[i] in the following way.
p[j] = bK + r0 where 0 <= r0 < K
p[i] = a
K + r1 where 0<= r1 < K
Then p[j] - p[i] = (b*K + r0) - (a*K + r1)
= b*K - a*K + r0 - r1 = K*(b-a) + r0 - r1
Again: p[j] - p[i] = K*(b-a) + (r0-r1), in other words
K only divides p[j] - p[i] iff r0-r1 = 0 <-> r0 = r1 QED
But we should not forget about elements in the array that do not need a pairing, namely those that are are divisible by K. That's why I add mod[0] at the end.


Also counting pairs => N choose 2 = > n*(n-1) / 2.
https://leetcode.com/problems/subarray-sums-divisible-by-k/discuss/217979/Pictured-Explanation-Python-O(n)-Clean-Solution-8-Lines!


Running Sum[i]%K == Running Sum[j]%k that means we have sum(i,j) which is divisible by K.
Thus, we keep HashMap = {RunningSum%K : Frequency_Count}
Time Complexity : O(n)
Space Complexity : O(K) - As map keys are always Modulus of K with running sum.



X. https://blog.csdn.net/fuxuemingzhu/article/details/86438244
定义DP数组,其中dp[i]代表以i结尾的能被K整除的子数组的最多个数。既然要求子数组的和,那么我们知道,肯定需要求数组累积和sums的。那么,对于sums每个位置i向前找,找到第一个差能被K整除的位置j,就停止即可。此时的dp[i] = dp[j] + 1. 题目要求的结果是sum(dp).

下面分析这个递推公式怎么来的。dp[j]表示以j位置结尾的子数组,该子数组的和能被K整除。那么当我们寻找到(sums[i] - sums[j]) % K == 0的时候,说明子数组sums[i, j]也能被K整除,那么,以i结尾的所有子数组个数等于以j结尾的子数组后面拼接上[i,j]子数组,加上[i,j]子数组.即dp[i] = dp[j] + 1。那么,为什么会break掉呢?这是因为,我们dp的状态是以i结尾的子数组的最大个数,再往前搜索则不是最大的。

这个代码的时间复杂度是O(N^2)的,也AC了,我认为主要是break的功劳。
    int subarraysDivByK(vector<int>& A, int K) {
        const int N = A.size();
        vector<int> sums = {0};
        for (int a : A)
            sums.push_back(a + sums.back());
        vector<int> dp(N + 1, 0);
        int res = 0;
        for (int i = 1; i <= N; ++i) {
            for (int j = i - 1; j >= 0; --j) {
                if ((sums[i] - sums[j]) % K == 0) {
                    dp[i] = dp[j] + 1;
                    res += dp[i];
                    break;
                }
            }
        }
        return res;
    }
前缀和求余
其实,在上面这个做法当中,我们对于每个i位置都向前去查询(sums[i] - sums[j]) % K == 0的j,找到之后立马break,这一步可以更加简化,只要我们使用前缀和,并且相同余数的和。

如果(sums[i] - sums[j]) % K == 0,说明sums[i]和sums[j]都是K的倍数,所以得出sums[i] % K == sums[j] % K。也就是说,我们找到sums[i] % K这个数字的之前的状态即可。根据上面的DP,我们知道只需要找到最后的这个结果,然后break掉的意思就是,我们只需要保存最后的状态。总之,我们需要维护一个hash_map,保存每个余数在每个位置前面,出现的次数。

刚开始时,m[0] = 1,即假设0的出现了1次。然后遍历每个数字,对前缀和+当前数字再求余,在C++里面由于负数求余得到的是负数,所以要把负余数+K才行。把字典中保存的之前的结果累计,就是结果。

想了很久为什么是加的当前数字之前的结果,而不是现在的结果?这是因为,我们当前再次遇到了这个数字,才能说明形成了一个同余的区间。所以加的永远是前面的余数存在了多少次。这样,当某个余数只出现了1次时,并不会计入到结果里。
    int subarraysDivByK(vector<int>& A, int K) {
        unordered_map<int, int> m;
        int preSum = 0;
        int res = 0;
        m[0] = 1;
        for (int a : A) {
            preSum = (preSum + a) % K;
            if (preSum < 0) preSum += K;
            res += m[preSum]++;
        }
        return res;
    }

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts