John_Phoenix
Expert Member
- Joined
- Jul 8, 2017
- Messages
- 1,087
- Reaction score
- 624
Thanks Cguy, nice explanation, and "caching" the results in a object(hashmap) is genius.
South Africa’s biggest forum. Discuss, discover, and connect with thousands of members.
Yeah dude, your solution was badass. NiceAs a brief explanation, the brute force approach goes through n values in the array, and for each one of these values (call it x) checks another n values to see if one of these values when added to the x, will equal the target. The inside part of the two for loops can run up to n*n times, so it is O(n*n). If you can avoid doing the second loop entirely, it will be O(n).
The purpose of the hash maps, is that they can look up the value "target - x", where x is the number for the current loop iteration, to see if it has been seen already in the current iteration - if it has, that value and the current value are the pair we want, and we are done. This lookup can be done without a loop (since hash maps typically have expected time O(1)).
What I did with the big "indexof" array, was essentially implement a very big, very fast hashmap, with worse case O(1) complexity, and a trivial hash function, so that it would be very fast. The downside of this, is that if the range of numbers was larger, the array could easily become too big, in which case, I would go for a more traditional hash map data structure.
Amusingly, the leading solution posted earlier did something similar, but must have submitted a bunch of times to narrow down the array range of the test set inputs as closely as possible.
Yeah dude, your solution was badass. Nice
#include<inttypes.h>
int* twoSum(int* nums, int numsSize, int target) {
int tot=32768,mid=16384;
short indexofbase[tot];
short * indexof = &indexofbase[mid];
//int * result = (int*)malloc(2*sizeof(int));
static int64_t sr;
int * result = (int*)&sr;
int64_t * d = (int64_t*)indexofbase;
for( int i=0; i < tot/32; i++ ) {
d[ 0 ] = 0;
d[ 1 ] = 0;
d[ 2 ] = 0;
d[ 3 ] = 0;
d[ 4 ] = 0;
d[ 5 ] = 0;
d[ 6 ] = 0;
d[ 7 ] = 0;
d += 8; // Clear 64 bytes per iteration
}
int num0 = nums[0];
indexof[num0] = 0;
int i=1;
for( ; i + 3 < numsSize; i += 4 ) {
int n0 = nums[i+0];
int n1 = nums[i+1];
int n2 = nums[i+2];
int n3 = nums[i+3];
int delta0 = target - n0;
int delta1 = target - n1;
int delta2 = target - n2;
int delta3 = target - n3;
if ( indexof[delta0] | (num0 == delta0) ) { result[0] = indexof[delta0]; result[1] = i+0; return result; }
indexof[n0] = i+0;
if ( indexof[delta1] | (num0 == delta1) ) { result[0] = indexof[delta1]; result[1] = i+1; return result; }
indexof[n1] = i+1;
if ( indexof[delta2] | (num0 == delta2) ) { result[0] = indexof[delta2]; result[1] = i+2; return result; }
indexof[n2] = i+2;
if ( indexof[delta3] | (num0 == delta3) ) { result[0] = indexof[delta3]; result[1] = i+3; return result; }
indexof[n3] = i+3;
}
for( ; i < numsSize; i++ ) {
int n = nums[i];
int delta = target - n;
if ( indexof[delta] || num0 == delta ) {
result[0] = indexof[delta];
result[1] = i;
return result;
}
indexof[n] = i;
}
return NULL;
}
As a brief explanation, the brute force approach goes through n values in the array, and for each one of these values (call it x) checks another n values to see if one of these values when added to the x, will equal the target. The inside part of the two for loops can run up to n*n times, so it is O(n*n). If you can avoid doing the second loop entirely, it will be O(n).
The purpose of the hash maps, is that they can look up the value "target - x", where x is the number for the current loop iteration, to see if it has been seen already in the current iteration - if it has, that value and the current value are the pair we want, and we are done. This lookup can be done without a loop (since hash maps typically have expected time O(1)).
What I did with the big "indexof" array, was essentially implement a very big, very fast hashmap, with worse case O(1) complexity, and a trivial hash function, so that it would be very fast. The downside of this, is that if the range of numbers was larger, the array could easily become too big, in which case, I would go for a more traditional hash map data structure.
Amusingly, the leading solution posted earlier did something similar, but must have submitted a bunch of times to narrow down the array range of the test set inputs as closely as possible.
func twoSum0(_ nums: [Int], _ target: Int) -> [Int] {
var d = [Int: Int]()
for (i, num) in nums.enumerated() {
guard let key = d[target - num] else { d[num] = i; continue }
return [key, i]
}
fatalError("No solution.")
}
enum Result<E,A> {
case Done(A)
case Call(E, A)
}
func trampoline<E, A>(with fn: @escaping (E, A) -> Result<E, A>) -> ((E, A) -> A) {
return { (value: E, accumulator: A) -> A in
var result = fn(value, accumulator)
while true {
switch result {
case let .Done(accumulator):
return accumulator
case let .Call(number, accumulator):
result = fn(number, accumulator)
}
}
}
}
func twoSum(_ nums: [Int], _ target: Int) -> [Int]
{
let c = nums.count, m = c / 2
var found = false
let twoSumR = trampoline(with: { (n: (Int, Int), array: [Int]) -> Result<(Int, Int), [Int]> in
guard n.0 < c && n.1 < c else { return .Done([]) }
if target - nums[n.0] - nums[n.1] == 0 { found = true; return .Done([n.0, n.1]) }
if n.0 == m - 1 || n.0 == c - 2 || found { return .Done([]) }
if n.1 >= c - 2 { return .Call((n.0 + 1, n.0 + 2), array)}
return .Call((n.0, n.1 + 1), array)
})
return c < 3 ? nums : twoSumR((0, 1), []) + twoSumR((m, m + 1), [])
}
Impressive [)roi(], welcome to the challenge...great time there.[)roi(];20049272 said:/snip
Re offline tests, it's pretty much the same 19 tests (well at least most of them, some of the bigger ones I simply double up to make up the difference):Impressive [)roi(], welcome to the challenge...great time there.
I agree the timing is not exactly 100% accurate, but when you say offline/local, you can't be running the same test set because there are 19 tests it runs for the Two Sum (waaaay more for some others), and the time taken is some combination of all of those - be it a sum of all times, some weighted avg, who knows. Unless you managed to get hold of the entire 19 tests of course, in which case well played![]()
[)roi(];20049272 said:Here's a Swift run at this.
1st attempt, I hit the problem straight on using a single loop and memoization;
Time: 9ms (leet server)
"Your runtime beats 96.7 % of Swift submissions."
Note:
The leet server appears to be quite a bit load sensitive; as time's vary a bit between runs; plus its obvious no LLVM compiler optimisations are applied (e.g. Fast, Whole Module Optimisation), which makes quite a bit difference with Swift code. e.g. leet states my 1st attempt took 9ms; whereas repeating that offline with the same test data it runs sub 1ms (0,72ms).
PHP:func twoSum0(_ nums: [Int], _ target: Int) -> [Int] { var d = [Int: Int]() for (i, num) in nums.enumerated() { guard let key = d[target - num] else { d[num] = i; continue } return [key, i] } fatalError("No solution.") }
For my second attempt I decided to tackle the problem using recursion, thunks and result composition; the solution is less concise, but it's fast. Basically it recursively tackles the problem over two thunks. The thunk's role is primarily to provide the tail call optimisation, but they're also quite fast. BTW The other benefit of thunks over standard recursion is the ability to cross communicate between thunks; in this case if 1 thunk finds the solution it changes the found variable; the other thunk then terminates on the next recursive .Call i.e. minimal wasted cycles.
Time: 2ms (leet server), and 0.05ms (local) with the same test set.
"Your runtime beats 100.00 % of Swift submissions."
PHP:enum Result<E,A> { case Done(A) case Call(E, A) } func trampoline<E, A>(with fn: @escaping (E, A) -> Result<E, A>) -> ((E, A) -> A) { return { (value: E, accumulator: A) -> A in var result = fn(value, accumulator) while true { switch result { case let .Done(accumulator): return accumulator case let .Call(number, accumulator): result = fn(number, accumulator) } } } } func twoSum(_ nums: [Int], _ target: Int) -> [Int] { let c = nums.count, m = c / 2 var found = false let twoSumR = trampoline(with: { (n: (Int, Int), array: [Int]) -> Result<(Int, Int), [Int]> in guard n.0 < c && n.1 < c else { return .Done([]) } if target - nums[n.0] - nums[n.1] == 0 { found = true; return .Done([n.0, n.1]) } if n.0 == m - 1 || n.0 == c - 2 || found { return .Done([]) } if n.1 >= c - 2 { return .Call((n.0 + 1, n.0 + 2), array)} return .Call((n.0, n.1 + 1), array) }) return c < 3 ? nums : twoSumR((0, 1), []) + twoSumR((m, m + 1), []) }
As it happens, I'm coming out of the tail end of very a busy period; and surprisingly enough have been mulling over a revamp of the site; with a series of articles dedicated to (no surprise); Functional Programming (FP).@[)roi(]
Could you try your solution in Go? I am really curious as to the performance difference (especially locally).
Your blog (codefunc.github.io) hasn't had any updates lately, would you be kind enough to add articles with your leetcode solutions explained in-depth? I for one would definitely read them.![]()
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.
public static int countVolume (List<Integer> levels) {
String currentLevels = String.join("", levels.stream().map(x -> x.toString()).collect(Collectors.toList()));
int volumeCount = 0;
int baseValue = 0;
while (!currentLevels.isEmpty()) {
// if top reached
if (currentLevels.replaceAll(Integer.toString(baseValue), "").isEmpty())
break;
// replace leading/trailing base values
currentLevels = new StringBuilder(currentLevels.replaceFirst("^" + baseValue + "+(?!$)", "")).reverse().toString().replaceFirst("^" + baseValue + "+(?!$)", "");
// count at base level
Matcher m = Pattern.compile(Integer.toString(baseValue)).matcher(currentLevels);
while (m.find())
volumeCount++;
// "flood" the base level
currentLevels = currentLevels.replaceAll(Integer.toString(baseValue), Integer.toString(baseValue + 1));
baseValue++;
}
return volumeCount;
}
Awesome Hamster, whats your time and % submissions beaten with this?I should really get Go up and running again. Anyway, Java + regex:
Probably not the best way to solve it, but I felt like regex. C# would've made this "cleaner".
Code:public static int countVolume (List<Integer> levels) { String currentLevels = String.join("", levels.stream().map(x -> x.toString()).collect(Collectors.toList())); int volumeCount = 0; int baseValue = 0; while (!currentLevels.isEmpty()) { // if top reached if (currentLevels.replaceAll(Integer.toString(baseValue), "").isEmpty()) break; // replace leading/trailing base values currentLevels = new StringBuilder(currentLevels.replaceFirst("^" + baseValue + "+(?!$)", "")).reverse().toString().replaceFirst("^" + baseValue + "+(?!$)", ""); // count at base level Matcher m = Pattern.compile(Integer.toString(baseValue)).matcher(currentLevels); while (m.find()) volumeCount++; // "flood" the base level currentLevels = currentLevels.replaceAll(Integer.toString(baseValue), Integer.toString(baseValue + 1)); baseValue++; } return volumeCount; }
Awesome Hamster, whats your time and % submissions beaten with this?
Ok, I can do that. I agree it'll be easier to follow and contribute. I'll start doing that from next week.If you guys are going to do this every week, then perhaps you should make a new post with a week identifier?
That way guys can still play with and add onto old posts, as well as see where/when a new one begins?
Haha, well then it doesn't count! /jkNot sure I follow. You mean on leetcode? Don't think I have an account. Was just trying to solve it a "different" way
EDIT: OK, I ran it on leetcode and it bombs at test 314/315 because the code assumes there won't be any double digit numbers and will treat 10 as a 1 and a 0.
func trap(_ height: [Int]) -> Int {
var values = (left: 0, right: height.count - 1, level: 0, water: 0)
while values.left < values.right {
var lowerIndex = 0
if height[values.left] < height[values.right] {
lowerIndex = values.left
values.left += 1
}
else
{
lowerIndex = values.right
values.right -= 1
}
let lowerLevel = height[lowerIndex]
values.level = max(values.level, lowerLevel)
values.water += values.level - lowerLevel
}
return values.water
}

class Solution {
public:
int trap(vector<int>& height) {
int firstRain = 0;
int secondRain = 0;
int localRain = 0;
int previousHigh = 0;
int highestPosition = 0;
int length = height.size();
int lastPosition = length-1;
for (int i = 0; i < length; i++)
{
if (height[i] >= previousHigh)
{
previousHigh = height[i];
highestPosition = i;
firstRain = localRain;
}
else
{
localRain += (previousHigh - height[i]);
}
}
previousHigh = 0;
localRain = 0;
for (int i = lastPosition; i >= highestPosition; i--)
{
if (height[i] >= previousHigh)
{
previousHigh = height[i];
secondRain = localRain;
}
else
{
localRain += (previousHigh - height[i]);
}
}
return firstRain + secondRain;
}
};

I should really get Go up and running again. Anyway, Java + regex:
Probably not the best way to solve it, but I felt like regex. C# would've made this "cleaner".
Code:String currentLevels = String.join("", levels.stream().map(x -> x.toString()).collect(Collectors.toList()));
String currentLevels = levels.stream().map(Object::toString).reduce("", (a, b) -> a + b);