Open In App

Palo Alto Networks Interview Experience

Last Updated : 30 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Online Assessment:

  • MCQ Questions based on the Data Structures and Algorithm’s
  • 2 Basic DSA Questions.

Technical Round 1:

Q1: Given two sorted arrays find the median of the two sorted arrays.

C++
#include<bits/stdc++.h>
using namespace std;
// function to merge two sorted arrays and return the median
double findMedianSortedArrays(vector<int>& nums1,vector<int>& nums2)
{
    int n1 = nums1.size();
    int n2 = nums2.size();
    int n = n1 + n2;

    // vector for storing the merged array.
    vector<int> merged(n);

    int i = 0, j = 0, k = 0;
    while (i < n1 && j < n2) {
        if (nums1[i] <= nums2[j]) {
            merged[k++] = nums1[i++];
        }
        else {
            merged[k++] = nums2[j++];
        }
    }

    while (i < n1) {
        merged[k++] = nums1[i++];
    }

    while (j < n2) {
        merged[k++] = nums2[j++];
    }

    if (n % 2 == 0) {
        return (merged[n / 2 - 1] + merged[n / 2]) / 2.0;
    }
    else {
        return merged[n / 2];
    }
}

int main()
{
    vector<int> nums1 = { 1, 3 };
    vector<int> nums2 = { 2,5};
    double median = findMedianSortedArrays(nums1, nums2);
    cout << "Median: " << median << endl;
    return 0;
}
Python3
def find_median_sorted_arrays(nums1, nums2):
    n1, n2 = len(nums1), len(nums2)
    n = n1 + n2
    merged = [0] * n
    
    i, j, k = 0, 0, 0
    while i < n1 and j < n2:
        if nums1[i] <= nums2[j]:
            merged[k] = nums1[i]
            i += 1
        else:
            merged[k] = nums2[j]
            j += 1
        k += 1
    
    while i < n1:
        merged[k] = nums1[i]
        i += 1
        k += 1
    
    while j < n2:
        merged[k] = nums2[j]
        j += 1
        k += 1
    
    if n % 2 == 0:
        return (merged[n // 2 - 1] + merged[n // 2]) / 2.0
    else:
        return merged[n // 2]

if __name__ == "__main__":
    nums1 = [1, 3]
    nums2 = [2, 5]
    median = find_median_sorted_arrays(nums1, nums2)
    print(f"Median: {median}")


Q2 : Find the bottom view of binary Tree.

C++
#include<bits/stdc++.h>
using namespace std;

struct BinaryTreeNode {
    int val;
    BinaryTreeNode* left;
    BinaryTreeNode* right;
    BinaryTreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
/*
Intuition :
1.The last node in the every vertical level gives the bottom View of the binary tree.

TC : O(nlogn)
m -- No of vertical levels.
level Order Traversal runs for the O(n) and we are inserting the node into the map
which taks logm time.
SC : O(m + n)
m -- space for the map
n -- space for the queue
*/
void findBottomView(BinaryTreeNode* root,map<int,int> &bottomViewMap)
{  
    /*In queue stores pair in which 
    first  -- currNode
    second -- vertical level of the node*/
    queue<pair<BinaryTreeNode*,int>> q;
    q.push({root,0});
    while(!q.empty())
    {
        auto it = q.front();
        q.pop();
        BinaryTreeNode* currNode = it.first;
        int currVerticalLevel = it.second;
        /*As we are doing level order traversal the maps value get updated as we 
        processed each level,so at last the node which is in the last level
        i.e bottom most node will be stored*/
        bottomViewMap[currVerticalLevel] = currNode->val;
        if(currNode->left)
           q.push({currNode->left,currVerticalLevel-1});
        if(currNode->right)
          q.push({currNode->right,currVerticalLevel+1});   
    }
} 
void bottomView(BinaryTreeNode* root){
    /*In map
    key   -- vertical level
    value -- last node in that vertical level*/
    map<int,int> bottomViewMap;
    findBottomView(root,bottomViewMap);
    for(auto it : bottomViewMap)
     cout<<it.second<<" ";
}
int main() {
    // Create the binary tree
    BinaryTreeNode* root = new BinaryTreeNode(1);
    root->left = new BinaryTreeNode(2);
    root->right = new BinaryTreeNode(3);
    root->left->right = new BinaryTreeNode(4);
    root->left->right->right = new BinaryTreeNode(5);
    root->left->right->right->right = new BinaryTreeNode(6);

    cout << "Bottom View of binary Treee is \n";
    bottomView(root);
    return 0;
} 
Python3
class BinaryTreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

def find_bottom_view(root):
    # Dictionary to store the last node in each vertical level
    bottom_view_map = {}
    
    # Queue stores pairs (currNode, vertical level of the node)
    queue = [(root, 0)]
    
    while queue:
        curr_node, curr_vertical_level = queue.pop(0)
        
        # Update the map with the current node's value
        bottom_view_map[curr_vertical_level] = curr_node.val
        
        if curr_node.left:
            queue.append((curr_node.left, curr_vertical_level - 1))
        if curr_node.right:
            queue.append((curr_node.right, curr_vertical_level + 1))
    
    # Print the bottom view nodes
    for vertical_level, node_val in sorted(bottom_view_map.items()):
        print(node_val, end=" ")

if __name__ == "__main__":
    # Create the binary tree
    root = BinaryTreeNode(1)
    root.left = BinaryTreeNode(2)
    root.right = BinaryTreeNode(3)
    root.left.right = BinaryTreeNode(4)
    root.left.right.right = BinaryTreeNode(5)
    root.left.right.right.right = BinaryTreeNode(6)
    
    print("Bottom View of binary tree:")
    find_bottom_view(root)


Technical Round 2:


Q1: Implement a Hash Table without using any inbuilt map.

C++
#include <iostream>
#include <vector>

class HashTable {
private:
    int n;
    std::vector<std::vector<std::pair<int, int>>> container;

public:
    HashTable(int size) {
        n = size;
        container.resize(n);
    }

    void insert(int key, int value) {
        int hash = key % n;
        std::vector<std::pair<int, int>>& bucket = container[hash];
        for (auto& pair : bucket) {
            if (pair.first == key) {
                pair.second = value;
                return;
            }
        }
        bucket.push_back(std::make_pair(key, value));
    }

    void remove(int key) {
        int hash = key % n;
        std::vector<std::pair<int, int>>& bucket = container[hash];
        for (auto it = bucket.begin(); it != bucket.end(); ++it) {
            if (it->first == key) {
                bucket.erase(it);
                return;
            }
        }
    }

    int search(int key) {
        int hash = key % n;
        std::vector<std::pair<int, int>>& bucket = container[hash];
        for (const auto& pair : bucket) {
            if (pair.first == key) {
                return pair.second;
            }
        }
        return -1;
    }
};

int main() {
    HashTable hashTable(10);
    hashTable.insert(5, 100);
    hashTable.insert(15, 200);
    hashTable.insert(25, 300);

    int result = hashTable.search(15);
    if (result != -1) {
        std::cout << "Value found: " << result << std::endl;
    } else {
        std::cout << "Value not found." << std::endl;
    }

    hashTable.remove(15);

    result = hashTable.search(15);
    if (result != -1) {
        std::cout << "Value found: " << result << std::endl;
    } else {
        std::cout << "Value not found." << std::endl;
    }

    return 0;
}


Q2: Explain the re Hashing technique

Rehashing is a technique used in hash tables to handle collisions. When a collision occurs, meaning two or more keys are mapped to the same hash value, rehashing is performed to find an alternative location to store the key-value pairs.

The rehashing process involves resizing the hash table and redistributing the existing key-value pairs to new locations based on the new hash function. This helps to reduce the chances of collisions and improve the overall performance of the hash table.

The steps involved in rehashing are as follows:

  • Create a new hash table with a larger size.
  • Iterate through the existing hash table and reinsert each key-value pair into the new hash table using the new hash function.
  • Update the references to the new hash table and deallocate the memory of the old hash table.

Rehashing allows for a more uniform distribution of keys and reduces the likelihood of collisions, resulting in faster access and retrieval times. It is an essential technique for maintaining the efficiency and performance of hash tables in scenarios where the number of elements or the distribution of keys changes over time.

Q3: What are different types of hash functions to mapping number to number.

Different types of hash functions used for mapping numbers to numbers include:

  • Division Hash Function: This method involves taking the remainder of the division of the key by a prime number. The result is the hash value.
  • Multiplication Hash Function: In this approach, the key is multiplied by a constant value between 0 and 1. The fractional part of the product is multiplied by the size of the hash table, and the integer part is taken as the hash value.
  • Folding Hash Function: The key is divided into equal-sized chunks, and these chunks are added together. The sum is then used as the hash value.
  • Mid-Square Hash Function: The key is squared, and then the middle portion of the result is taken as the hash value. This method requires careful selection of the number of digits to extract.
  • Digit Extraction Hash Function: This technique involves extracting specific digits from the key and combining them to form the hash value. The extracted digits can be from the beginning, end, or specific positions within the key.

These are just a few examples of hash functions used for mapping numbers to numbers. The choice of hash function depends on various factors such as the distribution of input keys, collision avoidance, and performance requirements.

Q4: What happens in browser when search www.google.com.

When searching for www.google.com in a browser, the following steps typically occur:

  • The browser checks its cache to see if it has a cached copy of the webpage.
  • If there is no cached copy, the browser sends a request to a DNS (Domain Name System) server to resolve the IP address of the website.
  • The DNS server returns the IP address of the website to the browser.
  • The browser sends a request to the IP address of the website.
  • The server hosting the website receives the request and prepares the response.
  • The server sends the response, which includes the HTML, CSS, JavaScript, and other assets of the webpage, back to the browser.
  • The browser receives the response and starts rendering the webpage.
  • The browser parses the HTML, executes any JavaScript code, and applies the CSS styles to display the webpage.
  • The webpage is rendered and displayed to the user.

Please note that the specific steps and processes involved may vary depending on the browser and network configuration.

Q5: Explain polymorphism and different types of polymorphism with example.

Polymorphism is the ability of an object to take on different forms or have multiple behaviors depending on its context. In object-oriented programming, polymorphism allows objects of different classes to be treated as objects of a common superclass.

There are several types of polymorphism:

  • Compile-time Polymorphism (Static Polymorphism): This type of polymorphism is resolved during compile-time. It is achieved through function overloading or operator overloading.
  • Function overloading allows multiple functions with the same name but different parameters to coexist in a class. The appropriate function is selected based on the arguments passed during compilation.
  • Operator overloading allows operators to have different meanings depending on the context in which they are used.

Example of Function Overloading:

C++
class Math {
public:
    int add(int a, int b) {
        return a + b;
    }


    double add(double a, double b) {
        return a + b;
    }
};

Example of Operator Overloading:

C++
#include<bits/stdc++.h>
using namespace std;

// overloading + operator to add 2 complex numbers
class ComplexNumber
{
public : 
  double real;
  double imaginary;
  ComplexNumber(double _real, double _imaginary)
  {
      real = _real;
      imaginary = _imaginary;
  }
  void printComplexNumber()
  {
      cout<<"("<<real<<")"<<" + "<<"i"<<"("<<imaginary<<")"<<endl;
  }
};

ComplexNumber operator+(ComplexNumber &c1, ComplexNumber &c2)
{
    ComplexNumber c3(0.0,0.0);
    c3.real = c1.real + c2.real;
    c3.imaginary = c1.imaginary + c2.imaginary;
    return c3;
}

int main()
{
    ComplexNumber c1(2.3,4.5);
    ComplexNumber c2(2.4,5.6);
    ComplexNumber c3 = c1 + c2;
    c3.printComplexNumber();
    return 0;
}
  • Run-time Polymorphism (Dynamic Polymorphism): This type of polymorphism is resolved during runtime. It is achieved through function overriding and virtual functions.
  • Function overriding allows a derived class to provide a different implementation of a method that is already defined in its base class. Virtual functions are declared in the base class and can be overridden by the derived classes. The appropriate function implementation is determined at runtime based on the actual object type.
  • Polymorphism allows for code reusability, flexibility, and abstraction. It enables objects to exhibit different behaviors based on their types, leading to more modular and extensible code.

Example of function overriding and virtual functions:

C++
#include<bits/stdc++.h>
using namespace std;

class Animal {
public:
    virtual void makeSound() {
        cout << "Animal makes a sound" << endl;
    }
};


class Dog : public Animal {
public:
    void makeSound() override {
        cout << "Dog barks" << endl;
    }
};

int main()
{
  Animal* animal = new Dog();
  animal->makeSound(); // Output: "Dog barks"
  return 0;
}


Q6 : Explain JWT Authentication and it’s workflow. [ Used in my Project]


JWT (JSON Web Token) authentication is a method of securely transmitting information between parties as a JSON object. It is commonly used for user authentication and authorization in web applications.

The JWT authentication workflow typically involves the following steps:

  • User Authentication: The user provides their credentials (e.g., username and password) to the server.
  • Server Verification: The server verifies the user’s credentials and generates a JWT token.
  • JWT Token Issuance: The server signs the JWT token with a secret key and sends it back to the client.
  • Client Storage: The client stores the JWT token (usually in session storage or a cookie) for subsequent requests.
  • Request Authorization: The client includes the JWT token in the header (typically as a Bearer token) of each subsequent request to the server.
  • Server Validation: The server validates the JWT token by verifying its signature and expiration time.
  • User Authentication and Authorization: The server extracts the user identity and other relevant information from the JWT token and uses it to authenticate and authorize the user for the requested resources.
  • Response: The server processes the request and sends a response back to the client.

JWT authentication offers several benefits, including statelessness (no need to store session information on the server), scalability, and the ability to share authentication between different services. By securely encoding user information in the token, JWT authentication allows for efficient and secure communication between the client and server.

It’s important to note that JWT tokens should be transmitted over secure channels (e.g., HTTPS) to prevent interception and tampering.

Verdict: Not Selected

Remarks: Lack of Core CS Knowledge [ CN and OS]. I am not from CS.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads