2-SAT (2-Satisfiability) — Interview Preparation¶
One-line summary: Everything you need to discuss 2-SAT in an interview — the implication-graph + SCC reduction, the SAT criterion, assignment recovery, modeling tricks, and complexity boundaries — plus 42 graded Q&A, 5 behavioral prompts, and 4 full coding challenges in Go, Java, and Python.
Table of Contents¶
- Quick-Reference Cheat Sheet
- Junior Questions (12 Q&A)
- Middle Questions (12 Q&A)
- Senior Questions (10 Q&A)
- Professional Questions (8 Q&A)
- Behavioral / System-Design Questions (5)
- Coding Challenges
Quick-Reference Cheat Sheet¶
| Item | Fact |
|---|---|
| Problem | Is a 2-CNF formula (AND of clauses, each an OR of ≤ 2 literals) satisfiable? |
| Key rewrite | (a ∨ b) ≡ (¬a ⇒ b) ∧ (¬b ⇒ a) |
| Graph | Implication graph: 2n vertices (one per literal), 2m directed edges |
| Literal encoding | xi=true → 2i, xi=false → 2i+1, neg(v) = v ^ 1 |
| Edges per clause | exactly two: neg(a) → b and neg(b) → a |
Unit clause (a) | model as (a ∨ a) → edge neg(a) → a |
| SAT criterion | satisfiable ⇔ no variable x has x and ¬x in the same SCC |
| Assignment | xi = true iff comp[2i] < comp[2i+1] (Tarjan reverse-topo numbering) |
| Complexity | O(n + m) time and space (optimal) |
| SCC engine | Tarjan (1 pass) or Kosaraju (2 passes); sibling topic 08-tarjan-scc |
| Why 2 is special | 3-SAT is NP-complete; 2-SAT is in P (NL-complete) |
| Counting | #2-SAT is #P-complete (hard) |
| Optimization | MAX-2-SAT is NP-hard |
| Random threshold | SAT whp if m/n < 1, UNSAT whp if m/n > 1 |
| Property | skew-symmetric: edge p→q implies edge ¬q→¬p |
30-second pitch: "2-SAT decides if a boolean formula with two-literal clauses is satisfiable. Rewrite each clause as two implications, build a directed implication graph over 2n literal-vertices, run SCC. It's satisfiable iff no variable and its negation share a component. Read the assignment from the reverse-topological SCC order. All linear time. Three literals and it becomes NP-complete."
Junior Questions (12 Q&A)¶
J1. What is 2-SAT?¶
The problem of deciding whether a boolean formula in 2-CNF — an AND of clauses where each clause is an OR of at most two literals — has a satisfying true/false assignment.
J2. What is a literal, a clause, and 2-CNF?¶
A literal is a variable xi or its negation ¬xi. A clause is an OR of literals. 2-CNF is an AND of clauses, each clause an OR of at most two literals.
J3. What is the single key idea that makes 2-SAT solvable quickly?¶
A clause (a ∨ b) is logically equivalent to two implications: (¬a ⇒ b) ∧ (¬b ⇒ a). This lets you model the whole formula as a directed graph and reduce satisfiability to a graph property.
J4. What is the implication graph?¶
A directed graph with 2n vertices — one for each literal (xi and ¬xi). Each clause (a ∨ b) adds two edges: ¬a → b and ¬b → a. An edge p → q means "if p is true, q must be true."
J5. How do you encode literals as vertices in code?¶
xi = true is vertex 2i; xi = false (i.e. ¬xi) is vertex 2i + 1. The negation of a literal-vertex v is v ^ 1 (XOR by 1 flips the low bit).
J6. What is the satisfiability criterion?¶
The formula is satisfiable if and only if no variable x has both x and ¬x in the same strongly connected component of the implication graph.
J7. Why does x and ¬x in the same SCC mean unsatisfiable?¶
Same SCC means x reaches ¬x and ¬x reaches x. So x = true forces x = false and vice versa — a contradiction no matter what value you pick.
J8. What's the time complexity and why?¶
O(n + m): building the graph is O(m), the SCC pass is O(2n + 2m) = O(n+m), and the criterion check plus assignment recovery is O(n). It's optimal since you must read every clause.
J9. How do you handle a unit clause (a)?¶
Model it as (a ∨ a), which becomes the single edge ¬a ⇒ a. Reaching ¬a forces a, so the only consistent value is a = true.
J10. What algorithm computes the SCCs?¶
Tarjan's algorithm (one DFS pass) or Kosaraju's (two passes). See the sibling topic 08-tarjan-scc.
J11. How many edges does each clause contribute?¶
Exactly two — the implication and its contrapositive. You never add four; the two-edge construction already includes both contrapositives.
J12 (analysis). Why is 3-SAT hard but 2-SAT easy?¶
In a 2-clause, forcing one literal false forces the other single literal true — a clean implication (one edge). In a 3-clause, forcing one literal false leaves a 2-clause — still a choice, not a forced literal — so you can't capture it with simple edges. That branching is the line between P (2-SAT) and NP-complete (3-SAT).
Middle Questions (12 Q&A)¶
M1. Walk through building the implication graph for (x0 ∨ ¬x1).¶
(x0 ∨ ¬x1) gives ¬x0 ⇒ ¬x1 and ¬(¬x1) ⇒ x0 = x1 ⇒ x0. As vertices: ¬x0 = 1, ¬x1 = 3, x1 = 2, x0 = 0. Edges: 1 → 3 and 2 → 0.
M2. How do you recover a satisfying assignment?¶
Compute SCCs with Tarjan (reverse-topo numbering). For each variable i, set xi = true iff comp[2i] < comp[2i+1] — pick the literal whose SCC is later in topological order (downstream), since downstream literals don't force upstream contradictions.
M3. Why does the assignment rule use < and not >?¶
Tarjan numbers SCCs in reverse topological order (sink first, id 0). Smaller id = later in topological order = downstream. We want the downstream literal, so smaller comp for the chosen literal → comp[2i] < comp[2i+1] for xi = true. With Kosaraju forward order it would flip to >. Always verify empirically.
M4. What does skew-symmetry of the implication graph mean and why does it matter?¶
For every edge p → q there is a mirror edge ¬q → ¬p (the contrapositive). It guarantees the SCC of ¬p is the mirror of the SCC of p and the two are distinct when SAT — which is exactly why the downstream-pick assignment is globally consistent.
M5. How do you model "exactly one of a, b is true"?¶
Two clauses: (a ∨ b) for "at least one" and (¬a ∨ ¬b) for "at most one." This is also the "must differ" constraint used in 2-coloring.
M6. How do you force xi = true?¶
Add the unit clause (xi ∨ xi) → edge ¬xi ⇒ xi. To force false, add (¬xi ∨ ¬xi).
M7. How do you encode "if A then B"?¶
A ⇒ B is the clause (¬A ∨ B). One clause, two edges (A → B and ¬B → ¬A).
M8. How do you encode "at most one of k variables is true" without O(k^2) clauses?¶
Use auxiliary prefix variables pi ("some xj, j ≤ i, is true") with clauses xi ⇒ pi, p(i-1) ⇒ pi, and xi ⇒ ¬p(i-1). This is O(k) clauses and O(k) extra variables.
M9. Compare Tarjan vs Kosaraju for 2-SAT.¶
Both O(n+m). Tarjan is one DFS pass with no transpose graph and reverse-topo numbering. Kosaraju is two passes and builds the transpose (more memory). Tarjan is the usual choice; just lock in the assignment-comparison convention for whichever you pick.
M10. Why must you use iterative SCC for large inputs?¶
Recursive DFS overflows the call stack once 2n exceeds ~10^5–10^6. The explicit-stack iterative Tarjan avoids that.
M11. How would you test a 2-SAT solver?¶
Brute-force oracle over 2^n for small n, fuzz thousands of random formulas comparing verdicts, and when SAT, re-evaluate every clause under the recovered assignment to catch direction/convention bugs.
M12 (analysis). Even though deciding 2-SAT is easy, what related questions are hard?¶
Counting satisfying assignments (#2-SAT) is #P-complete, and maximizing the number of satisfied clauses (MAX-2-SAT) is NP-hard. The easy decision problem does not make the counting or optimization variants easy.
Senior Questions (10 Q&A)¶
S1. How would you design 2-SAT as a service for config validation?¶
Separate the modeling layer (domain rules → clauses) from a stateless solver (solve(n, clauses) → SAT/assignment). The solver is a pure function: cacheable, parallelizable, trivially testable. All correctness risk lives in modeling, so concentrate testing there.
S2. A config is rejected as UNSAT. How do you tell the user why?¶
Bare 2-SAT returns only "no." When you detect comp[2i] == comp[2i+1], extract a cycle through both xi and ¬xi; the clauses on that cycle form a conflict core. Surface those specific rules as the explanation.
S3. How do you handle a formula with millions of variables and clauses?¶
Use CSR adjacency (offsets + packed edges) instead of list-of-lists for cache locality and low allocation, 32-bit indices when 2n < 2^31, iterative Tarjan (no recursion), and build edges via a streaming two-pass count-then-fill. Graph construction often dominates the SCC pass.
S4. Can you parallelize a single 2-SAT solve?¶
The SCC DFS is inherently sequential. Parallelize around it: independent instances on separate workers, or disconnected components of one formula in parallel. Don't lock a single Tarjan DFS — contention kills the linear advantage.
S5. Why choose 2-SAT over a general SAT/CSP solver when it fits?¶
Bounded latency. 2-SAT is always O(n+m), never times out, never needs heuristics — safe in a hard-latency request path. A CDCL solver can hit pathological exponential inputs. Give up generality for predictability when the constraints are genuinely 2-CNF.
S6. How do you answer "must xi be true in every satisfying assignment?"¶
Force xi = false (add (¬xi ∨ ¬xi)) on a copy and re-solve; if it's now UNSAT, then xi is true in every solution. Linear-time per query.
S7. What metrics would you emit from a production 2-SAT subsystem?¶
Input size (vars, clauses), solve_ms histogram, scc_count, unsat_rate, and conflict_core_size. A spike in clauses signals a modeling explosion; a jump in unsat_rate usually signals a bad modeling deploy.
S8. What's the dominant failure mode in practice?¶
Silent direction or numbering-convention bugs (pass small tests, fail at scale) and modeling explosions (algorithm fine, but the modeling layer emitted a huge formula). Mitigate with fuzz+verify and a per-request clause budget.
S9. How do incremental constraint changes affect the design?¶
Each change can re-solve from scratch in O(n+m) — usually fine. Sub-O(n+m) incremental updates require dynamic-SCC machinery and are typically over-engineering; only pursue them under a strict interactive-latency requirement.
S10 (analysis). Where does 2-SAT sit in the complexity hierarchy and why does it matter operationally?¶
2-SAT is NL-complete (and thus in P, in NC). Operationally that means it's not just polynomial but also low-space and parallelizable in principle — reinforcing its role as a cheap, predictable feasibility primitive rather than a heavyweight solver.
Professional Questions (8 Q&A)¶
P1. State and prove the satisfiability theorem.¶
Theorem: φ is SAT iff no xi has xi, ¬xi in one SCC. (⇒) A satisfying α makes every edge truth-preserving; a path xi ⇝ ¬xi would force xi's value to its negation — contradiction, so no such pair. (⇐) With no such pair, assign each variable to the literal whose SCC is later in topological order; using skew-symmetry, a falsified clause would create a strict cycle in the topological total order — impossible. So α satisfies φ.
P2. Prove the assignment-recovery rule is correct under Tarjan numbering.¶
Tarjan finalizes an SCC only after all SCCs reachable from it, so ids decrease along condensation edges: smaller id = later in topological order. The rule xi = true ⟺ comp[2i] < comp[2i+1] therefore picks the literal whose SCC is later in topological order — exactly the construction proved satisfying in the theorem's (⇐) direction.
P3. Why is 2-SAT in P but 3-SAT NP-complete — structurally?¶
A false literal in a 2-clause forces a single literal (unit propagation, one edge, no branching). A false literal in a 3-clause leaves a 2-clause — a disjunction, a branch. That branching is what backtracking SAT solvers must explore; it cannot be captured by simple graph edges, which is the P / NP-complete boundary.
P4. What is the complexity of counting 2-SAT solutions?¶
#P-complete (Valiant). Even though we decide SAT and produce one witness in linear time, counting all satisfying assignments is as hard as counting solutions to any NP-search problem. No polynomial counting algorithm is known.
P5. What about maximizing satisfied clauses (MAX-2-SAT)?¶
NP-hard (APX-complete). Reduces from MAX-CUT; hard to approximate beyond a fixed ratio (best known polynomial approximation ~0.940 via semidefinite programming). Full satisfiability is linear, but the optimization relaxation is intractable.
P6. Describe the random 2-SAT satisfiability threshold.¶
For random R(n, m) with density ρ = m/n: as n → ∞, SAT whp when ρ < 1, UNSAT whp when ρ > 1; sharp transition at ρ = 1. It mirrors the emergence of a giant SCC in the random implication digraph. The algorithm stays O(n+m) regardless — the threshold concerns the answer's distribution, not runtime.
P7. Why is the assignment-reading convention a recurring bug source, and how do you prove your implementation correct?¶
The correct comparison (< vs >) depends on whether the SCC routine numbers reverse- or forward-topologically. The fix is empirical proof: after solving, re-evaluate every clause under the recovered assignment and assert satisfaction; pair this with a brute-force fuzz oracle for small n. The proof that some consistent rule exists is the assignment theorem; which comparison realizes it is implementation-specific.
P8 (analysis). 2-SAT is in NC — why don't we usually run it in parallel?¶
2-SAT ∈ NL ⊆ NC₂, so it's theoretically efficiently parallelizable. In practice, work-efficient parallel SCC is hard to implement and the sequential O(n+m) solve is already milliseconds-fast; the parallel constant factors and synchronization overhead rarely pay off. The NC membership is a structural statement, not a practical mandate.
Behavioral / System-Design Questions (5)¶
B1. Tell me about a time you modeled a messy real-world problem as a clean algorithm.¶
Structure: Situation (binary-choice constraints buried in business rules), Task (decide feasibility fast), Action (recognized it as 2-CNF, built a modeling layer mapping rules → clauses, used a generic SCC solver), Result (linear-time exact feasibility with a witness, replacing a slow heuristic). Emphasize separating modeling from solving.
B2. Design a "seat assignment / two-room" feasibility checker.¶
Each guest is a boolean (room A / room B). Constraints ("X and Y can't share a room," "if X in A then Y in B") become 2-CNF clauses. Run 2-SAT for feasibility plus a concrete assignment; on infeasible, extract the conflict core to tell organizers which rules clash. Discuss latency budget and why 2-SAT's bounded runtime fits a request path.
B3. A teammate's 2-SAT solver passes unit tests but gives wrong answers in production. How do you debug?¶
Reproduce with the production input; add a fuzz harness against a brute-force 2^n oracle for small n; assert the recovered assignment re-satisfies every clause. Most likely culprits: edge direction (a→b vs ¬a→b) or the </> assignment convention after a SCC-library swap. Show systematic narrowing, not guessing.
B4. How would you decide between 2-SAT and a general SAT solver for a new feature?¶
Check expressiveness: are all constraints binary-variable with ≤2-literal clauses? If yes, 2-SAT gives linear, predictable, never-times-out behavior — ideal for a latency-bound path. If any constraint needs 3+ literals or non-boolean domains, use a real SAT/CSP solver and accept unpredictable tail latency, or move it off the hot path.
B5. Describe a trade-off you made between generality and predictability.¶
Talk through choosing the specialized 2-SAT solver over a general one: you lose the ability to express richer constraints but gain a hard latency bound and a correctness proof. Frame it as fitting the tool to the actual constraint shape and guarding the boundary (reject inputs that don't fit rather than bending the tool).
Coding Challenges¶
Challenge 1 — Basic satisfiability (decision only)¶
Problem. Given n variables and a list of 2-literal clauses, return whether the formula is satisfiable.
Approach. Build implication graph, run Tarjan SCC, return false iff some comp[2i] == comp[2i+1].
Go¶
package main
import "fmt"
func feasible(n int, clauses [][4]int) bool {
adj := make([][]int, 2*n)
node := func(v int, val int) int {
if val == 1 {
return 2 * v
}
return 2*v + 1
}
neg := func(x int) int { return x ^ 1 }
for _, c := range clauses {
x, y := node(c[0], c[1]), node(c[2], c[3])
adj[neg(x)] = append(adj[neg(x)], y)
adj[neg(y)] = append(adj[neg(y)], x)
}
N := 2 * n
index := make([]int, N)
low := make([]int, N)
on := make([]bool, N)
comp := make([]int, N)
for i := range index {
index[i], comp[i] = -1, -1
}
var stk []int
idx, cc := 0, 0
for s := 0; s < N; s++ {
if index[s] != -1 {
continue
}
type fr struct{ v, p int }
st := []fr{{s, 0}}
for len(st) > 0 {
f := &st[len(st)-1]
v := f.v
if f.p == 0 {
index[v], low[v] = idx, idx
idx++
stk = append(stk, v)
on[v] = true
}
if f.p < len(adj[v]) {
w := adj[v][f.p]
f.p++
if index[w] == -1 {
st = append(st, fr{w, 0})
} else if on[w] && index[w] < low[v] {
low[v] = index[w]
}
continue
}
if low[v] == index[v] {
for {
w := stk[len(stk)-1]
stk = stk[:len(stk)-1]
on[w] = false
comp[w] = cc
if w == v {
break
}
}
cc++
}
st = st[:len(st)-1]
if len(st) > 0 {
p := st[len(st)-1].v
if low[v] < low[p] {
low[p] = low[v]
}
}
}
}
for i := 0; i < n; i++ {
if comp[2*i] == comp[2*i+1] {
return false
}
}
return true
}
func main() {
fmt.Println(feasible(2, [][4]int{{0, 1, 1, 1}, {0, 0, 1, 1}, {0, 0, 1, 0}})) // true
fmt.Println(feasible(1, [][4]int{{0, 1, 0, 1}, {0, 0, 0, 0}})) // false
}
Java¶
import java.util.*;
public class Feasible {
static boolean feasible(int n, int[][] clauses) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < 2 * n; i++) adj.add(new ArrayList<>());
for (int[] c : clauses) {
int x = c[1] == 1 ? 2 * c[0] : 2 * c[0] + 1;
int y = c[3] == 1 ? 2 * c[2] : 2 * c[2] + 1;
adj.get(x ^ 1).add(y);
adj.get(y ^ 1).add(x);
}
int N = 2 * n;
int[] index = new int[N], low = new int[N], comp = new int[N];
boolean[] on = new boolean[N];
Arrays.fill(index, -1);
Arrays.fill(comp, -1);
int[] stk = new int[N];
int sp = 0, idx = 0, cc = 0;
for (int s = 0; s < N; s++) {
if (index[s] != -1) continue;
Deque<int[]> st = new ArrayDeque<>();
st.push(new int[]{s, 0});
while (!st.isEmpty()) {
int[] f = st.peek();
int v = f[0];
if (f[1] == 0) { index[v] = low[v] = idx++; stk[sp++] = v; on[v] = true; }
if (f[1] < adj.get(v).size()) {
int w = adj.get(v).get(f[1]++);
if (index[w] == -1) st.push(new int[]{w, 0});
else if (on[w]) low[v] = Math.min(low[v], index[w]);
continue;
}
if (low[v] == index[v]) {
while (true) { int w = stk[--sp]; on[w] = false; comp[w] = cc; if (w == v) break; }
cc++;
}
st.pop();
if (!st.isEmpty()) low[st.peek()[0]] = Math.min(low[st.peek()[0]], low[v]);
}
}
for (int i = 0; i < n; i++) if (comp[2 * i] == comp[2 * i + 1]) return false;
return true;
}
public static void main(String[] args) {
System.out.println(feasible(2, new int[][]{{0,1,1,1},{0,0,1,1},{0,0,1,0}})); // true
System.out.println(feasible(1, new int[][]{{0,1,0,1},{0,0,0,0}})); // false
}
}
Python¶
def feasible(n, clauses):
adj = [[] for _ in range(2 * n)]
node = lambda v, val: 2 * v if val else 2 * v + 1
for a, va, b, vb in clauses:
x, y = node(a, va), node(b, vb)
adj[x ^ 1].append(y)
adj[y ^ 1].append(x)
N = 2 * n
index = [-1] * N; low = [0] * N; comp = [-1] * N; on = [False] * N
stk = []; idx = cc = 0
for s in range(N):
if index[s] != -1:
continue
st = [(s, 0)]
while st:
v, p = st[-1]
if p == 0:
index[v] = low[v] = idx; idx += 1; stk.append(v); on[v] = True
if p < len(adj[v]):
w = adj[v][p]; st[-1] = (v, p + 1)
if index[w] == -1: st.append((w, 0))
elif on[w]: low[v] = min(low[v], index[w])
continue
if low[v] == index[v]:
while True:
w = stk.pop(); on[w] = False; comp[w] = cc
if w == v: break
cc += 1
st.pop()
if st:
pp = st[-1][0]; low[pp] = min(low[pp], low[v])
return all(comp[2 * i] != comp[2 * i + 1] for i in range(n))
print(feasible(2, [(0, True, 1, True), (0, False, 1, True), (0, False, 1, False)])) # True
print(feasible(1, [(0, True, 0, True), (0, False, 0, False)])) # False
Challenge 2 — Recover a satisfying assignment (the boolean puzzle)¶
Problem. Return an actual satisfying assignment, or report UNSAT. (This is the full solver — reuse the junior/middle Solve.) Use the assignment rule xi = true ⟺ comp[2i] < comp[2i+1].
Key point to state in interview: after the SCC pass and SAT check, recovery is a single O(n) loop comparing the two component ids per variable. Always re-verify by re-evaluating clauses.
# Reuse feasible()'s SCC computation but return comp, then:
def solve(n, clauses):
# ... identical SCC pass producing comp[] ...
# for brevity, call the middle.md TwoSAT.solve(); assignment line:
# assign[i] = comp[2*i] < comp[2*i+1]
...
The complete, verified solve appears in middle.md (with a clause-re-evaluation verify). State the rule and the verification step; that is what interviewers want to hear.
Challenge 3 — Two-room assignment ("choose one of two" puzzle)¶
Problem. n people, each must go to room A (true) or room B (false). Given a list of "pair (u, v) cannot share room A" and "pair (u, v) must be in different rooms" constraints, decide if a valid placement exists and produce one.
Modeling. - "u, v cannot both be in A" → (¬xu ∨ ¬xv) → add_clause(u, false, v, false). - "u, v must differ" → (xu ∨ xv) ∧ (¬xu ∨ ¬xv) → two clauses.
Go¶
package main
import "fmt"
// uses TwoSAT from junior.md (NewTwoSAT, AddClause, Solve)
func main() {
ts := NewTwoSAT(3) // 0,1,2 ; true = room A
ts.AddClause(0, false, 1, false) // 0 and 1 not both in A
// 1 and 2 must differ:
ts.AddClause(1, true, 2, true)
ts.AddClause(1, false, 2, false)
if a, ok := ts.Solve(); ok {
fmt.Println("placement (true=A):", a)
} else {
fmt.Println("infeasible")
}
}
Java¶
// uses TwoSAT from junior.md (addClause, solve)
public class Rooms {
public static void main(String[] args) {
TwoSAT ts = new TwoSAT(3);
ts.addClause(0, false, 1, false); // not both in A
ts.addClause(1, true, 2, true); // 1,2 differ (at least one A)
ts.addClause(1, false, 2, false); // 1,2 differ (at most one A)
boolean[] a = ts.solve();
System.out.println(a == null ? "infeasible" : "placement: " + java.util.Arrays.toString(a));
}
}
Python¶
# uses TwoSAT from junior.md (add_clause, solve)
def two_room(n, not_both_A, must_differ):
ts = TwoSAT(n)
for u, v in not_both_A:
ts.add_clause(u, False, v, False)
for u, v in must_differ:
ts.add_clause(u, True, v, True)
ts.add_clause(u, False, v, False)
a = ts.solve()
return a # None if infeasible
print(two_room(3, [(0, 1)], [(1, 2)]))
Talking point: this is graph 2-coloring with extra constraints. 2-SAT subsumes 2-colorability checks when constraints mix "same" and "different."
Challenge 4 — Are all variable-pairs jointly forceable? (forced-literal query)¶
Problem. Given a satisfiable formula, for a query "can xi = true in some satisfying assignment?", answer each query in linear time.
Approach. xi can be true in some assignment iff forcing xi = true (add (xi ∨ xi)) keeps the formula satisfiable. Equivalently, using the SCC structure: xi can be true iff comp[2i] != comp[2i+1] AND it's consistent — but the clean, always-correct method is to re-solve with the forcing clause added to a copy.
Go¶
package main
import "fmt"
func canBeTrue(n int, clauses [][4]int, i int) bool {
c := append([][4]int(nil), clauses...)
c = append(c, [4]int{i, 1, i, 1}) // force xi = true
return feasible(n, c) // feasible() from Challenge 1
}
func main() {
cls := [][4]int{{0, 1, 1, 1}, {0, 0, 1, 1}} // (x0∨x1)∧(¬x0∨x1)
fmt.Println(canBeTrue(2, cls, 0)) // can x0 be true? yes
fmt.Println(canBeTrue(2, cls, 1)) // can x1 be true? yes
}
Java¶
import java.util.*;
public class ForcedQuery {
static boolean canBeTrue(int n, int[][] clauses, int i) {
int[][] c = Arrays.copyOf(clauses, clauses.length + 1);
c[clauses.length] = new int[]{i, 1, i, 1}; // force xi true
return Feasible.feasible(n, c); // from Challenge 1
}
public static void main(String[] args) {
int[][] cls = {{0,1,1,1},{0,0,1,1}};
System.out.println(canBeTrue(2, cls, 0)); // true
System.out.println(canBeTrue(2, cls, 1)); // true
}
}
Python¶
def can_be_true(n, clauses, i):
c = clauses + [(i, True, i, True)] # force xi true
return feasible(n, c) # from Challenge 1
cls = [(0, True, 1, True), (0, False, 1, True)]
print(can_be_true(2, cls, 0)) # True
print(can_be_true(2, cls, 1)) # True
Talking point: each query is an independent linear-time solve. For many queries you can precompute the SCC structure once and answer "is xi forced?" by comparing comp[2i] and comp[2i+1] plus a downstream-reachability check — but the copy-and-resolve approach is the safe, clearly-correct baseline to state first.
Final interview tip: The two things that separate a confident 2-SAT answer from a shaky one are (1) crisply stating the clause→two-edges rewrite and the same-SCC criterion, and (2) acknowledging the assignment-convention subtlety and that you'd verify it by re-evaluating clauses. Mention the 2-vs-3 cliff (P vs NP-complete) to show you understand why the restriction matters.