How can the complexity of this function be decreased? - ruby

I got this function:
def get_sum_slices(a, sum)
count = 0
a.length.times do |n|
a.length.times do |m|
next if n > m
count += 1 if a[n..m].inject(:+) == sum
end
end
count
end
Given this [-2, 0, 3, 2, -7, 4] array and 2 as sum it will return 2 because two sums of a slice equal 0 - [2] and [3, 2, -7, 4]. Anyone an idea on how to improve this to a O(N*log(N))?

I am not familiar with ruby, but it seems to me you are trying to find how many contiguous subarrays that sums to sum.
Your code is doing a brute force of finding ALL subarrays - O(N^2) of those, summing them - O(N) each, and checking if it matches.
This totals in O(N^3) code.
It can be done more efficiently1:
define a new array sums as follows:
sums[i] = arr[0] + arr[1] + ... + arr[i]
It is easy to calculate the above in O(N) time. Note that with the assumption of non negative numbers - this sums array is sorted.
Now, iterate the sums array, and do a binary search for each element sums[i], if there is some index j such that sums[j]-sums[i] == SUM. If the answer is true, add by one (more simple work is needed if array can contains zero, it does not affect complexity).
Since the search is binary search, and is done in O(logN) per iteration, and you do it for each element - you actually have O(NlogN) algorithm.
Similarly, but adding the elements in sums to a hash-set instead of placing them in a sorted array, you can reach O(N) average case performance, since seeking each element is now O(1) on average.
pseudo code:
input: arr , sum
output: numOccurances - number of contiguous subarrays that sums to sum
currSum = 0
S = new hash set (multiset actually)
for each element x in arr:
currSum += x
add x to S
numOccurances= 0
for each element x in S:
let k = number of occurances of sum-x in the hashset
numOccurances += k
return numOccurances
Note that the hash set variant does not need the restriction of non-negative numbers, and can handle it as well.
(1) Assuming your array contains only non negative numbers.

According to amit's algorithm :
def get_sum_slices3(a, sum)
s = a.inject([]) { |m, e| m << e + m.last.to_i }
s.sort!
s.count { |x| s.bsearch { |y| x - y == sum } }
end
Ruby uses quicksort which is nlogn in most cases

You should detail better what you're trying to achieve here. Anyway computing the number of subarray that have a specific sum could be done like this:
def get_sum_slices(a, sum)
count = 0
(2..a.length).each do |n|
a.combination(n).each do |combination|
if combination.inject(:+) == sum
count += 1
puts combination.inspect
end
end
end
count
end
btw your example should return 6
irb> get_sum_slices [-2, 0, 3, 2, -7, 4], 0
[-2, 2]
[-2, 0, 2]
[3, -7, 4]
[0, 3, -7, 4]
[-2, 3, 2, -7, 4]
[-2, 0, 3, 2, -7, 4]
=> 6

Related

Finding the shortest contiguous subsequence of array for which the sum can be divided by K

For example, given input arr = [4,5,0,-2,-3,1], k = 5, there are 7 subarrays with a sum divisible by 5: [4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]. The shortest one is either [5] or [0], both are correct.
If there are no answers return an empty array.
This question is similar to leetcode no. 974 but I can't figure out how to apply the same algorithm to this (that one doesn't really account for the length of subarrays). Can someone help (preferably in python). Thank you!
Track the cumulative sums mod k in a map, with the cum_sums mod k as keys and the most recent index as values. Prior to updating the map for each index, first check if that key already exists. If it does, you have a contiguous subsequence divisible by k, and can calculate the length from the indices. Keep track of the min length.
Ruby code
def f(arr, k)
h = Hash.new
h[0] = -1 # to handle the case where the first r elts are divisible by k.
min_len = Float::INFINITY
cum_sum_mod_k = 0
arr.each_with_index do |val, i|
cum_sum_mod_k += val
cum_sum_mod_k %= k
if h.key?(cum_sum_mod_k)
cur_len = i - h[cum_sum_mod_k]
min_len = cur_len if cur_len < min_len
end
h[cum_sum_mod_k] = i
end
return min_len
end

How to find maximum sum of smallest and second smallest elements chosen from all possible subarrays

