Skip to content

Commit 6413be5

Browse files
authored
Merge branch 'master' into feat-stoer-wagner
2 parents f2bc599 + 4858ec9 commit 6413be5

File tree

7 files changed

+647
-43
lines changed

7 files changed

+647
-43
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@
372372
- 📄 [Edmonds](src/main/java/com/thealgorithms/graph/Edmonds.java)
373373
- 📄 [EdmondsKarp](src/main/java/com/thealgorithms/graph/EdmondsKarp.java)
374374
- 📄 [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
375+
- 📄 [HungarianAlgorithm](src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java)
375376
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
376377
- 📄 [PushRelabel](src/main/java/com/thealgorithms/graph/PushRelabel.java)
377378
- 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.Arrays;
4+
5+
/**
6+
* Hungarian algorithm (a.k.a. Kuhn–Munkres) for the Assignment Problem.
7+
*
8+
* <p>Given an n x m cost matrix (n tasks, m workers), finds a minimum-cost
9+
* one-to-one assignment. If the matrix is rectangular, the algorithm pads to a
10+
* square internally. Costs must be finite non-negative integers.
11+
*
12+
* <p>Time complexity: O(n^3) with n = max(rows, cols).
13+
*
14+
* <p>API returns the assignment as an array where {@code assignment[i]} is the
15+
* column chosen for row i (or -1 if unassigned when rows != cols), and a total
16+
* minimal cost.
17+
*
18+
* @see <a href="https://en.wikipedia.org/wiki/Hungarian_algorithm">Wikipedia: Hungarian algorithm</a>
19+
*/
20+
public final class HungarianAlgorithm {
21+
22+
private HungarianAlgorithm() {
23+
}
24+
25+
/** Result holder for the Hungarian algorithm. */
26+
public static final class Result {
27+
public final int[] assignment; // assignment[row] = col or -1
28+
public final int minCost;
29+
30+
public Result(int[] assignment, int minCost) {
31+
this.assignment = assignment;
32+
this.minCost = minCost;
33+
}
34+
}
35+
36+
/**
37+
* Solves the assignment problem for a non-negative cost matrix.
38+
*
39+
* @param cost an r x c matrix of non-negative costs
40+
* @return Result with row-to-column assignment and minimal total cost
41+
* @throws IllegalArgumentException for null/empty or negative costs
42+
*/
43+
public static Result solve(int[][] cost) {
44+
validate(cost);
45+
int rows = cost.length;
46+
int cols = cost[0].length;
47+
int n = Math.max(rows, cols);
48+
49+
// Build square matrix with padding 0 for missing cells
50+
int[][] a = new int[n][n];
51+
for (int i = 0; i < n; i++) {
52+
if (i < rows) {
53+
for (int j = 0; j < n; j++) {
54+
a[i][j] = (j < cols) ? cost[i][j] : 0;
55+
}
56+
} else {
57+
Arrays.fill(a[i], 0);
58+
}
59+
}
60+
61+
// Potentials and matching arrays
62+
int[] u = new int[n + 1];
63+
int[] v = new int[n + 1];
64+
int[] p = new int[n + 1];
65+
int[] way = new int[n + 1];
66+
67+
for (int i = 1; i <= n; i++) {
68+
p[0] = i;
69+
int j0 = 0;
70+
int[] minv = new int[n + 1];
71+
boolean[] used = new boolean[n + 1];
72+
Arrays.fill(minv, Integer.MAX_VALUE);
73+
Arrays.fill(used, false);
74+
do {
75+
used[j0] = true;
76+
int i0 = p[j0];
77+
int delta = Integer.MAX_VALUE;
78+
int j1 = 0;
79+
for (int j = 1; j <= n; j++) {
80+
if (!used[j]) {
81+
int cur = a[i0 - 1][j - 1] - u[i0] - v[j];
82+
if (cur < minv[j]) {
83+
minv[j] = cur;
84+
way[j] = j0;
85+
}
86+
if (minv[j] < delta) {
87+
delta = minv[j];
88+
j1 = j;
89+
}
90+
}
91+
}
92+
for (int j = 0; j <= n; j++) {
93+
if (used[j]) {
94+
u[p[j]] += delta;
95+
v[j] -= delta;
96+
} else {
97+
minv[j] -= delta;
98+
}
99+
}
100+
j0 = j1;
101+
} while (p[j0] != 0);
102+
do {
103+
int j1 = way[j0];
104+
p[j0] = p[j1];
105+
j0 = j1;
106+
} while (j0 != 0);
107+
}
108+
109+
int[] matchColForRow = new int[n];
110+
Arrays.fill(matchColForRow, -1);
111+
for (int j = 1; j <= n; j++) {
112+
if (p[j] != 0) {
113+
matchColForRow[p[j] - 1] = j - 1;
114+
}
115+
}
116+
117+
// Build assignment for original rows only, ignore padded rows
118+
int[] assignment = new int[rows];
119+
Arrays.fill(assignment, -1);
120+
int total = 0;
121+
for (int i = 0; i < rows; i++) {
122+
int j = matchColForRow[i];
123+
if (j >= 0 && j < cols) {
124+
assignment[i] = j;
125+
total += cost[i][j];
126+
}
127+
}
128+
return new Result(assignment, total);
129+
}
130+
131+
private static void validate(int[][] cost) {
132+
if (cost == null || cost.length == 0) {
133+
throw new IllegalArgumentException("Cost matrix must not be null or empty");
134+
}
135+
int c = cost[0].length;
136+
if (c == 0) {
137+
throw new IllegalArgumentException("Cost matrix must have at least 1 column");
138+
}
139+
for (int i = 0; i < cost.length; i++) {
140+
if (cost[i] == null || cost[i].length != c) {
141+
throw new IllegalArgumentException("Cost matrix must be rectangular with equal row lengths");
142+
}
143+
for (int j = 0; j < c; j++) {
144+
if (cost[i][j] < 0) {
145+
throw new IllegalArgumentException("Costs must be non-negative");
146+
}
147+
}
148+
}
149+
}
150+
}
Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,89 @@
11
package com.thealgorithms.others;
22

3-
import java.util.HashSet;
4-
import java.util.Set;
3+
import java.util.HashMap;
4+
import java.util.Map;
55

66
/**
7-
* References: https://en.wikipedia.org/wiki/Streaming_algorithm
7+
* Algorithm to find the maximum sum of a subarray of size K with all distinct
8+
* elements.
89
*
9-
* This model involves computing the maximum sum of subarrays of a fixed size \( K \) from a stream of integers.
10-
* As the stream progresses, elements from the end of the window are removed, and new elements from the stream are added.
10+
* This implementation uses a sliding window approach with a hash map to
11+
* efficiently
12+
* track element frequencies within the current window. The algorithm maintains
13+
* a window
14+
* of size K and slides it across the array, ensuring all elements in the window
15+
* are distinct.
1116
*
17+
* Time Complexity: O(n) where n is the length of the input array
18+
* Space Complexity: O(k) for storing elements in the hash map
19+
*
20+
* @see <a href="https://en.wikipedia.org/wiki/Streaming_algorithm">Streaming
21+
* Algorithm</a>
22+
* @see <a href="https://en.wikipedia.org/wiki/Sliding_window_protocol">Sliding
23+
* Window</a>
1224
* @author Swarga-codes (https://github.com/Swarga-codes)
1325
*/
1426
public final class MaximumSumOfDistinctSubarraysWithLengthK {
1527
private MaximumSumOfDistinctSubarraysWithLengthK() {
1628
}
1729

1830
/**
19-
* Finds the maximum sum of a subarray of size K consisting of distinct elements.
31+
* Finds the maximum sum of a subarray of size K consisting of distinct
32+
* elements.
2033
*
21-
* @param k The size of the subarray.
22-
* @param nums The array from which subarrays will be considered.
34+
* The algorithm uses a sliding window technique with a frequency map to track
35+
* the count of each element in the current window. A window is valid only if
36+
* all K elements are distinct (frequency map size equals K).
2337
*
24-
* @return The maximum sum of any distinct-element subarray of size K. If no such subarray exists, returns 0.
38+
* @param k The size of the subarray. Must be non-negative.
39+
* @param nums The array from which subarrays will be considered.
40+
* @return The maximum sum of any distinct-element subarray of size K.
41+
* Returns 0 if no such subarray exists or if k is 0 or negative.
42+
* @throws IllegalArgumentException if k is negative
2543
*/
2644
public static long maximumSubarraySum(int k, int... nums) {
27-
if (nums.length < k) {
45+
if (k <= 0 || nums == null || nums.length < k) {
2846
return 0;
2947
}
30-
long masSum = 0; // Variable to store the maximum sum of distinct subarrays
31-
long currentSum = 0; // Variable to store the sum of the current subarray
32-
Set<Integer> currentSet = new HashSet<>(); // Set to track distinct elements in the current subarray
3348

34-
// Initialize the first window
49+
long maxSum = 0;
50+
long currentSum = 0;
51+
Map<Integer, Integer> frequencyMap = new HashMap<>();
52+
53+
// Initialize the first window of size k
3554
for (int i = 0; i < k; i++) {
3655
currentSum += nums[i];
37-
currentSet.add(nums[i]);
56+
frequencyMap.put(nums[i], frequencyMap.getOrDefault(nums[i], 0) + 1);
3857
}
39-
// If the first window contains distinct elements, update maxSum
40-
if (currentSet.size() == k) {
41-
masSum = currentSum;
58+
59+
// Check if the first window has all distinct elements
60+
if (frequencyMap.size() == k) {
61+
maxSum = currentSum;
4262
}
63+
4364
// Slide the window across the array
44-
for (int i = 1; i < nums.length - k + 1; i++) {
45-
// Update the sum by removing the element that is sliding out and adding the new element
46-
currentSum = currentSum - nums[i - 1];
47-
currentSum = currentSum + nums[i + k - 1];
48-
int j = i;
49-
boolean flag = false; // flag value which says that the subarray contains distinct elements
50-
while (j < i + k && currentSet.size() < k) {
51-
if (nums[i - 1] == nums[j]) {
52-
flag = true;
53-
break;
54-
} else {
55-
j++;
56-
}
57-
}
58-
if (!flag) {
59-
currentSet.remove(nums[i - 1]);
65+
for (int i = k; i < nums.length; i++) {
66+
// Remove the leftmost element from the window
67+
int leftElement = nums[i - k];
68+
currentSum -= leftElement;
69+
int leftFrequency = frequencyMap.get(leftElement);
70+
if (leftFrequency == 1) {
71+
frequencyMap.remove(leftElement);
72+
} else {
73+
frequencyMap.put(leftElement, leftFrequency - 1);
6074
}
61-
currentSet.add(nums[i + k - 1]);
62-
// If the current window has distinct elements, compare and possibly update maxSum
63-
if (currentSet.size() == k && masSum < currentSum) {
64-
masSum = currentSum;
75+
76+
// Add the new rightmost element to the window
77+
int rightElement = nums[i];
78+
currentSum += rightElement;
79+
frequencyMap.put(rightElement, frequencyMap.getOrDefault(rightElement, 0) + 1);
80+
81+
// If all elements in the window are distinct, update maxSum if needed
82+
if (frequencyMap.size() == k && currentSum > maxSum) {
83+
maxSum = currentSum;
6584
}
6685
}
67-
return masSum; // the final maximum sum
86+
87+
return maxSum;
6888
}
6989
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.thealgorithms.physics;
2+
3+
/**
4+
* Models a damped harmonic oscillator, capturing the behavior of a mass-spring-damper system.
5+
*
6+
* <p>The system is defined by the second-order differential equation:
7+
* x'' + 2 * gamma * x' + omega₀² * x = 0
8+
* where:
9+
* <ul>
10+
* <li><b>omega₀</b> is the natural (undamped) angular frequency in radians per second.</li>
11+
* <li><b>gamma</b> is the damping coefficient in inverse seconds.</li>
12+
* </ul>
13+
*
14+
* <p>This implementation provides:
15+
* <ul>
16+
* <li>An analytical solution for the underdamped case (γ < ω₀).</li>
17+
* <li>A numerical integrator based on the explicit Euler method for simulation purposes.</li>
18+
* </ul>
19+
*
20+
* <p><strong>Usage Example:</strong>
21+
* <pre>{@code
22+
* DampedOscillator oscillator = new DampedOscillator(10.0, 0.5);
23+
* double displacement = oscillator.displacementAnalytical(1.0, 0.0, 0.1);
24+
* double[] nextState = oscillator.stepEuler(new double[]{1.0, 0.0}, 0.001);
25+
* }</pre>
26+
*
27+
* @author [Yash Rajput](https://github.com/the-yash-rajput)
28+
*/
29+
public final class DampedOscillator {
30+
31+
/** Natural (undamped) angular frequency (rad/s). */
32+
private final double omega0;
33+
34+
/** Damping coefficient (s⁻¹). */
35+
private final double gamma;
36+
37+
private DampedOscillator() {
38+
throw new AssertionError("No instances.");
39+
}
40+
41+
/**
42+
* Constructs a damped oscillator model.
43+
*
44+
* @param omega0 the natural frequency (rad/s), must be positive
45+
* @param gamma the damping coefficient (s⁻¹), must be non-negative
46+
* @throws IllegalArgumentException if parameters are invalid
47+
*/
48+
public DampedOscillator(double omega0, double gamma) {
49+
if (omega0 <= 0) {
50+
throw new IllegalArgumentException("Natural frequency must be positive.");
51+
}
52+
if (gamma < 0) {
53+
throw new IllegalArgumentException("Damping coefficient must be non-negative.");
54+
}
55+
this.omega0 = omega0;
56+
this.gamma = gamma;
57+
}
58+
59+
/**
60+
* Computes the analytical displacement of an underdamped oscillator.
61+
* Formula: x(t) = A * exp(-γt) * cos(ω_d t + φ)
62+
*
63+
* @param amplitude the initial amplitude A
64+
* @param phase the initial phase φ (radians)
65+
* @param time the time t (seconds)
66+
* @return the displacement x(t)
67+
*/
68+
public double displacementAnalytical(double amplitude, double phase, double time) {
69+
double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma));
70+
return amplitude * Math.exp(-gamma * time) * Math.cos(omegaD * time + phase);
71+
}
72+
73+
/**
74+
* Performs a single integration step using the explicit Euler method.
75+
* State vector format: [x, v], where v = dx/dt.
76+
*
77+
* @param state the current state [x, v]
78+
* @param dt the time step (seconds)
79+
* @return the next state [x_next, v_next]
80+
* @throws IllegalArgumentException if the state array is invalid or dt is non-positive
81+
*/
82+
public double[] stepEuler(double[] state, double dt) {
83+
if (state == null || state.length != 2) {
84+
throw new IllegalArgumentException("State must be a non-null array of length 2.");
85+
}
86+
if (dt <= 0) {
87+
throw new IllegalArgumentException("Time step must be positive.");
88+
}
89+
90+
double x = state[0];
91+
double v = state[1];
92+
double acceleration = -2.0 * gamma * v - omega0 * omega0 * x;
93+
94+
double xNext = x + dt * v;
95+
double vNext = v + dt * acceleration;
96+
97+
return new double[] {xNext, vNext};
98+
}
99+
100+
/** @return the natural (undamped) angular frequency (rad/s). */
101+
public double getOmega0() {
102+
return omega0;
103+
}
104+
105+
/** @return the damping coefficient (s⁻¹). */
106+
public double getGamma() {
107+
return gamma;
108+
}
109+
}

0 commit comments

Comments
 (0)