I want to introduce to you a great mathematician by the name of George Polya. He was a Hungarian mathematician and a professor of mathematics at Stanford University. He contributed a lot to number theory, numerical analysis, and probability theory. My interest is in the book he published in 1945 entitled How To Solve It. In the book, he outlines four crucial principles of problem-solving. These principles are the following;
Understand the problem
Devise a plan
Carryout the plan
Look back
We are going to use the same principles and learn how to apply them when solving problems related to programming. I believe by the end of this article you will be in a position to handle any kind of programming challenge. illustrations will be in Java but you don't have to worry, I will try to simplify it all.
Here is a problem from leetcode that we are going to use to illustrate Polya principles of problem-solving.
Given an array of integers nums sorted in non-decreasing order, find the starting and ending position of a given target value. If target is not found in the array, return [-1, -1]. You must write an algorithm with O(log n) runtime complexity.
Input: nums = [5,7,7,8,8,10], target = 8 Output: [3,4]
Input: nums = [5,7,7,8,8,10], target = 6 Output: [-1,-1]
Input: nums = [], target = 0 Output: [-1,-1]
Polya’s First Principle: Understand the problem
We all fight the temptation to jump right into solving the problem sometimes without trying to understand what it is all about. I am guilty of this. According to Polya, before attempting to solve the problem, ask yourself the following questions to understand what the problem is all about.
What are you asked to do?
Can you restate the problem in your own words?
Do you understand all the words used to state the problem?
Do you have enough knowledge or information to solve the problem?
Are there any constraints or edge cases to consider?
Let's try to understand our programming challenge. What is it asking us to do? It is asking us to find the starting and ending position of a given target value. To fully understand the problem, it is okay to restate the words in your native language, and don't be afraid to ask what you don't understand. Our aim at this point is to understand the problem before attempting to solve it.
Polya’s Second Principle: Devise a plan
Having understood the problem, Polya's second principle is to devise a plan for solving the problem. In programming, a single problem can have multiple ways of solving it. But as you practice and solve more challenges, the best strategy for solving a particular problem becomes easier to detect. So how should we plan?
Break down the problem into small pieces. Functions are a handy way to achieve this!
Solve a simpler version of the problem
look for patterns
Guess and check
Consider special cases
Be creative
You don't have to follow the strategies above in their order. As you face more challenges and as your skills improve, this planning phase will get simpler! Let's get back to our coding challenge and plan how we can solve the problem.
To get the first occurrence of the target element, we're going to traverse the array from the beginning and the moment we find the target value, we will store its index and break out of the loop.
For the last element, we're going to traverse the array starting from its end, and as soon as we find the target value, we store its index and break out of the loop. We finally going to print those indices to the console. Let's now draw the plan.
Let's create a helper function
findFirstAndLast
to handle our logic for finding the first and last positions.we need to create two integer variables
int first = -1
andint last = -1
to keep track of the first and last occurrences. We initializefirst
andlast
as -1 if the target is not present in the array.Loop through the array starting from the first element. if the target is found, store its index in the variable
first
and break out of the loop;Loop through the array starting from the last element. if the target is found, store its index in the variable
last
and break out of the loop;Print the indices of the first and last occurrences to the console.
Let's see an example ;
public class Main {
public static void main(String[] args) {
// create a function for finding the first and last position
// create first and last variables to keep track of the first and last occurrences
// loop through the array starting from the first element
// if the target is found, store its index and break out of the loop
// loop through the array starting from the last element.
// if the target is found, store its index and break out of the loop
// print the indices of the first and last occurence
}
}
Polya’s Third Principle: Carry out the plan
This step is easier than coming up with a plan. In programming, your job is to translate your plan into code. We have used comments so let's convert them to code as shown below;
public class Main {
public static void main(String[] args) {
int[] nums = { 5, 7, 7, 8, 8, 10 };
int target = 8;
findFirstAndLast(nums, target);
}
// function for finding the first and last position
static void findFirstAndLast(int[] arr, int target) {
// create first and last variables to keep track of the first and last occurrences
int first = -1;
int last = -1;
// loop through the array starting from the first element
for (int i = 0; i < arr.length; i++) {
// if the target is found, store its index and break out of the loop
if (target == arr[i]) {
first = i;
break;
}
}
// loop through the array starting from the last element.
for (int i = arr.length - 1; i >= 0; i--) {
// if the target is found, store its index and break out of the loop
if (target == arr[i]) {
last = i;
break;
}
}
// print the indices of the first and last occurence
System.out.println("First occurence: " + first);
System.out.println("Second occurence: " + last);
}
}
Running the program above will print the following output to the console.
First occurence: 3
Second occurence: 4
Polya’s Fourth Principle: Look back
Polya mentions that much can be gained by taking the time to reflect and look back at what you have done so far. Our program is working fine. But we need to look back and reflect on what to improve. The approach we have applied is linear search, the runtime complexity of the linear search is O(n). That is, the complexity is directly related to the size of the inputs. Therefore we need to get back to the second principle and plan better.
Let's Look back and improve our solution.
Instead of using the linear searching strategy, let's use another more efficient strategy for searching an element in a sorted array, that is, the binary search technique.
The ultimate purpose of using binary search is to minimize the runtime of our solution. Let's modify our plan so we can use binary search to search for the first and last occurrences of the target in the array.
First, we are going to create a helper function for searching the first occurrence of the target in the array. Let's call it
searchFirstIndex
.Inside the function, we are going to have integer variables
left
andright
to keep track of the left and right pointers in the array. We also need another integer variablefirst
to store the position of the first occurrence.Calculate the middle index
mid
of the array. If the element at the middle index is greater or equal to the target, we update the right pointer to get closer to the target and then search the left half of the array.If we don't find it, it means the element is smaller than the target; so we update the left pointer and widen our search to the right half of the array.
When we find the target at the middle point, we store its index as a possible solution and move on with our search until the search space is exhausted.
public static int searchFirstIndex(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
int first = -1;
while(left <= right) {
int mid = (left + right) / 2;
if(arr[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
if(arr[mid] == target) {
first = mid;
}
}
return first;
}
To find the last occurrence of the target in the array, we are going to follow the same procedures above but the conditions are going to be different.
Let's start by creating a helper function
searchLastIndex
for searching the last occurrence of the target in the array.Inside the function, we can initialize two integer variables
left
andright
to keep track of the left and right pointers in the array. We need the integer variablelast
to store the position of the last occurrence.Calculate the middle index
mid
in the array. If the element at the middle index is less or equal to the target, we update the left pointer and continue the search in the right half of the array.If we don't find it, it means the element is smaller than the target; so we update the left pointer and widen our search to the left half of the array.
When we find the target at the middle point, we store its index as a possible solution and move on with our search until the search space is exhausted.
public static int searchLastIndex(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
int last = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] <= target) {
left = mid + 1;
} else {
right = mid - 1;
}
if (arr[mid] == target) {
last = mid;
}
}
return last;
}
Since we have the helper functions for searching the first and last occurrences of the target in the array, Let's now create a function that searches for both the first and last occurrences and prints their indices.
public static void searchForFirstAndLast(int[] nums, int target) {
int first = searchFirstIndex(nums, target);
int last = searchLastIndex(nums, target);
System.out.println("First occurence of " + target + ": " + first);
System.out.println("Last occurence of " + target + ": " + last);
}
We can now test our solution in the main method.
public static void main(String[] args) {
int[] nums = { 5, 7, 7, 8, 8, 10 };
int target = 8;
searchForFirstAndLast(nums, target);
}
Running the above program will print the following result to the console;
First occurence of 8: 3
Last occurence of 8: 4
Conclusion
By using Polya's principles we can solve any kind of problem. We, first of all, have to understand the problem, plan how to solve it, and carry out the plan we have come up with, when we are done, we can look back and reflect on what we have done. If we ever get stuck, we can go back and revise our plan. We've discovered that as you solve more challenges, this will get easier with time, especially in the planning phase. Having learned about these principles, let's go solve those challenging problems!