How would I go about setting an index for each node after generating a binary tree?
(a) (1)
(x) (r) => (2) (3)
(o)(t)(t)(x) (4)(5)(6)(7)
So I can then use a call such as getIndex()at a particular node to return its index.
My tree class:
public class BT<E>{
E value;
BT<E> left, right;
int Index;
public BT(E value)
{
this.value=value;
}
public BT (E value, BT left, BT right)
{
this.value = value;
this.left = left;
this.right = right;
}
Breadth-first traversal.
Queue<BT> queue = new LinkedList<BT>() ;
public void breadth(BT root) {
if (root == null)
return;
queue.clear();
queue.add(root);
int index = 0;
while(!queue.isEmpty()){
BT node = queue.remove();
node.Index = index;
index++;
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
}
Adapted from here.
If you are doing this after the tree is fully created, then something that uses level-order traversal will work. It's not terribly efficient, but it's straight-forward recursion:
/* Method to set index based on level-order traversal of tree */
public void initIndices(BT root) {
int maxIndexSoFar = 0;
for (int d = 1; d <= root.height(); ++d)
maxIndexSoFar = setIndexAtLevel(root, d, maxIndexSoFar);
}
/* Method to set index of all nodes at a given level */
private int setIndexAtLevel(BT node, int level, int index) {
if (tree == null)
return index;
if (level == 1) {
index++;
node.setIndex(index);
return index;
}
else if (level > 1) {
int newIndex = setIndexAtLevel(node.left, level-1, index);
newIndex = setIndexAtLevel(node.right, level-1, newIndex);
return newIndex;
}
return -1;
}
I'll leave you to create the height() method and setIndex() methods. Fair warning, I have not tested this at all, so pardon any typos.
So you are to implement a procedure getIndex(int index) which has to return you the node with that index?
If so, you are looking for an efficient way to represent a binary tree.
You could traverse the tree for each call to getIndex but this wouldn't be efficient...
An efficient solution is to store the complete binary tree in an array, because of the O(1) access it provides. Store a node n at index n in the array and its child nodes at index 2*n and (2*n) - 1. But here the restrictions are that the tree has to be complete and the size of an array is not variable (if the binary tree becomes too big, a bigger array (usually twice as big) should be made and all elements should be copied).
This is a handy solution because :
Node access is in O(1) but a procedure like addNode() would become amortized in O(1). (*)
A node does not have to remember it's child nodes --> this.left becomes this.left() with the implementation of left() provided below.
A possible implementation for left() procedure.
static int[] binaryTreeArray = new int[maxTreeSize]; // BT of integers for example
...
public int left() { // returns integer or ... (type of your nodes)
return binaryTreeArray[(this.Index)*2]; // O(1)
}
(*) An addNode()-like procedure would add nodes in O(1) (binaryTreeArray[index] = nodeValue;) most of the time but when the binaryTreeArray is full it will have to make a bigger array that is usually twice as big (O(n) for the copying). It can be shown that this has an amortized cost of O(1) but this has no added value for this answer.
Related
I am trying to find the "maximum" value in a linked list recursively using a helper function. I am just starting to learn about these in my class and am pretty confused. We have a custom class that defines the type Node and another function to calculate the size of the Node or linkedlist. I solved this problem when I was comparing integers, but with characters I am lost. Here is my code:
'''
static class Node {
public Node (char item, Node next) { this.item = item; this.next = next; }
public char item;
public Node next;
}
Node first; // this is the only instance variable,
// the access point to the list
// size
//
// a function to compute the size of the list, using a loop
// an empty list has size 0
public int size () {
int count = 0;
for (Node tmp = first; tmp != null; tmp = tmp.next)
count++;
return count;
}
/*
* maxCharacter
*
* a function to compute the 'maximum' character in the list using recursion
* You will want to create a helper function to
* do the recursion
*
* precondition: list is not empty
*
* Examples:
* ["ababcdefb"].maxCharacter() == 'f'
* ["eezzg"].maxCharacter() == 'z'
* ["a"].maxCharacter() == 'a'
*/
public char maxCharacter () {
return maxCharacterHelper(first, first.size());
}
public char maxCharacterHelper(Node first, int index) {
char[] alpha = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
int max = 0;
while(index > 0 )
max = alpha.indexOf(first.item) > max ? first.item : max;
maxCharacterHelper(first, index-1);
return max;
}
'''
If you could explain how I would loop through the list recursively while maintaining the greatest char I would greatly appreciate it.
The golden rule with recursion is "Think of the base case first, then write the recurrence".
In this case, the base is the empty list. In this case, the maximum is the last value you've seen.
The recurrence is just a call to the rest of the list with the highest value you've called.
public static MaxNode(Node n, char currentMax) {
if (n == null) // base case, we're at the end.
return currentMax;
// recurrence
return MaxNode(n.next, currentMax > n.item ? currentMax : n.item);
}
For simple ASCII values, you can treat the maximum using the > operator.
Your while loop is confusing because of indentation and because you never change index. However, I don't think you need it if your intent is to use recursion. Generally with recursion you need to establish a base case from which you cannot recurse. For a linked list the natural base case is where there is no next node, rather than index-based.
if (current.next == null)
return alpha.indexOf(current.item);
Otherwise combine the recursion return with the current value
int remainingMax = maxCharacterHelper(current);
int currentValue = alpha.indexOf(current.item);
return (remainingMax > currentValue) ? remainingMax : currentValue;
Here is how I would put it together
//I made it static because it is not a method of a specific Node
public static int maxCharacterHelper(Node currentNode){
// remaining list includes only current node, so this one has max value
if (current.next == null)
return alpha.indexOf(current.item);
//otherwise take the larger of remaining list and current node
int remainingMax = maxCharacterHelper(current.next);
int currentValue = alpha.indexOf(current.item);
return (remainingMax > currentValue) ? remainingMax : currentValue;
}
I've been grinding leetcode recently and am perplexed on why my solution is timing out when I submit it to Leetcode.
Here is the question:
https://leetcode.com/explore/learn/card/data-structure-tree/133/conclusion/942/
Given inorder and postorder traversal of a tree, construct the binary tree.
Note:
You may assume that duplicates do not exist in the tree.
For example, given
inorder = [9,3,15,20,7]
postorder = [9,15,7,20,3]
Return the following binary tree:
3
/ \
9 20
/ \
15 7
Here is my solution that times out in one of the test cases:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (inorder == null || inorder.length == 0) {
return null; // input error
}
if (postorder == null || postorder.length == 0) {
return null; // input error
}
if (postorder.length != inorder.length) {
return null; // input error
}
List<Integer> inOrder = new ArrayList<Integer>();
List<Integer> postOrder = new ArrayList<Integer>();
for (int i = 0; i < inorder.length; i++) {
inOrder.add(inorder[i]);
postOrder.add(postorder[i]);
}
return buildBinaryTree(inOrder, postOrder);
}
public TreeNode buildBinaryTree(List<Integer> inOrder, List<Integer> postOrder) {
boolean found = false;
int root = 0;
int rootIndex = 0;
// for given in-order scan the post-order right to left to find the root
for (int j = postOrder.size() - 1; j >= 0 && !found; j--) {
root = postOrder.get(j);
if (inOrder.contains(root)) {
rootIndex = inOrder.indexOf(root);
root = inOrder.get(rootIndex);
found = true;
break;
}
}
if (found) {
List<Integer> leftOfRoot = new ArrayList<Integer>();
List<Integer> rightOfRoot = new ArrayList<Integer>();
if (rootIndex > 0) {
leftOfRoot.addAll(inOrder.subList(0, rootIndex));
}
if ((rootIndex + 1) < inOrder.size()) {
rightOfRoot.addAll(inOrder.subList(rootIndex + 1, inOrder.size()));
}
TreeNode node = new TreeNode(root);
node.left = buildBinaryTree(leftOfRoot, postOrder);
node.right = buildBinaryTree(rightOfRoot, postOrder);
return node;
}
return null;
}
}
Can anyone help determine why this is happening? I'm thinking it is the Leetcode judge at fault here and my code is fine.
Leetcode's judge is probably OK. This code is too casual about nested linear array operations and heap allocations. Creating ArrayLists and calling contains, addAll, subList and indexOf may appear innocuous, but they should all be thought of as extremely expensive operations when inside a recursive function that spawns two child calls in every frame.
Let's unpack the code a bit:
List<Integer> inOrder = new ArrayList<Integer>();
List<Integer> postOrder = new ArrayList<Integer>();
for (int i = 0; i < inorder.length; i++) {
inOrder.add(inorder[i]);
postOrder.add(postorder[i]);
}
This is a minor up-front cost but it's an omen of things to come. We've done 2 heap allocations that weren't necessary and walked n. I'd stick to primitive arrays here--no need to allocate objects other than the result nodes. A lookup map for inOrder with value -> index pairs might be useful to allocate if you feel compelled to create a supporting data structure here.
Next, we step into buildBinaryTree. Its structure is basically:
function buildBinaryTree(root) {
// do some stuff
if (not base case reached) {
buildBinaryTree(root.left)
buildBinaryTree(root.right)
}
}
This is linear on the number of nodes in the tree, so it's important that // do some stuff is efficient, hopefully constant time. Walking n in this function would give us quadratic complexity.
Next there's
for (int j = postOrder.size() - 1; j >= 0 && !found; j--) {
root = postOrder.get(j);
if (inOrder.contains(root)) {
rootIndex = inOrder.indexOf(root);
This looks bad, but by definition the root is always the last element in a postorder traversal array, so if we keep a pointer to it, we can remove this outer loop. You can use indexOf directly and avoid the contains call since indexOf returns -1 to indicate a failed search.
The code:
if (found) {
List<Integer> leftOfRoot = new ArrayList<Integer>();
List<Integer> rightOfRoot = new ArrayList<Integer>();
does more unnecessary heap allocations for every call frame.
Here,
leftOfRoot.addAll(inOrder.subList(0, rootIndex));
Walks the list twice, once to create the sublist and again to add the entire sublist to the ArrayList. Repeat for the right subtree for two full walks on n per frame. Using start and end indices per call frame means you never need to allocate heap memory or copy anything to prepare the next call. Adjust the indices and pass a reference to the same two arrays along the entire time.
I recommend running your code with a profiler to see exactly how much time is spent copying and scanning your ArrayLists. The correct implementation should do at most one walk through one of the lists per call frame to locate root in inOrder. No array copying should be done at all.
With these modifications, you should be able to pass, although wrangling the pointers for this problem is not obvious. A hint that may help is this: recursively process the right subtree before the left.
Yes, it would be much faster with arrays. Try this:
public static TreeNode buildTree(int[] inorder, int[] postorder, int start,
int end) {
for (int i = postorder.length-1; i >= 0; --i) {
int root = postorder[i];
int index = indexOf(inorder, start, end, root);
if (index >= 0) {
TreeNode left = index == start
? null
: buildTree(inorder, postorder, start, index);
TreeNode right = index+1 == end
? null
: buildTree(inorder, postorder, index+1, end);
return new TreeNode(root, left, right);
}
}
return null;
}
private static int indexOf(int[] array, int start, int end, int value) {
for (int i = start; i < end; ++i) {
if (array[i] == value) {
return i;
}
}
return -1;
}
I have a tree structure.
class Element {
private List<Element> children;
}
Element treeStructure = produceSomeTreeStructure();
//How to get its height and number of elements.
Straight-forward solution is to make two loops. In first I can find number of nodes
A question on getting number of nodes in a Binary Tree
(change this algorithm for non-binary tree),
and the second loop to get the tree's height
http://www.geeksforgeeks.org/iterative-method-to-find-height-of-binary-tree/
Again, adapt this algorithm to non-binary tree.
My question, how to do this in one walk. It is acceptable for me to keep results in global variables.
If you want to know the number of nodes, then you'll need to explore the whole tree. The easiest way to do this is with a depth-first search, where you count the nodes as you go.
A depth-first search algorithm can also easily enough count the depth to which it's currently exploring, and the maximum depth reached overall. Modify the depth-first search algorithm to take both of these as arguments.
If you code it recursively (easiest), then you can simply add one to the depth argument every time you make a recursive call. And if this gives you a number that's greater than the maximum that you're keeping track of, then update the maximum to the current depth.
Yes that can be done as it is shown in the below code. Just add the counter totalNodeCount and do +1 each time you traverse a node in BFS style.
// Iterative method to find height and node-count of Binary Tree
int treeHeightAndNumOfNodes(node *root)
{
// Base Case
if (root == NULL)
return 0;
// Create an empty queue for level order tarversal
queue<node *> q;
// Enqueue Root and initialize height
q.push(root);
int height = 0;
int totalNodeCount = 0; // <-- Use this counter to store total number of node traversed.
while (1)
{
// nodeCount (queue size) indicates number of nodes
// at current lelvel.
int nodeCount = q.size();
if (nodeCount == 0)
return height;
height++;
// Dequeue all nodes of current level and Enqueue all
// nodes of next level
while (nodeCount > 0)
{
node *node = q.front();
q.pop();
if (node->left != NULL)
q.push(node->left);
if (node->right != NULL)
q.push(node->right);
nodeCount--;
totalNodeCount++; // <-- Update this counter
}
}
}
Again, adapt this algorithm to non-binary tree.
To do that, replace the given below lines of codes with a loop that traverse through each child nodes and push the NON-NULL children into queue.
if (node->left != NULL)
q.push(node->left);
if (node->right != NULL)
q.push(node->right);
What you are looking for is a bit like a BFS. You just have to walk your tree like the following:
int getHeight(Element e) {
int max_height = 0;
for (Element child : e.children) {
max_height = max(max_height, getHeight(child));
}
return max_height + 1;
}
Similarly, getting the total number of elements is easy: instead of getting the maximum value among the node's children, you just add them up.
int getTotalCount(Element e) {
int total_count = 0;
for (Element child : e.children) {
total_count += getTotalCount(el);
}
return total_count + 1;
}
If you must return the two numbers using the same function, just pack them in a common class to traverse your tree only once.
Thanks guys for your answers.
And this is what I have coded.
public static TreeData countElementsAndFindHeight(Element root) {
TreeData treePair = new TreeData();
if (root == null) {
return treePair;
}
treePair.nElements = 1;
treePair.height = 1;
//Nodes queue will contain all the elements of the trees, so its size is the number of elements.
List<Element> nodesQueue = new LinkedList<Element>();
treePair.walkedNodes = nodesQueue;
List<Element> children = root.getChildren();
if (CommonUtils.isCollectionEmpty(children)) {
return treePair;
}
treePair.height = countElementsAndFindHeight(root, nodesQueue);
nodesQueue.add(root);
treePair.nElements = nodesQueue.size();
return treePair;
}
private static int countElementsAndFindHeight(Element root, List<Element> nodesQueue) {
int maxHeight = 1;
List<Element> children = root.getChildren();
if (CommonUtils.isCollectionEmpty(children)) {
return maxHeight;
}
for (Element childElement : children) {
int childHeight = countElementsAndFindHeight(childElement, nodesQueue);
if (childHeight > maxHeight) {
maxHeight = childHeight;
}
nodesQueue.add(childElement);
}
return maxHeight + 1;
}
public static class TreeData {
protected int height = 0;
protected int nElements = 0;
}
I was practicing one algorithm exercise today from HackerRank: https://www.hackerrank.com/challenges/find-the-merge-point-of-two-joined-linked-lists
I decided to solve this problem with two solutions.
First algorithm, based on Floyd's algorithm:
/*
Insert Node at the end of a linked list
head pointer input could be NULL as well for empty list
Node is defined as
class Node {
int data;
Node next;
}
*/
int FindMergeNode(Node headA, Node headB) {
// Complete this function
// Do not write the main method.
int length1 = countLength(headA);
int length2 = countLength(headB);
int d = Math.abs(length1 - length2);
return (length1 > length2) ?
findIntersection(d, headA, headB) : findIntersection(d, headB, headA);
}
int countLength(Node head) {
Node current = head;
int counter = 0;
while (current != null) {
current = current.next;
counter++;
}
return counter;
}
int findIntersection(int d, Node headA, Node headB) {
Node currentA = headA;
Node currentB = headB;
for (int i = 0; i < d; i++) {
currentA = currentA.next;
}
while (currentA != null && currentB != null) {
if (currentA == currentB) return currentA.data;
currentA = currentA.next;
currentB = currentB.next;
}
return -1;
}
Second algorithm, using one outer and inner loop:
/*
Insert Node at the end of a linked list
head pointer input could be NULL as well for empty list
Node is defined as
class Node {
int data;
Node next;
}
*/
int FindMergeNode(Node headA, Node headB) {
Node currentA = headA;
while (currentA != null) {
Node currentB = headB;
while (currentB != null) {
if (currentA == currentB) {
return currentA.data;
}
currentB = currentB.next;
}
currentA = currentA.next;
}
return -1;
}
Honestly, I'm sure that the first algorithm is better than the second because of its performance. I would like to demonstrate this performance using SPACE and TIME COMPLEXITY, I have not dominated those topics.
According to the material, this solution should be Time Complexity: O(N). But I'm not quite sure that the first algorithm will be O(N).
The first algorithm scans headA and headB once to find the lengths, then skips the extra elements of the longer chain, then scans in parallel the two chains. The time complexity is proportional to the length of the chains, so it is O(N). It doesn't matter if you scan the lists 2, 3, or 5 times, as long as that number is constant, the time complexity is still O(N).
The second algorithm is worse, for each element in headA before the merge point, it scans the entire headB. In the worst case, when the lists don't intersect at the last node, it will scan all elements of headB for each element of headA. So the time complexity of this is O(N^2).
The space complexity of both algorithms is O(1), because you use constant storage in both (a bunch of local variables), that don't change, regardless the size of the input lists.
The first one is O(N) where N is in abstract the greatest of the two list length. Since you have two for loops and each can cost at max N, in the worst case the first algorithm will take 2 N cycle to end. So since O hide constant factor the algorithm is O(N)
In fact this is a interview question asked a few days ago.
The interviewer wants me to express the difference between ArrayList and LinkedList, and asked to optimize the insertion operation on ArrayList, in other words, to re-implement add(int index, E element) and of course the complexity of get(int index) operation can be sacrificed.
My answer was to separate the array into k sub-arrays and update a counting array representing the number of elements already in the corresponding sub-array. And the memory of every sub-array is allocated dynamically with an expected initial size. When I need to insert a data into the ArrayList, I can locate a sub-array first, and do the operation within a small array.
And if insertions are not too frequent or the indexes are uniform distributed, the time complexity of inserting can be O(log(k) + n/k + k) in average, where log(k) means we should locate the sub-array first with binary searching on the counting array's sum array, n/k is for data movement or even memory re-allocation, and k stands for the updating of the sum array.
I'm sure there are better solutions. I do need some suggestions, thanks!
One of the solutions could be:
add(int index, E element) always add element to the end of array (you have to also store the index where this element should be added) - complexity O(1)
get(int index) has to restore correct order of array (if some elements were added after the last invocation) - knowing the positions in which each element should be, you can restore correct order in O(n)
You can implement it in a balanced binary tree, so that both add() and get() cost O(logn)
An example implementation will look like (hand-crafted here, will not compile, corner cases not covered):
class Node {
int subTreeSize;
Node left,right;
Element e;
// all i 0-indexed
Node get(int i) {
if (i >= subTreeSize) {
return null;
}
if (left != null) {
if(left.subTreeSize > i) {
return left.get(i);
} else {
i -= left.subTreeSize;
}
}
if (i == 0) {
return this;
}
return right.get(i-1);
}
// add e to the last of the subtree
void append(Element e) {
if(right == null){
right = new Node(e);
} else {
right.append(e);
right = right.balance();
}
subTreeSize += 1;
}
// add e to i-th position
void add(int i, Element e) {
if (left != null) {
if(left.subTreeSize > i) {
add(i,left);
left=left.balance();
} else {
i -= left.subTreeSize;
}
}
if (i == 0) {
if (left == null){
left = new Node(e);
} else {
left.append(e);
left = left.balance();
}
} else {
if (right == null) {
// also in this case i == 1
right = new Node(e);
} else {
right.add(i-1, e);
right = right.balance();
}
}
subTreeSize += 1;
}
// the common balance operation used in balance tree like AVL or RB
// usually just left or right rotation
Node balance() {
...
}
}
public class Tree {
Node root;
public Element get(int i) {
return root.get(i).e;
}
public void add(int i, Element e) {
if (root == null) {
root = new Node(e);
} else {
root.add(i,e);
root = root.balance();
}
}
}
A variant of an order statistic tree would allow you to add and get by index in O(log n).
The basic idea is as follows:
Have each node store the size of the subtree rooted at that node.
The index of a node will correspond to its position in the in-order traversal of the tree.
This means that the ordering of the nodes is determined based on where in the tree they appear - this is not the way a binary search tree typically works, where the nodes' elements have some ordering that's not dependent on where in the tree it appears (e.g. f is greater than a in a regular BST ordered lexicographically, but in our case f may be smaller or greater than a, since it's ordered based on the index of f and a).
To add or get, we start at the root and recursively go through the tree, determining whether our insert or lookup position is to the left or right based on the target index and the subtree sizes.
More specifically, we have the following recursive definitions:
(with some added complexity for null nodes and actually inserting the node)
node.add(index, element):
if index <= left.subtreeSize
left.add(index, element)
else
// anything to the right is after left subtree and current node, so those must be excluded
right.add(index - left.subtreeSize - 1, element)
node.get(index, element):
if index == left.subtreeSize
return node
if index < left.subtreeSize
return left.get(index)
else
return right.get(index - left.subtreeSize - 1)
To understand this better, the following example tree might be helpful:
Values: Indices (in-order pos): Subtree sizes:
a 5 8
/ \ / \ / \
b g 1 6 5 2
/ \ \ / \ \ / \ \
f c h 0 3 7 1 3 1
/ \ / \ / \
e d 2 4 1 1
If we want to insert a new node at position 5, for example, it will be inserted to the right of d.
Below is a small test program to demonstrate this (creating the tree shown above).
Note that balancing will still need to be done to achieve O(log n) running time per operation.
class Test
{
static class Node<T>
{
Node<T> left, right;
T data;
int subtreeCount;
Node(T data) { this.data = data; subtreeCount = 1; }
public String toString(int spaces, char leftRight)
{
return String.format("%" + spaces + "s%c: %s\n", "", leftRight, data.toString())
+ (left != null ? left.toString(spaces+3, 'L') : "")
+ (right != null ? right.toString(spaces+3, 'R') : "");
}
int subtreeSize(Node<T> node)
{
if (node == null)
return 0;
return node.subtreeCount;
}
// combined add and get into 1 function for simplicity
// if data is null, it is an get, otherwise it's an add
private T addGet(int index, T data)
{
if (data != null)
subtreeCount++;
if (index == subtreeSize(left) && data == null)
return this.data;
if (index <= subtreeSize(left))
{
if (left == null && data != null)
return (left = new Node<>(data)).data;
else
return left.addGet(index, data);
}
else if (right == null && data != null)
return (right = new Node<>(data)).data;
else
return right.addGet(index-subtreeSize(left)-1, data);
}
}
static class TreeArray<T>
{
private Node<T> root;
public int size() { return (root == null ? 0 : root.subtreeCount); }
void add(int index, T data)
{
if (index < 0 || index > size())
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
if (root == null)
root = new Node<>(data);
else
root.addGet(index, data);
}
T get(int index)
{
if (index < 0 || index >= size())
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
return root.addGet(index, null);
}
#Override
public String toString() { return root == null ? "Empty" : root.toString(1, 'X'); }
}
public static void main(String[] args)
{
TreeArray<String> tree = new TreeArray<>();
tree.add(0, "a");
tree.add(0, "b");
tree.add(1, "c");
tree.add(2, "d");
tree.add(1, "e");
tree.add(0, "f");
tree.add(6, "g");
tree.add(7, "h");
System.out.println("Tree view:");
System.out.print(tree);
System.out.println("Elements in order:");
for (int i = 0; i < tree.size(); i++)
System.out.println(i + ": " + tree.get(i));
}
}
This outputs:
Tree view:
X: a
L: b
L: f
R: c
L: e
R: d
R: g
R: h
Elements in order:
0: f
1: b
2: e
3: c
4: d
5: a
6: g
7: h
Live demo.
LinkedList is a linked-list with access\insert\remove requires O(n), linked-lists support sequential access O(n).
ArrayList is an array with insert\remove requires O(2n), but access requires O(1), arrays support random access O(1).
to find a more optimal hybrid structure, you can start with this:
template <T>
public class LinkedArrayList
{
LinkedList<ArrayList<T>> list;
public LinkedArrayList ()
{
list = new LinkedList<ArrayList<T>> ();
}
// ..
}
You'll have to balance segments (arrays) in the list between access complexity, and insert\remove complexity