A seemingly simple binary search problem using Python - python

The problem:
There is a list of positive integers where the elements are unique and monotonically increasing. The list is s. For example s=[1,2,3,4,5,6,7,8,9,10].
The length of the list is n. The goal is to find the total number of ordered pairs (L, W) where L and W are in s that satisfies L*W <= a
The solution of the problem seems straight forward, yet I cannot figure out how did I do it wrong. This was an online assessment problem and I have failed already. If someone can tell me how did I fail, I will be very happy.
The approach is straight forward: for each possible L, find an upper bound of W in s using binary search. My code using Python:
def configurationCount(n, s, a):
# n is the length of s.
num_ways = 0
# Consider every L.
for i in range(n):
L = s[i]
goal = a//L
# Binary search for this upper bound in s.
i = 0
j = len(s) - 1
mid = (i+j)//2
exact_match = False
valid = False
cursor = -1
while(i<=j):
if goal > s[mid]:
i = mid+1
cursor = mid
mid = (i+j)//2
elif goal < s[mid]:
j = mid - 1
cursor = mid
mid = (i+j)//2
else:
exact_match = True
cursor = mid
break
if cursor in range(n) and L*s[cursor]<=a:
valid = True
if cursor in range(n) and valid:
num_ways += (cursor + 1)
return num_ways
This code only gave one correct output for one test case, but failed for the other 10 test cases. In one case where I could see that my out put was wrong, the correct output was more than 2000, where mine was about 1200. So I probably did not count some cases. Yet this approach seems so standard but how did I do it wrong?

Related

Python program to check whether a list is a valley or not

example of a valley:
9,6,5,4,10,13,40,55,68
The list must be strictly decreasing until one point and then after the list must be strictly increasing. Then it is considered as a valley.
constraints:
Use two pointer approach
Guys, I have come up with this code:
def isValley(n,j,n1):
i = 0
res = False
while(i<j):
if (n[i]>n[i+1]) and (n[j]>n[j-1]):
n1.append(1)
else:
n1.append(0)
res = n1.count(n1[0]) == len(n1)
i+=1
j-=1
print(n1)
if res:
if(n1[0] == 1):
print("Valley")
else:
print("Not a valley")
else:
print("Not a valley")
n = list(map(int,input("Enter the numbers : ").split()))
n1 = []
j = len(n)-1
print(n)
isValley(n,j,n1)
my code is working only when the no of elements on the left side of the element is equal to
the number of elements on the right side of the element.
please correct this and come up with a code that even satisfies irrespective of the number
of elements left of the element and right of the element.
hint:
I think i have issue while making comparisions
(Use Two pointer Approach only)
Please help me out guys
I think you can just iterate from left to right (and from right to left), while the elements are strictly decreasing (increasing). Then it's a valley if and only if the pointers end up at the same location
def isValley(array):
N = len(array)
front_i = 0
back_i = N-1
while front_i < N-1 and array[front_i] > array[front_i + 1]:
front_i += 1
while back_i > 0 and array[back_i] > array[back_i - 1]:
back_i -= 1
is_valley = (front_i == back_i)
return is_valley
print(isValley([9,6,5,4,10,13,40,55,68])) # prints True
You can check that there is a strictly negative gradient followed by a strictly positive gradient. One way of doing this (using numpy) is:
import numpy as np
def isValley(array):
d = np.diff(array)
return (
np.array_equal(
np.concatenate((np.argwhere(d < 0), np.argwhere(d > 0))).flatten(),
np.arange(len(array) - 1)
)
)

leetcode two sum problem algorithm efficiency

