1:  $\epsilon_{\mathrm{best}},\mathcal{L}_{\mathrm{lower}}^{\mathrm{best}},\beta_{\mathrm{upper}}^{\mathrm{best}},\mathcal{L}_{\mathrm{upper}}^{\mathrm{best}} = \infty , - \infty ,0$  0 Initialize best optimality gap to  $\infty$  best lower bound to  $-\infty$  best solution to 0 and its loss to 0.
2:  $\mathcal{Q} \gets$  initialize an empty queue (FIFO) structure  $>FIFO$  corresponds to a breadth-first search.
3:  $\mathcal{N}_{\mathrm{select}} = \emptyset, \mathcal{N}_{\mathrm{avoid}} = \emptyset \triangleright$  Coordinates in  $\mathcal{N}_{\mathrm{select}}$  can be selected during heuristic search and lower bound calculation for a node; Coordinates in  $\mathcal{N}_{\mathrm{avoid}}$  must be avoided.
4: RootNode = CreateNode(D, k, λ2, N_select, N_avoid) ▷ Create the first node, which is the root node in the BnB tree.
5:  $\mathcal{Q} = \mathcal{Q}$  .push(RootNode) Put the root node in the queue.
6:  $l_{\text{unsolved}} = 1$  ▷Smallest depth where at least one node is unsolved in the BnB tree
7: WHILE  $\mathcal{Q} \neq \emptyset$  and  $T_{\mathrm{elapsed}} < T_{\mathrm{max}}$  do >Keep searching until the time limit is reached or there is no node in the queue.
8: Node = Q.getNode()
9:  $\mathcal{L}_{\mathrm{lower}} =$  Node.lowersolveFast()  $\triangleright$  Get the lower bound for this node through the fast method
10: if  $\mathcal{L}_{\mathrm{lower}} < \mathcal{L}_{\mathrm{best}}^{\mathrm{upper}}$  then  $\mathcal{L}_{\mathrm{lower}} = \mathrm{Node.lowestTight()} \triangleright \mathrm{Get a tighter lower bound for this}$  node through the subgradient ascent
1: if  $\mathcal{L}_{\mathrm{lower}} \geq \mathcal{L}_{\mathrm{best}}^{\mathrm{upper}}$  then continue  $▷$ Prune Node if its lower bound is higher than or equal to the current best upper bound.
2: if All nodes in depth  $l_{\mathrm{unsolved}}$  of the BnB tree are solved then
3:  $\mathcal{L}_{\mathrm{lower}}^{\mathrm{best}} = \max_q(\{\mathcal{L}^q,\mathrm{for}q\in \{1,2,\dots,l_{\mathrm{unsolved}}\})$ $Tighten$  the best lower bound;  $\mathcal{L}^q$  is the smallest lower bound of all nodes in depth q
4:  $l_{\mathrm{unsolved}} = l_{\mathrm{unsolved}} + 1$  Raise up the smallest unsolved depth by 1
5:  $\epsilon_{\mathrm{best}} = (\mathcal{L}_{\mathrm{upper}}^{\mathrm{best}} - \mathcal{L}_{\mathrm{lower}}^{\mathrm{best}}) / |\mathcal{L}_{\mathrm{upper}}^{\mathrm{best}}|$
6: if  $\epsilon_{\mathrm{best}} < \epsilon$  then return  $\beta_{\mathrm{best}} > \text{Optimality gap tolerance is reached, return the current best solution}$
7:  $\beta_{\mathrm{upper}},\mathcal{L}_{\mathrm{upper}} = \mathrm{Node.}u p p e r S o l v e()$  >Get feasible sparse solution and its loss from beam search, which is an upper bound for our BnB tree.
8: if  $\mathcal{L}_{\mathrm{upper}} < \mathcal{L}_{\mathrm{upper}}^{\mathrm{best}}$  then
9:  $\mathcal{L}_{\mathrm{upper}}^{\mathrm{best}},\beta_{\mathrm{upper}}^{\mathrm{best}} = \mathcal{L}_{\mathrm{upper}},\beta_{\mathrm{upper}}$  Update our best solution and best upper bound
20:  $\epsilon_{\mathrm{best}} = (\mathcal{L}_{\mathrm{upper}}^{\mathrm{best}} - \mathcal{L}_{\mathrm{lower}}^{\mathrm{best}}) / |\mathcal{L}_{\mathrm{upper}}^{\mathrm{best}}|$
21: if  $\epsilon_{\mathrm{best}} < \epsilon$  then return  $\beta_{\mathrm{best}} > \text{Optimality gap tolerance is reached, return the current best solution}$
22:  $j =$  Node.getBranchCoord  $(\beta_{\mathrm{upper}})$  ▷Get which feature coordinate to branch
23: ChildNode1 = CreateNode(D, k, λ2, (Node → N_select) ∪ {j}, Node → N_avoid) ▷ New child node where feature j must be selected
24: ChildNode2 = CreateNode(D, k, λ2, Node → Nselect, (Node → Navoid) ∪ {j}) ▷New child node where feature j must be avoided
25: Q.push(ChildNode1, ChildNode2) ▷Put the two newly generated nodes into our queue.
26: return  $\beta_{\mathrm{best}}$  Return the current best solution when time limit is reached or there is no node in the queue