I'm trying to solve a pretty complex problem with strings:
Given is a string with up to 100000 characters, consisting of only two different characters 'L' and 'R'. A sequence 'RL' is considered "bad", and such occurrences have to be reduced by applying swaps.
However, the string is to be considered circular, so even the string 'LLLRRR' has an 'RL' sequence formed by the last 'R' and the first 'L'.
Swaps of two consecutive elements can be made. So we can swap only elements that are on positions i and i+1, or on position 0 and n-1, if n is the length of the string (the string is 0-indexed).
The goal is to find the minimum number of swaps needed to leave only one bad connection in the string.
Example
For the string 'RLLRRL' the problem can be solved with exactly one swap: swap the first and the last characters (since the string is circular). The string will thus become 'LLLRRR' with one bad connection.
What I tried
My idea is to use dynamical programming, and to calculate for any given 'L' how many swaps are needed to put all other 'L's left to that 'L', and, alternatively, to the right of this 'L'. For any 'R' I calculate the same.
This algorithm works in O(N) time, but it doesn't give the correct result.
It doesn't work when I have to swap the first and the last elements. What should I add to my algorithm to make it work also for those swaps?
The problem can be solved in linear time.
Some observations and definitions:
The target of having only one bad connection is another way of saying that the L letters should all be grouped together, as well as the R letters (in a circular string)
Let a group denote a series of subsequent letters of the same kind that cannot be made larger (because of surrounding letters that differ). By combining individual swaps you can "move" a group with one or more "steps". An example -- I will write . instead of L so it is more easily readable:
RRR...RR....
There are 4 groups here: RRR, ..., RR and ..... Suppose you want to join the group of two "R" with the left-sided "R" group in the above string. Then you could "move" that middle group with 3 steps to the left by performing 6 swaps:
RRR...RR....
RRR..R.R....
RRR..RR.....
RRR.R.R.....
RRR.RR......
RRRR.R......
RRRRR.......
These 6 swaps are what constitutes one group move. The cost of the move is 6, and is the product of the size of the group (2) and the distance it travels (3). Note that this move is exactly the same as when we would have moved the group with three "L" characters (cf. the dots) to the right.
I will use the word "move" in this meaning.
There is always a solution that can be expressed as a series of group moves, where each group move reduces the number of groups with two, i.e. with each such move, two R groups are merged into one, and consequently also two L groups are merged. In other words, there is always a solution where none of the groups has to split with one part of it moving to the left and another to the right. I will not give a proof of this claim here.
There is always a solution that has one group that will not move at all: all other groups of the same letter will move towards it. As a consequence there is also a group of the opposite letter that will not move, somewhere at the other end of the circle. Again, I will not prove this here.
The problem is then equivalent to minimizing the total cost (swaps) of the moves of the groups that represent one of the two letters (so half of all the groups). The other half of the groups move at the same time as was shown in the above example.
Algorithm
An algorithm could go like this:
Create an array of integers, where each value represents the size of a group. The array would list the groups in the order they appear. This would take the circular property into account, so that the first group (with index 0) would also account for the letter(s) at the very end of the string that are the same as the first letter(s). So at even indices you would have groups that represent counts of one particular letter, and at odd indices there would be counts of the other letter. It does not really matter which of the two letters they represent. The array of groups will always have an even number of entries. This array is all we need to solve the problem.
Pick the first group (index 0), and assume it will not move. Call it the "middle group". Determine which is the group of the opposite colour (with odd index) that will not have to move either. Call this other group the "split group". This split group will split the remaining odd groups into two sections, where the sum of their values (counts) is each less or equal the total of both sums. This represents the fact that it is cheaper for even groups to move in one direction than in the other in order to merge with the group at index 0.
Now determine the cost (number of swaps) for moving all even groups towards the middle group.
This may or may not be a solution, since the choice of the middle group was arbitrary.
The above would have to be repeated for the scenarios where any of the other even groups was taken as middle group.
Now the essence of the algorithm is to avoid redoing the whole operation when taking another group as the middle group. It turns out it is possible to take the next even group as middle group (at index 2) and adjust the previously calculated cost in constant time (on average) to arrive at the cost for this choice of the middle group. For this one has to keep a few parameters in memory: the cost for performing the moves in the left direction, and the cost for performing the moves in the right direction. Also the sum of even-group sizes needs to be maintained for each of both directions. And finally the sum of the odd-group sizes needs to be maintained as well for both directions. Each of these parameters can be adjusted when taking the next even group as middle group. Often the corresponding split group has to be re-identified as well, but also that can happen on average in constant time.
Without going to deep into this, here is a working implementation in simple JavaScript:
Code
function minimumSwaps(s) {
var groups, start, n, i, minCost, halfSpace, splitAt, space,
cost, costLeft, costRight, distLeft, distRight, itemsLeft, itemsRight;
// 1. Get group sizes
groups = [];
start = 0;
for (i = 1; i < s.length; i++) {
if (s[i] != s[start]) {
groups.push(i - start);
start = i;
}
}
// ... exit when the number of groups is already optimal
if (groups.length <= 2) return 0; // zero swaps
// ... the number of groups should be even (because of circle)
if (groups.length % 2 == 1) { // last character not same as first
groups.push(s.length - start);
} else { // Ends are connected: add to the length of first group
groups[0] += s.length - start;
}
n = groups.length;
// 2. Get the parameters of the scenario where group 0 is the middle:
// i.e. the members of group 0 do not move in that case.
// Get sum of odd groups, which we consider as "space", while even
// groups are considered items to be moved.
halfSpace = 0;
for (i = 1; i < n; i+=2) {
halfSpace += groups[i];
}
halfSpace /= 2;
// Get split-point between what is "left" from the "middle"
// and what is "right" from it:
space = 0;
for (i = 1; space < halfSpace; i+=2) {
space += groups[i];
}
splitAt = i-2;
// Get sum of items, and cost, to the right of group 0
itemsRight = distRight = costRight = 0;
for (i = 2; i < splitAt; i+=2) {
distRight += groups[i-1];
itemsRight += groups[i];
costRight += groups[i] * distRight;
}
// Get sum of items, and cost, to the left of group 0
itemsLeft = distLeft = costLeft = 0;
for (i = n-2; i > splitAt; i-=2) {
distLeft += groups[i+1];
itemsLeft += groups[i];
costLeft += groups[i] * distLeft;
}
cost = costLeft + costRight;
minCost = cost;
// 3. Translate the cost parameters by incremental changes for
// where the mid-point is set to the next even group
for (i = 2; i < n; i += 2) {
distLeft += groups[i-1];
itemsLeft += groups[i-2];
costLeft += itemsLeft * groups[i-1];
costRight -= itemsRight * groups[i-1];
itemsRight -= groups[i];
distRight -= groups[i-1];
// See if we need to change the split point. Items that get
// at the different side of the split point represent items
// that have a shorter route via the other half of the circle.
while (distLeft >= halfSpace) {
costLeft -= groups[(splitAt+1)%n] * distLeft;
distLeft -= groups[(splitAt+2)%n];
itemsLeft -= groups[(splitAt+1)%n];
itemsRight += groups[(splitAt+1)%n];
distRight += groups[splitAt];
costRight += groups[(splitAt+1)%n] * distRight;
splitAt = (splitAt+2)%n;
}
cost = costLeft + costRight;
if (cost < minCost) minCost = cost;
}
return minCost;
}
function validate(s) {
return new Set(s).size <= 2; // maximum 2 different letters used
}
// I/O
inp.oninput = function () {
var s, result, start;
s = inp.value;
start = performance.now(); // get timing
if (validate(s)) {
result = minimumSwaps(s); // apply algorithm
} else {
result = 'Please use only 2 different characters';
}
outp.textContent = result;
ms.textContent = Math.round(performance.now() - start);
}
rnd.onclick = function () {
inp.value = Array.from(Array(100000), _ =>
Math.random() < 0.5 ? "L" : "R").join('');
if (inp.value.length != 100000) alert('Your browser truncated the input!');
inp.oninput(); // trigger input handler
}
inp.oninput(); // trigger input handler
input { width: 100% }
<p>
<b>Enter LR series:</b>
<input id="inp" value="RLLRRL"><br>
<button id="rnd">Produce random of size 100000</button>
</p><p>
<b>Number of swaps: </b><span id="outp"></span><br>
<b>Time used: </b><span id="ms"></span>ms
</p>
Time Complexity
The preprocessing (creating the groups array, etc..), and calculation of the cost for when the first group is the middle group, all consist of non-nested loops with at most n iterations, so this part is O(n).
The calculation of the cost for when the middle group is any of the other even groups consists of a loop (for the choosing the middle group), and another inner loop for adjusting the choice of the split group. Even though this inner loop may iterate multiple times for one iteration of the outer loop, in total this inner loop will not have more iterations than n, so the total execution time of this outer loop is still O(n).
Therefore the time complexity is O(n).
Note that the result for a string of 100 000 characters is calculated in a fraction of a second (see the number of milliseconds displayed by the above snippet).
The task is to re-order the items in a circular list like this:
LRLLLRLLLRRLLRLLLRRLRLLLRLLRLRLRLRRLLLRRRLRLLRLLRL
so that we get a list like this:
RRRRRRLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRR
or this:
LLLLLLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRRRLLLLL
where the two types of items are grouped together, but the exact position of these two groups is not important.
The first task is to count the number of items in each group, so we iterate over the list once, and for the example above the result would be:
#L = 30
#R = 20
Then, the simple brute-force solution would be to consider every position in the list as the start of the L-zone, starting with position 0, iterate over the whole list and count how many steps each item is away from the border of the zone where it should be:
LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRRR <- desired output
LRLLLRLLLRRLLRLLLRRLRLLLRLLRLRLRLRRLLLRRRLRLLRLLRL <- input
< < << < >> > > > >< < <<< > >> >> > <- direction to move
We would then consider the L-zone to start at position 1, and do the whole calculation again:
RLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRR <- desired output
LRLLLRLLLRRLLRLLLRRLRLLLRLLRLRLRLRRLLLRRRLRLLRLLRL <- input
<< < << < >> > > > > < <<< > >> >> > <- direction to move
After calculating the total number of steps for every position of the L-zone, we would know which position requires the least number of steps. This is of course a method with N2 complexity.
If we could calculate the number of required steps with the L-zone at position X, based on the calculation for the L-zone at position X-1 (without iterating over the whole list again), this could bring the complexity down to N.
To do so, we'd need to keep track of the number of wrong items in each half of each zone, and the total number of steps for the wrong items in each of these four half-zones:
LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRRR <- desired output
<<<<<<<<<<<<<<<>>>>>>>>>>>>>>><<<<<<<<<<>>>>>>>>>> <- half-zones
LRLLLRLLLRRLLRLLLRRLRLLLRLLRLRLRLRRLLLRRLRRLLRLLRL <- input
< < << < >> > > > >< < <<< > >> >> > <- direction to move
5 6 5 6 <- wrong items
43 45 25 31 <- required steps
When we move right to the next position, the total number of steps in left-moving zones will decrease by the number of wrong items in that zone, and the total number of steps in right-moving zones will increase by the number of wrong items in that zone (because every item is now one step closer/further from the edge of the zone.
5 6 5 6 <- wrong items
38 51 20 37 <- required steps
However, we need to check the four border-points to see whether any wrong items have moved from one half-zone to another, and adjust the item and step count accordingly.
In the example, the L that was the first item of the L-zone has now become the last item in the R-zone, so we increment the R> half-zone's item and step count to 7 and 38.
Also, the L that was the first item in the R-zone, has become the last item of the L zone, so we decrement the item count for the R< half-zone to 4.
Also, the L in the middle of the R-zone has moved from the R> to the R< half-zone, so we decrement and increment the item counts for R> and R< to 6 and 5, and decrease and increase the step counts with 10 (the length of the R> and R< half-zones) to 28 and 30.
RLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRR <- desired output
><<<<<<<<<<<<<<<>>>>>>>>>>>>>>><<<<<<<<<<>>>>>>>>> <- half-zones
LRLLLRLLLRRLLRLLLRRLRLLLRLLRLRLRLRRLLLRRLRRLLRLLRL <- input
>< < << < >> > > > > < <<< < >> >> > <- direction to move
5 6 5 6 <- wrong items
38 51 30 28 <- required steps
So the total number of required steps when the L-zone starts at position 0 was 144, and we have calculated that when the L-zone starts at position 1, the total is now 147, by looking at what happens at four positions in the list, instead of having to iterate over the whole list again.
UPDATE
While thinking of how to implement this, I realised that the number of wrong items moving right in a zone must be the same as the number of wrong items moving left in the other zone; otherwise the border between the zones ends up in the wrong place. This means that the L and R zones aren't split into two half-zones of equal length, and the "mid" point in a zone moves according to how many wrong items are to the left and right of it. I still think it's possible to turn this into working code with O(N) efficiency, but it's probably not as straightforward as I first described it.
O(n) time solution:
L L R L L R R R L L R R L R
Number of R's to the next group of L's to the left:
1 1 1 1 3 3 2
NumRsToLeft: [1, 1, 3, 2]
Number of swaps needed, where 0 indicates the static L group, and | represents
the point to the right of which L's move right, wrapping only when not at the end
(enough L's must move to their right to replace any R's left of the static group):
2*0 + 2*1 + 2*(3+1) + 1*(2+3+1) |
2*1 + 2*0 + 2*3 | + 1*(1+1)
There are not enough L's to place the static group in the third or fourth position.
Variables: 0 1 4 6 |
1 0 3 | 2
Function: 2*v_1 + 2*v_2 + 2*v_3 + 1*v_4
Coefficients (group sizes): [2, 2, 2, 1]
Change in the total swaps needed when moving the static L group from i to (i+1):
Subtract: PSum(CoefficientsToBeGoingLeft) * NumRsToLeft[i+1]
Subtract: c_j * PSum(NumRsToLeft[i+1...j]) for c_j <- CoefficientsNoLongerGoingLeft
Add: (PSum(CoefficientsAlreadyGoingRight) + Coefficients[i]) * NumRsToLeft[i+1]
Add: c_j * PSum(NumRsToLeft[j+1...i+1]) for c_j <- NewCoefficientsGoingRight
(PSum can be calculated in O(1) time with prefix sums; and the count of coefficients
converting from a left move to a right move throughout the whole calculation is not
more than n. This outline does not include the potential splitting of the last new
group converting from left move to right move.)
Related
Problem: Choose an element from the array to maximize the sum after XOR all elements in the array.
Input for problem statement:
N=3
A=[15,11,8]
Output:
11
Approach:
(15^15)+(15^11)+(15^8)=11
My Code for brute force approach:
def compute(N,A):
ans=0
for i in A:
xor_sum=0
for j in A:
xor_sum+=(i^j)
if xor_sum>ans:
ans=xor_sum
return ans
Above approach giving the correct answer but wanted to optimize the approach to solve it in O(n) time complexity. Please help me to get this.
If you have integers with a fixed (constant) number of c bites then it should be possible because O(c) = O(1). For simplicity reasons I assume unsigned integers and n to be odd. If n is even then we sometimes have to check both paths in the tree (see solution below). You can adapt the algorithm to cover even n and negative numbers.
find max in array with length n O(n)
if max == 0 return 0 (just 0s in array)
find the position p of the most significant bit of max O(c) = O(1)
p = -1
while (max != 0)
p++
max /= 2
so 1 << p gives a mask for the highest set bit
build a tree where the leaves are the numbers and every level stands for a position of a bit, if there is an edge to the left from the root then there is a number that has bit p set and if there is an edge to the right there is a number that has bit p not set, for the next level we have an edge to the left if there is a number with bit p - 1 set and an edge to the right if bit p - 1 is not set and so on, this can be done in O(cn) = O(n)
go through the array and count how many times a bit at position i (i from 0 to p) is set => sum array O(cn) = O(n)
assign the root of the tree to node x
now for each i from p to 0 do the following:
if x has only one edge => x becomes its only child node
else if sum[i] > n / 2 => x becomes its right child node
else x becomes its left child node
in this step we choose the best path through the tree that gives us the most ones when xoring O(cn) = O(n)
xor all the elements in the array with the value of x and sum them up to get the result, actually you could have built the result already in the step before by adding sum[i] * (1 << i) to the result if going left and (n - sum[i]) * (1 << i) if going right O(n)
All the sequential steps are O(n) and therefore overall the algorithm is also O(n).
I have a task: given a value N. I should generate a list of length L > 1 such that the sum of the squares of its elements is equal to N.
I wrote a code:
deltas = np.zeros(L)
deltas[0] = (np.random.uniform(-N, N))
i = 1
while i < L and np.sum(np.array(deltas)**2) < N**2:
deltas[i] = (np.random.uniform(-np.sqrt(N**2 - np.sum(np.array(deltas)**2)),\
np.sqrt(N**2 - np.sum(np.array(deltas)**2))))
i += 1
But this approach takes long time, if I generate such list many times. (I think because of loop).
Note, that I don't want my list to consist of just one unique value. The distribution of values does not have to be uniform - I took uniform just for example.
Could you suggest any faster approach? May be there is special function in any lib?
If you didn't mind a few repeating 1s, you could do something like this:
def square_list(integer):
components = []
total = 0
remaining = integer
while total != integer:
component = int(remaining ** 0.5)
remaining -= component ** 2
components.append(component)
total = sum([x ** 2 for x in components])
return components
This code works by finding the taking the largest square, and then decreasing to the next largest square. It continues until the largest square is 1, which could at worse result in 3 1s in a list.
If you are looking for a more random distribution, it might make sense to randomly transform remaining as a separate variable before subtracting from it.
IE:
value = transformation(remaining)
component = int(value ** 0.5)
which should give you more "random" values.
Say you have an ordered array of values representing x coordinates.
[0,25,50,60,75,100]
You might notice that without the 60, the values would be evenly spaced (25). This would be indicative of a repeating pattern, something that I need to extract using this list (regardless of the length and the values of the list). In this particular example, the algorithm should find and remove the 60.
There are no time or space complexity requirements.
Both the values in the list and the ideal spacing (e.g 25) are unknown. So the algorithm must obtain this by looking at the values. In addition, the number of values, and where the outliers are in the array are not guaranteed. There may be more than one outlier. The algorithm should return a list with the outliers removed. Extra points if the algorithm uses a threshold for the spacing.
Edit: Here is an example image
Here there is one outlier on the x axis. (green-line) There are two on the y axis. The x-coordinates of the array represent the rho of the line on that axis.
arr = [0,25,50,60,75,100]
First construct the distances array
dist = np.array([arr[i+1] - arr[i] for (i, _) in enumerate(arr) if i < len(arr)-1])
print(dist)
>> [25 25 10 15 25]
Now I'm using np.where and np.percentile to cut the array in 3 part: the main , the upper values and the lower values. I arbitrary set them to 5%.
cond_sup = np.where(dist > np.percentile(dist, 95))
print(cond_sup)
>> (array([]),)
cond_inf = np.where(dist < np.percentile(dist, 5))
print(cond_inf)
>> (array([2]),)
You now got indexes where the value is different from the others.
So, dist[2] has a problem, which mean by construction the problem is between arr[2] and arr[2+1]
I don't know if you want to remove 1 or more numbers from this array. So I think the way to solve this problem will be like this:
array A[] = [0,25,50,60,75,100];
sort array (if needed).
create a new array B[] with value i-th: B[i] = A[i+1] - A[i]
find the value of B[] elements that appear most time. It's will be our distance.
find i such that A[i+1]-A[i] != distance
find k (k>i and k min) such that A[i+k]-A[i] == distance
so, we need remove A[i+1] => A[i+k-1]
I hope it is right.
The problem is as follows: given a directed acyclic graph, where each node is labeled with a character, find all the longest paths of nodes in the graph that form a palindrome.
The initial solution that I thought of was to simply enumerate all the paths in the graph. This effectively generates a bunch of strings, on which we can then apply Manacher's algorithm to find all the longest palindromes. However, this doesn't seem that efficient, since the amount of paths in a graph is exponential in the number of nodes.
Then I started thinking of using dynamic programming directly on the graph, but my problem is that I cannot figure out how to structure my "dynamic programming array". My initial try was to use a 2d boolean array, where array[i][j] == true means that node i to node j is a palindrome but the problem is that there might be multiple paths from i to j.
I've been stuck on this problem for quite a while now I can't seem to figure it out, any help would be appreciated.
The linear-time trick of Manacher's algorithm relies on the fact that if you know that the longest palindrome centered at character 15 has length 5 (chars 13-17), and there's a palindrome centered at node 19 of length 13 (chars 13-25), then you can skip computing the longest palindrome centered at character 23 (23 = 19 + (19 - 15)) because you know it's just going to be the mirror of the one centered at character 15.
With a DAG, you don't have that kind of guarantee because the palindromes can go off in any direction, not just forwards and backwards. However, if you have a candidate palindrome path from node m to node n, whether you can extend that string to a longer palindrome doesn't depend on the path between m and n, but only on m and n (and the graph itself).
Therefore, I'd do this:
First, sort the graph nodes topologically, so that you have an array s[] of node indexes, and there being an edge from s[i] to s[j] implies that i < j.
I'll also assume that you build up an inverse array or hash structure sinv[] such that s[sinv[j]] == j and sinv[s[n]] == n for all integers j in 0..nodeCount-1 and all node indexes n.
Also, I'll assume that you have functions graphPredecessors, graphSuccessors, and graphLetter that take a node index and return the list of predecessors on the graph, the list of successors, or the letter at that node, respectively.
Then, make a two-dimensional array of integers of size nodeCount by nodeCount called r. When r[i][j] = y, and y > 0, it will mean that if there is a palindrome path from a successor of s[i] to a predecessor of s[j], then that path can be extended by adding s[i] to the front and s[j] to the back, and that the extension can be continued by y more nodes (including s[i] and s[j]) in each direction:
for (i=0; i < nodeCount; i++) {
for (j=i; j < nodeCount; j++) {
if (graphLetter(s[i]) == graphLetter(s[j])) {
r[i][j] = 1;
for (pred in graphPredecessors(s[i])) {
for (succ in graphSuccessors(s[j])) {
/* note that by our sorting, sinv[pred] < i <= j < sinv[succ] */
if (r[sinv[pred]][sinv[succ]] >= r[i][j]) {
r[i][j] = 1 + r[sinv[pred]][sinv[succ]];
}
}
}
} else {
r[i][j] = 0;
}
}
}
Then find the maximum value of r[x][x] for x in 0..nodeSize-1, and of r[lft][rgt] where there is an edge from s[lft] to s[rgt]. Call that maximum value M, and say you found it at location [i][j]. Each such i, j pair will represent the center of a longest palindrome path. As long as M is greater than 1, you then extend each center by finding a pred in graphPredecessors(s[i]) and a succ in graphSuccessors(s[j]) such that r[sinv[pred]][sinv[succ]] == M - 1 (the palindrome is now pred->s[i]->s[j]->succ). You then extend that by finding the appropriate index with an r value of M - 2, etc., stopping when you reach a spot where the value in r is 1.
I think this algorithm overall ends up with a runtime of O(V^2 + E^2), but I'm not entirely certain of that.
Basically I'm trying to solve this problem :
Given N unit cube blocks, find the smaller number of piles to make in order to use all the blocks. A pile is either a cube or a pyramid. For example two valid piles are the cube 4 *4 *4=64 using 64 blocks, and the pyramid 1²+2²+3²+4²=30 using 30 blocks.
However, I can't find the right angle to approach it. I feel like it's similar to the knapsack problem, but yet, couldn't find an implementation.
Any help would be much appreciated !
First I will give a recurrence relation which will permit to solve the problem recursively. Given N, let
SQUARE-NUMS
TRIANGLE-NUMS
be the subset of square numbers and triangle numbers in {1,...,N} respectively. Let PERMITTED_SIZES be the union of these. Note that, as 1 occurs in PERMITTED_SIZES, any instance is feasible and yields a nonnegative optimum.
The follwing function in pseudocode will solve the problem in the question recursively.
int MinimumNumberOfPiles(int N)
{
int Result = 1 + min { MinimumNumberOfPiles(N-i) }
where i in PERMITTED_SIZES and i smaller than N;
return Result;
}
The idea is to choose a permitted bin size for the items, remove these items (which makes the problem instance smaller) and solve recursively for the smaller instances. To use dynamic programming in order to circumvent multiple evaluation of the same subproblem, one would use a one-dimensional state space, namely an array A[N] where A[i] is the minimum number of piles needed for i unit blocks. Using this state space, the problem can be solved iteratively as follows.
for (int i = 0; i < N; i++)
{
if i is 0 set A[i] to 0,
if i occurs in PERMITTED_SIZES, set A[i] to 1,
set A[i] to positive infinity otherwise;
}
This initializes the states which are known beforehand and correspond to the base cases in the above recursion. Next, the missing states are filled using the following loop.
for (int i = 0; i <= N; i++)
{
if (A[i] is positive infinity)
{
A[i] = 1 + min { A[i-j] : j is in PERMITTED_SIZES and j is smaller than i }
}
}
The desired optimal value will be found in A[N]. Note that this algorithm only calculates the minimum number of piles, but not the piles themselves; if a suitable partition is needed, it has to be found either by backtracking or by maintaining additional auxiliary data structures.
In total, provided that PERMITTED_SIZES is known, the problem can be solved in O(N^2) steps, as PERMITTED_SIZES contains at most N values.
The problem can be seen as an adaptation of the Rod Cutting Problem where each square or triangle size has value 0 and every other size has value 1, and the objective is to minimize the total value.
In total, an additional computation cost is necessary to generate PERMITTED_SIZES from the input.
More precisely, the corresponding choice of piles, once A is filled, can be generated using backtracking as follows.
int i = N; // i is the total amount still to be distributed
while ( i > 0 )
{
choose j such that
j is in PERMITTED_SIZES and j is smaller than i
and
A[i] = 1 + A[i-j] is minimized
Output "Take a set of size" + j; // or just output j, which is the set size
// the part above can be commented as "let's find out how
// the value in A[i] was generated"
set i = i-j; // decrease amount to distribute
}