Adjacent Unique Sequences
Problem Statement
You are given a sequence of integers.
You would like to determine if follows the following defintion:
- An adjacent subsequence for any value of and any starting index is a subsequence of such that all of its elements are adjacent.
- Every adjacent subsequence in contains at least one unique element.
Output true
if follows the above properties or false
if it doesn’t.
Bonus (but not required): Write a proof of correctness for your algorithm.
public boolean adjacentSequences(int[] s) {
# your code here
}
Examples
Example 1
Input: [1]
Output: True
There are two subsequences one is []
which is our empty subsequence, and [1]
where 1
is a unique element.
Example 2
Input: [1, 1]
Output: False
The subsequence [1, 1]
is a nonempty adjacent subsequence that contains no unique element, 1
is not unique.
Example 3
Input: [1, 2, 3, 2]
Output: True
The possible adjacent subsequences are: [1]
, [2]
, [3]
, [1, 2]
, [1, 2, 3]
, [1, 2, 3, 2]
, [2, 3, 2]
, [3, 2]
. All of these contain a unique element.
For example in [2, 3, 2]
the 3
is a unique element.
Example 4
Input: [1, 2, 2, 3]
Output: False
The subsequence [2,2]
does not contain a unique element.
Example 5
Input: [1, 2, 1, 2, 4]
Output: False
The subsequence [1, 2, 1, 2]
does not contain a unique element.
Extra Test Cases
Input: [1, 2, 3, 1, 2]
Output: True
Input: [1, 2, 1]
Output: True
Input: [1, 2, 2, 1, 4, 5, 6, 7, 8]
Output: False
Hints
Click Here
-
Think about how you can solve this in one pass using the concept of ‘states’ and history.
-
Try playing around with the idea of how you can solve this logically by simulating the behavior of an “unordered stack.”
-
Consider a single state where unique element mapped to its current count (mod 2).
Click below for the solution
Click Here
Algorithm Description
Consider a state of traversal, with each unique element mapped to its current count (mod 2). Note: before traversal our is an empty map as no elements have been seen, which is an implicit . Traverse the sequence one element at a time, each time creating a new with the updated count. For each , hash all states into a statelookup to allow efficient collision detection. If we reach some , return false, as we have found a nonempty adjacent subsequence with no unique element. Upon traversing to the end of the list, return true as none was found.
Note for coding: Because we are using a unique element mapped to its current count (mod 2). We can effectively store each state in a HashSet as the field of integers modulo 2 (similar to ) which only contain elements 0 and 1 which is essentially equivalent to the same state that can be modeled by a HashSet.
Proof of Correctness
Consider the state collision , and the elements traversed . To go from to , we have processed the new elements . Because , we know that for all , . This means that the element path between the two states contains no unique element. This element path is nonempty bceause . Trivially, the path between the two states is an adjacent subsequence, and therefore we have found a nonempty adjacent subsequence that contains no unique element.
If we traverse the list with no such collision, there is no such path and therefore no such nonempty adjacent subsequence that contains no unique element.
Time and Space Complexity
Time: , we do one pass through out list.
Space: , for unique elements, as worst case we store every state with each unique element.
Sample Solution
import java.util.HashSet;
public boolean adjacentSequences(int[] s) {
HashSet<HashSet<Integer>> previousStates = new HashSet<>();
HashSet<Integer> currState = new HashSet<>();
previousStates.add(currState); //empty state
for (int i = 0; i < s.length; i++) {
int e = s[i];
if (currState.contains(e)) { // if current count (mod 2) == 1
currState.remove(e);
} else { // if current count (mod 2) == 0
currState.add(e);
}
HashSet<Integer> currStateClone = (HashSet<Integer>) currState.clone(); //SEE NOTE
if (!previousStates.add(currStateClone)) { //if we have reached this state before
return false;
}
}
return true;
}
NOTE: We make a copy of the set to get around the fact that when we add currState to previousStates, a pointer to currState will be added. This is because currState is a HashSet Object and when mutated, previousStates will also be mutated.
However, this means in the worst case scenarios the code above will actually run in time on certain inputs such as [1, 2, 3, 4, 5]
. To truly run in time, we can hash our current state as a primitive variable type, such as an integer, but the code given above is good enough for most normal-use cases and is much easier conceptually to understand.