Iterative DFS for Detecting Cycle in any Directed Graph

Note:

The algorithm for detecting cycles in any directed graph is easier using recursive DFS. But Iterative DFS approach to detect a cycle is a bit complex and tricky. I have commented on the code where necessary to make the algorithm more understandable.

In any case, the time complexity of the algorithm is O(V+E).

#include <bits/stdc++.h>

using namespace std;

#define MAX 100005

bool detectCycle(int nodes, vector<vector<int> >& adjList)
{
    if(nodes==1) return false;
    stack<int> st;
    bool on_stack[MAX+1]= {0};
    bool visited[MAX+1]= {0};
    for(int i=0; i<nodes; i++) //In case the graph is disconnected
    {
        if(visited[i]) continue; //For time efficiency
        st.push(i);
        while(!st.empty())
        {
            int u=st.top();
            if(!visited[u])
            {
                visited[u]=1;
                on_stack[u]=1;
            }
            else //backtracking, but keeping the visited array unchanged for time efficiency
            {
                on_stack[u]=0;
                st.pop();
                continue;
            }
            for(int j=0; j<adjList[u].size(); j++)
            {
                int v=adjList[u][j];
                if(!visited[v])
                {
                    st.push(v);
                }
                else
                {
                    if(on_stack[v]) return true; //cycle detected
                }
            }
        }
    }
    return false;
}


int main()
{
    vector<vector<int> > adjList(MAX+1);
    int nodes, edges;
    cout<<"Enter the number of nodes: ";
    cin>>nodes;
    cout<<endl;
    cout<<"Enter the number of edges: ";
    cin>>edges;
    cout<<endl;
    cout<<"Enter the pair of edges: "<<endl;
    for(int i=0; i<edges; i++)
    {
        int from, to;
        cin>> from>> to;
        adjList[from].push_back(to);
    }
    if(detectCycle(nodes, adjList)==1) cout<<"Cycle found!"<<endl;
    else cout<<"Cycle not found!"<<endl;
    return 0;
}

In-order Successor of any Binary Tree

1. Binary tree:

The successor of a node p is the node with the smallest key greater than p.val.

So there can be three cases to find the successor of a node:

  1. If the node has the right child, the successor must reside in its right subtree. If not, go to case 2. Note that, I didn’t mention the right “child”, I mentioned “subtree”. Because the node’s right child may not have the smallest value greater than p.value. In that case, the smallest value would be the leftmost child of the right child.
  2. If the node has no right child, then in the inorder traversal process, the successor node to be traversed would be this(node p’s) subtree’s parent node, which is the immediate node in the stack. If the stack is empty already, then go to case 3.
  3. If case 1 and 2 fails, that means node p has no successor.

We can use this process to find the inorder successor of a BST(Binary Search Tree) too. But in that case, we are not using the properties of BST, so that won’t be optimized. That is, this code’s time and space complexity is O(N), N being the number of nodes. In the case of BST, if we use the BST properties, then this problem can be solved in O(N) time and O(1) space.

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        stack<TreeNode*> st;
        TreeNode *node=root;
        bool c=0;
        while(!st.empty() || node)
        {
            while(node)
            {
                st.push(node);
                node=node->left;
            }
            node=st.top();
            st.pop();
            if(c==1) return node;
            if(node==p) 
            {
                c=1;
                if(!node->right)
                {
                    if(!st.empty()) return st.top();
                    else return NULL;
                }
            }
            node=node->right;
        }
        return NULL;  
    }
};

2. Binary Search Tree:

Here using the BST properties, each time, we will discard half of the tree. Thus our usual time complexity for a balanced binary tree would be logarithmic instead of linear i.e. O(log(N)), and our space complexity would be constant i.e O(1) as we are not using any extra data structure to store the tree. Though in case the BST is not balanced, so we will get a skewed tree, in that worst-case scenario, the time complexity would be O(N).

Here, all the above 3 cases are taken care of at once.

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        TreeNode *node=root;
        TreeNode *successor=NULL;
        while(node)
        {
            if(node->val > p->val)
            {
                successor=node;
                node=node->left;
            }
            else if(node->val <= p->val)
            {
                node=node->right;
            }
        }
        return successor;  
    }
};

LeetCode 692. Top K Frequent Words

Problem link: https://leetcode.com/problems/top-k-frequent-words/

Note:

This is mainly a basic code for sorting a map according to its value. That can be done in multiple ways but I usually do that with the help of a vector, by copying the map into a vector of pairs, then sorting that vector by the second value of each pair.

Also, it’s a bit more challenging while sorting the vector since if the values are the same then the strings should be printed according to their lexicographical order.

Code:

