Problem
Given a digit string, return all possible letter combinations that the number could represent. (Check out your cellphone to see the mappings) Input:Digit string "23", Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
Question
I'm confused about the solution code below from LeetCode . Why does passing the result
array through recursive calls change the result
array in letterCombinations
? Is it because the result array in ever recursive getString
call is referencing the same result array?
public List<String> letterCombinations(String digits) {
HashMap<Integer, String> map = new HashMap<>();
map.put(2, "abc");
map.put(3, "def");
map.put(4, "ghi");
map.put(5, "jkl");
map.put(6, "mno");
map.put(7, "pqrs");
map.put(8, "tuv");
map.put(9, "wxyz");
map.put(0, "");
ArrayList<String> result = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return result;
}
ArrayList<Character> temp = new ArrayList<>();
getString(digits, temp, result, map);
return result;
}
public void getString(String digits, ArrayList<Character> temp, ArrayList<String> result,
HashMap<Integer, String> map) {
if (digits.length() == 0) {
char[] arr = new char[temp.size()];
for (int i = 0; i < temp.size(); i++) {
arr[i] = temp.get(i);
}
result.add(String.valueOf(arr));
return;
}
Integer curr = Integer.valueOf(digits.substring(0, 1));
String letters = map.get(curr);
for (int i = 0; i < letters.length(); i++) {
temp.add(letters.charAt(i));
getString(digits.substring(1), temp, result, map);
temp.remove(temp.size() - 1);
}
}
Is it because the result array in ever recursive getString
call is referencing the same result array?
The answer is yes .
Why does passing the result
array through recursive calls change the result
array in letterCombinations?
The passing of an array result
in letterCombinations
changes the array and getString
call is referencing the same result array. As it is a recursive method call, it gets upadtes after each iteration and stores the value to the same reference. That is the main reason, why you have different values for each iteration or a recursive call. Thus it affects the actual array as well.
Firstly I'll point out that despite the name of the site you got it from, this isn't especially clear code.
The call to getString()
has three changing parameters - digits
, temp
and result
.
map
never changes - it would be better and clearer if it were a constant. Let's pretend it is, so the signature of getString()
is getString(String digits, List<Character> temp
.
The naming isn't obvious, but temp
contains the "work done so far", so the first time we call it, it's an empty list.
Let's look at what happens the first time it is called, with digits == 234
and temp
an empty list:
digits.length() != 0
-- so we skip the whole of the first block. 2
and look up its letters in the map - "a"
'a'
onto the end of temp
, making temp == ['a']
getString("34", ['a'])
temp
, making temp == []
'b'
-- getString("34",['b'])
'c'
-- getString("34",['c'])
Then we're done. But what happened in those recursive calls?
Follow the logic through getString("34",['a'])
and you'll see how it grabs 3
from its local digits
and makes calls like getString("4", ['a','d'])
.
In turn getString("4", ['a','d'])
makes calls like getString("",['a','d','g'])
.
Finally we're at level where the recursion stops. Look at what happens when we call getString("",['a','d','g'])
:
digits.length == 0
, so we go into the if
block and return -- we don't progress into the part that would call getString()
again. temp
into a String
, and add it to result
. Better code:
if(digits.isEmpty()) {
result.add(String.join("",temp));
return;
}
We have never created a new result
- we're just passing the same one (and the same map
too) to every invocation of getString()
. So when one getString()
adds an item, that item's still there when the next getString()
adds a second.
Recursive methods can usually be read as:
def recursivemethod(params) {
if(it's a no-brainer) {
output an answer
} else {
do a little bit of the job
call recursiveMethod(newParams)
}
}
In this case, it's a no-brainer when digits
is empty -- the whole of the answer is in temp
and just needs adding to the result list.
If it's not a no-brainer, the "little bit of the job" is to handle the first digit, recursing for each possible letter it could represent.
Cleaner in my opinion, while maintaining the spirit of the original:
private static final Map<Character, String> DECODINGS = new HashMap<>();
static {
DECODINGS.put('2', "abc");
// <snip>
}
public List<String> letterCombinations(String digits) {
ArrayList<String> result = new ArrayList<>();
addCombinationsToList(digits, "", result);
return result;
}
private void addCombinationsToList(String digits, String prefix, List<String> list) {
if (digits.isEmpty()) {
list.add(prefix);
} else {
String letters = DECODINGS.get(digits.charAt(0));
for (int i = 0; i < letters.length(); i++) {
addCombinationsToList(digits.substring(1), prefix + letters.charAt(i), list);
}
}
}
By building an immutable string prefix + letters.charAt(i)
rather than manipulating a mutable List<Character>
, you avoid having to put it back the way you found it, making code that's much easier to understand.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.