Understanding the range issue in binary seach problem - c++

EDIT:
Add more details about the logic behind the code.
Thx to #Stef.
I am trying to do an algorithm problem in LeetCode (https://leetcode.com/problems/find-the-duplicate-number/).
Below is my method to solve this, which uses binary search thinking.
The basic logic of the code is, I am trying to find the duplicated number in the range [1, n] inclusive using binary search.
For instance, if I am going to find the duplicated num in the list [1, 3, 4, 2, 2].
Firstly, count the midpoint of [1, 4], because the start point is 1, the endpoint is 4, hence the midpoint is 2. Then I use the cntRange function to count how many numbers in the list are among the range of [1, 2]. if the number of numbers(we have 1, 2, 2, three numbers) is more than it should be (should be 2), we shrink the range by setting the endpoint as midpoint and continue the binary search, until we finish the search and we return the present value of start point, which is the duplicated one.
class Solution {
public:
int findRepeatNumber(vector<int> &nums) {
// special case we return -1
if (nums.size() < 2) {
return -1;
}
// binary search to cnt the numbers in certain range
int start = 1;
int end = nums.size() - 1;
while (end >= start) {
int mid = ((end - start) >> 1) + start;
int cnt = cntRange(nums, start, mid);
if (end == start) {
if (cnt > 1) {
return start;
} else {
break;
}
}
if (cnt > (mid - start + 1))
end = mid;
else
start = mid + 1;
}
return -1;
}
int cntRange(vector<int> &nums, int start, int end) {
int cnt = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] >= start && nums[i] <= end)
cnt++;
}
return cnt;
}
};
This method passes in LeetCode, however, I am curious about the range [1, n], what if the range is [0, n-1]?
I tried with two test sets:
one is [0, 1, 2, 0, 4, 5, 6]
the other is [2, 3, 1, 0, 2, 5, 3]
they all failed, so I go back to my code to try to fix this.
I initialize the start int to 0 instead and change the cnt compare condition
from cnt > (mid - start + 1) to cnt > (mid - start).
But in this case, only the first test is passed, I still can not pass the second one.
I still think this problem arose in the cnt compare process, but do not know how to solve this.
Can anybody help me on this?

Your problem for the 2 cases you've mentioned is with:
start = mid + 1;
The value of mid can never go negative and therefore the minimum value for start can never be less than 1 after the first time this line is reached. This means you never see the value at index 0 when doing binary search.

Related

Finding the number of sub arrays that have a sum of K

