I am looking for a least time-complex algorithm that would solve a variant of the perfect sum problem (initially: finding all variable size subset combinations from an array [*] of integers of size n that sum to a specific number x) where the subset combination size is of a fixed size k and return the possible combinations without direct and also indirect (when there's a combination containing the exact same elements from another in another order) duplicates.
I'm aware this problem is NP-hard, so I am not expecting a perfect general solution but something that could at least run in a reasonable time in my case, with n close to 1000 and k around 10
Things I have tried so far:
Finding a combination, then doing successive modifications on it and its modifications
Let's assume I have an array such as:
s = [1,2,3,3,4,5,6,9]
So I have n = 8, and I'd like x = 10 for k = 3
I found thanks to some obscure method (bruteforce?) a subset [3,3,4]
From this subset I'm finding other possible combinations by taking two elements out of it and replacing them with other elements that sum the same, i.e. (3, 3) can be replaced by (1, 5) since both got the same sum and the replacing numbers are not already in use. So I obtain another subset [1,5,4], then I repeat the process for all the obtained subsets... indefinitely?
The main issue as suggested here is that it's hard to determine when it's done and this method is rather chaotic. I imagined some variants of this method but they really are work in progress
Iterating through the set to list all k long combinations that sum to x
Pretty self explanatory. This is a naive method that do not work well in my case since I have a pretty large n and a k that is not small enough to avoid a catastrophically big number of combinations (the magnitude of the number of combinations is 10^27!)
I experimented several mechanism related to setting an area of research instead of stupidly iterating through all possibilities, but it's rather complicated and still work in progress
What would you suggest? (Snippets can be in any language, but I prefer C++)
[*] To clear the doubt about whether or not the base collection can contain duplicates, I used the term "array" instead of "set" to be more precise. The collection can contain duplicate integers in my case and quite much, with 70 different integers for 1000 elements (counts rounded), for example
With reasonable sum limit this problem might be solved using extension of dynamic programming approach for subset sum problem or coin change problem with predetermined number of coins. Note that we can count all variants in pseudopolynomial time O(x*n), but output size might grow exponentially, so generation of all variants might be a problem.
Make 3d array, list or vector with outer dimension x-1 for example: A[][][]. Every element A[p] of this list contains list of possible subsets with sum p.
We can walk through all elements (call current element item) of initial "set" (I noticed repeating elements in your example, so it is not true set).
Now scan A[] list from the last entry to the beginning. (This trick helps to avoid repeating usage of the same item).
If A[i - item] contains subsets with size < k, we can add all these subsets to A[i] appending item.
After full scan A[x] will contain subsets of size k and less, having sum x, and we can filter only those of size k
Example of output of my quick-made Delphi program for the next data:
Lst := [1,2,3,3,4,5,6,7];
k := 3;
sum := 10;
3 3 4
2 3 5 //distinct 3's
2 3 5
1 4 5
1 3 6
1 3 6 //distinct 3's
1 2 7
To exclude variants with distinct repeated elements (if needed), we can use non-first occurence only for subsets already containing the first occurence of item (so 3 3 4 will be valid while the second 2 3 5 won't be generated)
I literally translate my Delphi code into C++ (weird, I think :)
int main()
{
vector<vector<vector<int>>> A;
vector<int> Lst = { 1, 2, 3, 3, 4, 5, 6, 7 };
int k = 3;
int sum = 10;
A.push_back({ {0} }); //fictive array to make non-empty variant
for (int i = 0; i < sum; i++)
A.push_back({{}});
for (int item : Lst) {
for (int i = sum; i >= item; i--) {
for (int j = 0; j < A[i - item].size(); j++)
if (A[i - item][j].size() < k + 1 &&
A[i - item][j].size() > 0) {
vector<int> t = A[i - item][j];
t.push_back(item);
A[i].push_back(t); //add new variant including current item
}
}
}
//output needed variants
for (int i = 0; i < A[sum].size(); i++)
if (A[sum][i].size() == k + 1) {
for (int j = 1; j < A[sum][i].size(); j++) //excluding fictive 0
cout << A[sum][i][j] << " ";
cout << endl;
}
}
Here is a complete solution in Python. Translation to C++ is left to the reader.
Like the usual subset sum, generation of the doubly linked summary of the solutions is pseudo-polynomial. It is O(count_values * distinct_sums * depths_of_sums). However actually iterating through them can be exponential. But using generators the way I did avoids using a lot of memory to generate that list, even if it can take a long time to run.
from collections import namedtuple
# This is a doubly linked list.
# (value, tail) will be one group of solutions. (next_answer) is another.
SumPath = namedtuple('SumPath', 'value tail next_answer')
def fixed_sum_paths (array, target, count):
# First find counts of values to handle duplications.
value_repeats = {}
for value in array:
if value in value_repeats:
value_repeats[value] += 1
else:
value_repeats[value] = 1
# paths[depth][x] will be all subsets of size depth that sum to x.
paths = [{} for i in range(count+1)]
# First we add the empty set.
paths[0][0] = SumPath(value=None, tail=None, next_answer=None)
# Now we start adding values to it.
for value, repeats in value_repeats.items():
# Reversed depth avoids seeing paths we will find using this value.
for depth in reversed(range(len(paths))):
for result, path in paths[depth].items():
for i in range(1, repeats+1):
if count < i + depth:
# Do not fill in too deep.
break
result += value
if result in paths[depth+i]:
path = SumPath(
value=value,
tail=path,
next_answer=paths[depth+i][result]
)
else:
path = SumPath(
value=value,
tail=path,
next_answer=None
)
paths[depth+i][result] = path
# Subtle bug fix, a path for value, value
# should not lead to value, other_value because
# we already inserted that first.
path = SumPath(
value=value,
tail=path.tail,
next_answer=None
)
return paths[count][target]
def path_iter(paths):
if paths.value is None:
# We are the tail
yield []
else:
while paths is not None:
value = paths.value
for answer in path_iter(paths.tail):
answer.append(value)
yield answer
paths = paths.next_answer
def fixed_sums (array, target, count):
paths = fixed_sum_paths(array, target, count)
return path_iter(paths)
for path in fixed_sums([1,2,3,3,4,5,6,9], 10, 3):
print(path)
Incidentally for your example, here are the solutions:
[1, 3, 6]
[1, 4, 5]
[2, 3, 5]
[3, 3, 4]
You should first sort the so called array. Secondly, you should determine if the problem is actually solvable, to save time... So what you do is you take the last k elements and see if the sum of those is larger or equal to the x value, if it is smaller, you are done it is not possible to do something like that.... If it is actually equal yes you are also done there is no other permutations.... O(n) feels nice doesn't it?? If it is larger, than you got a lot of work to do..... You need to store all the permutations in an seperate array.... Then you go ahead and replace the smallest of the k numbers with the smallest element in the array.... If this is still larger than x then you do it for the second and third and so on until you get something smaller than x. Once you reach a point where you have the sum smaller than x, you can go ahead and start to increase the value of the last position you stopped at until you hit x.... Once you hit x that is your combination.... Then you can go ahead and get the previous element so if you had 1,1,5, 6 in your thingy, you can go ahead and grab the 1 as well, add it to your smallest element, 5 to get 6, next you check, can you write this number 6 as a combination of two values, you stop once you hit the value.... Then you can repeat for the others as well.... You problem can be solved in O(n!) time in the worst case.... I would not suggest that you 10^27 combinations, meaning you have more than 10^27 elements, mhmmm bad idea do you even have that much space??? That's like 3bits for the header and 8 bits for each integer you would need 9.8765*10^25 terabytes just to store that clossal array, more memory than a supercomputer, you should worry about whether your computer can even store this monster rather than if you can solve the problem, that many combinations even if you find a quadratic solution it would crash your computer, and you know what quadratic is a long way off from O(n!)...
A brute force method using recursion might look like this...
For example, given variables set, x, k, the following pseudo code might work:
setSumStructure find(int[] set, int x, int k, int setIdx)
{
int sz = set.length - setIdx;
if (sz < x) return null;
if (sz == x) check sum of set[setIdx] -> set[set.size] == k. if it does, return the set together with the sum, else return null;
for (int i = setIdx; i < set.size - (k - 1); i++)
filter(find (set, x - set[i], k - 1, i + 1));
return filteredSets;
}
I want to make changes to a list in-place such that if the list is l1 = [0, 1, A, B], then I can replace the alphabetical values with numbers.
I tried using the following code for it:
for x in l1:
if x == 'A':
x = 10
However, when I look at the list again, it is still the same and A has not been changed to 10. Why is this code not working?
I tried using the indices instead as well, but that did not work either.
for i in range(len(l1)):
if l1[i] == 'A':
l1[i] = 10
In Python things are always passed by value. The 'x' variable in the for loop is a copy of the elements in the list not the pointer/reference of the list elements.
You can change the value of the list by directly indexing it.
One way to achieve that is as follows..
for i,x in enumerate(l1):
If x == 'A':
l1[i] = 10
Assuming A variable points to value 'A' !
I'm trying to make a vector adding of two arrays with the same dimensions, but all that I get is a "list index out of range" error. The code I used is:
x = [0, 0, 0]
y = [1, 2, 3]
i = 0
c = []
while (i <= len(x)):
c.append(a[i] + b[i])
i = i + 1
print c
Can you point me where is te problem? Any help or idea will be appreciated.
In python, try to avoid looping over indices when possible. A more pythonic way of doing this is the following list comprehension
c = [sum(items) for items in zip(x, y)]
list comprehensions allow you to operate on items in an iterable and return a list. zip allows you to iterate over multiple iterables at the same time. This is a good pattern to look out for as you keep learning python
You are iterating both the lists for following index values : [0,1,2,3].
As the length of both the lists is 3, iterating it upto index value 3, won't make any sense because index value begins from 0.
In the condition for while loop, you should change the condition from i <= len(x) to i < len(x).
problem link from leetcode
I came up with two solutions wrote in Python but did not pass and do not know why.
Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array),
some elements appear twice and others appear once.
Find all the elements of [1, n] inclusive that do not appear in this
array.
Here is my first solution:
class Solution(object):
def findDisappearedNumbers(self, nums):
nums=sorted(list(set(nums)))
for x in range(1, nums[-1] + 1):
if x in nums:
nums.remove(x)
else:
nums.append(x)
return nums
the result is " Runtime Error Message: Line 4: IndexError: list index out of range". But I did not get it.
The second solution:
return [x for x in range(1, len(nums) + 1) if x not in nums]
The result is "Time Limit Exceeded",still,confused.
Both solutions works okay in my Pycharm with python 2.7.11.Maybe there are some test cases my solutions did not pass but I can not find it.
First of all, try to use xrange instead of range as this uses less space when the value of nums is very large. Also, you are trying to iterate as well as delete/append a value at the same time in the same array. This is most likely the reason why you are getting the error.
Also, removing a value in the list (if it is not at the end) takes a lot of time because all other elements before it need to moved.
From the first solution: DO NOT modify the list you are iterating. Always brings problems. Better copy the list and modify the list!
class Solution(object):
def findDisappearedNumbers(self, nums):
nums=sorted(list(set(nums)))
nums_copy = nums.copy(nums)
for x in range(1, nums[-1] + 1):
if x in nums:
nums_copy.remove(x)
else:
nums_copy.append(x)
return nums_copy
On the other hand, if num is very large (has many elemente)range can bring problems because it creates the list first (and VERY large lists occupy a LOT of memory). For me cases it is better to xrange than to return a generator.
This does not happen in python3, where the default range returns a generator.
You can use nums = set(nums) which will sort and remove all the duplicates. Then you can run a loop to append all the numbers not present in nums to output array.
Your first solution will fail if the test input is an empty list as num[-1] would give an index out of bound.
Your second solution will be slow as it has to iterate through the list. Would the below solution work? Set operations are optimised. But is the space complexity ok for you?
ret = set(range(1, len(nums)+1))
ret = ret - set(nums)
return list(ret)
Alright, so I have a small issue:
def common(a,b,c):
a.sort()
b.sort()
c.sort()
i = 0
j = 0
k = 0
common=False
while i<len(a) and j<len(b):
if a[i] == b[j]:
if b[j] == c[k]:
return True
else:
k = k+1
continue
else:
if i == len(a):
j = j+1
else:
i = i+1
return common
a=[3, 1, 5, 10]
b=[4, 2, 6, 1]
c=[5, 3, 1, 7]
print common(a,b,c)
Basically, it has to tell me if there are common elements in the lists. With 1 it works, but if I replace the 1's with 8's, it doesn't work anymore.
Your 'j' never increase, 1 is working because after sort it is the 1st element and doesn't need j to be increased.
My suggestion is convert your lists to sets and check the common elements using intersection(&)
def common(a,b,c):
common = set(a) & set(b) & set(c)
return True if common else False
a=[3, 8, 5, 10]
b=[4, 2, 6, 8]
c=[5, 3, 8, 7]
print common(a,b,c)
Your current algorithm only works if the smallest value in the b list is common to the other lists. If there's a different common value, you'll never find it, because you'll increment i until it is len(a), then quit.
I think you need to change your logic so that you increment the index of the list that points at the smallest value. If a[i] is less than b[j], you need to increment i. If c[k] is less still, you should increment it instead.
What you've programmed is not doing at all what you expect, so you need to rethink your logic. If you look at your loop, you first check if a[0] matches b[0]. Nope, so add to i. Then you compare a[1] to b[0], nope and so on. If nothing matches b[0], you exit the loop without ever checking the other elements of b. It works with 1's in your lists because after you sort, those happen to be in the first position in all three lists.
In any case, this is pretty clunky. There's a much easier way of doing this via set intersection. See this related question.