Leetcode 1044. Longest Duplicate Substring (small question in terms of modulus) - java

I was solving Leetcode 1044 and the answer is using binary search and rolling hash. Basically use binary search to select a length and then do a search for duplicate string of that length. Here rolling hash comes into play to save space (instead of using a set to store all substring, we store substring's hash). That is the background for the solution.
My question is in terms of the modulus used to prevent overflow. I chose Long.MAX_VALUE which I believe is big enough to handle it but the answer is not correct when I use Long.MAX_VALUE. However, when I use Long.MAX_VALUE / 26 or Math.pow(2, 32), they both work. Sorry I'm pretty bad about modulus and I think I definitely missed some things here. Could anyone shed some light on it? Thanks! The following is my solution:
public static long modulus = Long.MAX_VALUE / 26;
public String longestDupSubstring(String S) {
int n = S.length();
int l = 1;
int r = n - 1;
int index = -1;
while (l <= r) {
int m = l + (r - l) / 2;
int temp = findDuplicate(S, m);
if (temp != -1) {
index = temp;
l = m + 1;
}
else {
r = m - 1;
}
}
return index == -1 ? "" : S.substring(index, index + r);
}
private int findDuplicate(String s, int len) {
Set<Long> set = new HashSet<>();
long hash = 0;
long p = 1;
for (int i = 0; i < len; i++) {
hash = (hash * 26 + s.charAt(i) - 'a') % modulus;
p = (p * 26) % modulus;
}
set.add(hash);
for (int i = len; i < s.length(); i++) {
hash = (hash * 26 + (s.charAt(i) - 'a')
- (s.charAt(i - len) - 'a') * p) % modulus;
if (hash < 0) {
hash += modulus;
}
if (set.contains(hash)) {
return i - len + 1;
}
set.add(hash);
}
return -1;
}

26 is not part of the modulus, is part of hashing. If we would separate those in the algorithm, then we might see how it'd work. For modulus usually a large number would simply suffice, does not have to be a long:
public final class Solution {
int a = 26;
int mod = 1 << 29;
public final String longestDupSubstring(
final String s
) {
int lo = 1;
int hi = s.length() - 1;
while (lo <= hi) {
int mid = lo + ((hi - lo) >> 1);
int startIndex = search(s, mid);
if (startIndex == - 1) {
hi = mid - 1;
}
else {
lo = -~mid;
}
}
int startIndex = search(s, hi);
return startIndex == -1 ? "" : s.substring(startIndex, startIndex + hi);
}
public final int search(
final String s,
final int len
) {
long h = 0;
long aL = 1;
for (int i = 0; i < len; i++) {
h = (h * a % mod + s.charAt(i)) % mod;
aL = aL * a % mod;
}
HashMap<Long, List<Integer>> visited = new HashMap<>();
visited.put(h, new ArrayList<Integer>());
visited.get(h).add(0);
for (int i = 1; i < -~s.length() - len; i++) {
h = ((h * a % mod - s.charAt(i - 1) * aL % mod + mod) % mod + s.charAt(i + len - 1)) % mod;
if (visited.containsKey(h)) {
for (int start : visited.get(h)) {
if (s.substring(start, start + len).equals(s.substring(i, i + len))) {
return i;
}
}
} else {
visited.put(h, new ArrayList<Integer>());
}
visited.get(h).add(i);
}
return -1;
}
}

Related

binary search right should be nums.length or nums.length - 1?

I'm always confused with Binary search right boarder.
For example, if we want to find the last index of target in a sorted array, the code should be:
public int binarySearchLarger(int[] nums, int target) {
int l = 0;
int r = nums.length - 1;
while (l < r) {
int m = l + (r - l + 1) / 2;
if (nums[m] < target) l = m;
else if(nums[m] > target) r = m - 1;
else l = m;
}
if (nums[l] == target) return l;
else return -1;
}
but I see lots of posts said that if we want while(l<r), then r should be nums.length, in this case, the code becomes:
public int binarySearchLarger(int[] nums, int target) {
int l = 0;
int r = nums.length;
while (l < r) {
int m = l + (r - l + 1) / 2;
if (nums[m] < target) l = m;
else if(nums[m] > target) r = m - 1;
else l = m;
}
if (nums[l] == target) return l;
else return -1;
}
but in this case, if (nums[m] < target) l = m; will throw arrayindexoutofbound exception.
My questions is: when should we use r = nums.length - 1 and when should we use r = nums.length?
See this answer for a discussion about the various options when implementing binary search: Binary Search algorithm implementations
For your problem, which seems to be to find the last instance of the target, I do it like this:
int findLastIndexOfTarget(int[] nums, int toFind) {
// find the position that divides <= toFind from > toFind
// these are the lowest and highest possible indexes
int low = 0;
int high = nums.length;
// while there is a range of possible indexes
while(low<high) {
int test = low+((high-low)/2);
if (nums[test] <= toFind) {
//too low
low = test+1; //guaranteed > low, <= high
} else {
//not too low
high = test; //guaranteed >= low, < high
}
}
// guaranteed low == high
// We found the position we were looking for. See if the target is on the left
return (low > 0 && nums[low-1]==target) ? low-1 : -1;
}
I do it like this:
int findEndIndex( vector<int> &nums, int target ){
int l = 0, r = nums.size() - 1;
while( l < r ){
int m = (l + (r - l) / 2) + 1;
if( nums[m] == target )
l = m;
else if( nums[m] < target )
l = m + 1;
else
r = m - 1;
}
return l >= nums.size() || nums[l] != target ? -1 : l;
}

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;
}
}