I am trying to find the number of sub arrays that have a sum equal to k:
int subarraySum(vector<int>& nums, int k)
{
int start, end, curr_sum = 0, count = 0;
start = 0, end = 0;
while (end < (int)nums.size())
{
curr_sum = curr_sum + nums[end];
end++;
while (start < end && curr_sum >= k)
{
if (curr_sum == k)
count++;
curr_sum = curr_sum - nums[start];
start++;
}
}
return count;
}
The above code I have written, works for most cases, but fails for the following:
array = {-1, -1, 1} with k = 0
I have tried to add another while loop to iterate from the start and go up the array until it reaches the end:
int subarraySum(vector<int>& nums, int k)
{
int start, end, curr_sum = 0, count = 0;
start = 0, end = 0;
while (end < (int)nums.size())
{
curr_sum = curr_sum + nums[end];
end++;
while (start < end && curr_sum >= k)
{
if (curr_sum == k)
count++;
curr_sum = curr_sum - nums[start];
start++;
}
}
while (start < end)
{
if (curr_sum == k)
count++;
curr_sum = curr_sum - nums[start];
start++;
}
return count;
}
Why is this not working? I am sliding the window until the last element is reached, which should have found a sum equal to k? How can I solve this issue?
Unfortunately, you did not program a sliding window in the correct way. And a sliding window is not really a solution for this problem. One of your main issues is, that you do not move the start of the window based on the proper conditions. You always sum up and wait until the sum is greater than the search value.
This will not really work. Especially for your example -1, -1, 1. The running sum of this is: -1, -2, -1 and you do not see the 0, although it is there. You may have the idea to write while (start < end && curr_sum != k), but this will also not work, because you handle the start pointer not correctly.
Your approach will lead to the brute force solution that typically takes something like N*N loop operations, where N is the size of the array. This, because we need a double nested loop.
That will of course always work, but maybe very time-consuming, and, in the end, too slow.
Anyway. Let us implement that. We will start from each value in the std::vector and try out all sub arrays starting from the beginning value. We must evaluate all following values in the std::vector, because for example the last value could be a big negative number and bring down the sum again to the search value.
We could implement this for example like the following:
#include <iostream>
#include <vector>
using namespace std;
int subarraySum(vector<int>& numbers, int searchSumValue) {
// Here we will store the result
int resultingCount{};
// Iterate over all values in the array. So, use all different start values
for (std::size_t i{}; i < numbers.size(); ++i) {
// Here we stor the running sum of the elements in the vector
int sum{ numbers[i] };
// Check for trivial case. A one-element sub-array does already match the search value
if (sum == searchSumValue) ++resultingCount;
// Now we build all subarrays beginning with the start value
for (std::size_t k{ i + 1 }; k < numbers.size(); ++k) {
sum += numbers[k];
if (sum == searchSumValue) ++resultingCount;
}
}
return resultingCount;
}
int main() {
vector v{ -1,-1,1 };
std::cout << subarraySum(v, 0);
}
.
But, as said, the above is often too slow for big vectors and there is indeed a better solution available, which is based on a DP (dynamic programming) algorithm.
It uses so-called prefix sums, running sums, based on the running sum before the current evaluated value.
We need to show an example. Let's use a std::vector with 5 values {1,2,3,4,5}. And we want to look subarrays with a sum of 9.
We can “guess” that there are 2 subarrays: {2,3,4} and {4,5} that have a sum of 9.
Let us investigate further
Index 0 1 2 3 4
Value 1 2 3 4 5
We can now add a running sum and see, how much delta we have between the current evaluated element and the left neighbor or over-next neighbor and so on. And if we have a delta that is equal to our search value, then we must have a subarray building this sum.
Running Sum 1 3 6 10 15
Deltas of 2 3 4 5 against next left
Running sum 5 7 9 against next next left
9 12 against next next next left
Example {2,3,4}. If we evaluate the 4 with a running sum of 10, and subtract the search value 9, then we get the previous running sum 1. “1+9=10” all values are there.
Example {4,5}. If we evaluate the 5 with a running sum of 15, and subtract the search value 9, then we get the previous running sum = 6. “6+9=15” all values are there.
We can find all solutions using the same approach.
So, the only thing we need to do, is to subtract the search value from the current running sum and see, if we have this running sum already calculated before.
Like: “Search-Value” + “previously Calculated Sum” = “Current Running Sum”.
Or: “Current Running Sum” – “Search-Value” = “previously Calculated Sum”
Again, we need to do the subtraction and check, if we already calculated such a sum previously.
So, we need to store all previously calculated running sums. And, because such a sum may appear more than one, we need to find occurrences of equal running sums and count them.
It is very hard to digest, and you need to think a while to understand.
With the above wisdom, you can draft the below potential solution.
#include <iostream>
#include <vector>
#include <unordered_map>
int subarraySum(std::vector<int>& numbers, int searchSumValue) {
// Here we will store the result
int resultingSubarrayCount{};
// Here we will stor all running sums and how ofthen their value appeared
std::unordered_map<int, int> countOfRunningSums;
// Continuosly calculating the running sum
int runningSum{};
// And initialize the first value
countOfRunningSums[runningSum] = 1;
// Now iterate over all values in the vector
for (const int n : numbers) {
// Calculate the running sum
runningSum += n;
// Check, if we have the searched value already available
// And add the number of occurences to our resulting number of subarrays
resultingSubarrayCount += countOfRunningSums[runningSum - searchSumValue];
// Store the new running sum. Respectively. Add 1 to the counter, if the running sum was alreadyy existing
countOfRunningSums[runningSum]++;
}
return resultingSubarrayCount;
}
int main() {
std::vector v{ 1,2,3,4,5 };
std::cout << subarraySum(v, 9);
}

