Finding number of comparisons - java

I'm trying to track the number of key comparisons in this binary search code. The comparison result should be around 17 for an array size of 2^16. Can you explain why I'm getting 33?
public class AdvancedBinarySearch {
int count = 0;
// Returns location of key, or -1 if not found
int AdvancedBinarySearch(int arr[], int l, int r, int x) {
int m;
while (r - l > 1) {
count++;
m = l + (r - l) / 2;
if (arr[m] <= x) {
l = m;
count++;
} else {
r = m;
count++;
}
}
if (arr[l] == x) {
count++;
return l;
}
if (arr[r] == x) {
count++;
return r;
} else {
count++;
return -1;
}
}
public void setCount(int i) {
this.count = i;
}
}

Your code is counting the if (arr[m] <= x) comparison twice. if you remove the count++ from below l = m and also from below r = m, this will no longer happen.
I have tested this before and after this change with a search for 189 in an array of the integers from 0 to 65535. This array had a size of 2^16 and before the change, 33 comparisons were counted and after the change, 17 comparisons were counted, so I think this change does what you want it to do.

Related

find a position of an element occurrence in array

I've below code snippet, A is non-decreasing order and need to check if X is present in A. If X is present in A then return position of occurrence of X in A otherwise returns -1. This code is not correct for some inputs, and need to fix it. Utmost I can modify 3 lines.
class Checking{
int check(int[] A, int X) {
int N = A.length;
if (N == 0) {
return -1;
}
int l = 0;
int r = N - 1;
while (l < r) {
int m = (l + r) / 2;
if (A[m] > X) {
r = m - 1;
} else {
l = m;
}
}
if (A[l] == X) {
return l;
}
return -1;
}
}
I couldnt figure out the fix, any suggestions will be helpful ?
Wiki page says that for such implementation of binary search middle element should be calculated using ceiling, so you can change to
int m = (l + r + 1) / 2;

perfect squares leetcode - recursive solution with memoization

