I recently took an online test on codility as part of a recruitment process. I was given two simple problems to solve in 1 hour. For those who don't know codility, its an online coding test site where you can solve ACM style problems in many different languages.
If you have 30 or so mins then check this http://codility.com/demo/run/
My weapon of choice is usually Java.
So, one of the problems I have is as follows (I will try to remember, should have taken a screenshot)
Lets say you have array A[0]=1 A[1]=-1 ....A[n]=x
Then what would be the smartest way to find out the number of times when A[i]+A[j] is even where i < j
So if we have {1,2,3,4,5}
we have 1+3 1+5 2+4 3+5 = 4 pairs which are even
The code I wrote was some thing along the lines
int sum=0;
for(int i=0;i<A.length-1;i++){
for (int j=i+1;j<A.length;j++){
if( ((A[i]+A[j])%2) == 0 && i<j) {
sum++;
}
}
}
There was one more restriction that if the number of pairs is greater than 1e9 then it should retrun -1, but lets forget it.
Can you suggest a better solution for this. The number of elements won't exceed 1e9 in normal cases.
I think I got 27 points deducted for the above code (ie it's not perfect). Codility gives out a detailed assessment of what went wrong, I don't have that right now.
The sum of two integers is even if and only if they are either both even or both odd. You can simply go through the array and count evens and odds. The number of possibilities to combine k numbers from a set of size N is N! / ((N - k)! · k!). You just need to put the number of evens/odds as N and 2 as k. For this, the above simplifies to (N · (N - 1)) / 2. All the condition i < j does is to specify that each combination counts only once.
You can find the sum without calculating every pair individually.
A[i]+A[j] is even if A[i] is even and A[j] is even; or A[i] is odd and A[j] is odd.
A running total of odd and even numbers up to j can be kept, and added to sum depending on whether A[j] is odd or even:
int sum = 0;
int odd = 0;
int even = 0;
for(int j = 0; j < A.length; j++) {
if(A[j] % 2 == 0) {
sum += even;
even++;
} else {
sum += odd;
odd++;
}
}
Edit:
If you look at A={1,2,3,4,5}, each value of j would add the number of pairs with A[j] as the second number.
Even values:
A[j]=2 - sum += 0
A[j]=4 - sum += 1 - [2+4]
Odd values:
A[j]=1 - sum += 0
A[j]=3 - sum += 1 - [1+3]
A[j]=5 - sum += 2 - [1+5, 3+5]
Please check this
if (A == null || A.length < 2) {
return 0;
}
int evenNumbersCount = 0;
int oddNumberCount = 0;
for (int aA : A) {
if (aA % 2 == 0) {
evenNumbersCount++;
} else {
oddNumberCount++;
}
}
int i = (evenNumbersCount * (evenNumbersCount - 1)) / 2 + (oddNumberCount * (oddNumberCount - 1)) / 2;
return i > 1000000000 ? -1 : i;
If someone has a problem with understanding what Sante said here is another explanation:
Only odd+odd and even+even gives even. You have to find how many even and odd numbers are there. When you have it imagine that this as a problem with a meeting. How many people distinkt pairs are in the odd numbers list and even numbers list. This is the same problem as how many pairs will say hallo to each other at the party. This is also the number of edges in full graph. The answer is n*(n-1)/2 because there are n people, and you have to shake n-1 peoples hands and divide by 2 because the other person cant count your shake as distinct one. As you have here two separate "parties" going on you have to count them independently.
It's very simple
First you need to find the number of odds and even number in collection.
eg. x is odd if x&1 ==1, even otherwise,
if you have this, knowing that adding two even or two odds to each you get even.
You need to calc the sum of Combinations of two elements from Even numbers and Odd numbers.
having int A[] = {1,2,3,4,5};
int odds=0, evens=0;
for (int i=0; i< A.length; ++i)
{
if (A[i]&1==1) odds++;
else evens++;
}
return odds*(odds-1)/2 + evens*(evens-1)/2;
// Above goes from fact that the number of possibilities to combine k numbers from a set of size N is N! / ((N - k)! · k!). For k=2 this simplifies to (N · (N - 1)) / 2
See this answer also
int returnNumOFOddEvenSum(int [] A){
int sumOdd=0;
int sumEven=0;
if(A.length==0)
return 0;
for(int i=0; i<A.length; i++)
{
if(A[i]%2==0)
sumEven++;
else
sumOdd++;
}
return factSum(sumEven)+factSum(sumOdd);
}
int factSum(int num){
int sum=0;
for(int i=1; i<=num-1; i++)
{
sum+=i;
}
return sum;
}
public int getEvenSumPairs(int[] array){
int even=0;
int odd=0;
int evenSum=0;
for(int j=0; j<array.length; ++j){
if(array[j]%2==0) even++;
else odd++;
}
evenSum=((even*(even-1)/2) + (odd *(odd-1)/2) ;
return evenSum;
}
A Java implementation that works great based on the answer by "Svante":
int getNumSumsOfTwoEven(int[] a) {
long numOdd = 0;
long numEven = 0;
for(int i = 0; i < a.length; i++) {
if(a[i] % 2 == 0) { //even
numOdd++;
} else {
numEven++;
}
}
//N! / ((N - k)! · k!), where N = num. even nums or num odd nums, k = 2
long numSumOfTwoEven = (long)(fact(numOdd) / (fact(numOdd - 2) * 2));
numSumOfTwoEven += (long)(fact(numEven) / (fact(numEven - 2) * 2));
if(numSumOfTwoEven > ((long)1e9)) {
return -1;
}
return numSumOfTwoEven;
}
// This is a recursive function to calculate factorials
long fact(int i) {
if(i == 0) {
return 1;
}
return i * fact(i-1);
}
Algorithms are boring, here is a python solution.
>>> A = range(5)
>>> A
[0, 1, 2, 3, 4]
>>> even = lambda n: n % 2 == 0
>>> [(i, j) for i in A for j in A[i+1:] if even(i+j)]
[(0, 2), (0, 4), (1, 3), (2, 4)]
I will attempt another solution using vim.
You can get rid of the if/else statement and just have the following:
int pair_sum_v2( int A[], int N ) {
int totals[2] = { 0, 0 };
for (int i = 0; i < N; i++ ) {
totals[ A[i] & 0x01 ]++;
}
return ( totals[0] * (totals[0]-1) + totals[1] * (totals[1]-1) ) / 2;
}
Let count odd numbers as n1 and count even numbers as n2.
The sum of Pair(x,y) is even, only if we choose both x and y from the set of even numbers or both x and y from odd set (selecting x from even set and y from odd set or vice-versa will always result in the pair's sum to be an odd number).
So total combination such that each pair's sum is even = n1C2 + n2C2.
= (n1!) / ((n1-2)! * 2!) + (n2!) / ((n2-2)! * 2!)
= (n1 * (n1 - 1)) / 2 + (n2 * (n2 - 1)) / 2
--- Equation 1.
e.g : let the array be like: {1,2,3,4,5}
number of even numbers = n1 = 2
number of odd numbers = n2 = 2
Total pair such that the pair's sum is even from equation: 1 = (2*1)/2 + (3*2)/2 = 4
and the pairs are: (1,3), (1,5), (2,4), (3,5).
Going by traditional approach of adding and then checking might result in an integer overflow in programming on both positive as well as on negative extremes.
This is some pythonic solution
x = [1,3,56,4,3,2,0,6,78,90]
def solution(x):
sumadjacent = [x[i]+x[i+1] for i in range(len(x)-1) if x[i] < x[i+1]]
evenpairslist = [ True for j in sumadjacent if j%2==0]
return evenpairslist
if __name__=="__main__":
result=solution(x)
print(len(result))
int total = 0;
int size = A.length;
for(int i=0; i < size; i++) {
total += (A[size-1] - A[i]) / 2;
}
System.out.println("Total : " + total);
Related
that, given a non-empty zero-indexed array A of N integers, returns the minimal positive integer (greater than 0) that does not occur in A. For example, given:
A[0] = 4
A[1] = 6
A[2] = 2
A[3] = 2
A[4] = 6
A[5] = 6
A[6] = 1
the function should return 4. Assume that:
in other words, A[K] = 2 for each K (0 <= K <= 50000), the given implementation works too slow, but the function would return 50,000
Write an efficient algorithm for the following assumptions:
N is an integer within the range [1...100,000]
each element of array A is an integer the range [1..N]
My Answer is not yet successful! What is wrong with it? First let me state the obvious errors
return value - I return 4 in the first result and that was successful
but the second return is 1 that is not correct for the reason that
the expected result is 50,000
my code, which works but the only first result is correct and second is not.
import java.util.*;
class Solution {
int solution(int[] A) {
int[] AN = Arrays.stream(A).filter(n -> n > 0).distinct().sorted().toArray();
int N = AN.length;
int min = 1;
int i = 0;
while (i < N) {
min = i+1;
if (min == N) {
min = Math.max(min , Math.abs(i-N));
}
i++;
}
return min;
}
}
Note: In the example you gave, the missing integer is 3 and not 4
The performance issue is probably caused by using the distinct and sorted.
I'll create an algorithm based on the sorted array, so its complexity is O(n log n).
First we sort the array:
Arrays.sort(A);
Next we skip negative or zero numbers
while (i < A.length && A[i] <= 0)
i++;
If now the array is fully parsed or the current element A[i] is not 1, then the solution is 1.
If it's not the case, we continue parsing the array A, we initiliase missing variable to 1 and if the current element equals the missing element we increment i, or if the current element is equal missing + 1 we increment missing otherwise we stop, we found our missing integer.
In the end if our array is fully parsed without any mismatch between missing variable and A[i] then we increment missing.
The full code:
Arrays.sort(A);
int missing = 1;
int i = 0;
while (i < A.length && A[i] <= 0)
i++;
if (i < A.length && A[i] == 1) {
while (i < A.length) {
if (A[i] == missing) {
i++;
} else if (A[i] == missing + 1) {
missing++;
} else {
missing++;
break;
}
}
missing = i >= A.length ? missing + 1 : missing;
}
System.out.println(missing);
I've came across the following problem statement.
You have a list of natural numbers of size N and you must distribute the values in two lists A and B of size N/2, so that the squared sum of A elements is the nearest possible to the multiplication of the B elements.
Example:
Consider the list 7 11 1 9 10 3 5 13 9 12.
The optimized distribution is:
List A: 5 9 9 12 13
List B: 1 3 7 10 11
which leads to the difference abs( (5+9+9+12+13)^2 - (1*3*7*10*11) ) = 6
Your program should therefore output 6, which is the minimum difference that can be achieved.
What I've tried:
I've tried Greedy approach in order to solve this problem. I took two variables sum and mul. Now I started taking elements from the given set one by one and tried adding it in both the variables and calculated current
square of sum and multiplication. Now finalize the element in one of the two sets, such that the combination gives minimum possible value.
But this approach is not working in the given example itselt. I can't figure out what approach could be used here.
I'm not asking for exact code for the solution. Any possible approach and the reason why it is working, would be fine.
EDIT:
Source: CodinGame, Community puzzle
Try out this:
import java.util.Arrays;
public class Test {
public static void main(String [] args){
int [] arr = {7, 11, 1, 9, 10, 3, 5, 13, 9, 12};
int [][] res = combinations(5, arr);
int N = Arrays.stream(arr).reduce(1, (a, b) -> a * b);
int min = Integer.MAX_VALUE;
int [] opt = new int [5];
for (int [] i : res){
int k = (int) Math.abs( Math.pow(Arrays.stream(i).sum(), 2) - N/(Arrays.stream(i).reduce(1, (a, b) -> a * b)));
if(k < min){
min = k;
opt = i;
}
}
Arrays.sort(opt);
System.out.println("minimum difference is "+ min + " with the subset containing this elements " + Arrays.toString(opt));
}
// returns all k-sized subsets of a n-sized set
public static int[][] combinations(int k, int[] set) {
int c = (int) binomial(set.length, k);
int[][] res = new int[c][Math.max(0, k)];
int[] ind = k < 0 ? null : new int[k];
for (int i = 0; i < k; ++i) {
ind[i] = i;
}
for (int i = 0; i < c; ++i) {
for (int j = 0; j < k; ++j) {
res[i][j] = set[ind[j]];
}
int x = ind.length - 1;
boolean loop;
do {
loop = false;
ind[x] = ind[x] + 1;
if (ind[x] > set.length - (k - x)) {
--x;
loop = x >= 0;
} else {
for (int x1 = x + 1; x1 < ind.length; ++x1) {
ind[x1] = ind[x1 - 1] + 1;
}
}
} while (loop);
}
return res;
}
// returns n choose k;
// there are n choose k combinations without repetition and without observance of the sequence
//
private static long binomial(int n, int k) {
if (k < 0 || k > n) return 0;
if (k > n - k) {
k = n - k;
}
long c = 1;
for (int i = 1; i < k+1; ++i) {
c = c * (n - (k - i));
c = c / i;
}
return c;
}
}
Code taken from this stackoverflow answer, also take a look at this wikipedia article about Combinations.
I am not sure if there is any exact solution in polynomial time. But you could try a simulated annealing based approach.
My approach would be:
Initialize listA and listB to a random state
With probability p run greedy step, otherwise run a random step
Keep track of the state and corresponding error (with a HashMap)
Greedy step: Find one element you can move between the list that optimizes the error.
Random Step: Pick a random element from either of these two sets and calculate the error. If the error is better, keep it. Otherwise with probability of q keep it.
At either of these two steps make sure that the new state is not already explored (or at least discourage it).
Set p to a small value (<0.1) and q could depend on the error difference.
this is the question, and yes it is homework, so I don't necessarily want anyone to "do it" for me; I just need suggestions: Maximum sum: Design a linear algorithm that finds a contiguous subsequence of at most M in a sequence of N long integers that has the highest sum among all such subsequences. Implement your algorithm, and confirm that the order of growth of its running time is linear.
I think that the best way to design this program would be to use nested for loops, but because the algorithm must be linear, I cannot do that. So, I decided to approach the problem by making separate for loops (instead of nested ones).
However, I'm really not sure where to start. The values will range from -99 to 99 (as per the range of my random number generating program).
This is what I have so far (not much):
public class MaxSum {
public static void main(String[] args){
int M = Integer.parseInt(args[0]);
int N = StdIn.readInt();
long[] a = new long[N];
for (int i = 0; i < N; i++) {
a[i] = StdIn.readLong();}}}
if M were a constant, this wouldn't be so difficult. For example, if M==3:
public class MaxSum2 {
public static void main(String[] args){
int N = StdIn.readInt(); //read size for array
long[] a = new long[N]; //create array of size N
for (int i = 0; i < N; i++) { //go through values of array
a[i] = StdIn.readLong();} //read in values and assign them to
//array indices
long p = a[0] + a[1] + a[2]; //start off with first 3 indices
for (int i =0; i<N-4; i++)
{if ((a[i]+a[i+1]+a[1+2])>=p) {p=(a[i]+a[i+1]+a[1+2]);}}
//if sum of values is greater than p, p becomes that sum
for (int i =0; i<N-4; i++) //prints the subsequence that equals p
{if ((a[i]+a[i+1]+a[1+2])==p) {StdOut.println((a[i]+a[i+1]+a[1+2]));}}}}
If I must, I think MaxSum2 will be acceptable for my lab report (sadly, they don't expect much). However, I'd really like to make a general program, one that takes into consideration the possibility that, say, there could be only one positive value for the array, meaning that adding the others to it would only reduce it's value; Or if M were to equal 5, but the highest sum is a subsequence of the length 3, then I would want it to print that smaller subsequence that has the actual maximum sum.
I also think as a novice programmer, this is something I Should learn to do. Oh and although it will probably be acceptable, I don't think I'm supposed to use stacks or queues because we haven't actually covered that in class yet.
Here is my version, adapted from Petar Minchev's code and with an important addition that allows this program to work for an array of numbers with all negative values.
public class MaxSum4 {
public static void main(String[] args)
{Stopwatch banana = new Stopwatch(); //stopwatch object for runtime data.
long sum = 0;
int currentStart = 0;
long bestSum = 0;
int bestStart = 0;
int bestEnd = 0;
int M = Integer.parseInt(args[0]); // read in highest possible length of
//subsequence from command line argument.
int N = StdIn.readInt(); //read in length of array
long[] a = new long[N];
for (int i = 0; i < N; i++) {//read in values from standard input
a[i] = StdIn.readLong();}//and assign those values to array
long negBuff = a[0];
for (int i = 0; i < N; i++) { //go through values of array to find
//largest sum (bestSum)
sum += a[i]; //and updates values. note bestSum, bestStart,
// and bestEnd updated
if (sum > bestSum) { //only when sum>bestSum
bestSum = sum;
bestStart = currentStart;
bestEnd = i; }
if (sum < 0) { //in case sum<0, skip to next iteration, reseting sum=0
sum = 0; //and update currentStart
currentStart = i + 1;
continue; }
if (i - currentStart + 1 == M) { //checks if sequence length becomes equal
//to M.
do { //updates sum and currentStart
sum -= a[currentStart];
currentStart++;
} while ((sum < 0 || a[currentStart] < 0) && (currentStart <= i));
//if sum or a[currentStart]
} //is less than 0 and currentStart<=i,
} //update sum and currentStart again
if(bestSum==0){ //checks to see if bestSum==0, which is the case if
//all values are negative
for (int i=0;i<N;i++){ //goes through values of array
//to find largest value
if (a[i] >= negBuff) {negBuff=a[i];
bestSum=negBuff; bestStart=i; bestEnd=i;}}}
//updates bestSum, bestStart, and bestEnd
StdOut.print("best subsequence is from
a[" + bestStart + "] to a[" + bestEnd + "]: ");
for (int i = bestStart; i<=bestEnd; i++)
{
StdOut.print(a[i]+ " "); //prints sequence
}
StdOut.println();
StdOut.println(banana.elapsedTime());}}//prints elapsed time
also, did this little trace for Petar's code:
trace for a small array
M=2
array: length 5
index value
0 -2
1 2
2 3
3 10
4 1
for the for-loop central to program:
i = 0 sum = 0 + -2 = -2
sum>bestSum? no
sum<0? yes so sum=0, currentStart = 0(i)+1 = 1,
and continue loop with next value of i
i = 1 sum = 0 + 2 = 2
sum>bestSum? yes so bestSum=2 and bestStart=currentStart=1 and bestEnd=1=1
sum<0? no
1(i)-1(currentStart)+1==M? 1-1+1=1 so no
i = 2 sum = 2+3 = 5
sum>bestSum? yes so bestSum=5, bestStart=currentStart=1, and bestEnd=2
sum<0? no
2(i)-1(currentStart)+1=M? 2-1+1=2 so yes:
sum = sum-a[1(curentstart)] =5-2=3. currentStart++=2.
(sum<0 || a[currentStart]<0)? no
i = 3 sum=3+10=13
sum>bestSum? yes so bestSum=13 and bestStart=currentStart=2 and bestEnd=3
sum<0? no
3(i)-2(currentStart)+1=M? 3-2+1=2 so yes:
sum = sum-a[1(curentstart)] =13-3=10. currentStart++=3.
(sum<0 || a[currentStart]<0)? no
i = 4 sum=10+1=11
sum>bestSum? no
sum<0? no
4(i)-3(currentStart)+1==M? yes but changes to sum and currentStart now are
irrelevent as loop terminates
Thanks again! Just wanted to post a final answer and I was slightly proud for catching the all negative thing.
Each element is looked at most twice (one time in the outer loop, and one time in the while loop).
O(2N) = O(N)
Explanation: each element is added to the current sum. When the sum goes below zero, it is reset to zero. When we hit M length sequence, we try to remove elements from the beginning, until the sum is > 0 and there are no negative elements in the beginning of it.
By the way, when all elements are < 0 inside the array, you should take only the largest negative number. This is a special edge case which I haven't written below.
Beware of bugs in the below code - it only illustrates the idea. I haven't run it.
int sum = 0;
int currentStart = 0;
int bestSum = 0;
int bestStart = 0;
int bestEnd = 0;
for (int i = 0; i < N; i++) {
sum += a[i];
if (sum > bestSum) {
bestSum = sum;
bestStart = currentStart;
bestEnd = i;
}
if (sum < 0) {
sum = 0;
currentStart = i + 1;
continue;
}
//Our sequence length has become equal to M
if (i - currentStart + 1 == M) {
do {
sum -= a[currentStart];
currentStart++;
} while ((sum < 0 || a[currentStart] < 0) && (currentStart <= i));
}
}
I think what you are looking for is discussed in detail here
Find the subsequence with largest sum of elements in an array
I have explained 2 different solutions to resolve this problem with O(N) - linear time.
I have the below C code(pardon me if the syntax is not as per C language) which prints Fibonacci number of given index.It works fine.
But I have two questions here .First one is , I want to know at what index it might get overflow(assume 16 bit complier max value int data type can hold is 65535).And second one is when it overflows what values it will print for indexes after that ?
I know for my first questions it all depends on the value N, but I want to know if ther is a way I can predict for a given index n , if it gets overflowed before we calculate the Fibonacci at index n
And my last question is how do we ensure it never overflows for a given user input of value n and print correct Fibonacci value.
public static int fib_loop(int n)
{
int[] fib = new int[n];
Scanf("%d",&n);
if(n==0)
fib[0] = 0;
if(n==1)
fib[1] = 1;
for (int i = 2; i < n; i++)
fib[i] = fib[i - 1] + fib[i - 2];
return fib[n-1]; //because we only want to return for index 4 in case if n =4 :-)
}
If you are using unsigned variables, overflow is well defined. Given fib(n-1) and fib(n-2) did not overflow, fib(n) overflowed if it is less then fib(n-1).
unsigned fib[n];
...
fib[i] = fib[i-1] + fib[i-2];
if (fib[i] < fib[i-1]) {
; // handle overflow
}
OP says "I want to know if there is a way I can predict for a given index N , if it gets overflowed before we calculate the Fibonacci at index N".
Say your maximum representable number is Big. In closed form:
r = (1 + sqrt(5))/2
fib(x) = (power(r,x) - power(-r,-x))/sqrt(5)
or
fib(x) = floor(power(r,x)/sqrt(5) + 0.5)
Let fib(x) be set to Big, and then solver for x. Then insure all your indexes are <= x.
static size_t FibMaxIndex = 0;
if (FibMaxIndex == 0) {
FibMaxIndex = (size_t) (log(Big *sqrt(5) - 0.5)/log((1+sqrt(5))/2)) + 1;
}
if (index > FibMaxIndex) {
; //handle too large an index
}
Use the asymptotic ratio of terms (the golden ratio) to predict the size of the biggest representable number:
int biggest = (int)( Integer.MAX_VALUE / 1.618);
for (int i = 2; i < n; i++) {
if (fib[i - 1] > biggest)
// do something
fib[i] = fib[i - 1] + fib[i - 2];
}
I'm writing this Java program that finds all the prime numbers between a given range. Because I'm dealing with really big numbers my code seems to be not fast enough and gives me a time error. Here is my code, does anyone know to make it faster? Thanks.
import java.util.*;
public class primes2
{
private static Scanner streamReader = new Scanner(System.in);
public static void main(String[] args)
{
int xrange = streamReader.nextInt();
int zrange = streamReader.nextInt();
for (int checks = xrange; checks <= zrange; checks++)
{
boolean[] checkForPrime = Primes(1000000);
if (checkForPrime[checks])
{
System.out.println(checks);
}
}
}
public static boolean[] Primes(int n)
{
boolean[] isPrime = new boolean[n + 1];
if (n >= 2)
isPrime[2] = true;
for (int i = 3; i <= n; i += 2)
isPrime[i] = true;
for (int i = 3, end = sqrt(n); i <= end; i += 2)
{
if (isPrime[i])
{
for (int j = i * 3; j <= n; j += i << 1)
isPrime[j] = false;
}
}
return isPrime;
}
public static int sqrt(int x)
{
int y = 0;
for (int i = 15; i >= 0; i--)
{
y |= 1 << i;
if (y > 46340 || y * y > x)
y ^= 1 << i;
}
return y;
}
}
You'll get an enormous improvement just by changing this:
for (int checks = xrange; checks <= zrange; checks++)
{
boolean[] checkForPrime = Primes(1000000);
to this:
boolean[] checkForPrime = Primes(1000000);
for (int checks = xrange; checks <= zrange; checks++)
{
Your current code regenerates the sieve zrange - xrange + 1 times, but you actually only need to generate it once.
The obvious problem is that you're computing the primes up to 1000000 many time (zrange - xrange times). Another is that you dont need to compute the primes up to 1000000, you just need to check to primes up to zrange, so you're wasting time when zrange < 1000000, and getting a buffer overflow when zrange > 1000000.
You can start your inner loop from i*i, i.e. instead of for (int j = i * 3; j <= n; j += i << 1) you can write for (int j = i * i; j <= n; j += i << 1) for a minor speed-up.
Also, you have to be sure that your zrange is not greater than 1000000.
If xrange is much greater than sqrt(zrange), you can also split your sieve array in two, for an offset sieve scheme. The lower array will span from 2 to sqrt(zrange). The upper one will span from xrange to zrange. As you sieve your lower array, as each new prime becomes identified by it, inside your inner loop, in addition to marking the lower array up to its end also sieve the upper array. You will have to calcuate the starting offset for each prime i, and use the same step of 2*i as you do for the lower half. If your range is wider than a few primes, you will get speed advantage (otherwise just trial division by odds will suffice).
Another thing to try is, if evens > 2 are not primes anyway, why represent them in the array and waste half of the space? You can treat each i as representing an odd number, 2*i+1, thus compressing your array in half.
Last simple trick is to eliminate the multiples of 3 in advance as well, by marking ON not just odds (i.e. coprimes with 2), by { ... i+=2; ...}, but only coprimes with 2 and 3, by { ... i+=2; ... i+=4; ... } instead. Also, when marking OFF multiples of primes > 3, use { ... j+=2*i; ... j+=4i; ...} too. E.g., in 5*5, 5*7, 5*9, 5*11, ... you don't need to mark OFF 5*9, if no multiple of 3 was marked ON in the first place.