solving the knapsack problem recursively, I want to know which items (item's weight) are taken in the bag that gives the maximum value.
so far I have this:
int MAX(int a, int b) { return (a > b) ? a : b ; }
int thief(int W, int weight[], int value[], int n)
{
int a,b,c;
//basecase:
if(n == 0 || weight <= 0) return 0;
// each item's weight can't be more than W:
if(weight[n-1] > W){
return thief(W, weight, value, n-1);}
a=value[n-1] + thief(W-weight[n-1], weight, value, n-1);// a: nth item included
b=thief(W, weight, value, n-1);// b:nth item not included
c= MAX(a,b);//answer is the maximum of situation a and b
if (c==a) { //if situation a occurs then nth item is included
cout<<weight[n]<<endl;
}
return c;
}
consider n=4 and maximum weight (W) = 30
let weights be : 30 10 20 5
and values : 100 50 60 10
but this code outputs: 20 5 20 10 5
I just want to output 10 and 20.
also I have tried to define a bool array with default values of false and its nth element changes to true if c==a occurs but this won't give the correct result as well.
I'm supposed to do it recursively.
Your basic algorithm doesn't work. You can't do the printing while you test different combinations.
However, first you must fix a bug:
cout<<weight[n-1]<<endl; // n-1 instead of n
Your algorithm does this:
a = value[3] + thief(30-weight[3], weight, value, 3); // Use item 3
b = thief(30, weight, value, 3); // Don't use item 3
The second line will lead to
a = value[2] + thief(30-weight[2], weight, value, 2); // Use item 2
b = thief(30, weight, value, 2); // Don't use item 2
The second line will lead to
a = value[1] + thief(30-weight[1], weight, value, 1); // Use item 1
b = thief(30, weight, value, 1); // Don't use item 1
The second line will lead to
a = value[0] + thief(30-weight[0], weight, value, 0); // Use item 0
b = thief(30, weight, value, 0); // Don't use item 0
This causes
a = 30
b = 0
so your code will select item 0 and print 30 but that is a bug!
So as I stated in the start: You can't do the printing while you test different combinations.
Instead you need to keep track of which elements you use in the different combinations and only keep the "best".
I haven't tested the code below but I think you can do it like this (assuming your code calculates the best combination correctly):
#include <vector>
// The vector v is used for holding the index of the items selected.
// The caller must supply a vector containing the items included so far.
// This function will test whether item "n-1" shall be included or
// excluded. If item "n-1" is included the index is added to the vector.
int thief(int W, int weight[], int value[], int n, vector<int>& v) // Added vector
{
vector<int> v1, v2; // Vector to hold elements of the two combinations
int a,b,c;
//basecase:
if(n == 0 || weight <= 0) return 0;
// each item's weight can't be more than W:
if(weight[n-1] > W){
return thief(W, weight, value, n-1, v2);}
v1.push_back(n-1); // Put n-1 in vector v1 and pass the vector v1
a=value[n-1] + thief(W-weight[n-1], weight, value, n-1, v1);// a: nth item included
// Don't put anything in v2 but pass the vector v2
b=thief(W, weight, value, n-1, v2);// b:nth item not included
c= MAX(a,b);//answer is the maximum of situation a and b
if (c==a) { //if situation a occurs then nth item is included
// cout<<weight[n-1]<<endl;
// Copy elements from v1 to v
for (auto e : v1)
{
v.push_back(e);
}
}
else
{
// Copy elements from v2 to v
for (auto e : v2)
{
v.push_back(e);
}
}
return c;
}
int main() {
vector<int> v;
int weight[4] = {30, 10, 20, 5};
int value[4] = {100, 50, 60, 10};
cout << "result=" << thief(30, weight, value, 4, v) << endl;
// Print the elements used
for (auto e : v)
{
cout << "elem=" << e << endl;
}
return 0;
}
Finally notice - your brute force method is very expensive in terms of execution time in the start value for n is high. There are much better ways to solve this problem.
can you write the code in c?
I wrote this but didn't work. (I think the difference is in the bold)
int knapSack(int W, int wt[], int val[], int n, int arr[])
{
int x, y, c, j, arr1[50], arr2[50];
// Base Case
if (n == 0 || W == 0)
return 0;
// If weight of the nth item is more than Knapsack capacity W, then
// this item cannot be included in the optimal solution
if (wt[n - 1] > W)
return knapSack(W, wt, val, n - 1, arr2);
// Return the maximum of two cases:
// x nth item included
// y not included
**arr1[n - 1] = val[n - 1];**
x = val[n - 1] + knapSack(W - wt[n - 1], wt, val, n - 1, arr1);
//copyArr(arr, out, n);
y = knapSack(W, wt, val, n - 1, arr2);
if (x > y)
c = x;
else
c = y;
if (c == x)
for (j = 0; j < 50; j++)
arr[j] = arr1[j];
else
for (j = 0; j < 50; j++)
arr[j] = arr2[j];
return c;
}
Related
I need to insert two values num1 = 50 and num2 = 80 into an array which has been sorted into ascending order. I cannot use dynamic arrays or lists. Also no structs or classes. This is for a class assignment so I must follow the guidlines. The professor suggested that I should create a new array, newarray and copy the values from my orignal numarray until I reach a condition which would prompt the numbers to be inserted where they would fit in ascending order. I set my loop up to run as many times as it should take to fill up my newarray and I set up the conditionals to check if the previous number in the numarray is less than num and if the next value is greater than num. The numbers get inserted at the correct locations, however, inserting the new value overwrites the value that would be there.
I thought that
newarray[newindex] = num1;
newarray[newindex+1] = numarray[newindex]; would write the inserted value into the current index, and then write numarray[newindex] into the index after it.
I will attach the function below. Everything is self contained in the function except numarray which is just sorted values up to 100. Thanks in advance. Maybe walking away for a while will help me figure it out.
void insertArray(int* numarray) {
int num1 = 50;
int num2 = 80;
int counter = 0;
int newarray[103] = {};
for (int newindex = 0; newindex < 103; newindex++) {
if (numarray[newindex] <= num1 && numarray[newindex+1] > num1) {
newarray[newindex] = num1;
newarray[newindex+1] = numarray[newindex];
}
else if (numarray[newindex] <= num2 && numarray[newindex+1] > num2) {
newarray[newindex] = num2;
newarray[newindex+1] = numarray[newindex];
}
else {
newarray[newindex] = numarray[newindex];
}
cout << newarray[newindex] << endl;
}
}
int main() {
int numarray[100] = {};
randomgenerator();
read(numarray);
printArray(numarray);
searchArray(numarray);
Delete(numarray);
sortArray(numarray);
insertArray(numarray);
return 0;
}
UPDATE:
After input for you guys, I tried the function suggested, and it successfully inserts two values. It was inserting 0 instead of 50, and it inserts 78 instead of 80. I got it to insert 50, but I don't understand what condition is causing it to insert 78, or what would be 80. I tried writing it in the same format as the for loop to insert 50, but it doesn't work.
void insertArray(int* numarray) {
int num1 = 50;
int num2 = 80;
int index = 0;
int newarray[102] = {};
for (; index < 100 && numarray[index] < num1; ++index) {
newarray[index] = numarray[index];
}
newarray[index++] = num1;
for (; index < 101 && numarray[index - 1] < num2; ++index) {
newarray[index] = numarray[index - 1];
}
if (index == 102) {
newarray[index++] = num2;
}
for (; index < 102; ++index) {
newarray[index] = numarray[index - 2];
}
for (int i = 0; i <= 101; i++) {
cout << newarray[i] << endl;
}
}
In my opinion the approach in general is wrong.
The new array should be declared in the function main where it should be outputted after inserting a number.
The function should not insert two numbers at once. It should insert only one number but called as many times as new numbers should be inserted in an array.
The function might be called to insert a value when the destination and source arrays are the same one array.
So I am suggesting the following approach shown in the demonstrative program below.
#include <iostream>
void insert( const int *a1, size_t n, int *a2, int value )
{
const int *p = a1 + n;
a2 += n;
while ( ( p != a1 ) && ( value < *( p - 1 ) ) ) *a2-- = *--p;
*a2-- = value;
while ( p != a1 ) *a2-- = *--p;
}
int main()
{
int a1[] = { 5, 15, 25, 35, 45, 55, 65, 75, 85, 95 };
const size_t N = sizeof( a1 ) / sizeof( *a1 );
int a2[N + 2];
insert( a1, N, a2, 50 );
for ( size_t i = 0; i < N + 1; i++ )
{
std::cout << a2[i] << ' ';
}
std::cout << '\n';
insert( a2, N + 1, a2, 80 );
for ( size_t i = 0; i < N + 2; i++ )
{
std::cout << a2[i] << ' ';
}
std::cout << '\n';
return 0;
}
The program output is
5 15 25 35 45 50 55 65 75 85 95
5 15 25 35 45 50 55 65 75 80 85 95
As for your code then for example this loop
for (int newindex = 0; newindex < 103; newindex++) {
if (numarray[newindex] <= num1 && numarray[newindex+1] > num1) {
//...
invokes undefined behavior because the array numarray does not have elements with indices 100, 101, and 102.
Also in general it can occur such away that the source array does not have an element that is greater than num1 or num2.
Another approach is to place the inserted values in a separate array and then combain the source array and the array of the inserted values in the destination array.
Here is a demonstrative program.
#include <iostream>
void insert( const int *a1, size_t n1, const int *a2, size_t n2, int *result )
{
const int *p1 = a1;
const int *p2 = a2;
while ( p1 != a1 + n1 && p2 != a2 + n2 )
{
if ( *p2 < *p1 )
{
*result++ = *p2++;
}
else
{
*result++ = *p1++;
}
}
while ( p1 != a1 + n1 ) *result++ = *p1++;
while ( p2 != a2 + n2 ) *result++ = *p2++;
}
int main()
{
int a1[] = { 5, 15, 25, 35, 45, 55, 65, 75, 85, 95 };
const size_t N1 = sizeof( a1 ) / sizeof( *a1 );
int a2[] = { 50, 80 };
const size_t N2 = sizeof( a2 ) / sizeof( *a2 );
int result[N1 + N2];
insert( a1, N1, a2, N2, result );
for ( int item : result )
{
std::cout << item << ' ';
}
std::cout << '\n';
return 0;
}
Its output is
5 15 25 35 45 50 55 65 75 80 85 95
I agree whole heartedly with #Vlad from Moscow that your new array should be declared in the caller and passed as a parameter to your insert function. This is mandatory if you are using a POA (plain old array) as a new POA declared within your insert function ceases to exist when your function returns. You can always allocate storage for your array within the insert function and return a pointer to the allocated block, but even then, if you want to keey your insert function type as void, you may as well declare it in the caller and pass it as a parameter.
There are a number of ways to do an insertion into a new array in sorted order, and all are fine for small arrays (say 1000 or so elements). However, since your original array is already sorted, for larger arrays, taking advantage of that fact you can write a routine that is orders of magnitude more efficient. An efficient approach using the sorted array is similar to a bsearch.
There you simply compare your value to be inserted with the middle-element in the array. If it is greater than the first element of your array and smaller than then middle element, you know you will insert it in the first 1/2 of the original array. You can copy the entire second 1/2 of the array to the middle-element + 1 of the new array at that point and repeat.
If the value is greater than the middle element, you can copy the entire first 1/2 of the original to the beginning of your new array and continue your search in the second 1/2 of your array.
When your value is less than or equal to the first element in the subarray, you have found the insertion point and can set the value in the new array to the value to be inserted copying the remainder of the subarray to your new array beginning at the next index.
Similarly, if your value is greater than the end value of the subarray, you can insert the value at the end of range in your new array to the value and then copy the remaining value from the original to the beginning of the range in your new array.
What this does is dramatically reduce your worst case number of iterations required to find the insertion point and allows you to use a much more efficient copy to fill in the elements of the new array above and below the inserted value. For example with a 1,000,000 element array, if you start your iteration at the end and work back toward the beginning and your actual new element is the 1st element in the array, you will iterate 1,000,000 times to insert the new value and copy the remaining elements from your original to the new array.
Contrast that with the method described above where you are effectively bisecting the original into subarrays, your worst case scenario is 20-iterations max to find the insertion point, and 2-copies to copy the elements below and above the inserted value.
While you can do it all in one function, it helps to split the function into two-separate functions. One to find the insertion position for the new element which will make recursive calls (say call it findinspos which does not need the new array as a parameter at all), and the second function (say insinsorted that calls findinspos and then copies the remaining elements)
They could be implemented as:
/* returns index where v should be inserted in a for a given
* start index and an array of nelem elements.
*/
int findinspos (const int *a, int start, int nelem, int v)
{
int mid = (start + nelem) / 2;
if (v <= a[start]) /* exit conditon ins at start */
return start;
else if (a[nelem - 1] < v) /* exit condition ins at end */
return nelem;
else if (v < a[mid]) /* v in 1st 1/2 subarray */
return findinspos (a, start, mid, v);
else /* v in 2nd 1/2 subarray */
return findinspos (a, mid, nelem, v);
}
/* inserts v in sorted positon within the nelem elements of a with
* the results stored in b.
*/
void insinsorted (const int *a, int nelem, int v, int *b)
{
int inspos = findinspos (a, 0, nelem, v); /* get insert positon */
b[inspos] = v; /* set value at inspos in new array */
if (inspos == 0) /* if ins at start copy a beginning at next element */
memcpy (b + 1, a, nelem * sizeof *b);
else if (inspos == nelem) /* if at end, copy a to b */
memcpy (b, a, nelem * sizeof *b);
else { /* otherwise, copy begin and end of a to b */
memcpy (b, a, inspos * sizeof *b);
memcpy (b + inspos + 1, a + inspos, (nelem - inspos) * sizeof *b);
}
}
A complete test program (that borrows the test array from Vlad from Moscow), and lets you enter any number you would like to insert as the 1st argument to the program (default: 4 if no arguments entered) could be:
#include <iostream>
#include <cstring>
/* returns index where v should be inserted in a for a given
* start index and an array of nelem elements.
*/
int findinspos (const int *a, int start, int nelem, int v)
{
int mid = (start + nelem) / 2;
if (v <= a[start]) /* exit conditon ins at start */
return start;
else if (a[nelem - 1] < v) /* exit condition ins at end */
return nelem;
else if (v < a[mid]) /* v in 1st 1/2 subarray */
return findinspos (a, start, mid, v);
else /* v in 2nd 1/2 subarray */
return findinspos (a, mid, nelem, v);
}
/* inserts v in sorted positon within the nelem elements of a with
* the results stored in b.
*/
void insinsorted (const int *a, int nelem, int v, int *b)
{
int inspos = findinspos (a, 0, nelem, v); /* get insert positon */
b[inspos] = v; /* set value at inspos in new array */
if (inspos == 0) /* if ins at start copy a beginning at next element */
memcpy (b + 1, a, nelem * sizeof *b);
else if (inspos == nelem) /* if at end, copy a to b */
memcpy (b, a, nelem * sizeof *b);
else { /* otherwise, copy begin and end of a to b */
memcpy (b, a, inspos * sizeof *b);
memcpy (b + inspos + 1, a + inspos, (nelem - inspos) * sizeof *b);
}
}
int main (int argc, char **argv) {
int a[] = { 5, 15, 25, 35, 45, 55, 65, 75, 85, 95 },
nelem = sizeof a / sizeof *a,
*b = new int[nelem+1] {},
v = argc > 1 ? strtol (argv[1], NULL, 0) : 4;
for (int i = 0; i < nelem; i++)
std::cout << " " << a[i];
std::cout << '\n';
insinsorted (a, nelem, v, b);
for (int i = 0; i < nelem + 1; i++)
std::cout << " " << b[i];
std::cout << '\n';
delete[] b;
}
(note: the array b is allocated with new, if that is outside your assignment, just replace it with an array with enough storage to hold the original plus the new elements -- and don't forget to remove delete[] b; from the end)
Like I began with, for small arrays -- how you do it really doesn't matter, but for larger arrays, taking advantage of the fact the original array is already sorted can provide orders of magnitude improvements in efficiency.
The professor suggested that I should create a new array, newarray and copy the values from my orignal numarray until I reach a condition which would prompt the numbers to be inserted where they would fit in ascending order.
So let's do exactly that.
void copyAndInsert(int* numarray, int size, int* newarray, int num1, int num2)
{
if (num1 > num2) { std::swap(num1, num2); } // ensure we write the smaller number first
int * num1pos = std::lower_bound(numarray, numarray + size, num1); // find where to put num1
int * new1pos = std::copy(numarray, num1pos, newarray); // copy the numbers less than num1
*new1pos++ = num1; // copy num1 and move past it
int * num2pos = std::lower_bound(num1pos, numarray + size, num2); // find where to put num2
int * new2pos = std::copy(num1pos, num2pos, new1pos); // copy the numbers between num1 and num2
*new2pos++ = num2; // copy num2 and move past it
std::copy(num2pos, numarray + size, new2pos); // copy the numbers greater than num2
}
int main() {
int numarray[100] = {};
int newarray[102] = {};
randomgenerator();
read(numarray);
printArray(numarray);
searchArray(numarray);
Delete(numarray);
sortArray(numarray);
copyAndInsert(numarray, 100, newarray, 50, 80);
return 0;
}
If you can't use things from the standard header <algorithm>, you can implement them as follows (adapted from cppreference)
int* lower_bound(int* first, int* last, int value)
{
int count = last - first;
while (count > 0) {
int step = count / 2;
int* it = first + step;
if (*it < value) {
first = ++it;
count -= step + 1;
}
else {
count = step;
}
}
return first;
}
int* copy(int* first, int* last, int* d_first)
{
while (first != last) {
*d_first++ = *first++;
}
return d_first;
}
I suggest to use std::merge from the c++ standard library. It merges two sorted ranges into one sorted range. As numarray is already sorted, the only thing we need to do is to make sure that the numbers we are adding are sorted as well.
#include<algorithm>
void insertArray(int* numarray) {
int num1 = 50;
int num2 = 80;
std::vector<int> numbersToAdd{num1, num2};
//make sure numbersToAdd is sorted
std::sort(begin(numbersToAdd), end(numbersToAdd));
int newarray[102] = {};
std::merge(numarray, numarray + 100, begin(numbersToAdd), end(numbersToAdd), newarray);
}
this function prints all triplets in an array whose product is a number k
input first line number of elements, second array elements, third target product .. passes parameters to recursive function f along with a vector that stores elements whose product might give k
thought process -> for each element we can include or exclude it to get the product k. If p > 24 or number elements multiplied > 3 we backtrack. once the prod = k, we print all numbers from vector v and pop them out and set number of elements count to 0 and product to 1 and continue
for an input of:
9
1 2 3 12 4 7 5 24 9
24
my output looks something like this :
12
2
1
9
9
9
| ->cursor justs stops here ..no further outputs...
naming scheme used :
count -> number of elements multiplied till now whose product is stored in -> p
n-> number of elements in array
k -> target pdt
i -> index of element in array currently at
code :
#include <iostream>
#include <vector>
using namespace std;
// all triplets whose product is a number k
void f(int i, int count, int p, int k, vector<int>&v, int *a, int n)
{
// success condition
if(count == 3 && p == k)
{
for(int i = 2; i >= 0; --i)
{
cout << v[i] << " " << endl;
v.pop_back();
}
p = 1;
count = 0;
}
if(count>=3 || i > n - 1 || p > k)
{
return;
}
v.push_back(a[i]);
f(i + 1, count + 1, p * a[i], k, v, a, n);
v.pop_back();
f(i + 1, count, p, k, v, a, n);
}
int main()
{
int n;
cin >> n;
int *a=new int[n];
for(int i = 0; i < n; ++i)
{
cin >> a[i];
}
int k;
cin >> k;
//int p = 1;
vector<int>v;
f(0, 0, 1, k, v, a, n);
delete[] a;
return 0;
}
Your “reset” of p and count on success is immediately strange: why does the function need to keep looking when its caller will already try the other possibilities? But that’s just a distraction from the real issue: the balanced push_back and pop_back around the first recursive call establish and rely on an invariant where every call leaves v the same length as when it started. But the success path clears the vector and leaves it shorter, so eventually you pop_back when it’s empty and—
The joys of undefined behavior (which happened to give an infinite loop here) aside, the fix is trivial: just return after printing without modifying v at all. (Then you may find some further simplifications.)
I have a code for "Minimum number of jumps to reach end of the array with its sequence using recursion". But I am not able to print the sequence. ( There is nothing in vector vec to print )
Any help will be appreciated.
Explanation :
I want to reach from 1st element ( i.e. 2) to
last element ( i.e. 4) of the array in minimum Jump.
How Jump will be :
1st element is 2. It means I can make upto 2 jumps in array. If I take 1st jump then I can reach 2nd element ( i.e. 3) or if I take
2nd jump then I can reach 3rd element (i.e. 1)
2nd element is 3 ,so I can make maximum 3 jumps. In 1st jump I can reach to 1 , in 2nd jump I can reach to 0 and in 3rd jump I can
reach to 4
In this way I want to reach from 1st element to last element of the array in minimum number of jumps.
So output will be like , from 1st element 2, I will jump to 3. Then from 3 I will jump to 4 (last element). So 2 Jumps. ( 2 - 3 - 4 )
#include<iostream>
#include<vector>
#include<climits>
using namespace std;
int jump(int arr[], int n, int start, vector<int> &vec)
{
if(start == n-1) // if start is the last element in array
return 0;
if( arr[start] == 0) // if array element is 0
return 0;
vector<int> vec1 = vec;
vector<int> vec2 = vec;
int minimum = INT_MAX;
for( int i = 1 ; i <= arr[start]; i++ )
{
vec1.push_back(start);
int _jump = 1 + jump( arr, n, start+i, vec1); // considering every jump
vec = (_jump < minimum) ? vec1 : vec2;
minimum = min(minimum, _jump);
}
return minimum;
}
int main()
{
int arr[] = { 2, 3, 1, 0, 4 };
int n = sizeof(arr) / sizeof(arr[0]);
vector<int> vec;
cout << "Number of jumps " << jump(arr, n, 0, vec) << endl;
cout<<"Sequence is "<<endl;
for( auto x : vec)
cout << x <<" ";
return 0;
}
output
Number of jumps 2
Sequence is
Expected output
Number of jumps 2
Sequence is 2 3 4
Here is an example that will set a vector where each index stores the correct next step in the sequence after visiting that index. I leave it to you to code following the sequence from the first element to the end, using the result vector. I also corrected this condition if( arr[start] == 0) to return "infinity" since if we visit this element, we cannot complete the sequence.
#include<iostream>
#include<vector>
#include<climits>
using namespace std;
int jump(int arr[], int n, int start, vector<int> &vec)
{
if(start == n-1) // if start is the last element in array
return 0;
if( arr[start] == 0) // if array element is 0
return INT_MAX - n;
int minimum = INT_MAX;
int step;
for( int i = 1 ; i <= arr[start]; i++ )
{
int _jump = 1 + jump( arr, n, start+i, vec); // considering every jump
if (_jump < minimum){
minimum = _jump;
step = start + i;
}
}
vec.at(start) = step;
return minimum;
}
int main()
{
int arr[] = { 2, 3, 1, 0, 4 };
int n = sizeof(arr) / sizeof(arr[0]);
vector<int> vec(n, -1);
cout << "Number of jumps " << jump(arr, n, 0, vec) << endl;
cout<<"Vector: "<<endl;
for( auto x : vec)
cout << x <<" ";
return 0;
}
Essentially, this is the minimal fix so that the sample data would works. I have not check all edge cases. For example, one might want to print something else than the value of INT_MAX is the end is not reachable.
Problem 1
You want to output values (i.e. 2, 3, 4 in your example) and not index (0, 1, 4). Thus you must push values instead of indexes.
vec1.push_back(arr[start]);
Problem 2
if(start == n-1) // if start is the last element in array
return 0;
This will not add the final value when the end is reached. You must add last value with:
vec.push_back(arr[start]);
Problem 3
if( arr[start] == 0) // if array element is 0
return 0;
A sequence that does not reach the end, would be considered to be very good. You should return a large value. Since _jump is 1 + return value of jump, the return value should be INT_MAX - 1 and minimum should also be initialized to that value for same reason.
Alternatively, you could return other values like n too instead.
Problem 4
Finally, the following condition is incorrect:
vec = (_jump < minimum) ? vec1 : vec2;
When the condition is not verified, it is vect2 that need to be copied in vec1 since the loop uses vect1.
Briefly about the condition of the problem:
Given the numbers from 1 to n, and m stages of purification, after that follows in m lines with two numbers left and right (borders, inclusive), the range of deleting numbers (1..n), you must output all living elements after removal.
I will give an example:
n = 10, m = 3
Suppose we make an array a[1,2,3,4,5,6,7,8,9,10];
left = 1, right = 2;
After 1 deletion: a[3,4,5,6,7,8,9,10];
left = 4, right = 5;
After 2 deletion: a[3,4,5,8,9,10];
left = 3, right = 5;
After 3 deletion: a[3,4,10];
Conclusion: 3 4 10
So not everything is so simple, the restrictions are strict, namely:
n, m <= 3 * 10 ^ 5
left <= right
My attempt was as follows: I created a vector from numbers from 1 to n and deleted all elements in range [left, right], but Time Limit is coming because of the complexity.
#include <iostream>
#include <vector>
using namespace std;
#define ll uint64_t
int main() {
ll i, n, k, l, r;
cin >> n >> k;
vector <ll> a;
for (i = 1; i <= n; i++) {
a.push_back(i);
}
for (i = 1; i <= k; i++) {
cin >> l >> r;
a.erase(a.begin()+l-1,a.begin()+r);
}
cout << a.size() << endl;
for (auto i : a) {
cout << i << ' ';
}
}
How to solving this problem?
The problem is solvable using a segment tree with lazy propagation and order statistics in O((N + Q) * log(N)) which should pass in a second or two on most online judges given your constraints.
Brief Explanation
'Still Exists' Boolean Array
Let's imagine that we have a boolean array of size N that indicates for each item whether it still exists or removed. The array will be initialized with ones since no elements are removed yet.
Segment Tree
Info Query: Let's build a range-sum segment tree on top of this boolean array (True is mapped to 1 and false is mapped to 0). If we query for any [L, R] range, the segment tree answers with the number of still existing elements. (Note that L and R are indices in the original array -that includes removed and non-removed elements-)
Update Query: The only update query done on the segment tree is to set a range with zeros (marking a range of elements as removed). Since we update a range of elements to zeros, we need to use lazy propagation (No need for it if the problem required removing a single item).
The Final Output
After updating all ranges given to zeros, we can iterate over each index and check if it's zero or one, and print it if it's one, however the solution is not that easy since the ranges provided in the input are not ranges in the original array, it's actually indices in the updated array.
Updated Ranges Problem
To understand the problem more let's go through an example:
Let's assume we're working with an array of length 6, the array is initially: 1 2 3 4 5 6 and the boolean array is initially: 1 1 1 1 1 1
Let's assume that the first deletion is [2, 4], now the new array is: 1 5 6 and the new updated boolean array is: 1 0 0 0 1 1
At this point, if we were asked to print the array, we will simply go through the original array and print the values that only corresponds to true in the boolean array.
Now let's try to delete another range [1, 2], if we simply set the first two elements to zeros, then we will end up with: 0 0 0 0 1 1. Which means that we still have 5, 6 on in our array, while we actually have only 6 after the last deletion.
Order-Statistics to solve the updated ranges problem
To solve the problem, we need to add the order-statistics property to our segment tree. this property will answer the following question: Given X, find the index were the prefix sum of ones that ends with it is X, this will help us map the current [L, R] into new [L, R] that can be used with the original indexing.
To understand the mapping better, let's go back to the second step of our example:
The boolean array was: 1 0 0 0 1 1, Delete elements between L=1 and R=2, using the order-statistics property, L will be mapped to 1 and R will be mapped to 5, now we will update the range between the newL and the newR to zeros and the boolean array becomes 0 0 0 0 0 1.
Code
#include <bits/stdc++.h>
using namespace std;
class SegmentTree {
vector<int> seg, lazy;
int sz;
void build(int ind, int ns, int ne, const vector<int> &v) {
if (ns == ne) {
seg[ind] = v[ns];
return;
}
int mid = ns + (ne - ns) / 2;
build(ind * 2, ns, mid, v);
build(ind * 2 + 1, mid + 1, ne, v);
seg[ind] = seg[ind * 2] + seg[ind * 2 + 1];
}
void probagateLazy(int ind) {
if (lazy[ind]) {
lazy[ind] = 0, seg[ind] = 0;
if (ind * 2 + 1 < 4 * sz)
lazy[ind * 2] = lazy[ind * 2 + 1] = 1;
}
}
int query(int s, int e, int ind, int ns, int ne) {
probagateLazy(ind);
if (e < ns || s > ne)
return 0;
if (s <= ns && ne <= e)
return seg[ind];
int mid = ns + (ne - ns) / 2;
return query(s, e, ind * 2, ns, mid) + query(s, e, ind * 2 + 1, mid + 1, ne);
}
void update(int s, int e, int ind, int ns, int ne) {
probagateLazy(ind);
if (e < ns || s > ne)
return;
if (s <= ns && ne <= e) {
lazy[ind] = 1;
probagateLazy(ind);
return;
}
int mid = ns + (ne - ns) / 2;
update(s, e, ind * 2, ns, mid);
update(s, e, ind * 2 + 1, mid + 1, ne);
seg[ind] = seg[ind * 2] + seg[ind * 2 + 1];
}
int find(int pos, int ind, int ns, int ne) {
probagateLazy(ind);
if (ns == ne)
return ns;
probagateLazy(ind * 2);
probagateLazy(ind * 2 + 1);
int mid = ns + (ne - ns) / 2;
if (pos <= seg[ind * 2])
return find(pos, ind * 2, ns, mid);
return find(pos - seg[ind * 2], ind * 2 + 1, mid + 1, ne);
}
public:
SegmentTree(int sz, const vector<int> &v) {
this->sz = sz;
seg = vector<int>(sz * 4);
lazy = vector<int>(sz * 4);
build(1, 0, sz - 1, v);
}
int query(int s, int e) {
return query(s, e, 1, 0, sz - 1);
}
int update(int s, int e) {
update(s, e, 1, 0, sz - 1);
}
int find(int pos) {
return find(pos, 1, 0, sz - 1);
}
};
int main() {
int i, n, k, l, r;
scanf("%d %d", &n, &k);
vector<int> a;
for (i = 1; i <= n; i++) {
a.push_back(i);
}
vector<int> v(n, 1);
SegmentTree st(n, v);
while (k--) {
scanf("%d %d", &l, &r);
int newL = st.find(l);
int newR = st.find(r);
st.update(newL, newR);
}
vector<int> ans;
for (int i = 0; i < n; i++) {
if (st.query(i, i))
ans.push_back(a[i]);
}
printf("%d\n", ans.size());
for (int i = 0; i < ans.size(); i++) {
printf("%d ", ans[i]);
}
}
Black Boxing the Segment Tree
If you're not familiar with segment trees, then it's expected to find the code hard to understand, so I'll try to make it easier by ignoring the internal implementation of the segment tree and give you a quick look at its functionalities.
Query Method
The query method takes as an input the start and end index of the range to be queried and returns the summation of the elements inside this range.
Update Method
The update method takes as input the start and end index of the range to be updated, and set all items inside this range to zeros
Find Method
The find method takes as an input X and returns the first element Y were the sum of elements in the range [0, Y] is X
Other Solutions
The problem can also be solved using Splay Tree or Treap data structure.
Not allowed to modify the array ( The array is read only ).
Using constant extra space is allowed.
ex:
A : [2 1 4 3 2]
k : 3
answer : 2
I did it below way. The answer is correct but need to be more memory efficient.
void insert_sorted(vector<int> &B, int a,int k)
{
for(int i=0;i<k;i++)
{
if(B[i]>=a)
{
for(int j=k-1;j>i;j--)
B[j]=B[j-1];
B[i]=a;
return;
}
}
}
int Solution::kthsmallest(const vector<int> &A, int k) {
vector <int> B;
for(int i=0;i<k;i++)
{
B.push_back(INT_MAX);
}
int l=A.size();
for(int i=0;i<l;i++)
{
if(B[k-1]>=A[i])
insert_sorted(B,A[i],k);
}
return B[k-1];
}
One possible solution is binary search.
Let A be the input array; we want to find a number b such that exactly k items in A are smaller than b.
Obviously, b must be inside the range [0, max(A)].
And we do binary search starting with this range.
Suppose we are searching within range [lo, hi].
Let c = (lo + hi)/2 which is the middle pivot.
There are three cases:
number of items in A less than c are less than k.
In this case the number we search for should be larger than c, so it should be in range (c, hi]
number of items in A less than c are larger than k.
Similarly, the number we search for is in range [lo, c)
number of items in A less than c equals to k.
In this case, the answer is the minimum element in A that is greater than or equals to c. This can be find by doing a linear search in A again
The complexity is O(n log m), where m is the max element in A.
/* assume k is 0 based, i.e. 0 <= k < n */
int kth_element(const vector<int> &A, int k){
int lo = 0, hi = *max_element(A.begin(), A.end());
while (lo <= hi){
int mid = (lo + hi) / 2;
int rank_lo = count_if(A.begin(), A.end(), [=](int i){ return i < mid;});
int rank_hi = count_if(A.begin(), A.end(), [=](int i){ return i <= mid;});
if (rank_lo <= k && k < rank_hi)
return mid;
if (k >= rank_hi)
lo = mid + 1;
else
hi = mid - 1;
}
}
Although it's not the answer to this particular problem (as it requires a modifiable collection), there is a function called std::nth_element, which rearranges the elements so that the kth element is at position k, and all elements at positions less than k are smaller than or equal to the kth element, where k is a input parameter.
The question does not ask for any time constraints. An O(nk) solution is fairly simple, by iterating the array k times (at most), and discarding one element (and its duplicates) each time.
int FindKthSmallesr(const std::vector<int>& v, int k) {
// assuming INT_MIN cannot be a value. Could be relaxed by an extra iteration.
int last_min = INT_MIN;
while (k > 0) {
int current_min = INT_MAX;
for (int x : v) {
if (x <= last_min) continue;
current_min = std::min(current_min, x);
}
last_min = current_min;
for (int x : v) {
if (x == current_min) k--;
}
}
return last_min;
}
Code on ideone: http://ideone.com/RjRIkM
If only constant extra space is allowed, we can use a simple O(n*k) algorithm.
int kth_smallest(const vector<int>& v, int k) {
int curmin = -1;
int order = -1;
while (order < k) { // while kth element wasn't reached
curmin = *min_element(v.begin(), v.end(), [curmin](int a, int b) {
if (a <= curmin) return false;
if (b <= curmin) return true;
return a < b;
}); // find minimal number among not counted yet
order += count(v.begin(), v.end(), curmin); // count all 'minimal' numbers
}
return curmin;
}
online version to play with: http://ideone.com/KNMYxA