Making an array equal by decreasing elements adjacent to each other

I was doing this exercise in my computer science class for a warm up earlier, which was to find the minimum number of moves to make all the elements in an array equal to each other, where the only operation is to subtract one from an element in an array.
This made me curious about extensions to the problem; for example I immediately thought of an extension, how many moves will it take to make all the elements in an array equal to each other, where the only operation is to subtract one from two elements that are adjacent to each other.
For example:
Given array [4, 6, 4], we can decrease elements in index 0 and 1 to get [3, 5, 4], then [3, 4, 3], then [2, 3, 3], and then decrease elements in index 1 and 2 to get [2, 2, 2]. This would take 4 moves. However, how would we extend this kind of thinking to larger arrays, where we cannot trace this out by hand like I just did above?
You can find whether this is possible (and minimum number of moves) in O(n) time, in a way that should generalize to larger array decreases.
The first step is to convert our array A to the first-difference array D, whose elements are D = [A[1]-A[0], A[2]-A[1], ..., A[n-1]-A[n-2]]. We are done when D is the all-zero array. Now, think about how the operations on A translate to changes in D. It's different when n is even and odd, too, so focus on one case at a time.
Odd length
When A has odd length, there are more cases where no solution is possible.
Suppose 'A' has 7 elements. There's 6 possible moves, so let's calculate what effect each has on D.
A = [a_0, a_1, a_2, a_3, a_4, a_5, a_6]
D = [d_0, d_1, d_2, d_3, d_4, d_5]
Moves available:
d_1 += 1
(d_0 -= 1, d_2 += 1)
(d_1 -= 1, d_3 += 1)
(d_2 -= 1, d_4 += 1)
(d_3 -= 1, d_5 += 1)
d_4 -= 1
Some observations:
The interactions between even and odd indices of D are completely disjoint (this is true for the even-length case too).
d_5 can only increase, d_0 can only decrease.
I found it helpful to think of this as a 'flow' from left to right in D:
d_0 --> d_2 --> d_4 --> SINK
SOURCE --> d_1 --> d_3 --> d_5
this makes things easier: there's only one way to try to make D all zero, so we can just try that.
If D[0] is negative, this is impossible. If it's positive, add D[0] moves to our counter, and add D[0] to D[2]
If D[2] is negative, this is impossible. If it's positive, add D[2] moves to our counter, and add D[2] to D[4]
If D[4] is negative, this is impossible. Otherwise, add D[4] to our counter. D[4] points to a sink, so we are done.
I haven't explained the process for the odd vertices, but it's the same logic-- there's at most one way to make D all zero, following the flow.
Even-length
The even-length array case is similar, except the flow-network has different endpoints.
A = [a_0, a_1, a_2, a_3, a_4, a_5, a_6, a_7]
D = [d_0, d_1, d_2, d_3, d_4, d_5, d_6]
Moves available:
d_1 += 1
(d_0 -= 1, d_2 += 1)
(d_1 -= 1, d_3 += 1)
(d_2 -= 1, d_4 += 1)
(d_3 -= 1, d_5 += 1)
(d_4 -= 1, d_6 += 1)
d_5 -= 1
d_0 --> d_2 --> d_4 --> d_6
SOURCE --> d_1 --> d_3 --> d_5 --> SINK
This means that the odd-indices can have any values without changing the solvability, while the even indices must become all zero with only positive transfers from left to right. Positive flow still moves in exactly one direction in both cases.
I've written a Python solution implementing the logic above. There's one caveat-- I haven't been able to rigorously test it against random input. There's no obviously correct brute-force solution to this problem, so there may be failing testcases that I missed.
def min_decreases_to_get_equal(nums: List[int]) -> Optional[int]:
"""Given an array of integers, returns the number of moves
to make all elements equal, or None if impossible.
A move is selecting two adjacent elements and dec. both by 1."""
n = len(nums)
assert n >= 2
if n == 2:
return 0 if nums[0] == nums[1] else None
differences = [nums[i] - nums[i - 1] for i in range(1, n)]
evens = differences[0::2]
odds = differences[1::2]
if n % 2 == 0: # Even length case
total_cost = 0
m1 = len(evens)
for i, x in enumerate(evens):
if x < 0:
return None
elif x > 0:
# Last element can't be decreased
if i == m1 - 1:
return None
total_cost += abs(x)
evens[i + 1] += abs(x)
m2 = len(odds)
for i, x in enumerate(odds):
if x < 0:
total_cost += abs(x) * (i + 1)
elif x > 0:
total_cost += abs(x)
if i < m2 - 1:
odds[i + 1] += x
return total_cost
else: # Odd length case
total_cost = 0
m1 = len(evens)
for i, x in enumerate(evens):
if x < 0:
return None
elif x > 0:
if i < m1 - 1:
evens[i + 1] += abs(x)
total_cost += abs(x)
m2 = len(odds)
for i in reversed(range(m2)):
x = odds[i]
if x < 0:
if i != 0:
odds[i - 1] -= abs(x)
total_cost += abs(x)
elif x > 0:
return None
return total_cost
Example tests:
[4, 6, 4] --> 4,
[3, 2, 3] --> None,
[0, 8, 8, 14, 8] --> 34,
[2, 5, 1, 6, 12, 11, 7, 0, 1, 1] --> 33
Let's start with the problem that we want to find out what is the smallest number of operations. If we know what is the largest number that equalizes all arrays? We can subtract that number from each other in the array to get the answer. Naively, you can solve this problem by searching for the smallest number in the array and loop checking from 0 to min(a[i]) then find the minimum operations.
My code in C++
// print -1 if not possible to equalize array
void solve() {
int N;
scanf("%d", &N);
vector<int> a(N);
int minimum=INT_MAX;
for(int i=0; i<N; ++i) {
scanf("%d", &a[i]);
minimum=min(minimum, a[i]);
}
if(N==1) { // Initially check when N=1 or N=2
printf("0\n");
return;
}
else if(N==2) {
printf("%d\n", (a[0]==a[1] ? 0 : -1));
return;
}
vector<int> temp=a; // temporary array for storing array before operation
for(int target=minimum; target>=0; --target) {
a=temp;
bool check=true; // check array equal?
int operation=0; // count number operations
for(int j=0; j<N-1; ++j) {
int subtract=a[j]-target;
operation+=2*subtract;
a[j]-=subtract;
a[j+1]-=subtract;
if(a[j]<target || a[j+1]<target) { // all element should equal to target
check=false;
break;
}
}
if(check==true) {
printf("%d\n", operation);
return;
}
}
printf("-1\n"); // not possible
}
But the above method will be slow when N is large.
For this problem, the Greedy Algorithm can be used to solve it. Now let's look at the situations when this array can't be equalized. If you want to make an array with a[i-1] != a[i] to be equal, what do you have to do? Assume we're iterating over an array and come into the following cases.
First, if a[i-1] > a[i] , then we have to perform operation at the left-hand in order to equal make it to the right-hand side, that is, a[i-2] and a[i-1] must be executed. For example 8 8 7 to 7 7 7.
Please note that if i equals 1, it needs the initial element of an array. It will be unable to operate.
Second, if a[i-1] < a[i] , then we have to perform operation at the right-hand in order to make it equal to the left-hand side, that is, a[i] and a[i+1] must be executed. For example 5 6 6 to 5 5 5.
Please note that if i equals N-1, it is the last element of an array. It will be unable to operate. Naturally, the prior ones have been equalized.
After that, we can use Greedy to keep the array equal. We'll traverse over the array one by one, checking for the conditions listed above. If it can do it, it ought to. Continue until you are unable to do so or discover that this array is stuck at the very first or final position. This one will be quicker than the previous one.
Here is my code.
// print -1 if not possible to equalize array
void solve() {
int N;
scanf("%d", &N);
vector<int> a(N);
for(int i=0; i<N; ++i) {
scanf("%d", &a[i]);
}
if(N==1) {
printf("0\n");
return;
}
else if(N==2) {
printf("%d\n", (a[0]==a[1] ? 0 : -1));
return;
}
if(a[N-2]<a[N-1] || a[0]>a[1]) {
printf("-1\n");
return;
}
long long operation=0;
bool check=true; // check if the array has changed
while(check) {
check=false;
for(int i=1; i<N; ++i) {
if(a[i-1]<a[i]) {
if(i==N-1) {
printf("-1\n");
return;
}
check=true;
int subtract=a[i]-a[i-1];
operation+=2*subtract;
a[i]-=subtract;
a[i+1]-=subtract;
if(a[i]<0 || a[i+1]<0) {
printf("-1\n");
return;
}
}
else if(a[i-1]>a[i]) {
if(i==1) {
printf("-1\n");
return;
}
check=true;
int subtract=a[i-1]-a[i];
operation+=2*subtract;
a[i-2]-=subtract;
a[i-1]-=subtract;
if(a[i-2]<0 || a[i-1]<0) {
printf("-1\n");
return;
}
}
}
}
printf("%lld\n", (a[N-1]>=0 ? operation : -1)); // array already equal but may less than zero
}
This may be accomplished in a variety of ways, and there is a better approach than the one I've presented. However, I believe that this is sufficient to go through test cases 1 <= N <= 105 and 0 <= a[i] <= 109.