Problem: Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
Input: nums = [2,7,11,15],
target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].
Here's my code:
def twoSum(nums, target):
cnt = 0
i = 0
while cnt < len(nums):
temp = 0
if i == cnt:
i += 1
else:
temp = nums[cnt] + nums[i]
if temp == target and i < cnt:
return [i,cnt]
i += 1
if i == len(nums)-1:
i = 0
cnt += 1
The code seems to work fine for 55/57 test cases. But it doesn't work for really big input cases. But i don't understand why this is happening because i have used only one loop and the time complexity should be O(N) which is efficient enough to run in the given time. So any idea what am i missing? And what can i do to make the algorithm more efficient?
You can make a dictionary of the last position of the complement value of each number. Then use it to find the position of the value for which the complement exists in the list (at a greater index in case you have a value that is half the target):
nums = [2,7,11,15]
target = 9
pos = {target-n:i for i,n in enumerate(nums)}
sol = next([i,pos[n]] for i,n in enumerate(nums) if i<pos.get(n,i))
print(sol)
[0, 1]
This works in O(n) time and space
if we`re not talking about space complexity:
def search(values, target):
hashmap = {}
for i in range(len(values)):
current = values[i]
if target - current in hashmap:
return current, hahsmap[target - current]
hashmap[current] = i
return None
Your code isn't really O(n), it's actually O(n^2) in disguise.
You go through i O(n) times for each cnt (and then reset i back to 0), and go through cnt O(n) times.
For a more efficient algorithm, sites like this one (https://www.educative.io/edpresso/how-to-implement-the-two-sum-problem-in-python) have it down pretty well.
I am not sure of the time complexity but I think this solution will be better. p1 and p2 act as two pointers of indexes:
def twoSum(nums, target):
nums2 = nums[:]
nums2.sort()
p1 = 0
p2 = len(nums2)-1
while nums2[p1]+nums2[p2]!=target:
if nums2[p1]+nums2[p2]<target:
p1 += 1
elif nums2[p1]+nums2[p2]>target:
p2 -= 1
return nums.index(nums2[p1]), nums.index(nums2[p2])

How to count the number of unique numbers in sorted array using Binary Search?

I am trying to count the number of unique numbers in a sorted array using binary search. I need to get the edge of the change from one number to the next to count. I was thinking of doing this without using recursion. Is there an iterative approach?
def unique(x):
start = 0
end = len(x)-1
count =0
# This is the current number we are looking for
item = x[start]
while start <= end:
middle = (start + end)//2
if item == x[middle]:
start = middle+1
elif item < x[middle]:
end = middle -1
#when item item greater, change to next number
count+=1
# if the number
return count
unique([1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,5,5])
Thank you.
Edit: Even if the runtime benefit is negligent from o(n), what is my binary search missing? It's confusing when not looking for an actual item. How can I fix this?
Working code exploiting binary search (returns 3 for given example).
As discussed in comments, complexity is about O(k*log(n)) where k is number of unique items, so this approach works well when k is small compared with n, and might become worse than linear scan in case of k ~ n
def countuniquebs(A):
n = len(A)
t = A[0]
l = 1
count = 0
while l < n - 1:
r = n - 1
while l < r:
m = (r + l) // 2
if A[m] > t:
r = m
else:
l = m + 1
count += 1
if l < n:
t = A[l]
return count
print(countuniquebs([1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,5,5]))
I wouldn't quite call it "using a binary search", but this binary divide-and-conquer algorithm works in O(k*log(n)/log(k)) time, which is better than a repeated binary search, and never worse than a linear scan:
def countUniques(A, start, end):
len = end-start
if len < 1:
return 0
if A[start] == A[end-1]:
return 1
if len < 3:
return 2
mid = start + len//2
return countUniques(A, start, mid+1) + countUniques(A, mid, end) - 1
A = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,3,4,5,5,5,5,5,5,5,5,5,5]
print(countUniques(A,0,len(A)))

Minimum window of days required to travel all cities

This is an interesting question that I came across in a coding challenge:
There are k cities and n days. A travel agent is going to show you city k on day n. You're supposed to find the minimum number of days in which you can visit all cities. You're also allowed to visit cities more than once, but ideally you wouldn't want to do that since you want to minimize the number of days.
Input :You're given an array of days and cities where days are indices and cities are values.
A=[7,4,7,3,4,1,7] So A[0]=7 means travel agent will show you city 7 on day 0, city 4 on day 1 etc.
So Here if you start out on day 0, you'll have visited all cities by day 5, but you can also start on day 2 and finish up on day 5.
Output:4 Because it took you 4 days to visit all the cities at least once
My solution : I do have an O(N^2) solution that tries out all combinations of cities. But the test said that the ideal time and space complexity should be O(N). How do I do this?
def findmin(A):
hashtable1={}
locationcount=0
#get the number of unique locations
for x in A:
if A[x] not in hashtable1:
locationcount+=1
index1=0
daycount=sys.maxint
hashtable2={}
#brute force
while index1<len(A):
index2=index1
prevday=index2
ans=0
count1=0
while index2<len(A):
if A[index2] not in hashtable2:
count1+=1
ans+=(index2-prevday)
hashtable2[A[index2]]=1
index2+=1
if count1==count:
daycount=min(ans,daycount)
hashtable2.clear()
index1+=1
return daycount+1
This problem might be solved with two-pointer approach.
Some data structure should contain element counts in current window. Perhaps your hash table is suitable.
Set left and right pointer to the start of list.
Move right pointer, incrementing table entries for elements like this:
hashtable2[A[rightindex]] = hashtable2[A[rightindex]] + 1
When all (locationcount) table entries become non-zero, stop moving right pointer. You have left-right interval covering all cities. Remember interval length.
Now move left pointer, decrementing table entries. When some table entry becomes zero, stop moving left pointer.
Move right pointer again. Repeat until the list end.
Note that indexes run the list only once, and complexity is linear (if table entry update is O(1), as hash map provides in average)
I had this problem in interview and failed as I thought about a moving windows too late. I took it a few days later and here is my C# solution which I think is O(n) (the array will be parsed at most 2 times).
The remaining difficulty after my flash was to understand how to update the end pointer. There's probably a better solution, my solution will always provide the highest possible starting and ending days even if the vacation could be started earlier.
public int solution(int[] A) {
if (A.Length is 0 or 1) {
return A.Length;
}
var startingIndex = 0;
var endingIndex = 0;
var locationVisitedCounter = new int[A.Length];
locationVisitedCounter[A[0] - 1] = 1;
for (var i=1; i<A.Length; i++)
{
var locationIndex = A[i] - 1;
locationVisitedCounter[locationIndex]++;
if (A[i] == A[i - 1])
{
continue;
}
endingIndex=i;
while (locationVisitedCounter[A[startingIndex] - 1] > 1)
{
locationVisitedCounter[A[startingIndex] - 1]--;
startingIndex++;
}
}
return endingIndex - startingIndex + 1;
}
I solved it using two-pointer approach, pointer i is for moving the pointer forward, pointer j is to move towards getting the optimal solution.
Time Complexity: O(2*N)
def solution(A):
n = len(A)
hashSet = dict()
max_count = len(set(A))
i = 0
j = 0
result = float("inf")
while i < n:
if A[i] in hashSet:
hashSet[A[i]] += 1
else:
hashSet[A[i]] = 1
if len(hashSet) == max_count:
result = min(result, i-j)
while len(hashSet) == max_count and j<=i:
hashSet[A[j]] -= 1
if hashSet[A[j]] == 0:
del hashSet[A[j]]
j+=1
if len(hashSet) < max_count:
break
result = min(result, i-j)
if result == max_count:
return result
j+=1
i+=1
return result
Python solution
def vacation(A):
# Get all unique vacation locations
v_set = set(A)
a_l = len(A)
day_count = 0
# Maximum days to cover all locations will be the length of the array
max_day_count = a_l
for i in range(a_l):
count = 0
v_set_copy = v_set.copy()
# Starting point to find next number of days
#that covers all unique locations
for j in range(i, a_l):
# Remove from set, if the location exists,
# meaning we have visited the location
if (A[j] in v_set_copy):
v_set_copy.remove(A[j])
else:
pass
count = count + 1
# If we have visited all locations,
# determine the current minimum days needed to visit all and break
if (len(v_set_copy) == 0):
day_count = min(count, max_day_count)
max_day_count = day_count
break
return day_count
from L = 0 move right until all distinct locations are visited; say R
maintain a map of element to frequency from L to R inclusive
until R == n - 1 OR L - R + 1 == distinct element count:-
Increase L, until we get an invalid window, i.e. map's element freq becomes 0
Increase R by 1 and update map.
For reference, this question kind of is related to leetcode question 76.Minimum Window Substring. You can watch the solution here NeetCode. My solution in python following the same tutorial.
def solution(A):
if not A: return
locations = dict()
for location in A:
locations[location] = 0
res,resLen = [-1,-1],float("infinity")
# left_pointer, right_pointer
lp,rp = 0,0
for rp in range(len(A)):
locations[A[rp]] = locations.get(A[rp],0) + 1
while (0 not in locations.values()):
if(rp - lp + 1) < resLen:
res = [lp,rp]
resLen = (rp-lp + 1)
locations[A[lp]] -= 1
lp += 1
lp,rp = res
return len(A[lp:rp+1]) if resLen != float("infinity") else 0
A = [7,4,7,3,4,1,7]
# A= [2,1,1,3,2,1,1,3]
# A = [7,3,2,3,1,2,1,7,7,1]
print(solution(A=A))
Although others have posted their answers, I think my solution is a little bit simpler and neater, I hope it helps.
from collections import defaultdict
from typing import List
# it is a sliding window problem
def min_days_to_visit_all_cities(arr: List[int]):
no_of_places = len(set(arr))
l, r = 0, 0
place_to_count = defaultdict(int)
res = len(arr)
while r < len(arr):
while r < len(arr) and len(place_to_count) < no_of_places:
place_to_count[arr[r]] += 1
r += 1
while len(place_to_count) >= no_of_places:
res = min(res, r - l)
place_to_count[arr[l]] -= 1
if place_to_count[arr[l]] == 0:
del place_to_count[arr[l]]
l += 1
return res

Longest non-decreasing subsequence with minimal sum

I am trying to find topic algorithm and am stuck. Basically, I adopted the code given in zzz's answer here, which is Longest Increasing Subsequence algorithm, to get Longest Non-decreasing Subsequence. What I aim to find is LNDS that has a minimal sum (MSLNDS) and don't know do I have one. But as far as I can tell, original LIS algorithm as presented on wikipedia does locate minimal sum LIS. Docstring of its code says that LIS algorithm guarantees that if multiple increasing subsequences exist, the one that ends with the smallest value is preferred, and if multiple occurrences of that value can end the sequence, then the earliest occurrence is preferred. Don't know what earliest occurrence means, but would love not to be in the position to generate all LNDS to find my MSLNDS. It seems to me that clever transformation given by templatetypedef may be used to show that unique MSLIS transforms to MSLNDS, but dont have the proof. So,
a) Will LIS algorithm as given on wikipedia always output minimal sum LIS?
b) If LIS algorithm is adopted this way, will LNDS algorithm retain this property?
def NDS(X):
n = len(X)
X = [0.0] + X
M = [None]*(n+1)
P = [None]*(n+1)
L = 0
for i in range(1,n+1):
#########################################
# for LIS algorithm, this line would be
# if L == 0 or X[M[1]] >= X[i]:
#########################################
if L == 0 or X[M[1]] > X[i]:
j = 0
else:
lo = 1
hi = L+1
while lo < hi - 1:
mid = (lo + hi)//2
#########################################
# for LIS algorithm, this line would be
# if X[M[mid]] < X[i]:
#########################################
if X[M[mid]] <= X[i]:
lo = mid
else:
hi = mid
j = lo
P[i] = M[j]
if j == L or X[i] < X[M[j+1]]:
M[j+1] = i
L = max(L,j+1)
output = []
pos = M[L]
while L > 0:
output.append(X[pos])
pos = P[pos]
L -= 1
output.reverse()
return output

Categories