Trying to solve this problem with recursion and memoization but for input 7168 I'm getting wrong answer.
public int numSquares(int n) {
Map<Integer, Integer> memo = new HashMap();
List<Integer> list = fillSquares(n, memo);
if (list == null)
return 1;
return helper(list.size()-1, list, n, memo);
}
private int helper(int index, List<Integer> list, int left, Map<Integer, Integer> memo) {
if (left == 0)
return 0;
if (left < 0 || index < 0)
return Integer.MAX_VALUE-1;
if (memo.containsKey(left)) {
return memo.get(left);
}
int d1 = 1+helper(index, list, left-list.get(index), memo);
int d2 = 1+helper(index-1, list, left-list.get(index), memo);
int d3 = helper(index-1, list, left, memo);
int d = Math.min(Math.min(d1,d2), d3);
memo.put(left, d);
return d;
}
private List<Integer> fillSquares(int n, Map<Integer, Integer> memo) {
int curr = 1;
List<Integer> list = new ArrayList();
int d = (int)Math.pow(curr, 2);
while (d < n) {
list.add(d);
memo.put(d, 1);
curr++;
d = (int)Math.pow(curr, 2);
}
if (d == n)
return null;
return list;
}
I'm calling like this:
numSquares(7168)
All test cases pass (even complex cases), but this one fails. I suspect something is wrong with my memoization but cannot pinpoint what exactly. Any help will be appreciated.
You have the memoization keyed by the value to be attained, but this does not take into account the value of index, which actually puts restrictions on which powers you can use to attain that value. That means that if (in the extreme case) index is 0, you can only reduce what is left with one square (1²), which rarely is the optimal way to form that number. So in a first instance memo.set() will register a non-optimal number of squares, which later will get updated by other recursive calls which are pending in the recursion tree.
If you add some conditional debugging code, you'll see that map.set is called for the same value of left multiple times, and with differing values. This is not good, because that means the if (memo.has(left)) block will execute for cases where that value is not guaranteed to be optimal (yet).
You could solve this by incorporating the index in your memoization key. This increases the space used for memoization, but it will work. I assume you can work this out.
But according to Lagrange's four square theorem every natural number can be written as the sum of at most four squares, so the returned value should never be 5 or more. You can shortcut the recursion when you get passed that number of terms. This reduces the benefit of using memoization.
Finally, there is a mistake in fillSquares: it should add n itself also when it is a perfect square, otherwise you'll not find solutions that should return 1.
Not sure about your bug, here is a short dynamic programming Solution:
Java
public class Solution {
public static final int numSquares(
final int n
) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
int j = 1;
int min = Integer.MAX_VALUE;
while (i - j * j >= 0) {
min = Math.min(min, dp[i - j * j] + 1);
++j;
}
dp[i] = min;
}
return dp[n];
}
}
C++
// Most of headers are already included;
// Can be removed;
#include <iostream>
#include <cstdint>
#include <vector>
#include <algorithm>
// The following block might slightly improve the execution time;
// Can be removed;
static const auto __optimize__ = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
#define MAX INT_MAX
using ValueType = std::uint_fast32_t;
struct Solution {
static const int numSquares(
const int n
) {
if (n < 1) {
return 0;
}
static std::vector<ValueType> count_perfect_squares{0};
while (std::size(count_perfect_squares) <= n) {
const ValueType len = std::size(count_perfect_squares);
ValueType count_squares = MAX;
for (ValueType index = 1; index * index <= len; ++index) {
count_squares = std::min(count_squares, 1 + count_perfect_squares[len - index * index]);
}
count_perfect_squares.emplace_back(count_squares);
}
return count_perfect_squares[n];
}
};
int main() {
std::cout << std::to_string(Solution().numSquares(12) == 3) << "\n";
return 0;
}
Python
Here we can simply use lru_cache:
class Solution:
dp = [0]
#functools.lru_cache
def numSquares(self, n):
dp = self.dp
while len(dp) <= n:
dp += min(dp[-i * i] for i in range(1, int(len(dp) ** 0.5 + 1))) + 1,
return dp[n]
Here are LeetCode's official solutions with comments:
Java: DP
class Solution {
public int numSquares(int n) {
int dp[] = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
// bottom case
dp[0] = 0;
// pre-calculate the square numbers.
int max_square_index = (int) Math.sqrt(n) + 1;
int square_nums[] = new int[max_square_index];
for (int i = 1; i < max_square_index; ++i) {
square_nums[i] = i * i;
}
for (int i = 1; i <= n; ++i) {
for (int s = 1; s < max_square_index; ++s) {
if (i < square_nums[s])
break;
dp[i] = Math.min(dp[i], dp[i - square_nums[s]] + 1);
}
}
return dp[n];
}
}
Java: Greedy
class Solution {
Set<Integer> square_nums = new HashSet<Integer>();
protected boolean is_divided_by(int n, int count) {
if (count == 1) {
return square_nums.contains(n);
}
for (Integer square : square_nums) {
if (is_divided_by(n - square, count - 1)) {
return true;
}
}
return false;
}
public int numSquares(int n) {
this.square_nums.clear();
for (int i = 1; i * i <= n; ++i) {
this.square_nums.add(i * i);
}
int count = 1;
for (; count <= n; ++count) {
if (is_divided_by(n, count))
return count;
}
return count;
}
}
Java: Breadth First Search
class Solution {
public int numSquares(int n) {
ArrayList<Integer> square_nums = new ArrayList<Integer>();
for (int i = 1; i * i <= n; ++i) {
square_nums.add(i * i);
}
Set<Integer> queue = new HashSet<Integer>();
queue.add(n);
int level = 0;
while (queue.size() > 0) {
level += 1;
Set<Integer> next_queue = new HashSet<Integer>();
for (Integer remainder : queue) {
for (Integer square : square_nums) {
if (remainder.equals(square)) {
return level;
} else if (remainder < square) {
break;
} else {
next_queue.add(remainder - square);
}
}
}
queue = next_queue;
}
return level;
}
}
Java: Most efficient solution using math
Runtime: O(N ^ 0.5)
Memory: O(1)
class Solution {
protected boolean isSquare(int n) {
int sq = (int) Math.sqrt(n);
return n == sq * sq;
}
public int numSquares(int n) {
// four-square and three-square theorems.
while (n % 4 == 0)
n /= 4;
if (n % 8 == 7)
return 4;
if (this.isSquare(n))
return 1;
// enumeration to check if the number can be decomposed into sum of two squares.
for (int i = 1; i * i <= n; ++i) {
if (this.isSquare(n - i * i))
return 2;
}
// bottom case of three-square theorem.
return 3;
}
}