Why do I get a stack overflow when I include the pivot in recursive calls in the quicksort algorithm?

I am trying to implement the quicksort algorithm in c++ as a project in a lecture. The program works fine when I exclude the pivot from the recursive calls, but sometimes causes a stack overflow error when i include the pivot in either of the recursive calls.
The program malfunctions only for a specific array size, but I can't figure out what relation they have with the errors. For example, the program malfunctions when I give 40, but works just fine for 50.
void quicksort(double* arr, int init, int fin) {
if (init == fin) return;
if (init == fin - 1) {
if (arr[init] > arr[fin]) {
double temp = arr[fin];
arr[fin] = arr[init];
arr[init] = temp;
}
return;
}
int smaller = init - 1;
double pivot = arr[fin];
for (int ind = init; ind < fin; ind++) {
if (arr[ind] < pivot) {
smaller++;
double temp = arr[ind];
arr[ind] = arr[smaller];
arr[smaller] = temp;
}
}
arr[fin] = arr[smaller + 1];
arr[smaller + 1] = pivot;
if(smaller>=init) quicksort(arr, init, smaller);
if(smaller+2<=fin) quicksort(arr, smaller + 2, fin);
return;
}
This is the code in question. It works fine when i put it this way, but causes errors when i replace
if(smaller+2<=fin) quicksort(arr, smaller + 2, fin);
with
if(smaller+1<=fin) quicksort(arr, smaller + 1, fin);
if(smaller+1<=fin) is equivalent to if(true) (since smaller+1 starts out as init and is incremented at most fin-init times), so any call with at least three elements will necessarily recurse at that line — and the recursive call may not accomplish anything, if (for example) all three elements are equal.
Another way to look at it. Suppose the selected pivot element is the smallest value in the partition. It will move to the beginning of the range.
[ 3 5 2 1 4] // supposed we select 3 as the pivot
[ 2 1 3 5 4] // after partitioning
[ 3 5 4] // the recursive call for the right "half"
If we select 3 as the pivot again, nothing changes in this range. And thus, when we recurse on the right "half" again, we're in exactly the same situation. We've not made any progress, so recursion will continue until the stack overflows.
Omitting the pivot from the ranges for the recursive calls guarantees we make progress and thus that the recursion will eventually terminate.

