<p>There is an <strong>undirected</strong> graph consisting of <code>n</code> nodes numbered from <code>1</code> to <code>n</code>. You are given the integer <code>n</code> and a <strong>2D</strong> array <code>edges</code> where <code>edges[i] = [a<sub>i</sub>, b<sub>i</sub>]</code> indicates that there is an edge between nodes <code>a<sub>i</sub></code> and <code>b<sub>i</sub></code>. The graph can be disconnected.</p>

<p>You can add <strong>at most</strong> two additional edges (possibly none) to this graph so that there are no repeated edges and no self-loops.</p>

<p>Return <code>true</code><em> if it is possible to make the degree of each node in the graph even, otherwise return </em><code>false</code><em>.</em></p>

<p>The degree of a node is the number of edges connected to it.</p>

<p>&nbsp;</p>
<p><strong>Example 1:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2022/10/26/agraphdrawio.png" style="width: 500px; height: 190px;" />
<pre>
<strong>Input:</strong> n = 5, edges = [[1,2],[2,3],[3,4],[4,2],[1,4],[2,5]]
<strong>Output:</strong> true
<strong>Explanation:</strong> The above diagram shows a valid way of adding an edge.
Every node in the resulting graph is connected to an even number of edges.
</pre>

<p><strong>Example 2:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2022/10/26/aagraphdrawio.png" style="width: 400px; height: 120px;" />
<pre>
<strong>Input:</strong> n = 4, edges = [[1,2],[3,4]]
<strong>Output:</strong> true
<strong>Explanation:</strong> The above diagram shows a valid way of adding two edges.</pre>

<p><strong>Example 3:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2022/10/26/aaagraphdrawio.png" style="width: 150px; height: 158px;" />
<pre>
<strong>Input:</strong> n = 4, edges = [[1,2],[1,3],[1,4]]
<strong>Output:</strong> false
<strong>Explanation:</strong> It is not possible to obtain a valid graph with adding at most 2 edges.</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>3 &lt;= n &lt;= 10<sup>5</sup></code></li>
	<li><code>2 &lt;= edges.length &lt;= 10<sup>5</sup></code></li>
	<li><code>edges[i].length == 2</code></li>
	<li><code>1 &lt;= a<sub>i</sub>, b<sub>i</sub> &lt;= n</code></li>
	<li><code>a<sub>i</sub> != b<sub>i</sub></code></li>
	<li>There are no repeated edges.</li>
</ul>

class Solution:
    def isPossible(self, n: int, edges: List[List[int]]) -> bool:
        # Let's build an undirected graph from our edges
        g = {}
        # Since the task mentions there should be disconnected parts,
        # let's explicitly add every node
        for i in range(1, n + 1):
            g[i] = set()

        # In this loop, let's connect our nodes
        for fr, to in edges:
            g[fr].add(to)
            g[to].add(fr)

        # This function will return the nodes that need to be connected
        def to_join():
            return [key for key, val in g.items() if len(val) % 2 != 0]
        
        # This function checks if we can connect two nodes,
        # given the restriction that we can't connect the same nodes
        # more than once
        def can_connect(a, b):
            return b not in g[a] and a not in g[b]
        
        # This function connects two nodes
        def connect(a, b):
            g[a].add(b)
            g[b].add(a)
        
        # This function disconnects two nodes (we need this for backtracking)
        def disconnect(a, b):
            g[a].discard(b)
            g[b].discard(a)
        
        # Backtrace odd values to find proper connections
        def backtrace(edges):
            join_vals = to_join()
            if edges == 0 or not join_vals:
                return len(join_vals) == 0
            
            for i in join_vals:
                for j in join_vals:
                    if i != j and can_connect(i, j):
                        connect(i, j)
                        if backtrace(edges - 1):
                            return True
                        disconnect(i, j)
            return False
        
        # Special case to connect two odd nodes to one even node
        def join_two():
            join_vals = to_join()
            if len(join_vals) != 2:
                return False
            a, b = join_vals
            return any(x for x, conn in g.items() if a not in conn and b not in conn)

        # Before we start connecting nodes, let's consider if we can succeed at all
        if len(to_join()) > 4 or len(to_join()) == 1:
            return False
        
        # Let's also consider the case when all nodes are even 
        if len(to_join()) == 0:
            return True
        
        # Try backtracing and the special case if the first attempt fails
        res = backtrace(2) or join_two()

        return res
        
73259