1+ /*
2+ * Bellman–Ford — Single-Source Shortest Paths (+ Negative-Cycle Check)
3+ * ===================================================================
4+ * ✍️ Algorithm Description
5+ * -----------------------
6+ * Bellman–Ford computes shortest path distances from a single source in a
7+ * weighted directed graph — even when there are negative edge weights.
8+ * It can also detect the presence of any negative-weight cycle reachable
9+ * from the source (or anywhere, using a super-source trick).
10+ *
11+ * Core idea:
12+ * 1) Initialize dist[src] = 0, all others = +∞.
13+ * 2) Relax all edges V-1 times (where V is #vertices).
14+ * - A "relax" on edge (u -> v, w) does: dist[v] = min(dist[v], dist[u] + w).
15+ * 3) Do one more pass over all edges:
16+ * - If any distance can still be improved, a negative-weight cycle exists.
17+ *
18+ * Use cases:
19+ * - Shortest paths with negative edges (no negative cycles reachable from src)
20+ * - Financial arbitrage detection (negative cycles)
21+ * - As a subroutine in Johnson’s algorithm (reweighting for all-pairs)
22+ *
23+ * 📊 Complexity Analysis
24+ * ----------------------
25+ * Let V = number of vertices, E = number of edges.
26+ * Time: O(V * E) — V-1 relaxation rounds plus a final scan.
27+ * Space: O(V) — distance array (+ optional parent array).
28+ *
29+ * 💡 Notes
30+ * - `bellmanFord1` (array distances) is fastest when vertices are 0..V-1.
31+ * - `bellmanFordMap` (HashMap distances) is handy if vertex ids are sparse or labeled.
32+ * - `hasNegativeCycleAnywhere` adds a super-source → detects ANY neg cycle in the graph.
33+ * - Distances use `long` to reduce overflow risk. Use `INF` as a large sentinel.
34+ */
35+
36+ import java .util .*;
37+
38+ class Solution {
39+
40+ // Use a large INF sentinel to avoid overflow when adding weights.
41+ private static final long INF = (long )1e18 ;
42+
43+ /**
44+ * Bellman–Ford with array distances (vertices assumed 0..V-1).
45+ *
46+ * @param V number of vertices
47+ * @param edges directed weighted edges as [u, v, w]
48+ * @param src source vertex
49+ * @return distances array; if a negative cycle is reachable from src,
50+ * returns null to signal "no well-defined shortest paths"
51+ */
52+ public static long [] bellmanFord1 (int V , int [][] edges , int src ) {
53+ long [] dist = new long [V ];
54+ Arrays .fill (dist , INF );
55+ dist [src ] = 0 ;
56+
57+ // Relax edges V-1 times
58+ for (int i = 1 ; i <= V - 1 ; i ++) {
59+ boolean changed = false ;
60+ for (int [] e : edges ) {
61+ int u = e [0 ], v = e [1 ];
62+ long w = e [2 ];
63+ if (dist [u ] != INF && dist [u ] + w < dist [v ]) {
64+ dist [v ] = dist [u ] + w ;
65+ changed = true ;
66+ }
67+ }
68+ // Early exit if no update in a round
69+ if (!changed ) break ;
70+ }
71+
72+ // Check for a negative cycle reachable from src
73+ for (int [] e : edges ) {
74+ int u = e [0 ], v = e [1 ];
75+ long w = e [2 ];
76+ if (dist [u ] != INF && dist [u ] + w < dist [v ]) {
77+ return null ; // Negative cycle detected (reachable from src)
78+ }
79+ }
80+ return dist ;
81+ }
82+
83+ /**
84+ * Bellman–Ford with HashMap distances (good for sparse / labeled vertices).
85+ * Vertices are still 0..V-1 here, but you can adapt to arbitrary ids
86+ * by mapping them to [0..n-1] and keeping only present keys in the map.
87+ *
88+ * @param V number of vertices
89+ * @param edges directed weighted edges as [u, v, w]
90+ * @param src source vertex
91+ * @return distance map; null if a negative cycle is reachable from src
92+ */
93+ public static HashMap <Integer , Long > bellmanFordMap (int V , int [][] edges , int src ) {
94+ HashMap <Integer , Long > dist = new HashMap <>();
95+ for (int i = 0 ; i < V ; i ++) dist .put (i , INF );
96+ dist .put (src , 0L );
97+
98+ for (int i = 1 ; i <= V - 1 ; i ++) {
99+ boolean changed = false ;
100+ for (int [] e : edges ) {
101+ int u = e [0 ], v = e [1 ];
102+ long w = e [2 ];
103+ long du = dist .get (u );
104+ long dv = dist .get (v );
105+ if (du != INF && du + w < dv ) {
106+ dist .put (v , du + w );
107+ changed = true ;
108+ }
109+ }
110+ if (!changed ) break ;
111+ }
112+
113+ for (int [] e : edges ) {
114+ int u = e [0 ], v = e [1 ];
115+ long w = e [2 ];
116+ long du = dist .get (u );
117+ long dv = dist .get (v );
118+ if (du != INF && du + w < dv ) {
119+ return null ; // Negative cycle detected (reachable from src)
120+ }
121+ }
122+ return dist ;
123+ }
124+
125+ /**
126+ * Detect ANY negative-weight cycle in the graph (not just reachable from a given src).
127+ * Technique: add a super-source S connected to all nodes with 0-weight edges, run BF.
128+ *
129+ * @param V number of vertices
130+ * @param edges directed weighted edges as [u, v, w]
131+ * @return true if any negative-weight cycle exists; false otherwise
132+ */
133+ public static boolean hasNegativeCycleAnywhere (int V , int [][] edges ) {
134+ // Build a new edge list with an extra super-source (-1) conceptually.
135+ // Implementation trick: initialize all dist[i]=0 and run V rounds.
136+ long [] dist = new long [V ];
137+ Arrays .fill (dist , 0L ); // Equivalent to relaxing from a super-source with 0 edges to all
138+
139+ // Relax V times; if we can still relax on the V-th time, a neg cycle exists anywhere.
140+ for (int i = 1 ; i <= V ; i ++) {
141+ boolean changed = false ;
142+ for (int [] e : edges ) {
143+ int u = e [0 ], v = e [1 ];
144+ long w = e [2 ];
145+ if (dist [u ] + w < dist [v ]) {
146+ dist [v ] = dist [u ] + w ;
147+ changed = true ;
148+ if (i == V ) return true ; // improvement on the V-th round => negative cycle
149+ }
150+ }
151+ if (!changed ) break ;
152+ }
153+ return false ;
154+ }
155+
156+ // ----------------------------
157+ // ✅ Test Cases / Examples
158+ // ----------------------------
159+ public static void main (String [] args ) {
160+ // 1) Positive weights only
161+ int V1 = 5 ;
162+ int [][] E1 = {
163+ {0 ,1 ,6 }, {0 ,3 ,7 },
164+ {1 ,2 ,5 }, {1 ,3 ,8 }, {1 ,4 ,-4 },
165+ {2 ,1 ,-2 }, {3 ,2 ,-3 }, {3 ,4 ,9 },
166+ {4 ,0 ,2 }, {4 ,2 ,7 }
167+ };
168+ System .out .println ("Case 1: Positive & some negative edges (no neg cycle, src=0)" );
169+ System .out .println ("hasNegativeCycleAnywhere: " + hasNegativeCycleAnywhere (V1 , E1 )); // false
170+ System .out .println ("bellmanFord1 distances from 0: " + Arrays .toString (bellmanFord1 (V1 , E1 , 0 )));
171+
172+ // 2) Negative edge but no negative cycle
173+ int V2 = 4 ;
174+ int [][] E2 = {
175+ {0 ,1 ,1 }, {1 ,2 ,-1 }, {2 ,3 ,-1 }
176+ };
177+ System .out .println ("\n Case 2: Negative edges, no cycle (src=0)" );
178+ System .out .println ("hasNegativeCycleAnywhere: " + hasNegativeCycleAnywhere (V2 , E2 )); // false
179+ System .out .println ("bellmanFordMap distances from 0: " + bellmanFordMap (V2 , E2 , 0 ));
180+
181+ // 3) Graph with a negative cycle reachable from src
182+ // Cycle: 1 -> 2 -> 3 -> 1 with total weight -3
183+ int V3 = 4 ;
184+ int [][] E3 = {
185+ {0 ,1 ,2 }, {1 ,2 ,-2 }, {2 ,3 ,-2 }, {3 ,1 ,1 }
186+ };
187+ System .out .println ("\n Case 3: Negative cycle reachable from src=0" );
188+ System .out .println ("hasNegativeCycleAnywhere: " + hasNegativeCycleAnywhere (V3 , E3 )); // true
189+ long [] d3 = bellmanFord1 (V3 , E3 , 0 );
190+ System .out .println ("bellmanFord1 distances from 0: " + (d3 == null ? "NEGATIVE CYCLE" : Arrays .toString (d3 )));
191+
192+ // 4) Disconnected graph (unreachable nodes remain INF)
193+ int V4 = 6 ;
194+ int [][] E4 = {
195+ {0 ,1 ,3 }, {1 ,2 ,4 }, // component 1
196+ {3 ,4 ,1 }, {4 ,5 ,1 } // component 2 (disconnected from src=0)
197+ };
198+ System .out .println ("\n Case 4: Disconnected graph (src=0)" );
199+ System .out .println ("hasNegativeCycleAnywhere: " + hasNegativeCycleAnywhere (V4 , E4 )); // false
200+ long [] d4 = bellmanFord1 (V4 , E4 , 0 );
201+ System .out .println ("bellmanFord1 distances from 0: " + Arrays .toString (d4 ));
202+ // Expect: [0, 3, 7, INF, INF, INF]
203+
204+ // 5) Single vertex, no edges
205+ int V5 = 1 ;
206+ int [][] E5 = {};
207+ System .out .println ("\n Case 5: Single node (src=0)" );
208+ System .out .println ("hasNegativeCycleAnywhere: " + hasNegativeCycleAnywhere (V5 , E5 )); // false
209+ System .out .println ("bellmanFord1 distances from 0: " + Arrays .toString (bellmanFord1 (V5 , E5 , 0 )));
210+ }
211+ }
212+
0 commit comments