Using a for loop to check order of numbers, decreasing and increasing order

User will input five values (range 2-9), into an array. Code is then supposed to check whether or not the five values entered are either in decreasing or increasing order.
Example:
2, 3, 4, 5, 6 - would result in a bool value set to 'true'
6, 5, 4, 3, 2 - would have the same result as above
If however there are two of the same numbers or the sequence is not in order, either increasing or decreasing then the bool value is 'false'
Example:
2, 3, 2, 5, 6 - false
2, 3, 5, 4, 6 - false
7, 8, 6, 5, 4 - false
I have completed two cases individually, when there is a pair and when checking for increasing order.
For loop I have set up to check increasing order/pair
for(int count = 0; count < 5; count++){
if((cards[count] > cards[count + 1]) || (cards[count] == cards[count + 1]))
result = false;
else
continue;
}
For loop I made to check decreasing order
for(int count = 0; count < 5; count++){
if((cards[count] < cards[count+ 1]) || (cards[count] == cards[count + 1]))
result = false;
else
continue;
}
The for loop that is meant to check increasing order does not work, and lastly putting it all together. I tried breaking it down into individuals parts in order to make it easier.
Edit:
per assignment guidelines, I am unable to sort the array or anything that modifies it. creating a copy and working with that is also not allowed.
First, you don't need the else continue; – that will happen automatically.
Second, you're on the right track, but you have an error in your array accesses. Your loop goes from 0 to 4 (when it reaches 5 it will end the loop). When you're looking at item 4 (the 5th item in the array) of the array and compare it to item 5 (the sixth item in the array), you're comparing against a value that isn't there. This can cause undefined behavior.
The solution is to set the condition on your loop to < 4 so that on the last loop you compare the second-to-last item with the last item.
You can check monotonicity in one loop.
bool isArrayMonotonic(std::vector<int>& arr) {
if (arr.empty()) {
return true;
}
bool isAscending = true, isDescending = true;
int pre = arr[0];
for (int i = 1; i < arr.size(); ++i) {
isAscending = isAscending && arr[i] > pre;
isDescending = isDescending && arr[i] < pre;
pre = arr[i];
}
return isAscending || isDescending;
}