Array OutOfBounds Exception with MergeSort

I am doing a simple MergeSort implementation taking it form a pseudocode. I use Java Generics for that purpose. However I get such exception on the last element in the first for-loop. I have already made some changes (hope for the better) but still this one inevitably comes up. Why is that so?
private Comparable[] mergesort(Comparable[] elements, int l, int r) {
if(l < r){
int m = (l + r - 1)/2;
mergesort(elements, l, m);
mergesort(elements, m + 1, r);
int i = l;
int j = m + 1;
int k = l;
Comparable[] elements1 = (Comparable[])new Comparable[l + r]; //changed from [l + r - 1] and in the function caller also mergesort(elements, elements.length - elements.length, elements.length - 1)
while(i <= m && j <= r){
if(elements[i].compareTo(elements[j]) <= 0 ){
elements1[k] = elements[i];
i++;
} else {
elements1[k] = elements[j];
j++;
}
k++;
}
for(int h = i; i <= m; h++){
elements[k + (h - i)] = elements[h];
//ArrayIndexOutOfBoundsException: 4(the length of the input array)
}
for(int h = j; h <= k - 1; h++){
elements[h] = elements1[h];
}
}
return elements;
}
While your code is hard to read, I think you are comparing the wrong value.
for(int h = i; i <= m; h++){
^
should be h
elements[k + (h - i)] = elements[h];
//ArrayIndexOutOfBoundsException: 4(the length of the input array)
}
You use:
for(int h = i; i <= m; h++) {
elements[k + (h - i)] = elements[h];
}
You always increase h but compare i <= m. Since you never change i you have an endless loop.

How to increment integer Array values?