Check if number is the median in a double array

You have to use a for-each loop to check if the number you enter as a parameter is the median in an array you also enter as a parameter. I thought my logic was fine but it returns false for everything. Any guidance would be appreciated.
public static boolean isMedian(double[] arr, double m)
{
int countLow = 0;
int countHigh = 0;
int count = 0;
for(double e : arr)
if(arr[count] > m)
{
countHigh++;
count++;
}
else if(arr[count] < m)
{
countLow++;
count++;
}
if(countLow == countHigh)
return true;
else
return false;
}
public static void main(String[] args)
{
double[] array = {1.0, 2.0, 3.0, 4.0 , 5.0, 6.0, 7.0};
System.out.println(isMedian(array, 4.0));
}
You don’t change count when you’re at the median. This is why you should use e instead:
public static boolean isMedian(double[] arr, double m)
{
int countLow = 0;
int countHigh = 0;
for(double e : arr)
if(e > m)
{
countHigh++;
}
else if(e < m)
{
countLow++;
}
if(countLow == countHigh)
return true;
else
return false;
}
public static void main(String[] args)
{
double[] array = {1.0, 2.0, 3.0, 4.0 , 5.0, 6.0, 7.0};
System.out.println(isMedian(array, 4.0));
}
Here is a method that will accomplish the task for you:
public static boolean isMedian(double[] arr, double m){
double med;
ArrayList<Double> a = new ArrayList<Double>();
a.add(arr[0]);
a.add(Double.MAX_VALUE);
for(double 1: arr){
for(int j=0; j<a.size()&&j>=-10;j++){
if(i<a.get(j)){
a.add(j,i);
j=-200;
}
}
}
a.remove(a.size()-1);
if(arr.length%2==1){
med=a.get(arr.length/2);
} else{
med=(double)(a.get(arr.length/2)+a.get(arr.length/2-1))/2.0;
}
if (med==m)return true;
return false;
}
The basic answer to the question, which was stated by Anonymous, is that with the extended for loop, you don't want to reference the array by index. The assignment was to use an extended for loop, and I wanted to go with the (one-pass, no sorting, so O(n) unless I'm missing something) basic algorithm specified in the question. The idea that you're the median if the same number of numbers are above you as below you, can break down in a couple of ways.
1) As Andreas pointed out, if your number occurs more than once for example 1, 3, 3 and you ask if 3 is the median, it will say no, because the number of numbers below differs from the number of numbers above.
2) When there are an even number of numbers, even though you have the same number of numbers below and above, you might still not be smack in the middle. For 1 and 3, only 2 will do, not 2.5.
So I adapted the algorithm to handle all those special cases. That required tracking how many times the number itself occurred (or at least that was simplest, one could also calculate that by subtracting the sum of the other counts from the number of numbers) as well as the numbers immediately below and above in case we have to average them.
It's a bit spaghetti-like, and possibly would be better if there were separate methods for odd and even sizes, or if one thought really hard about unifying parts of various cases. But after testing all the cases mentioned, I think it works. In the comments I noted possible tweaks, such as watching out more carefully for floating point errors in calculation (one might also do so in comparisons).
public static boolean isMedian(double[] arr, double m) {
int countLow = 0;
int countHigh = 0;
int countM = 0; //track how many times the candidate number itself occurs
double supLow = 0.0; //maximum number below m, only meaningful if countLow is positive
double infHigh = 0.0; //minimum number above m, only meaningful if countHigh is positive
// int count = 0; as Anonymous said, not needed extended for loop handles looping over all e in arr
for (double e: arr)
if (e > m) {
if (countHigh == 0 || e < infHigh) {
infHigh = e;
}
countHigh++;
}
else if (e < m) {
if (countLow == 0 || e > supLow) {
supLow = e;
}
countLow++;
} else //e==m
{
countM++;
}
//System.out.println("countLow = "+countLow+" countHigh = "+ countHigh + " countM = " + countM);
if (arr.length % 2 == 1) { //odd sized array, easier case because no averaging needed
if (countM == 0) { //number does not occur at all, so certainly not in the middle
return false;
} else if (countM == 1) //number occurs once, is it in the middle?
{
return (countLow == countHigh);
} else { //number occurs more than once, is one of the occurrences in the middle?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int middleIndex = arr.length / 2; // were the array to be sorted, 0-based index of the middle spot
return (middleIndex >= mStartIndex && middleIndex <= mEndIndex);
}
}
//still here, must be even size
//System.out.println("supLow = "+supLow+" infHigh = "+ infHigh);
if (countM == 0) {
if (countLow != countHigh) {
return false;
} else { //our number is between the two middle numbers, but is it the average?
return ((m + m) == (supLow + infHigh)); //using == with floating point addition, if that proves unreliable, do Math.abs(2*m-supLow-infHigh)<EPSILON
}
} else if (countM == 1) //number occurs once, which cannot be the median, if it is not in the 2 middle spots, it is lower or higher than both, even if it is, it cannot be the average of itself and a different number
{
return false;
} else { //number occurs more than once, does it occupy both middle spots?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int firstMiddleIndex = arr.length / 2 - 1; // were the array to be sorted, 0-based index of the first of two middle spots
int secondMiddleIndex = firstMiddleIndex + 1;
return (firstMiddleIndex >= mStartIndex && secondMiddleIndex <= mEndIndex);
}
}
REVISION: broke up the code so no method is too long. Also bracketed for loop bodies.
private static boolean isMedianForOdd(double[] arr, double m) {
int countLow = 0;
int countHigh = 0;
for (double e: arr) {
if (e > m) {
countHigh++;
} else if (e < m) {
countLow++;
}
}
int countM = arr.length - countHigh - countLow; //how many times the candidate number itself occurs
if (countM == 0) { //number does not occur at all, so certainly not in the middle
return false;
} else if (countM == 1) //number occurs once, is it in the middle?
{
return (countLow == countHigh);
} else { //number occurs more than once, is one of the occurrences in the middle?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int middleIndex = arr.length / 2; // were the array to be sorted, 0-based index of the middle spot
return (middleIndex >= mStartIndex && middleIndex <= mEndIndex);
}
}
private static boolean isMedianForEven(double[] arr, double m) {
int countLow = 0;
int countHigh = 0;
double supLow = 0.0; //maximum number below m, only meaningful if countLow is positive
double infHigh = 0.0; //minimum number above m, only meaningful if countHigh is positive
for (double e: arr) {
if (e > m) {
if (countHigh == 0 || e < infHigh) {
infHigh = e;
}
countHigh++;
} else if (e < m) {
if (countLow == 0 || e > supLow) {
supLow = e;
}
countLow++;
}
}
int countM = arr.length - countHigh - countLow; //how many times the candidate number itself occurs
if (countM == 0) {
if (countLow != countHigh) {
return false;
} else { //our number is between the two middle numbers, but is it the average?
return ((m + m) == (supLow + infHigh)); //using == with floating point addition, if that proves unreliable, do Math.abs(2*m-supLow-infHigh)<EPSILON
}
} else if (countM == 1) //number occurs once, which cannot be the median, if it is not in the 2 middle spots, it is lower or higher than both, even if it is, it cannot be the average of itself and a different number
{
return false;
} else { //number occurs more than once, does it occupy both middle spots?
int mStartIndex = countLow; //were the array to be sorted, the 0-based index of the first occurrence of m
int mEndIndex = mStartIndex + countM - 1; //were the array to be sorted, the 0-based index of the last occurrence of m
int firstMiddleIndex = arr.length / 2 - 1; // were the array to be sorted, 0-based index of the first of two middle spots
int secondMiddleIndex = firstMiddleIndex + 1;
return (firstMiddleIndex >= mStartIndex && secondMiddleIndex <= mEndIndex);
}
}
public static boolean isMedian(double[] arr, double m) {
if (arr.length % 2 == 1) {
return isMedianForOdd(arr, m);
} else {
return isMedianForEven(arr, m);
}
}
REVISION 2:
return(Math.abs(2*m-supLow-infHigh)< 0.000001);//avoid floating point issues, feel free to adjust the right hand side
should replace the line:
return ((m + m) == (supLow + infHigh));
or (MINOR REVISION 2'), with credit to Dawood says reinstate Monica for the idea, replace it with this set of lines if you do not wish to mess with specifying the precision.
BigDecimal bdM = BigDecimal.valueOf(m);
bdM = bdM.add(bdM).stripTrailingZeros();//2*m
BigDecimal bdInfSup = BigDecimal.valueOf(supLow);
bdInfSup = bdInfSup.add(BigDecimal.valueOf(infHigh)).stripTrailingZeros();
return(bdM.equals(bdInfSup));

How to binary search in an array which is in an descending order?

my code doesn't work, I want to be able to binary search in an array which is in an descending order.
static int searchDescendingGT( double[] a, int i, int j, double x )
{
while(i!=j){
// | >x | unknown | >=x |
int m = i+(j-i)/2;
if (a[m]<x){
j = m;
}
else{
i = m+1;
}
}
return i;
}
What might be it's problems and what am I not seeing?
Try foll instead.
Assumption: a is your array, i = start, j= end, x is the element you're trying to find. Foll will return -1 if x not in a
static int searchDescendingGT(double[] a, int i, int j, double x) {
while (i <= j) {
int m = (i + j) / 2;
if (a[m] == x) {
return m;
} else if (a[m] < x) {
j = m - 1;
} else {
i = m + 1;
}
}
return -1;
}

Find Perfect Number from 1-9999. Exercise from The Art and Science of Java

I am trying to find the perfect number by find out all their divisors. If their sum is equal to the number, then print out the the number. But apparently it's not working.
import acm.program.*;
public class PerfectNumber extends ConsoleProgram{
public void run() {
for (int n = 1; n < 9999; n++) {
for (int d = 2; d < n - 1; d++) {
//d is the potential divisor of n, ranging from 2 to n-1,//
//not including 1 and n because they must be the divisors.//
if (isPerfectNumber(n,d))
print(n );
}
}
}
//method that determines if n is perfect number.//
private boolean isPerfectNumber(int n, int d) {
while (n % d == 0) {
int spd = 1;
spd += d;
if (spd == n) {
return true;
} else {
return false;
}
}
}
}
Looking at the code in your case will return false most of the times. I think what you were looking for is a bit wrong.
Because d is smaller than n, and n divided by d will always be grater than 0. Also in that loop you never change the value of d.
A solution might be:
public void run() {
for (int n = 1; n < 9999; n++)
{ spd=1;
for (int d = 2; d <= n/2; d++) { //no need to go further than n/2
//d is the potential divisor of n, ranging from 2 to n-1,//
if(n%d==0) spd+=d; //if n divides by d add it to spd.
}
if(spd==n) print(n);
}
Try this and let me know if it works for you.
I find something cool here : http://en.wikipedia.org/wiki/List_of_perfect_numbers. You should the much faster using this formula: 2^(p−1) × (2^p − 1). You can see the formula better on the wikilink.
Method isPerfect should probably be something like that:
public static boolean isPerfect(int number) {
int s = 1;
int d = number / 2;
for(int i = 2; i <= d; i++) {
if (number % i == 0) s += i;
}
return s == number;
}

Categories