Compute series without being able to store values?

Problem statement[here]
Let be S a infinite secuence of integers:
S0 = a;
S1 = b;
Si = |Si-2 - Si-1| for all i >= 2.
You have two integers a and b. You must answer some queries about the n-th element in the sequence.(means print the nth number in the sequence i.e S(n) )
( 0 <= a,b <= 10^18),( 1 <= q <= 100000 )
What I Tried(This would give a runtime error) :
#include <bits/stdc++.h>
using namespace std;
long long int q,a,b,arr[100002];/*Can't declare an array of required size */
int main() {
// your code goes here
scanf("%lld%lld",&a,&b);
arr[0]=a,arr[1]=b;
scanf("%d",&q);
int p[100002];
long long int m = -1;//stores max index asked
for(int i=0;i<q;i++)
{
scanf("%lld",&p[i]);
m = (m>p[i])?m:p[i];
}
for(int i=2;i<=m;i++)//calculates series upto that index
{
arr[i]=abs(arr[i-1]-arr[i-2]);
}
for(int i=0;i<q;i++)
{
printf("%lld\n",arr[p[i]]);
}
return 0;
}
Given : qi fits in 64 bit integer. since index can be very large and i cant declare that bit an array, how should i approach this problem(since brute force would give TLE). Thanks!
HA! There is a solution that doesn't require (complete) iteration:
Considering some values Si and Sj, where i, j > 1. Then, looking at how the numbers of the sequence are built (using the absolute value), we can conclude that both numbers are positive.
Then the absolute value of their difference is guaranteed to be less (or equal) than the larger of the two.
Assuming it is strictly less than the larger of the two, within the next two steps, the larger value of the original values will go "out of scope". From that we can conclude that in this case, the numbers of the sequence are getting smaller and smaller.
(*) If the difference is equal to the larger one, then the other number must have been 0. In the next step, one of two things might happen:
a) The larger goes out of scope, then the next two numbers are the calculated difference (which is equal to the larger) and 0, which will yield again the larger value. Then we have the same situation as in ...
b) The zero goes out of scope. Then the next step will compute the difference between the larger and the calculated difference (which is equal to the larger), resulting in 0. In the next step, this leads back to the original (*) situation.
Result: A repeating pattern of L, L, 0, ...
Some examples:
3, 1, 2, 1, 1, 0, 1, 1, 0, ...
1, 3, 2, 1, 1, 0, 1, 1, 0, ...
3.5, 1, 2.5, 1.5, 1, .5, .5, 0, .5, .5, 0, ...
.1, 1, .9, .1, .8, .7, .1, .6, .5, .1, .4, .3, .1, .2, .1, .1, 0, ...
Applying that to the code: As soon as one value is 0, no more iteration is required, the next two numbers will be the same as the previous, then there will be again a 0 and so on:
// A and B could also be negative, that wouldn't change the algorithm,
// but this way the implementation is easier
uint64_t sequence(uint64_t A, uint64_t B, size_t n) {
if (n == 0) {
return A;
}
uint64_t prev[2] = {A, B};
for (size_t it = 1u; it < n; ++it) {
uint64_t next =
(prev[0] > prev[1]) ?
(prev[0] - prev[1]) :
(prev[1] - prev[0]);
if (next == 0) {
size_t remaining = n - it - 1;
if (remaining % 3 == 0) {
return 0;
}
return prev[0]; // same as prev[1]
}
prev[0] = prev[1];
prev[1] = next;
}
return prev[1];
}
Live demo here (play with the a and b values if you like).
If you have repeated queries for the same A and B, you could cache all values until next == 0 in a std::vector, giving you really constant time for the following queries.
I'm also pretty sure that there's a pattern before the sequence reaches 0, but I wasn't able to find it.
I just noticed that I missed that it should be the absolute value of the difference ...
If it's fast enough, here is an iterative version:
// deciding on a concrete type is hard ...
uint64_t sequence (uint64_t A, uint64_t B, uint64_t n) {
if (n == 0) {
return A;
}
uint64_t prev[2] = {A, B};
for (auto it = 1u; it < n; ++it) {
auto next =
(prev[0] > prev[1]) ?
(prev[0] - prev[1]) :
(prev[1] - prev[0]);
prev[0] = prev[1];
prev[1] = next;
}
return prev[1];
}
As you see you don't need to store all values, only the last two numbers are needed to compute the next one.
If this isn't fast enough you could add memorisation: Store the pairs of prev values in an ordered std::map (mapping n to those pairs). You can then start from the entry with the next, lower value of n instead of from the beginning. Of course you need to manage that map then, too: Keep it small and filled with "useful" values.
This is not a programming problem, it's an algorithmic one. Let's look at the first numbers of that sequence:
a
b
a-b
b-(a-b) = 2b-a
(a-b)-(b-(a-b)) = 2(a-b)-b = 2a-3b
2b-a-(2a-3b) = 5b-3a
2a-3b-(5b-3a) = 5a-8b
...
Looking only at the absolute value of the coefficients shows ...
b: 0 1 1 2 3 5 8 ...
a: (1) 0 1 1 2 3 5 ...
... that this is about the Fibonacci sequence. Then, there's also the sign, but this is pretty easy:
b: - + - + - ...
a: + - + - + ...
So the nth number in your sequence should be equal to
f(0) = a
f(n) = (-1)^n * fib(n-1) * a +
(-1)^(n-1) * fib(n) * b
Of course now we have to calculate the nth Fibonacci number, but fortunately there's already a solution for that:
fib(n) = (phi^n - chi^n) / (phi - chi)
with
phi = (1 + sqr(5)) / 2
chi = 1 - phi
So, bringing that to code:
unsigned long fib(unsigned n) {
double const phi = (1 + sqrt(5)) / 2.0;
double const chi = 1 - phi;
return (pow(phi, n) - pow(chi, n)) / (phi - chi);
}
long sequence (long A, long B, unsigned n) {
if(n ==0) {
return A;
}
auto part_a = fib(n-1) * A;
auto part_b = fib (n) * B;
return (n % 2 == 0) ? (part_a - part_b) : (part_b - part_a);
}
Some live demo is here, but this gets problematic when approaching larger numbers (I suspect the fib getting incorrect).
The demo contains also the iterative version of the sequence, as control. If that's fast enough for you, use that instead. No need to store anything more than the last two numbers.
To improve this further, you could use a lookup table with holes for the Fibonacci numbers, i.e. remembering every tenth (and their successor) number of the sequence.