In my work with Google Earth Engine, over the last few months, I like to believe that I have learnt a few tricks to interact with it. I recently received a query about finding the percentage coverage of satellite imagery over a region of interest. In this post, I would like share a robust approach towards this problem.
I use ee.Image.reduceToVectors()
to convert images to ee.FeatureCollection()
. Then I simply sum the total area of the collection and find the % coverage.
Consider the following large region of interest covering the eastern coast of North America.
var geo = ee.Geometry.Polygon([[-92.52413532482498,29.068796610642462],[-89.18455628452989,28.914934845703456],[-87.33896350523439,29.833880931200852],[-84.43882690085657,29.145338554288642],[-82.24181129937064,25.078787199542987],[-76.96873224229702,22.505730359809384],[-74.15638961145261,20.954634119919458],[-72.61837753681915,20.296610892350177],[-69.93789091531232,22.017775662520798],[-76.17776624731441,26.42435799592542],[-78.99005257253476,27.987520768911722],[-80.74769589300101,30.441776535545372],[-80.22037485372631,32.094264391741454],[-76.00200769123495,34.154844709247236],[-75.12320016955083,36.87260703790422],[-73.36561096544423,39.63248806420696],[-70.28983921701152,40.97286069808757],[-69.05954874360211,41.76428108977483],[-69.32323127521587,43.12600140897638],[-66.77478039524175,43.95416885980774],[-65.9838286583204,43.06190207518716],[-58.86583883168352,45.699391313240426],[-60.62353801334703,47.33193089878859],[-65.10533579067283,48.21761281648529],[-69.58702361515375,47.62863414289759],[-82.17543615981589,45.683704906902776],[-82.83450615124401,46.08134637079505],[-84.59202315164083,46.89813657607063],[-87.06195698922187,48.14757501580384],[-90.06226008975369,48.62591472970412]])
Map.addLayer(geo, {'color': 'green'})
Here is the region of interest.
I will now query Sentinel-2 for some satellite imagery over the region of interest.
var s2_col = ee.ImageCollection('COPERNICUS/S2_SR')
.filterDate('2019-08-30', '2019-09-15')
.filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 20))
.filterBounds(geo)
Typically, over a large area, multiple swaths of the same satellite cover the region of interest in the same day. To produce an easier-to-discern image collection, I like to mosaic the collection over unique dates. This also provides us with much larger images to work with, and thus demonstrate the method well.
var s2_col_list = s2_col.toList(s2_col.size())
var unique_dates_s2 = s2_col_list.map(function(image) {
return ee.Image(image).date().format('YYYY-MM-dd')
}).distinct()
var mosaick_s2 = unique_dates_s2.map(function(date) {
date = ee.Date(date)
return s2_col.filterDate(date, date.advance(1, 'day')).mosaic()
})
Alright, now that we have prepared our images, we find the features of an image from the image collection. I arbritarily took the 3rd image. You can choose any you like.
var img = ee.Image(mosaick_s1.get(3))
var img_abs = img.abs()
var img_true = img_abs.and(img_abs)
var features = img_true.select('B4').cast({'B4': 'uint32'}).reduceToVectors({'scale': 10000, 'geometry': geo})
Map.addLayer(img, {'bands': ['B4', 'B3', 'B2'], 'min': 0, 'max': 3000})
Map.addLayer(features)
Here are the image and the corresponding regions of interest.
Now we find the total area of features. This revolves around the ee.FeatureCollection().aggregate_sum()
method, which finds the sum of a property of the individual features of the collection. So we first create a property area
, and assign to it the area of the feature. Then we simply run aggregate_sum()
over the feature collection and get the total area.
features = features.map(function(feature) {
return feature.set('area', feature.area(ee.ErrorMargin(1)));
})
var sum = features.aggregate_sum('area')
Now, its a simple ‘find the percentage’ problem :). I find the ratio of the area of the feature collection to the area of the region of interest and multiply by 100.
var geo_area = geo.area(ee.ErrorMargin(1)) // the area of the region of interest
var ratio = sum.divide(geo_area).multiply(100)
print('Area covered by image', sum)
print('Area of the region of interest', geo_area)
print('% area covered', ratio)
Which gives me the following output:
Ofcourse, outputs could be beautified, but as such the areas are in square meters, (a quirk of the ee.geometry.area()
function) and can be modified by ee.Number.divide(1000000)
to represent square kilometers instead.
The final code here:
var geo = ee.Geometry.Polygon([[-92.52413532482498,29.068796610642462],[-89.18455628452989,28.914934845703456],[-87.33896350523439,29.833880931200852],[-84.43882690085657,29.145338554288642],[-82.24181129937064,25.078787199542987],[-76.96873224229702,22.505730359809384],[-74.15638961145261,20.954634119919458],[-72.61837753681915,20.296610892350177],[-69.93789091531232,22.017775662520798],[-76.17776624731441,26.42435799592542],[-78.99005257253476,27.987520768911722],[-80.74769589300101,30.441776535545372],[-80.22037485372631,32.094264391741454],[-76.00200769123495,34.154844709247236],[-75.12320016955083,36.87260703790422],[-73.36561096544423,39.63248806420696],[-70.28983921701152,40.97286069808757],[-69.05954874360211,41.76428108977483],[-69.32323127521587,43.12600140897638],[-66.77478039524175,43.95416885980774],[-65.9838286583204,43.06190207518716],[-58.86583883168352,45.699391313240426],[-60.62353801334703,47.33193089878859],[-65.10533579067283,48.21761281648529],[-69.58702361515375,47.62863414289759],[-82.17543615981589,45.683704906902776],[-82.83450615124401,46.08134637079505],[-84.59202315164083,46.89813657607063],[-87.06195698922187,48.14757501580384],[-90.06226008975369,48.62591472970412]])
Map.addLayer(geo, {'color': 'green'})
// added geo
var s2_col = ee.ImageCollection('COPERNICUS/S2_SR')
.filterDate('2019-08-30', '2019-09-15')
.filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 20))
.filterBounds(geo)
// created the collection
var s2_col_list = s2_col.toList(s2_col.size())
var unique_dates_s2 = s2_col_list.map(function(image) {
return ee.Image(image).date().format('YYYY-MM-dd')
}).distinct()
var mosaick_s2 = unique_dates_s2.map(function(date) {
date = ee.Date(date)
return s2_col.filterDate(date, date.advance(1, 'day')).mosaic()
})
// mosaiked the collection by date
var img = ee.Image(mosaick_s1.get(3))
var img_abs = img.abs()
var img_true = img_abs.and(img_abs)
var features = img_true.select('B4').cast({'B4': 'uint32'}).reduceToVectors({'scale': 10000, 'geometry': geo})
Map.addLayer(img, {'bands': ['B4', 'B3', 'B2'], 'min': 0, 'max': 3000})
Map.addLayer(features)
// retrived image from mosaiked list and created vectors
features = features.map(function(feature) {
return feature.set('area', feature.area(ee.ErrorMargin(1)));
})
var sum = features.aggregate_sum('area')
// found sum of areas of features
var geo_area = geo.area(ee.ErrorMargin(1)) // the area of the region of interest
var ratio = sum.divide(geo_area).multiply(100)
print('Area covered by image', sum)
print('Area of the region of interest', geo_area)
print('% area covered', ratio)
Changing the cloud cover tolerance to 100 gives us more satellite imagery and a larger percentage coverage.
In summary, we learnt how to use reduceToVectors() along with aggregate_sum() to come up with a robust approach towards calculating percentage of area covered by an image.
]]>This series was inspired by this article by Noel Gorelick. I will try and apply his article for a more specific use case of downloading satellite imagery for a large region of interest. This article requires an understanding of the limitations of earth engine, particularly ee.Image.getDownloadURL()
, because it is aimed at providing a work-around. Today we discuss the first step towards big downloads, tiling the region of interest.
This whole approach revolves around one function to facilitate parallel downloads, i.e. ee.FeatureCollection.aggregate_array('.geo').getInfo()
. This is because, aggregate_array()
returns a list, which can be passed on to map()
or starmap()
(in python) to be called in parallel. So any fast earth-engine downloader will have the following structure:
The aim of this process is to simply take a geometry and split it into smaller tiles. These tiles can later be used in chipping and downloading the satellite imagery.
First, we create a grid over the area of interest, by using ee.Geometry.coveringGrid()
. This function takes two arguments, projection and scale. The projection is ofcourse the projection of the region of interest. However, the scale, is 10000, because it is the maximum tile dimension supported by ee.Image.getDownloadURL()
.
// roi is an instance of ee.Geometry()
var grid = roi.coveringGrid(roi.projection(), 10000)
Map.addLayer(grid)
This results in the following output:
For smaller regions (about 200-1000 sq.km), this solution should be sufficiently quick to download the tiles and re-stitch.
For larger regions, we can go one step further, by clipping the tiles to the region of interest. This way we only download tiles containing useful information. This comes with a few caveats. Clipping is done using ee.Feature.intersection()
which accepts three arguments, right, maxError and proj and returns any ee.Geometry()
. However, for the sake of simplicity, we only want polygons. So we apply a filter to the final collection to remove any unwanted geometries.
// Create tiles by clipping the grid to the intersection
var tiles = grid.map(function(cell) {
return ee.Feature(cell).intersection(ee.Feature(roi), ee.ErrorMargin(1), roi.projection())
})
// Filter tiles based on their geometry. We only want the polygons.
// First set a property 'geo' as the type of the geometry of the tile
tiles = tiles.map(function(tile) {
return tile.set('geo', tile.geometry().type())
})
// Next, return those tiles with 'geo' = 'Polygon'
tiles_fil = tiles.filter(ee.Filter.eq('geo', 'Polygon'))
Map.addLayer(tiles_fil, {'color': 'blue'})
print('tiles', tiles)
print('filtered tiles', tiles_fil)
This gives the following output:
As you can see, we have clipped the tiles to the required region of interest. Further we have reduced the number of tiles slightly which should provide slight improvement to the downloads. This method delivers satisfactory results for slightly larger regions, i.e. 1,000-10,000 sq.km.
In general, the above method is the go-to for big downloads, given enough time. I have tested the tesselation method above for country sized (Ghana) mosaics and it gives me the final image in about ~10 minutes. However, you may be interested in single day pictures of the region of interest. Often times, the swaths from satellite imagery are not enough to cover the large region of interest. In these cases, you can improve the focus of the downloader by providing tiles where the image exists.
Therefore, we convert the raster image to a vector (geometry) and create tiles over them. I found this to be easiest using the ee.Image.reduceToVectors()
. We iterate over the ee.FeatureCollection()
returned and perform the above tiling algorithm on each ee.Feature()
.
Alternatively, we can also create a coveringGrid()
over the initial area and filter tiles intersecting with the result of ee.Image.reduceToVectors()
.
Ofcourse, this comes with an overhead per image. The tiling is done dynamically and ee.Image.reduceToVectors()
is susceptible to exceeding memory limits. However, for large areas, this significantly reduces the number of tiles created and therefore the download time.
// Preparing the collection
var collection = ee.ImageCollection('COPERNICUS/S1_GRD')
.filterDate('2019-08-30', '2019-09-15')
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'))
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH'))
.filterBounds(roi)
var coll_list = collection.toList(collection.size())
// This code to mosaic the collection by dates
var unique_dates = coll_list.map(function(image) {
return ee.Image(image).date().format('YYYY-MM-dd')
}).distinct()
var mosaic_by_date = unique_dates.map(function(date) {
date = ee.Date(date)
return collection.filterDate(date, date.advance(1, 'day')).mosaic()
})
// We get the final image
var img = ee.Image(mosaic_by_date.get(2))
// Create the vector
Map.addLayer(img)
var img_abs = img.select('VH').abs()
var img_set = img_abs.and(img_abs)
// This is important to reduce computation over the featureCollection() returned
// by the reduceToVectors() below
// To see an example, use a sentinel-2 image instead :)
var features = img_set.reduceToVectors({
scale: 10000,
geometry: roi,
bestEffort: true
})
// Tiling is not as straight-forward because of un-packing of ee.List()
var tiles = features.map(function(feature) {
feature = ee.Feature(feature)
var grid = feature.geometry().coveringGrid(roi.projection(), 10000)
var cells = grid.map(function(tile) {
return ee.Feature(tile.intersection(ee.Feature(roi), ee.ErrorMargin(1)))
})
return ee.FeatureCollection(cells.toList(cells.size()))
}).flatten()
// Alternatively, we can also filter grid tiles that intersect with the features
// var grid = roi.coveringGrid(roi.projection(), 10000)
// var tiles = features.map(function(feat) {
// feat = ee.Feature(feat)
// return grid.filterBounds(feat.geometry())
// }).flatten()
// You may adapt the above code to clip to the region of interest.
// Again, filtering tiles to remove non-polygons
tiles = tiles.map(function(tile) {
return tile.set('geo_type', tile.geometry().type())
})
tiles = tiles.filter(ee.Filter.eq('geo_type', 'Polygon'))
Map.addLayer(tiles)
This gives the following ouput:
As you can see, the tiling is localized to the raster and results in significantly less tiles generated.
Any downloader script should focus on the speed of the downloads as a whole. The computation times leading up to the final download (using ee.Image.getDownloadURL()
) usually determine and bottleneck the speed of the downloader. With that in mind, consider the following:
getInfo()
calls in the python API to absolute minimum. If they result in connection closed
errors, try to simplify the computations. If still failing, encapsulate in retries.As an exercise, using fixed dimension tiles, try to determine the best scale of the coveringGrid()
:) (and let me know :P)
In conclusion, we have learned how to create tiles and easily download large images. With very little external tooling we have greatly extended the knowledge imparted by Noel Gorelick in downloading parts of an image in parallel.
The next article in this series will discuss stitching said downloaded tiles using GDAL binaries, or using existing programs like QGIS.
]]>Head on over here to try the problem for yourself.
Hint says back-tracking, solutions say dynamic programming ðŸ˜….
The goal is to get all sorted combinations of the vowels for a string of length n
. I set up a count
variable outside the functions to ensure it stays out of scope for all the recursive calls. The helper()
function recieves the previous index and remaining charaters. helper()
loops till 4 (because there are 5 vowels). Everytime, n==0
, increment counter
.
public class solution {
int count = 0;
public int countVowelStrings(int n) {
helper(n,0);
return count;
}
public void helper(int n, int index) {
if(n <= 0) {
count++;
return;
}
for(; index < 5; index++) {
helper(n - 1, index);
// index not decremented because same
// letters are valid
}
}
}
The following pattern explains the intuition behind this approach.
a | e | i | o | u | |
---|---|---|---|---|---|
n = 1 | 1 | 1 | 1 | 1 | |
n = 2 | 5 | 4 | 3 | 2 | 1 |
n = 3 | 15 | 10 | 6 | 3 | 1 |
: |
The last string will always be uuuu.. n times
. Every other string will occur count of previous + count of next.
public int countVowelStrings(int n) {
int[] store = new int[5];
for(int i = 0; i < store.length; i++)
store[i] = 1;
// This is the base condition when n = 1
// so we check for n > 1 in the while
while(n-- > 1) {
for(int i = 3; i >= 0; i--) {
store[i] += store[i+1];
// follow the pattern :)
// i was giving i-1 and
// was really confused
}
}
int result = 0;
for(int i = 0; i < store.length; i++)
result += store[i];
return result;
}
Head on over here to try the problem for yourself.
Another permutation based problem solved using back-tracking.
The goal is to get all combinations (without repeat) of k
integers from 1-9 whose sum is n
. To achieve this, simply backtrack and decrement n
with the current integer. Also, to ensure no repeats, pass in the next integer to the recursive call.
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> result = new ArrayList<>();
helper(result, new ArrayList<>(), k, n, 1);
return result;
}
public void helper(List<List<Integer>> result, List<Integer> curr, int k, int n, int index) {
if(n < 0) return;
if(n == 0 && curr.size() == k) {
result.append(new ArrayList<>(curr));
return;
}
for(; index < 10; index++) {
// here i had to first append then pass curr
// because curr.add() returns boolean
curr.add(index);
helper(result, curr, k, n - index, index + 1);
// delete previous element to continue in
// for loop
curr.remove(curr.size() - 1);
}
}
Head on over here to try the problem for yourself.
Most permutation based problems seem to use a back-tracking solution, but don’t quote me on that ðŸ˜…
The goal is to get all permutations of letters corresponding to a given phone number. First, we create a mapping of numbers and the corresponding letters. Then we simply set up a backtracking function helper()
which will populate our result
list with the required solutions.
public List<String> letterCombinations(String digit) {
List<String> result = new ArrayList();
if(digits.length() == 0) return result;
char[][] map = {
{'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'}
};
StringBuilder curr = new StringBuilder();
helper(result, curr, digits, map, 0);
return result;
}
public void helper(List<String> result, StringBuilder curr, String digits, char[][] map, int index) {
if(index >= digits.length()) {
result.add(curr.toString());
return;
}
for(char c : map[digits.charAt(i++) -'2']) {
helper(result, curr.append(c), digits, map, i);
curr.deleteCharAt(curr.length() - 1);
// deleteCharAt() here makes the solution
// move forward correctly in the for loop
}
}
Head on over here to try the problem for yourself.
I spent way too long trying to figure out that they had already provided the NestedInteger
interface XD. The solution is simply a recursion over the elements of nestedList
.
The goal is to build an iterator over the provided nestedList
. So we need to implement next()
and hasNext()
for the given nestedList
. I approach this by converting the nestedList
into an ArrayList
. I defined a function called build()
which converts nestedList
, to an ArrayList
. It iterates over every element in the nestedList
and processes them as follows:
item.isInteger()
then arr.add(item.getInteger())
Here is an example that illustrates the problem.
nestedList = [1,2,[3,4,[5,6],7],8]
As you can see, element 2 of nestedList contains integers and a list. So we must check each element recursively to build the required arraylist.
public class NestedIterator implements Iterator<Integer> {
ArrayList<Integer> arr;
int index;
public NestedIterator(List<NestedInteger> nestedList) {
arr = new ArrayList<>();
index = 0;
for(NestedInteger item : nestedList) {
build(arr);
}
}
public void build(NestedInteger item) {
// isInteger() and getInteger() are
// part of the NestedInteger interface
if(item.isInteger())
arr.add(item.getInteger());
else {
for(NestedInteger element : item) {
build(element);
}
}
}
@Override
public Integer next() {
return arr.get(index++);
}
@Override
public boolean hasNext() {
return index < arr.size();
}
}
Head on over here to try the problem for yourself.
This one was so annoying :’(. I ended up following this tutorial for ironing out the final cases.
The goal is to maintain a descending monotonic stack to keep track of the minimum value present before each element in the stack. The ideal case is for the ith
index to be as small as possible, the jth
index to be as large as possible, and the kth
index to be somewhere in between. To keep track of the lowest ith
index, we insert into the stack, the smallest element before the current element.
Thats too many words so let’s try an example.
nums[] = [1,4,5,7,8,9,6]
Stack<int[] > st = {}
min = 1
n = nums[1:]
n | min | st | ith | jth | kth |
---|---|---|---|---|---|
4 | 1 | {[4,1]} | 1 | 4 | ? |
5 | 1 | {[5,1]} | 1 | 5 | ? |
7 | 1 | {[7,1]} | 1 | 7 | ? |
8 | 1 | {[8,1]} | 1 | 8 | ? |
9 | 1 | {[9,1]} | 1 | 9 | ? |
6 | 1 | {[9,1],[6,1]} | 1 | 9 | 6 |
We finally get a trio that satisfies so we return true
.
Here is a more annoying example.
nums[] = [1,4,0,-1,-2,-3,-1,-2]
n | min | st | ith | jth | kth |
---|---|---|---|---|---|
4 | 1 | {[4,1]} | 1 | 4 | ? |
0 | 1 | {[4,1],[0,1]} | 1 | 4 | ? |
-1 | 0 | {[4,1],[0,1],[-1,0]} | 0 | -1 | ? |
-2 | -1 | {[4,1],[0,1],[-1,0],[-2,-1]} | -1 | -2 | ? |
-3 | -2 | {[4,1],[0,1],[-1,0],[-2,-1],[-3,-2]} | -2 | -3 | ? |
-1 | -3 | {[4,1],[0,1],[-1,-3]} | -3 | -1 | ? |
-2 | -3 | {[4,1],[0,1],[-1,-3],[-2,-3]} | -3 | -1 | -2 |
We finally get a trio that satisfies so we return true
.
public boolean find132pattern(nums[]) {
if(nums.length < 3) return false;
Stack<int[] > st = new Stack();
int min = nums[0];
for(int i = 1; i < nums.length; i++) {
while(!st.empty() && nums[i] >= st.peek()[0]) st.pop();
if(!st.empty() && nums[i] > st.peek()[1]) return true;
st.push(new int[] {nums[i], min});
min = Math.min(min, nums[i]);
}
return false;
}
Here is a step-by-step guide to how to get one (for free) from freenom. I use github pages to host my hugo website so the tutorial will focus on the relevant steps.
In the search bar, type out the site name that you want, say example.com
. It should give you a few alternatives such as shown in the picture. Pick your favourite. If you are a first time user, create an account with freenom and proceed to checkout.
NOTE: Sometimes, you may get an error when you hit Get it now!
which says not available
. Don’t worry, just search again with the same domain name but with the exact extension that you wanted. So if the alternative was example.ga
search again with example.ga
. The problem should be resolved ðŸ˜„
In freenom, under Services
dropdown, go to My domains
and select manage domain
. You must provide an alias record for your github website. Go to GitHub pages documentation to find the ip addresses that correspond to your GitHub page domain name. In my case they are:
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
Navigate to manage freenom DNS
and enter this information in the prompt as follows:
Notice the last CNAME
entry. This entry should point to your original user site. Make sure it is of the format <user>.github.io
.
In the settings
of your github repo, in the code and automation
submenu, go to pages
. Put your recently acquired domain name under Custom domain
. (If the previous steps work, you can also enable enfore HTTPS
).
Go to config.toml
and change baseURL
to your new custom domain name. Build the website, push the changes and…
DNS entries take some time to propagate across the internet. It took my website about 10 minutes to load correctly. Once its done though, it works like a charm ðŸ˜„
]]>Head on over here to try to the problem for yourself.
The problem requires a stack based solution.
The goal is the find the shortest string possible by removing k
duplicated adjacent characters. A simple problem case is illustrated:
k = 2
s = "abbac"
the result must be c.
Iteration 1: remove bb => "aac"
Iteration 2: remove aa => "c"
The hint points out that instead of maintaining the character count outside the stack, just pass in an object that stores the counter. I have passed an object and an array to arrive at two similar solutions, one with less memory overhead, and the latter with better performance.
public class Pair {
char c;
int n;
public Pair(char curr) {
c = curr;
n = 1;
}
public void increment() {
n++;
}
public String stringRep() {
return Character.toString(c).repeat(n);
}
}
public String removeDuplicates(String s, int k) {
Stack<Pair> st = new Stack();
char curr;
for(int i = 0; i < s.length(); i++) {
curr = s.charAt(i);
if(st.empty() || st.peek().c != curr)
st.push(new Pair(curr));
else st.peek().increment();
if(st.peek().n == k) st.pop();
}
StringBuilder result = new StringBuilder();
for(Pair p : st) {
result.append(p.stringRep());
}
return result.toString();
}
public String removeDuplicates(String s, int k) {
Stack<int[] > st = new Stack();
char curr;
for(int i = 0; i < s.length(); i++) {
curr = s.charAt(i);
if(st.empty() || st.peek()[0] != curr)
st.push(new int[] {curr, 1});
else st.peek()[1]++;
if(st.peek()[1] == k) st.pop();
}
StringBuilder result = new StringBuilder();
for(int[] a : st) {
while(a[1]-- > 0)
result.append((char)a[0]);
}
return result.toString();
}
Head on over here to try the problem for yourself.
The solution talks about using 2 queues, which frankly I don’t understand :’). I used a single queue to implement this problem.
The goal is to implement a stack using the basic queue features provided such as push()
, peek()
and pop()
. The easiest way to implement this is by inserting elements in reverse order. Then the queue will pop the elements like a stack.
public class MyStack {
Queue<Integer> q;
public MyStack() {
q = new LinkedList<>();
}
public void push(int x) {
q.add(x);
for(int i = 0; i < q.size() - 1; i++) {
q.add(q.remove());
}
}
public int pop() {
return q.remove();
}
public int top() {
return q.peek();
}
public boolean empty() {
return q.size() == 0;
}
}
Head on over here to try the problem for yourself.
This is a varaition of the famous two-sum problem that we have all struggled with ðŸ˜…
The goal is the count unique pairs of integers that sum up to given k
. For this, we create a hashmap that stores unique values of the integers. Then we go over the keySet()
and update the count according to the following rules:
key = k/2
then count += occurence(key)/2
k - key
then count += Min(occurence(key), occurence(k - key))
.I keep track of the visited elements using a HashSet visited
. The case of key == k/2
is only raised when k, so I avoid any ugly conversions from double to Integer.
public static int maxOperations(int nums[], int k) {
int count = 0;
HashMap<Integer, Integer> map = new HashMap<>();
HashSet<Integer> visited = new HashSet<>();
for(int i = 0; i < nums.length; i++) {
if(map.containsKey(nums[i]))
map.put(nums[i], map.get(nums[i]) + 1);
else map.put(nums[i], 1);
}
for(Integer key : map.keySet()) {
System.out.printf("%d : %d\n", key, map.get(key));
}
for(Integer key : map.keySet()) {
if(visited.contains(key)) continue;
else {
if(k % 2 == 0 && key == k/2)
count += map.get(key)/2;
else if(map.containsKey(k - key))
count += Math.min(map.get(key), map.get(k - key));
visited.add(k - key);
}
}
return count;
}
As you can see, we explore the array first. Then we proceed with the counting. Fortunately, some very smart people in the comments came up with a way to do both simultaneously, with the use of HashMap.getOrDefault()
.
This code updates the count incrementally, rather than at once. As a result, it is able to populate the explored hashmap and count simultaneously. It checks if the pair of the element exists in the hashmap. On this basis it executes the following steps:
if((k-key) in map and occurence(k-key) > 0)
then remove an occurence and update count public int maxOperations(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
int count = 0;
for(int i = 0; i < nums.length; i++) {
if(map.containsKey(k - nums[i]) && map.get(k - nums[i]) > 0) {
count++;
map.put(k - nums[i], map.get(k - nums[i]) - 1);
}
else {
// getOrDefault makes this part a lot less ugly ðŸ˜„
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
}
return count;
}
Head on over here to try the problem for yourself.
I solved this problem using a two-pointer method.
The goal is to find the points where the array becomes unsorted. We use two loops, one to find the end of the unsorted sub-array and another to find the start of the unsorted sub-array. start = end = -1
as a way to check if they were updated or not during the loops. If they were not, then array is sorted and we return 0
. Else we return end - start + 1
.
pbulic int findUnsortedSubarray(int[] nums) {
int start = -1;
int end = -1;
int prev = 0;
//finding end
for(int i = 0; i < nums.length - 1; i++) {
// try to find the index of next
// smallest number from prev
if(nums[i] < nums[prev]) end = i;
else prev = i;
}
int prev = nums.length - 1;
//finding start
for(int i = nums.length - 1; i >= 0; i--) {
// try to find the index of nex
// biggest number from prev
if(nums[i] > nums[prev]) start = i;
else prev = i;
}
return (start < 0 && end < 0) ? 0 : end - start + 1;
}
[1,3,2,2,2]
start = 1
end = 4
An odd testcase that shows the strage loop working. In this case, the whole right side of the array is unsorted. For finding end
, we would have the following run:
i | prev | end |
---|---|---|
0 | 0 | -1 |
1 | 1 | -1 |
2 | 1 | 2 |
3 | 1 | 3 |
4 | 1 | 4 |
Finding start
happens simlarly and we get a good result.
Head on over here to try the problem for yourself.
I attempted a two-pointer approach to try and solve it.
The goal is to try to return the parity array in one pass. For this reason, I create a new array result[]
and two pointers, evenPtr
, pointing to the start of result[]
and oddPtr
, pointing to the end of result[]
. Now I loop over the initial array. If the element encountered is even, it is pushed at evenPtr
, and vice versa. The pointers are updated for the next iteration.
public int[] sortByParity(int[] nums) {
int evenPtr = 0;
int oddPtr = nums.length - 1;
int[] result = new int[nums.length];
for(int i = 0; i < nums.length; i++) {
if(nums[i] % 2 == 0) result[evenPtr++] = nums[i];
else result[oddPtr--] = nums[i];
}
return result;
}
Head on over here to try the problem for yourself.
There are a few ways discussed in the solutions. I will try to explain the two-pointer method.
The goal is to try to build the resultant string in one pass. For this reason, we will iterate over the string backwards. If we encounter #
, we simply skip the first valid character that follows. Ofcourse, the caveats are considering more backspaces (#
) than actual words, in which case we simply get an empty string. Further, we need a way to handle a series of backspaces and skip an equal number of valid characters.
public boolean backspaceCompare(String s, String t) {
int skip = 0; // variable to keep track of skips
int i = s.length() - 1; // iterates over the string in reverse
StringBuilder s1 = new StringBuilder(); // stores result of s
StringBuilder s2 = new StringBuilder(); // stores result of t
char c; // temporarily stores the current character
while(i >= 0) {
c = s.charAt(i);
if(c == '#') skip++; // increment skip to keep track of number of skips
else {
if(skip > 0) skip--; // encouters valid character but skip > 0 so decrement skip
else s1.append(c);
}
i--;
}
// reset
skip = 0;
i = t.length() - 1;
while(i >= 0) {
c = t.charAt(i);
if(c == '#') skip++;
else {
if(skip > 0) skip--;
else s2.append(c)
}
i--;
}
// I am comparing the built strings to each other, in reverse :P
if(s1.toString().compareTo(s2.toString()) == 0) return true;
return false;
}
Item | Quantity | Processing |
---|---|---|
Cauliflower | one head | cut small floretd & rinse |
Potato | 1-2 medium sized | cut into cubes and rinse |
Garlic | 1 large/ 2 small clove | crushed |
Cumin | 1 tsp | |
Hing | 1/4 tsp | |
Turmeric (haldi) | 1/2 tsp | |
Chilli powder | 1/4 tsp | |
Coriander powder | 1 tsp | |
Oil (sarson) | 1-2 serving spoon |
Put the pan/kadai on the fire. [deeper round bottom pan preferred] Put in 2 serving spoon of oil and turn the flame to high.
Add in the cumin once the oil is hot and let it sputter. Then add hing and cauliflower florets. [we shallow fry the gobi first with oil so it doesnâ€™t taste like gas]. Stir on high flame till the raw smell of cauliflower is gone and the edges have started to brown. This should take anywhere between 5-10 min depending on type and quantity of cauliflower. Once done reduce flame to low and take the cauliflower out onto a plate.
Now in the empty pan add 1 serving spoon of oil, and turn flame to high. Add in the potatoes and sautÃ© till golden brown and semi cooked.
Add back the cauliflowers to the pan and stir on high heat for 2 min. Then turn the flame to low and let the vegetables cook on low for about 5 min.
In the meantime, crush the garlic and in the same vessel add haldi, mirchi, dhaniya and salt. Then add water and create a masala mixture.
At this point the sabzi should have been cooking on low flame for about 5 min with intermittent stirring. Add in the masala mixture and continue to cook on low flame till the water reduces almost completely, and the raw smell of garlic is gone. [depending on gobi type, you can cover during this step to make sure the gobi and aloo soften]
Turn flame to high and stir rapidly. In this step you are trying to sear the masala and the veggies. Stir till you feel sticking at the bottom of the pan. Then add enough water to just cover the vegetables. Let it simmer on high flame.
Once the water starts to reduce, observe your veggies, if still not cooked either cover and cook on low flame, or add more water and cook on high flame.
If the vegetables have softened to you liking continue to cook on high flame till water is almost gone. At this point add in the fresh coriander leaves.
Stir and turn heat off when water is gone.
Item | Quantity | Processing |
---|---|---|
Seam | 250g | cut 2in pieces and rinse |
Potato | 1-2 medium sized | cut into cubes and rinse |
Tomato | 1 medium sized | |
Garlic | 1 large/ 2 small clove | crushed |
Cumin | 1 tsp | |
Hing | 1/4 tsp | |
Turmeric (haldi) | 1/2 tsp | |
Chilli powder | 1/4 tsp | |
Coriander powder | 1 tsp | |
Oil (sarson) | 1-2 serving spoon | |
Amchur Powder | 1tsp | |
Sarson | 1 1/2 tsp |
Cut the tomato into quarters and put it in the microwave for 4-5 min.
Add oil to a pan/kadhai (rounder, deep bottomed pan preferred) and wait till itâ€™s hot. Then add the cumin and let it splatter. Add hing and immediately add in the potatoes. [all this is done while the pan is on high heat. Be careful of oil splatter. DO NOT put the aloo into cold oil. It will not cook and instead just harden and drink the oil.]
Let the potatoes cook on medium high heat, till browned. Then add in the seam and stir it till covered in oil evenly. Cook till the seam starts getting seared or sticking to the bottom of the pan. Then turn the flame to low. And let it cook for 5 more min. [once again, the aim is to semi cook the potato. Depending on the type of potato you have cooking times may differ].
While the sabzi is on low flame, on the side crush the garlic, add haldi, mirchi, salt and dhaniya to a bowl and add some water to make a masala mix. Add this mix to the sabzi and stir. Let it keep cooking on low flame.
Take the tomatoes out of the microwave, remove the skin and crush the pulp. Once all the water from the masala mix has nearly dried, and the raw smell of garlic has gone from the sabzi, add the mashed tomatoes to the pan. Stir it to mix and then cover the pan. Let it cook covered for about 10 min. Uncover intermittently to stir and make sure nothing has burned or stuck to the bottom of the pan.
After about 10 min the tomatoes should be cooked through. Uncover the pan and cook on low flame uncovered for another 2-3 min. At this point check if the potatoes are cooked by breaking with your spatula. They should be almost fully cooked at this stage.
Turn the flame high now and stir rapidly till the tomato/masala water starts to fully dry out/crisp up. Then add enough water that all the vegetable are half submerged. Let this cook on high flame.
Once the water has almost completely reduced, check if the potatoes and seam are cooked soft. If not yet cooked can add more water and repeat till everything is cooked.
OPTIONAL 9. Then turn off heat. Taste your masala, and if more sourness is needed add and mix in amchur as required at this point.
]]>Item | Quantity | Processing |
---|---|---|
Broccoli | 1 head | cut into florets |
Mushroom | 250g | 1 can sliced |
Onion | 1 small | diced |
Garlic | 2 cloves | diced/sliced |
Cumin | 1 tsp | |
Salt | 2 tsp | |
Pepper | 1 tsp | |
Chili flakes | 1/4 tsp | |
Milk | 1 cup | |
Cream OR All-purpose flour | 1 tbsp | |
Oil | 2 serving spoon | |
Butter | 1 knob |
Item | Quantity | Processing |
---|---|---|
Bread | 10 slices | cut into squares |
Oil | 2 serving spoon |
Item | Quantity | Processing |
---|---|---|
Beans (runner/long) | 250g | cut and washed |
Potato | 1-2 medium sized | cut and washed |
Garlic | 1 big clove | crushed |
Cumin | 1 tsp | |
Hing | 1/4 tsp | |
Turmeric (haldi) | 1/2 tsp | |
Chilli powder | 1/4 tsp | |
Coriander powder | 1 tsp | |
Oil | 1-2 serving spoon |
If you want to add tomatoes to your sabzi, cut 1 tomato into quarters and precook in the microwave (~3 min). Take off the skins and mash it up. Add it to the sabzi after the masala in step 5, and cook as normal.
]]>