this is a problem tagged with “easy”, but problem like this, may cause lots of trouble. Because, all of a sudden, you will have so many potential ideas. and you have no idea which can be implemented in a simple and bug-free way.
and if you dig deeper about this, the lack of persepective and bad coding skills(both in speed and correctness) is the reason. but today we are not gonna to talk about these. let’s see when you meet a problem with multiple idea, how to deal with it in a elegant way.
Take LC392 Is Subsequence as an example.
Given a string s and a string t, check if s is subsequence of t.
it’s really simple, right?
The order matters for this problem, so we can use greedy. and if we want to use hashmap, we need to store the index of each char in string t.
Greedy:
Starting from both head, we compare chars of s and t. the pointer of t keeps moving, but when there is a match of s.char and t.char will the pointer of s moving. it’s greedy is: we match front chars as soon as we find them. why is this working? because if this is not gonna work, there is no way we can find match for every chars.
class Solution {
public boolean isSubsequence(String s, String t) {
if (s.length() == 0) return true;
if (t.length() == 0) return false;
return isSubsequence(s, t, 0, 0);
}
private boolean isSubsequence(String s, String t, int sIndex, int tIndex) {
if (sIndex == s.length()) { //if s reached its last index, then it means we found
return true;
}
if (tIndex == t.length()) {
return false;
}
if (s.charAt(sIndex) == t.charAt(tIndex)) { //only when we find a match will we move sIndex
sIndex++;
}
tIndex++; //and tIndex always keep moving
return isSubsequence(s, t, sIndex, tIndex);
}
}
or we can do this using two pointers and in iterative way.
class Solution {
public boolean isSubsequence(String s, String t) {
Integer leftBound = s.length(), rightBound = t.length();
Integer pLeft = 0, pRight = 0;
while (pLeft < leftBound && pRight < rightBound) {
// move both pointers or just the right pointer
if (s.charAt(pLeft) == t.charAt(pRight)) {
pLeft += 1;
}
pRight += 1;
}
return pLeft == leftBound;
}
}
HashMap:
if this is a situation which we need to query many different s for a fixed t. so it is better if we preprocess the t and retrieve its information ahead.
and hashmap will store every chars in t and its indexes.so each time we need to search index for a char, we use binary search.
class Solution {
public boolean isSubsequence(String s, String t) {
if (s.length() == 0) return true;
if (t.length() == 0) return false;
HashMap<Character, List<Integer>> map = new HashMap<>(); //chars and its indexes
//we divide t in to one char at a piece and store the char and index into hashmap
for (int i = 0; i < t.length(); i++) { //iterate t
if (map.get(t.charAt(i)) == null) {
List<Integer> temp = new ArrayList<>();
temp.add(i);
map.put(t.charAt(i), temp);
} else {
map.get(t.charAt(i)).add(i);
}
}
int prevIndex = -1;
for (int i = 0; i < s.length(); i++) { //iterate s
if (map.containsKey(s.charAt(i))) {
//find the first one that is larger than previous index
//how to use binary search to get the number which is least larger than target number.
//we extract it as an independent function:
List<Integer> list = map.get(s.charAt(i));
int index = binarySearch(list, prevIndex); //binary search a list for a list.get(i) that is larger than given target which is prevIndex
if (index == -1) {
return false;
} //if not, keep going to the next round
prevIndex = index;
} else {
return false;
}
}
return true;
}
private int binarySearch(List<Integer> list, int prevIndex) {//this function uses to find the index in list where list.get(index) is the first larger than
int start = 0;
int end = list.size() - 1;
while (start <= end) {
int mid = (end - start) / 2 + start;
int index = list.get(mid);
if (index < prevIndex) {
start = mid + 1; //binary earch tight bound, where will start pointer ends?
} else if (index > prevIndex) {
end = mid - 1;
}
} //start pointer stops at the poistio we want
return start == list.size() ? -1: list.get(start); //returns
}
}
and on second thought, this problem is a lot like edit distance problem.
so this problem converts to: given two strings, find the edit distance of those two strings, and the only operation we are gonna use deletion.
so dp[i][j] means how many matches we have when s has only first i chars and t has only j chars.
similarly, we have two subcases:
the first is if we have a match in s and t, then dp[i][j] = dp[i-1][j-1] + 1
the second is if this is not a match: dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])
so if dp[m][n] == s.length(), then we have a match, if it isn’t, then false;
class Solution {
public boolean isSubsequence(String s, String t) {
int m = s.length();
int n = t.length();
int[][] dp = new int[m+1][n+1];
//for the first line and first column, all cells are 0
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n]==m?true: false;
}
}
whether we can think of any method, you have to stick on that. and polish you coding speed and concise and precise.