Given an array arr[] of positive integers, find the length of the longest geometric progression (GP) that can be formed by rearranging elements from the array. The common ratio of the GP must be an integer (ratio ≥ 1 is allowed, including a GP of equal elements).
Examples:
Input: arr[] = [2, 4, 3]
Output: 2
Explanation: The longest geometric progression is [2, 4], with common ratio 2.Input: arr[] = [5, 7, 15, 10, 20, 29]
Output: 3
Explanation: The longest geometric progression is [5, 10, 20], with common ratio 2.
Table of Content
[Naive Approach] Check Every Pair as First Two Terms - O(n ^ 2 * L) Time and O(n) Space
The idea is to consider every pair of elements as the first two terms of a geometric progression. If their ratio is an integer, keep multiplying by the ratio and check whether the next term exists in the array. Store all elements in a hash set for fast lookup and update the maximum GP length found.
Working of Approach:
- Store all array elements in a hash set.
- Try every pair of elements as the first two terms of a GP.
- If the ratio is an integer, keep generating the next terms.
- Count the length while the next term exists in the set.
- Return the maximum length obtained.
#include <bits/stdc++.h>
using namespace std;
int lenOfLongestGP(vector<int> &arr)
{
int n = arr.size();
if (n == 0)
return 0;
// Store all elements for fast lookup
unordered_set<int> st(arr.begin(), arr.end());
// Store frequency to handle ratio = 1
unordered_map<int, int> freq;
int res = 1;
// Count frequency of every element
for (int x : arr)
{
freq[x]++;
res = max(res, freq[x]);
}
// Try every pair as the first two terms of a GP
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i == j)
continue;
// Ratio must be an integer
if (arr[j] % arr[i] != 0)
continue;
int r = arr[j] / arr[i];
// Ratio 1 is already handled
if (r <= 1)
continue;
int len = 2;
int term = arr[j] * r;
// Keep generating the next GP terms
while (term <= 40000 && st.count(term))
{
len++;
// Prevent integer overflow
if (term > 40000 / r)
break;
term *= r;
}
res = max(res, len);
}
}
return res;
}
int main()
{
vector<int> arr = {5, 7, 15, 10, 20, 29};
cout << lenOfLongestGP(arr);
return 0;
}
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class GFG {
public static int lenOfLongestGP(int[] arr)
{
int n = arr.length;
if (n == 0)
return 0;
// Store all elements for fast lookup
Set<Integer> st = new HashSet<>();
for (int x : arr) {
st.add(x);
}
// Store frequency to handle ratio = 1
Map<Integer, Integer> freq = new HashMap<>();
int res = 1;
// Count frequency of every element
for (int x : arr) {
freq.put(x, freq.getOrDefault(x, 0) + 1);
res = Math.max(res, freq.get(x));
}
// Try every pair as the first two terms of a GP
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j)
continue;
// Ratio must be an integer
if (arr[j] % arr[i] != 0)
continue;
int r = arr[j] / arr[i];
// Ratio 1 is already handled
if (r <= 1)
continue;
int len = 2;
int term = arr[j] * r;
// Keep generating the next GP terms
while (term <= 40000 && st.contains(term)) {
len++;
// Prevent integer overflow
if (term > 40000 / r)
break;
term *= r;
}
res = Math.max(res, len);
}
}
return res;
}
public static void main(String[] args)
{
int[] arr = { 5, 7, 15, 10, 20, 29 };
System.out.println(lenOfLongestGP(arr));
}
}
from collections import defaultdict
def lenOfLongestGP(arr):
n = len(arr)
if n == 0:
return 0
# Store all elements for fast lookup
st = set(arr)
# Store frequency to handle ratio = 1
freq = defaultdict(int)
res = 1
# Count frequency of every element
for x in arr:
freq[x] += 1
res = max(res, freq[x])
# Try every pair as the first two terms of a GP
for i in range(n):
for j in range(n):
if i == j:
continue
# Ratio must be an integer
if arr[j] % arr[i] != 0:
continue
r = arr[j] // arr[i]
# Ratio 1 is already handled
if r <= 1:
continue
len_gp = 2
term = arr[j] * r
# Keep generating the next GP terms
while term <= 40000 and term in st:
len_gp += 1
# Prevent integer overflow
if term > 40000 // r:
break
term *= r
res = max(res, len_gp)
return res
if __name__ == '__main__':
arr = [5, 7, 15, 10, 20, 29]
print(lenOfLongestGP(arr))
using System;
using System.Collections.Generic;
public class GFG {
public static int lenOfLongestGP(int[] arr)
{
int n = arr.Length;
if (n == 0)
return 0;
// Store all elements for fast lookup
HashSet<int> st = new HashSet<int>(arr);
// Store frequency to handle ratio = 1
Dictionary<int, int> freq
= new Dictionary<int, int>();
int res = 1;
// Count frequency of every element
foreach(int x in arr)
{
if (freq.ContainsKey(x))
freq[x]++;
else
freq[x] = 1;
res = Math.Max(res, freq[x]);
}
// Try every pair as the first two terms of a GP
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j)
continue;
// Ratio must be an integer
if (arr[j] % arr[i] != 0)
continue;
int r = arr[j] / arr[i];
// Ratio 1 is already handled
if (r <= 1)
continue;
int len = 2;
int term = arr[j] * r;
// Keep generating the next GP terms
while (term <= 40000 && st.Contains(term)) {
len++;
// Prevent integer overflow
if (term > 40000 / r)
break;
term *= r;
}
res = Math.Max(res, len);
}
}
return res;
}
public static void Main()
{
int[] arr = { 5, 7, 15, 10, 20, 29 };
Console.WriteLine(lenOfLongestGP(arr));
}
}
function lenOfLongestGP(arr)
{
const n = arr.length;
if (n === 0)
return 0;
// Store all elements for fast lookup
const st = new Set(arr);
// Store frequency to handle ratio = 1
const freq = new Map();
let res = 1;
// Count frequency of every element
for (const x of arr) {
if (freq.has(x)) {
freq.set(x, freq.get(x) + 1);
}
else {
freq.set(x, 1);
}
res = Math.max(res, freq.get(x));
}
// Try every pair as the first two terms of a GP
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (i === j)
continue;
// Ratio must be an integer
if (arr[j] % arr[i] !== 0)
continue;
const r = Math.floor(arr[j] / arr[i]);
// Ratio 1 is already handled
if (r <= 1)
continue;
let len = 2;
let term = arr[j] * r;
// Keep generating the next GP terms
while (term <= 40000 && st.has(term)) {
len++;
// Prevent integer overflow
if (term > 40000 / r)
break;
term *= r;
}
res = Math.max(res, len);
}
}
return res;
}
// Driver Code
const arr = [ 5, 7, 15, 10, 20, 29 ];
console.log(lenOfLongestGP(arr));
Output
3
Time Complexity: O(n ^ 2 · L), where L is the maximum GP length.
Space Complexity: O(n)
[Expected Approach] Using Dynamic Programming on Sorted Array - O(n ^ 2 * log n) Time and O(n ^ 2) Space
The idea is to sort the array and use dynamic programming. Let dp[i][j] denote the length of the longest geometric progression ending with arr[i] as second last and arr[j] as last element. For every pair, compute the required previous term using pred = (arr[i] * arr[i]) / arr[j] and extend the GP if such a term exists.
Working of Approach:
- Sort the array in increasing order.
- Store the indices of every value to handle duplicates.
- Let dp[i][j] store the GP length ending at arr[i] and arr[j].
- Find the required previous term and extend the GP if it exists.
- Return the maximum value in the DP table.
Let us understand with an example:
Input: arr[] = [5, 7, 15, 10, 20, 29]
- First, sort the array to get [5, 7, 10, 15, 20, 29].
- Consider every pair of elements as the last two terms of a possible GP.
- For each pair, compute the required previous term and check whether it exists before the current pair.
- If the previous term exists, extend the existing GP using the DP table; otherwise, start a new GP of length 2.
- Continue this process for all pairs while keeping track of the maximum GP length found.
- The longest geometric progression is {5, 10, 20}, so the output is 3.
#include <bits/stdc++.h>
using namespace std;
int lenOfLongestGP(vector<int> &arr)
{
int n = arr.size();
// 0 or 1 element is trivially its own GP
if (n <= 1)
return n;
// GP terms must be considered in increasing order
sort(arr.begin(), arr.end());
// positions[val] = sorted indices where 'val' occurs,
// needed to correctly handle duplicate elements
unordered_map<int, vector<int>> positions;
for (int idx = 0; idx < n; idx++)
positions[arr[idx]].push_back(idx);
// dp[i][j] = length of LGP with arr[i], arr[j] as its
// last two terms (i < j), valid only for integer ratio
vector<vector<int>> dp(n, vector<int>(n, 0));
// A single element is always a valid GP of length 1
int res = 1;
for (int j = 1; j < n; j++)
{
for (int i = 0; i < j; i++)
{
// Ratio arr[j]/arr[i] must be an integer,
// else this pair can never form a valid GP
if (arr[j] % arr[i] != 0)
continue;
// Predecessor value needed to extend the chain:
// pred * arr[j] = arr[i] * arr[i]
int num = arr[i] * arr[i];
int len = 2; // fallback: fresh GP of length 2
if (num % arr[j] == 0)
{
int predVal = num / arr[j];
auto it = positions.find(predVal);
if (it != positions.end())
{
// largest index strictly less than i
// holding value predVal
vector<int> &v = it->second;
int pos = upper_bound(v.begin(), v.end(), i - 1) - v.begin() - 1;
if (pos >= 0)
len = dp[v[pos]][i] + 1;
}
}
dp[i][j] = len;
res = max(res, len);
}
}
return res;
}
int main()
{
vector<int> arr = {5, 7, 15, 10, 20, 29};
cout << lenOfLongestGP(arr);
return 0;
}
import java.util.*;
public class GFG {
public static int lenOfLongestGP(int[] arr)
{
int n = arr.length;
// 0 or 1 element is trivially its own GP
if (n <= 1)
return n;
// GP terms must be considered in increasing order
Arrays.sort(arr);
// positions[val] = sorted indices where 'val'
// occurs, needed to correctly handle duplicate
// elements
Map<Integer, List<Integer> > positions
= new HashMap<>();
for (int idx = 0; idx < n; idx++)
positions
.computeIfAbsent(arr[idx],
k -> new ArrayList<>())
.add(idx);
// dp[i][j] = length of LGP with arr[i], arr[j] as
// its last two terms (i < j), valid only for
// integer ratio
int[][] dp = new int[n][n];
// A single element is always a valid GP of length 1
int res = 1;
for (int j = 1; j < n; j++) {
for (int i = 0; i < j; i++) {
// Ratio arr[j]/arr[i] must be an integer,
// else this pair can never form a valid GP
if (arr[j] % arr[i] != 0)
continue;
// Predecessor value needed to extend the
// chain: pred * arr[j] = arr[i] * arr[i]
int num = arr[i] * arr[i];
int len
= 2; // fallback: fresh GP of length 2
if (num % arr[j] == 0) {
int predVal = num / arr[j];
List<Integer> v
= positions.get(predVal);
if (v != null) {
// largest index strictly less than
// i holding value predVal
int pos = Collections.binarySearch(
v, i - 1);
if (pos < 0)
pos = -pos - 2;
if (pos >= 0)
len = dp[v.get(pos)][i] + 1;
}
}
dp[i][j] = len;
res = Math.max(res, len);
}
}
return res;
}
public static void main(String[] args)
{
int[] arr = { 5, 7, 15, 10, 20, 29 };
System.out.println(lenOfLongestGP(arr));
}
}
from collections import defaultdict
def lenOfLongestGP(arr):
n = len(arr)
# 0 or 1 element is trivially its own GP
if n <= 1:
return n
# GP terms must be considered in increasing order
arr.sort()
# positions[val] = sorted indices where 'val' occurs,
# needed to correctly handle duplicate elements
positions = defaultdict(list)
for idx in range(n):
positions[arr[idx]].append(idx)
# dp[i][j] = length of LGP with arr[i], arr[j] as its
# last two terms (i < j), valid only for integer ratio
dp = [[0] * n for _ in range(n)]
# A single element is always a valid GP of length 1
res = 1
for j in range(1, n):
for i in range(j):
# Ratio arr[j]/arr[i] must be an integer,
# else this pair can never form a valid GP
if arr[j] % arr[i] != 0:
continue
# Predecessor value needed to extend the chain:
# pred * arr[j] = arr[i] * arr[i]
num = arr[i] * arr[i]
len_ = 2 # fallback: fresh GP of length 2
if num % arr[j] == 0:
predVal = num // arr[j]
if predVal in positions:
v = positions[predVal]
pos = bisect_right(v, i - 1) - 1
if pos >= 0:
len_ = dp[v[pos]][i] + 1
dp[i][j] = len_
res = max(res, len_)
return res
def bisect_right(a, x):
lo = 0
hi = len(a)
while lo < hi:
mid = (lo + hi) // 2
if a[mid] <= x:
lo = mid + 1
else:
hi = mid
return lo
if __name__ == '__main__':
arr = [5, 7, 15, 10, 20, 29]
print(lenOfLongestGP(arr))
using System;
using System.Collections.Generic;
class GFG {
static int lenOfLongestGP(int[] arr)
{
int n = arr.Length;
// 0 or 1 element is trivially its own GP
if (n <= 1)
return n;
// GP terms must be considered in increasing order
Array.Sort(arr);
// positions[val] = sorted indices where 'val'
// occurs, needed to correctly handle duplicate
// elements
Dictionary<int, List<int> > positions
= new Dictionary<int, List<int> >();
for (int idx = 0; idx < n; idx++) {
if (!positions.ContainsKey(arr[idx]))
positions[arr[idx]] = new List<int>();
positions[arr[idx]].Add(idx);
}
// dp[i][j] = length of LGP with arr[i], arr[j] as
// its last two terms (i < j), valid only for
// integer ratio
int[, ] dp = new int[n, n];
// A single element is always a valid GP of length 1
int res = 1;
for (int j = 1; j < n; j++) {
for (int i = 0; i < j; i++) {
// Ratio arr[j]/arr[i] must be an integer,
// else this pair can never form a valid GP
if (arr[j] % arr[i] != 0)
continue;
// Predecessor value needed to extend the
// chain: pred * arr[j] = arr[i] * arr[i]
int num = arr[i] * arr[i];
int len
= 2; // fallback: fresh GP of length 2
if (num % arr[j] == 0) {
int predVal = num / arr[j];
if (positions.ContainsKey(predVal)) {
List<int> v = positions[predVal];
int pos = v.BinarySearch(i - 1);
if (pos < 0)
pos = ~pos - 1;
if (pos >= 0)
len = dp[v[pos], i] + 1;
}
}
dp[i, j] = len;
res = Math.Max(res, len);
}
}
return res;
}
static void Main()
{
int[] arr = { 5, 7, 15, 10, 20, 29 };
Console.WriteLine(lenOfLongestGP(arr));
}
}
function lenOfLongestGP(arr)
{
let n = arr.length;
// 0 or 1 element is trivially its own GP
if (n <= 1)
return n;
// GP terms must be considered in increasing order
arr.sort((a, b) => a - b);
// positions[val] = sorted indices where 'val' occurs,
// needed to correctly handle duplicate elements
let positions = new Map();
for (let idx = 0; idx < n; idx++) {
if (!positions.has(arr[idx])) {
positions.set(arr[idx], []);
}
positions.get(arr[idx]).push(idx);
}
// dp[i][j] = length of LGP with arr[i], arr[j] as its
// last two terms (i < j), valid only for integer ratio
let dp
= Array.from({length : n}, () => Array(n).fill(0));
// A single element is always a valid GP of length 1
let res = 1;
for (let j = 1; j < n; j++) {
for (let i = 0; i < j; i++) {
// Ratio arr[j]/arr[i] must be an integer,
// else this pair can never form a valid GP
if (arr[j] % arr[i] != 0)
continue;
// Predecessor value needed to extend the chain:
// pred * arr[j] = arr[i] * arr[i]
let num = arr[i] * arr[i];
let len = 2; // fallback: fresh GP of length 2
if (num % arr[j] == 0) {
let predVal = num / arr[j];
if (positions.has(predVal)) {
let v = positions.get(predVal);
let pos
= v.filter(val => val < i).length
- 1;
if (pos >= 0)
len = dp[v[pos]][i] + 1;
}
}
dp[i][j] = len;
res = Math.max(res, len);
}
}
return res;
}
// Driver Code
let arr = [ 5, 7, 15, 10, 20, 29 ];
console.log(lenOfLongestGP(arr));
Output
3
Time Complexity: O(n ^ 2 * log n), where n ^ 2 comes from checking all pairs of elements and log n comes from the binary search (upper_bound) used to find the predecessor for each pair.
Space Complexity: O(n ^ 2), where O(n ^ 2) space is required for the DP table and O(n) for storing the positions of each value. Since the DP table dominates, the overall auxiliary space is O(n ^ 2).