Given an array, find maximum sum of smallest and second smallest elements chosen from all possible subarrays. More formally, if we write all (nC2) subarrays of array of size >=2 and find the sum of smallest and second smallest, then our answer will be maximum sum among them.
Examples: Input : arr[] = [4, 3, 1, 5, 6] Output : 11`
Subarrays with smallest and second smallest are,
[4, 3] smallest = 3 second smallest = 4
[4, 3, 1] smallest = 1 second smallest = 3
[4, 3, 1, 5] smallest = 1 second smallest = 3
[4, 3, 1, 5, 6] smallest = 1 second smallest = 3
[3, 1] smallest = 1 second smallest = 3
[3, 1, 5] smallest = 1 second smallest = 3
[3, 1, 5, 6] smallest = 1 second smallest = 3
[1, 5] smallest = 1 second smallest = 5
[1, 5, 6] smallest = 1 second smallest = 5
[5, 6] smallest = 5 second smallest = 6
Maximum sum among all above choices is, 5 + 6 = 11
This question is on GFG but I didn't understand its explanation.
Please anybody gives its solution in O(n) time complexity.
The question is:
Why are we guaranteed to always find the maximum sum if we only look at all subarrays of length two?
To answer that question, lets assume we have some array A. Inside that array, obviously, there has to be at least one subarray S for which the smallest and second smallest elements, let's call them X and Y, sum up to our result.
If these two elements are already next to each other, this means that there is a subarray of A of length two that will contain X and Y, and thus, if we only look at all the subarrays of length two, we will find X and Y and output X+Y.
However, the question is: Is there any way for our two elements X and Y to not be "neighbors" in S? Well, if this was the case, there obviously would need to be other numbers, lets call them Z0, Z1, ..., between them.
Obviously, for all these values, it would have to hold that Zi >= X and Zi >= Y, because in S, X and Y are the smallest and second smallest elements, so there can be no other numbers smaller than X or Y.
If any of the Zi were bigger than X or Y, this would mean that there would be a subarray of A that only included this bigger Zi plus its neighbor. In this subarray, Zi and its neighbor would be the smallest and second smallest elements, and they would sum up to a larger sum than X+Y, so our subarray S would not have been the subarray giving us our solution. This is a contradiction to our definition of S, so this can not happen.
So, all the Zi can not be smaller than X or Y, and they can not be bigger than X or Y. This only leaves one possibility: For X == Y, they could all be equal. But, in this case, we obviously also have a subarray of length 2 that sums up to our correct result.
So, in all cases, we can show that there has to be a subarray of length two where both elements sum up to our result, which is why the algorithm is correct.
At first place you didn't understand the question! if you consider all sub-arrays carefully, at the end you can see all sub-arrays are related; in other words we are considering the result of previous sub-array into the current sub-array
Subarrays with smallest and second smallest are,
[4, 3] smallest = 3 second smallest = 4
[4, 3, 1] smallest = 1 second smallest = 3
[4, 3, 1, 5] smallest = 1 second smallest = 3
[4, 3, 1, 5, 6] smallest = 1 second smallest = 3
[3, 1] smallest = 1 second smallest = 3
[3, 1, 5] smallest = 1 second smallest = 3
[3, 1, 5, 6] smallest = 1 second smallest = 3
[1, 5] smallest = 1 second smallest = 5
[1, 5, 6] smallest = 1 second smallest = 5
[5, 6] smallest = 5 second smallest = 6
Maximum sum among all above choices is, 5 + 6 = 11
From each subarray:
1. we are taking the current largest value and if it is greater than previous largest we are replacing, and previous largest eventually becomes second most largest.
2. we are repeating this steps for every possible subarray (increasing in terms of index).
3. and at the end you can see, we are taking first-most and second-most value from the array.
so checking every-pair of values from the array reduced your overall complexity in O(N)
int res = arr[0] + arr[1]; //O(1)+O(1)
for (int i=1; i<N-1; i++) // O(N-2) -> O(N)
res = max(res, arr[i] + arr[i+1]); //O(1)+O(1)+O(1)
Overall complexity: O(N).

Find all unique sets of k non-negative integers summing to n

The accepted answer to this question provides an implementation of an algorithm that given two numbers k and n can generate all combinations (excluding permutations) of k positive integers which sum to n.
I'm looking for a very similar algorithm which essentially calculates the same thing except that the requirement that k > 0 is dropped, i.e. for k = 3, n = 4, the output should be
[0, 0, 0, 4], [0, 0, 1, 3], ... (in any order).
I have tried modifying the code snippet I linked but I have so far not had any success whatsoever. How can I efficiently implement this? (pseudo-code would be sufficient)
def partitions(Sum, K, lst, Minn = 0):
'''Enumerates integer partitions of Sum'''
if K == 0:
if Sum == 0:
print(lst)
return
for i in range(Minn, min(Sum + 1, Sum + 1)):
partitions(Sum - i, K - 1, lst + [i], i)
partitions(6, 3, [])
[0, 0, 6]
[0, 1, 5]
[0, 2, 4]
[0, 3, 3]
[1, 1, 4]
[1, 2, 3]
[2, 2, 2]
This code is quite close to linked answer idea, just low limit is 0 and correspondingly stop value n - size + 1 should be changed
You could use the code provided on the other thread provided as is.
Then you want to get all of the sets for set size 1 to k, and if your current set size is less than k then pad with 0's i.e
fun nonZeroSums (k, n)
for i in 1 to k
[pad with i - k 0's] concat sum_to_n(i, n)

no. of permutation of number from 1 to n in which i >i+1 and i-1

for a given N how many permutations of [1, 2, 3, ..., N] satisfy the following property.
Let P1, P2, ..., PN denote the permutation. The property we want to satisfy is that there exists an i between 2 and n-1 (inclusive) such that
Pj > Pj + 1 ∀ i ≤ j ≤ N - 1.
Pj > Pj - 1 ∀ 2 ≤ j ≤ i.
like for N=3
Permutations [1, 3, 2] and [2, 3, 1] satisfy the property.
Is there any direct formula or algorithm to find these set in programming.
There are 2^(n-1) - 2 such permutations. If n is the largest element, then the permutation is uniquely determined by the nonempty, proper subset of {1, 2, ..., n-1} which lies to the left of n in the permutation. This answer is consistent with the excellent answer of #גלעדברקן in view of the well-known fact that the elements in each row of Pascal's triangle sum to a power of two (hence the part of the row between the two ones is two less than a power of two).
Here is a Python enumeration which generates all n! permutations and checks them for validity:
import itertools
def validPerm(p):
n = max(p)
i = p.index(n)
if i == 0 or i == n-1:
return False
else:
before = p[:i]
after = p[i+1:]
return before == sorted(before) and after == sorted(after, reverse = True)
def validPerms(n):
nums = list(range(1,n+1))
valids = []
for p in itertools.permutations(nums):
lp = list(p)
if validPerm(lp): valids.append(lp)
return valids
For example,
>>> validPerms(4)
[[1, 2, 4, 3], [1, 3, 4, 2], [1, 4, 3, 2], [2, 3, 4, 1], [2, 4, 3, 1], [3, 4, 2, 1]]
which gives the expected number of 6.
On further edit: The above code was to verify the formula for nondegenerate unimodal permutations (to coin a phrase since "unimodal permutations" is used in the literature for the 2^(n-1) permutations with exactly one peak, but the 2 which either begin or end with n are arguably in some sense degenerate). From an enumeration point of view you would want to do something more efficient. The following is a Python implementation of the idea behind the answer of #גלעדברקן :
def validPerms(n):
valids = []
nums = list(range(1,n)) #1,2,...,n-1
snums = set(nums)
for i in range(1,n-1):
for first in itertools.combinations(nums,i):
#first will be already sorted
rest = sorted(snums - set(first),reverse = True)
valids.append(list(first) + [n] + rest)
return valids
It is functionally equivalent to the above code, but substantially more efficient.
Let's look at an example:
{1,2,3,4,5,6}
Clearly, any positioning of 6 at i will mean the right side of it will be sorted descending and the left side of it ascending. For example, i = 3
{1,2,6,5,4,3}
{1,3,6,5,4,2}
{1,4,6,5,3,2}
...
So for each positioning of N between 2 and n-1, we have (n - 1) choose (position - 1) arrangements. This leads to the answer:
sum [(n - 1) choose (i - 1)], for i = 2...(n - 1)
there are ans perm. and ans is as follows
ans equal to 2^(n-1) and
ans -= 2
as it need to be in between 2 <=i <= n-1 && we know that nC1 ans nCn = 1

Ruby count duplicates in diagonal rows of matrix

I'm implementing gomoku game in Ruby, this is a variation of tic-tac-toe played on 15x15 board, and the first player who places 5 O's or X's in horizontal, vertical or diagonal row wins.
First, I assigning Matrix to a variable and fill it with numbers from 0 to 224, so there are no repetitions and I could count them later
gomoku = Matrix.zero(15)
num = 0
15.times do |i|
15.times do |j|
gomoku[i, j] = num
num += 1
end
end
then players take turns, and after every turn I check a win with the method win?
def win? matrix
15.times do |i|
return true if matrix.row_vectors[i].chunk{|e| e}.map{|_, v| v.length}.max > 4 # thanks to sawa for this way of counting adjacent duplicates
return true if matrix.column_vectors[i].chunk{|e| e}.map{|_, v| v.length}.max > 4
end
return false
end
I know, that I'm probably doing it wrong, but my problem isn't that, though suggestions are welcome. The problem is with diagonal rows. I don't know how to count duplicates in diagonal rows
diagonal_vectors = (-10 .. 10).flat_map do |x|
i = x < 0 ? 0 : x
j = x < 0 ? -x : 0
d = 15 - x.abs
[
d.times.map { |k|
gomoku[i + k, j + k]
},
d.times.map { |k|
gomoku[i + k, 14 - j - k]
}
]
end
With this, you can apply the same test sawa gave you.
EDIT: What this does
When looking at diagonals, there's two kinds: going down-left, and going down-right. Let's focus on down-right ones for now. In a 15x15 matrix, there are 29 down-right diagonals: one starting at each element of the first row, one starting at each element of the first column, but taking care not to count the one starting at [0, 0] twice. But some diagonals are too short, so we want to only take those that start on the first eleven rows and columns (because others will be shorter than 5 elements). This is what the first three lines do: [i, j] will be [10, 0], [9, 0] ... [0, 0], [0, 1], ... [0, 10]. d is the length of a diagonal starting at that position. Then, d.times.map { |k| gomoku[i + k, j + k] } collects all the elements in that diagonal. Say we're working on [10, 0]: d is 5, so we have [10, 0], [11, 1], [12, 2], [13, 3], [14, 4]; and we collect values at those coordinates in a list. Simultaneously, we'll also work on a down-left diagonal; that's the other map's job, which flips one coordinate. Thus, the inner block will return a two-element array, which is two diagonals, one down-left, one down-right. flat_map will take care of iterating while squishing the two-element arrays so that we get one big array of diagonals, not array of two-element arrays of diagonals.

Resources