class Solution {
public:
    static bool comp(pair<string,int> x, pair<string,int> y)
    {
        if(x.second>y.second) return 1;
        else if(x.second<y.second) return 0;
        else
        {
            int a=x.first.size();
            int b=y.first.size();
            for(int i=0; i<x.first.size(); i++)
            {
                if(x.first[i]<y.first[i]) return 1;
                else if(x.first[i]>y.first[i]) return 0;
            }
            if(a<b) return 1;
            else return 0; 
        }
        
    }
    vector<string> topKFrequent(vector<string>& words, int k) {
        unordered_map<string,int> mp;
        vector<pair<string,int> > v;
        vector<string> ans;
        int sz=words.size();
        for(int i=0; i<sz; i++)
        {
            mp[words[i]]++;
        }
        
        unordered_map<string,int>:: iterator it;
        for(it=mp.begin(); it!=mp.end(); ++it)
        {
            v.push_back({it->first, it->second});
        }
        sort(v.begin(), v.end(), comp);
        for(int i=0; i<k; i++)
        {
            ans.push_back(v[i].first);
        }
        return ans;
    }
};

UVA 11413 – Fill the Containers

Problem Link: https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=2408

Note:

After reading the problem statement, most of our instant intuition will go with the Dynamic Programming approach understandably to solve this problem. Yes, that is obviously correct but another approach would be Binary Search which is unlikely to come to our mind at first. But Binary Search approach is more efficient in solving this problem compared to the Dynamic approach in terms of time and space complexity. Also, once you understand this approach, it’s easier to implement than the Dynamic one!

Here, the non-breaking property of the input array, that is you’ll have to maintain the serial of the containers in the conveyor belt (array). So we can choose to guess the minimal capacity of the container by calculating what would be the initialization of left and right in code. This initialization is an important step in this problem. In binary search, if the problem is not so straightforward as this one, initializing the left and right correctly is the first big step.

binarySearch():

Here, as I will guess minimum capacity, so the left limit would be the minimum possible value of the answer, thus the maximum element of the array. And the right limit would be the maximum possible value of the answer, the sum of all elements of the array.

Then we would run binary search within this range and would call the checkM function whether the mid satisfies the given limit of containers – m or not.

-If yes, then that’s a probable answer so we will keep that in our next iteration, and since in the next iteration we can find a lesser capacity amount than this one, we would update mid=right.

-If not, that means, our mid (the chosen probable capacity) is lower than any probable answer as it is requiring more containers than m. So we will update left=mid+1.

And in the end, if the condition left<right is not met, the code will exit the while loop, and now we can return left or right, that does not matter in this case(ultimately left and right would be equal while exiting the loop) due to the condition set for the loop and updating mid the way I did. In my code, I have written right as it seems easier to me to understand.

checkM():

This function is pretty straightforward. If with our predicted possible value (mid), all the elements of the array can be distributed in exactly m containers then it returns true, if it requires more than m containers we break immediately and return false.

I hope solving these types of problems using Binary search would increase the range of our intuition to solve a problem with an efficient and easier approach – Binary search.

Code:

#include <bits/stdc++.h>

using namespace std;

bool checkM(vector<int>& v, int m, int probableCap)
{
    int sz=v.size();
    int total=0, cnt=1;
    for(int i=0; i<sz; i++)
    {
        total+=v[i];
        if(total>probableCap)
        {
            total=v[i];
            cnt++;
            if(cnt>m) return 0;
        }
    }
    return 1;
}

int binarySearch(vector<int>& v, int left, int right, int m)
{
    int mid;
    while(left<right)
    {
        mid=left+(right-left)/2;
        if(checkM(v, m, mid)) right=mid;
        else left=mid+1;
    }
    return right;
}
int main()
{
    int n, m;
    while(cin>>n>>m)
    {
        vector<int> v;
        int sum=0, maxx_element=-1, x;
        for(int i=0; i<n; i++)
        {
            cin>>x;
            v.push_back(x);
            sum+=x;
            maxx_element=max(x,maxx_element);
        }
        int left=maxx_element, right=sum;
        cout<< binarySearch(v,left,right,m)<<endl;
    }
    return 0;
}

UVA 12032 – The Monkey and the Oiled Bamboo

Problem Link: https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=3183

Code:

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int t;
    cin>>t;
    for(int z=1; z<=t; z++)
    {
        vector<int> v;
        int n,x,max_distance=-1;
        cin>>n;
        for(int i=0; i<n; i++)
        {
            cin>>x;
            v.push_back(x);
            if(i==0) max_distance=v[0];
            else max_distance=max(max_distance, v[i]-v[i-1]);
        }
        int ans=max_distance;
        for(int i=0; i<n; i++)
        {
            if(i==0)
            {
                if(max_distance==v[0]) max_distance--;
            }
            else if(max_distance==v[i]-v[i-1]) max_distance--;
            else if(max_distance<v[i]-v[i-1])
            {
                ans=ans+1;
                break;
            }
        }
        cout<<"Case "<<z<<": "<<ans<<endl;
    }

    return 0;
}