I am designing a problem in which I have to use an int array to add or subtract values. For example instead of changing 100 to 101 by adding 1, I want to do the same thing using the int array. It work like this:
int[] val = new int[3];
val[0] = 1;
val[1] = 0;
val[2] = 0;
val[2] += 1;
so, If I have to get a value of 101, I will add 1 to val[2].
The only problem I have is finding a way to make int array work like how adding and subtracting from an ordinary integer data set works.
Is this possible using a for loop or a while loop?
Any help will be appreciated!
Here's your homework:
public static int[] increment(int[] val) {
for (int i = val.length - 1; i >= 0; i--) {
if (++val[i] < 10)
return val;
val[i] = 0;
}
val = new int[val.length + 1];
val[0] = 1;
return val;
}
Make sure you understand how and why it works before submitting it as your own work.
Solution of this problem is designed by using String
You can refer to this method which will return sum of 2 nos having input in String format.
Input String should contain only digits.
class Demo {
public static String add(String a1, String b1) {
int[] a = String_to_int_Array(a1);
int[] b = String_to_int_Array(b1);
int l = a.length - 1;
int m = b.length - 1;
int sum = 0;
int carry = 0;
int rem = 0;
String temp = "";
if (a.length > b.length) {
while (m >= 0) {
sum = a[l] + b[m] + carry;
carry = sum / 10;
rem = sum % 10;
temp = rem + temp;
m--;
l--;
}
while (l >= 0) {
sum = a[l] + carry;
carry = sum / 10;
rem = sum % 10;
temp = rem + temp;
l--;
}
if (carry > 0) {
temp = carry + temp;
}
} else {
while (l >= 0) {
sum = a[l] + b[m] + carry;
carry = sum / 10;
rem = sum % 10;
temp = rem + temp;
m--;
l--;
}
while (m >= 0) {
sum = b[m] + carry;
carry = sum / 10;
rem = sum % 10;
temp = rem + temp;
m--;
}
if (carry > 0) {
temp = carry + temp;
}
}
return temp;
}
public static int[] String_to_int_Array(String s) {
int arr[] = new int[s.length()], i;
for (i = 0; i < s.length(); i++)
arr[i] = Character.digit(s.charAt(i), 10);
return arr;
}
public static void main(String a[]) {
System.out.println(add("222", "111"));
}
}
Quick & dirty:
static void increment(int[] array){
int i = array.length-1;
do{
array[i]=(array[i]+1)%10;
}while(array[i--]==0 && i>=0);
}
Note the overflow when incementing e.g. {9, 9}. Result is {0, 0} here.
public static void increment() {
int[] acc = {9,9,9,9};
String s="";
for (int i = 0; i < acc.length; i++)
s += (acc[i] + "");
int i = Integer.parseInt(s);
i++;
System.out.println("\n"+i);
String temp = Integer.toString(i);
int[] newGuess = new int[temp.length()];
for (i = 0; i < temp.length(); i++)
{
newGuess[i] = temp.charAt(i) - '0';
}
printNumbers(newGuess);
}
public static void printNumbers(int[] input) {
for (int i = 0; i < input.length; i++) {
System.out.print(input[i] + ", ");
}
System.out.println("\n");
}
If someone is looking for this solution using JavaScript or if you can translate it to java, here's your optimum solution:
function incrementArr(arr) {
let toBeIncrementedFlag = 1, // carry over logic
i = arr.length - 1;
while (toBeIncrementedFlag) {
if (arr[i] === 9) {
arr[i] = 0; // setting the digit as 0 and using carry over
toBeIncrementedFlag = 1;
} else {
toBeIncrementedFlag = 0;
arr[i] += 1;
break; // Breaking loop once no carry over is left
}
if (i === 0) { // handling case of [9,9] [9,9,9] and so on
arr.unshift(1);
break;
}
i--; // going left to right because of carry over
}
return arr;
}

Java: Binary Search Implementation not working using Deferred detection of equality

I am trying to implement Binary Search Java version. From wikipedia http://en.wikipedia.org/wiki/Binary_search_algorithm#Deferred_detection_of_equality I noticed that deferred detection of equality version. It's working using that algorithm. However, when I was trying to change the if condition expression like this:
public int bsearch1(int[] numbers, int key, int start){
int L = start, R = numbers.length - 1;
while(L < R){
//find the mid point value
int mid = (L + R) / 2;
if (numbers[mid] >key){
//move to left
R = mid - 1;
} else{
// move to right, here numbers[mid] <= key
L = mid;
}
}
return (L == R && numbers[L] == key) ? L : -1;
}
It's not working properly, which goes into an infinity loop. Do you guys have any ideas about it? Thank you so much.
You've missed the effect of the assert in the Wiki you link to.
It states:
code must guarantee the interval is reduced at each iteration
You must exit if your mid >= R.
Added
The Wiki is actually a little misleading as it suggests that merely ensuring mid < r is sufficient - it is not. You must also guard against mid == min (say you have a 4 entry array and l = 2 and r = 3, mid would become 2 and stick there because 2 + 3 = 5 and 5 / 2 = 2 in integer maths).
The solution is to round up after the / 2 which can be easily achieved by:
int mid = (l + r + 1) / 2;
The final corrected and tidied code goes a little like this:
public int binarySearch(int[] numbers, int key, int start) {
int l = start, r = numbers.length - 1;
while (l < r) {
//find the mid point value
int mid = (l + r + 1) / 2;
if (numbers[mid] > key) {
//move to left
r = mid - 1;
} else {
// move to right, here numbers[mid] <= key
l = mid;
}
}
return (l == r && numbers[l] == key) ? l : -1;
}
public void test() {
int[] numbers = new int[]{1, 2, 5, 6};
for (int i = 0; i < 9; i++) {
System.out.println("Searching for " + i);
System.out.println("Found at " + binarySearch(numbers, i, 0));
}
}
There is a trivially similar algorithm here that suggests the correct approach looks more like:
public int binarySearch(int[] numbers, int key) {
int low = 0, high = numbers.length;
while (low < high) {
int mid = (low + high) / 2;
if (numbers[mid] < key) {
low = mid + 1;
} else {
high = mid;
}
}
return low < numbers.length && numbers[low] == key ? low : -1;
}
This takes a slightly different approach to the boundary conditions where high = max + 1 and also works perfectly.

Categories