← Home

699. Falling Squares

LeetCode article · C++ solution
Website made by wuisabel-gif · Original C++ code by keineahnung2345
binary searchC++Markdown
699

I like to read this solution as a small machine: keep the useful information, throw away the noise. For 699. Falling Squares, the solution in this repository is mainly a binary search solution.

Guide

What?

The first job is to translate the English prompt into state, transition, and stopping conditions. Instead of trying to be clever immediately, read the code as a sequence of questions:

  • What state are we keeping?
  • How do we move from one state to the next?
  • When do we know the answer is already determined?

For this file, the main tools are: binary search, two pointers, sliding window, greedy.

The notes already sitting in the source point us in the right direction:

  • https://leetcode.com/problems/falling-squares/discuss/108770/O(nlogn)-C%2B%2B-Segment-Tree
  • https://leetcode.com/articles/a-recursive-approach-to-segment-trees-range-sum-queries-lazy-propagation/
  • Approach 4: Segment Tree with Lazy Propagation
  • time: O(NlogN), space: O(N)

Guide

When?

This pattern shows up when the brute force version has too many repeated checks, too many possible branches, or too much bookkeeping to do by hand. The accepted code reduces that pressure by storing exactly the information that remains useful later.

The important function names to track are print, push_up, push_down, update, query.

Guide

Why?

The code is doing bookkeeping so your brain does not have to keep the entire search space open at once.

  • Sorting is used to make local choices comparable, so the later scan does not have to rediscover order.
  • A map keeps the lookup side cheap; the code pays a little memory to avoid repeated searching.
  • A set is doing the membership or uniqueness work, which keeps the main loop readable.
  • The two-dimensional vector is the memory of the solution: grid state, DP state, or adjacency shape.

Guide

How?

Walk through the solution in this order:

  1. Initialize the memory or helper structure.
  2. Process candidates in the order the invariant expects.
  3. Update the answer only when the current state is valid.
  4. Return the value that represents the fully processed input.

The most important competitive-programming habit here is to trust the invariant. Once the invariant is right, the loops become much less scary.

Guide

Complexity

  • Time: O(NlogN), space: O(N)
  • Space: O(n) in the usual case for auxiliary containers or recursion

Guide

C++ Solution

Your submission

The accepted solution

solution.cpp
01//https://leetcode.com/problems/falling-squares/discuss/108770/O(nlogn)-C%2B%2B-Segment-Tree
02//https://leetcode.com/articles/a-recursive-approach-to-segment-trees-range-sum-queries-lazy-propagation/
03//Approach 4: Segment Tree with Lazy Propagation
04//time: O(NlogN), space: O(N)
05//Runtime: 68 ms, faster than 34.15% of C++ online submissions for Falling Squares.
06//Memory Usage: 13.1 MB, less than 100.00% of C++ online submissions for Falling Squares.
07class LazySegTree {
08public:
09    int n;
10    vector<int> tree, lazy;
11    
12    //tree's index start from 0
13    
14    LazySegTree(int n){
15        this->n = n;
16        tree = vector<int>(n*4, 0);
17        lazy = vector<int>(n*4, 0);
18    };
19    
20    void print(){
21        for(int e : tree){
22            cout << e << " ";
23        }
24        cout << endl;
25        
26        for(int e : lazy){
27            cout << e << " ";
28        }
29        cout << endl;
30    }
31    
32    void push_up(int treeIndex) {
33        //update i by its children
34        tree[treeIndex] = max(tree[treeIndex*2+1], tree[treeIndex*2+2]);
35    }
36
37    void push_down(int treeIndex) {
38        //remove laziness of i
39        if (lazy[treeIndex]) {
40            lazy[treeIndex*2+1] = lazy[treeIndex*2+2] = lazy[treeIndex];
41            tree[treeIndex*2+1] = tree[treeIndex*2+2] = lazy[treeIndex];
42            lazy[treeIndex] = 0;
43        }
44    }
45
46    void update(int treeIndex, int l, int r, int ql, int qr, int val) {
47        //looking at [l,r], which totally falls in [ql,qr]
48        if (ql <= l && r <= qr) {
49            tree[treeIndex] = val;
50            //don't need to remove laziness first?
51            lazy[treeIndex] = val;
52            return;
53        }
54        push_down(treeIndex);
55        int mid = l + (r-l)/2;
56        if (ql < mid) update(treeIndex*2+1, l, mid, ql, qr, val);
57        if (qr > mid) update(treeIndex*2+2, mid, r, ql, qr, val);
58        push_up(treeIndex);
59    }
60
61    int query(int treeIndex, int l, int r, int ql, int qr) {
62        //looking at [l,r], which totally falls in [ql,qr]
63        if (ql <= l && r <= qr) return tree[treeIndex];
64        int res = 0;
65        
66        //remove laziness
67        push_down(treeIndex);
68
69        int mid = l + (r-l)/2;
70        /*
71        ql...mid...qr or ql...qr...mid
72        if query range has intersection with left subtree,
73        then recursively query in left subtree
74        */
75        if (ql < mid) res = max(res, query(treeIndex*2+1, l, mid, ql, min(mid,qr)));
76        /*
77        ql...mid...qr or mid...ql...qr
78        if query range has intersection with right subtree,
79        then recursively query in right subtree
80        */
81        if (qr > mid) res = max(res, query(treeIndex*2+2, mid, r, max(mid,ql), qr));
82        
83        return res;
84    }
85};
86
87class Solution {
88public:
89    vector<int> fallingSquares(vector<vector<int>>& positions) {
90        set<int> coords;
91        vector<int> sortedCoords;
92        vector<int> res;
93        map<int, int> index;
94        
95        for (auto& pos : positions) {
96            coords.insert(pos[0]);
97            coords.insert(pos[0]+pos[1]);
98        }
99        
100        sortedCoords = vector<int>(coords.begin(), coords.end());
101        sort(sortedCoords.begin(), sortedCoords.end());
102        
103        // cout << "sorted: ";
104        for(int i = 0; i < sortedCoords.size(); i++){
105            index[sortedCoords[i]] = i;
106            // cout << sortedCoords[i] << " ";
107        }
108        // cout << endl;
109    
110        LazySegTree tree((int)sortedCoords.size());
111        
112        for (auto& pos : positions) {
113            int l = index[pos[0]];
114            int r = index[pos[0]+pos[1]];
115            // cout << "[" << l << ", " << r << "]" << endl;
116            int maxh = tree.query(0, 0, tree.n-1, l, r);
117            // tree.print();
118            tree.update(0, 0, tree.n-1, l, r, maxh+pos[1]);
119            // tree.print();
120            res.push_back(tree.query(0, 0, tree.n-1, 0, tree.n-1));
121        }
122        
123        return res;
124    }
125};
126
127//Approach 1: Offline Propagation
128//Runtime: 84 ms, faster than 18.90% of C++ online submissions for Falling Squares.
129//Memory Usage: 9 MB, less than 100.00% of C++ online submissions for Falling Squares.
130//time: O(N^2), space: O(N)
131class Solution {
132public:
133    vector<int> fallingSquares(vector<vector<int>>& positions) {
134        vector<int> qans(positions.size());
135        for(int i = 0; i < positions.size(); i++){
136            int left = positions[i][0];
137            int height = positions[i][1];
138            int right = left + height -1;
139            qans[i] += height;
140            
141            // for(int j = 0; j < i; j++){ //why not?
142            for(int j = i+1; j < positions.size(); j++){
143                int left2 = positions[j][0];
144                int height2 = positions[j][1];
145                int right2 = left2 + height2 -1;
146                if(left2 <= right && right2 >= left){
147                    //the two squares have intersection
148                    qans[j] = max(qans[j], qans[i]);
149                }
150            }
151        }
152        
153        vector<int> ans;
154        int cur = -1;
155        for(int h : qans){
156            cur = max(cur, h);
157            ans.push_back(cur);
158        }
159        
160        return ans;
161    }
162};
163
164//Approach 2: Brute Force with Coordinate Compression
165//time: O(N^2), space: O(N)
166//Runtime: 100 ms, faster than 15.66% of C++ online submissions for Falling Squares.
167//Memory Usage: 12.3 MB, less than 100.00% of C++ online submissions for Falling Squares.
168class Solution {
169public:
170    vector<int> heights;
171    
172    int query(int L, int R){
173        return *max_element(heights.begin()+L, heights.begin()+R+1);
174    }
175    
176    void update(int L, int R, int h){
177        for(int i = L; i <= R; i++){
178            // heights[i] = max(h, heights[i]);
179            heights[i] = h;
180        }
181    }
182    
183    vector<int> fallingSquares(vector<vector<int>>& positions) {
184        set<int> coords;
185        for(vector<int>& pos : positions){
186            coords.insert(pos[0]);
187            coords.insert(pos[0]+pos[1]-1);
188        }
189        
190        vector<int> sortedCoords(coords.begin(), coords.end());
191        sort(sortedCoords.begin(), sortedCoords.end());
192        
193        map<int, int> index;
194        for(int i = 0; i < sortedCoords.size(); i++){
195            index[sortedCoords[i]] = i;
196        }
197        
198        heights =vector<int>(sortedCoords.size());
199        int best = 0;
200        vector<int> ans;
201        
202        for(vector<int>& pos : positions){
203            int L = index[pos[0]];
204            int R = index[pos[0]+pos[1]-1];
205            int h = query(L, R) + pos[1];
206            update(L, R, h);
207            best = max(best, h);
208            ans.push_back(best);
209        }
210        
211        return ans;
212    }
213};
214
215//Approach 3: Block (Square Root) Decomposition
216//time: O(Nsqrt(N)), space: O(N)
217//Runtime: 60 ms, faster than 45.78% of C++ online submissions for Falling Squares.
218//Memory Usage: 12.6 MB, less than 100.00% of C++ online submissions for Falling Squares.
219class Solution {
220public:
221    vector<int> heights;
222    vector<int> blocks;
223    vector<int> blocks_read;
224    int B;
225    
226    int query(int left, int right){
227        int ans = 0;
228        
229        //left is not at the beginning of a block
230        while(left % B > 0 && left <= right){
231            ans = max(ans, heights[left]);
232            //?
233            ans = max(ans, blocks[left/B]);
234            left++;
235        }
236        
237        //right is not at the end of a block
238        while(right % B != B-1 && left <= right){
239            ans = max(ans, heights[right]);
240            ans = max(ans, blocks[right/B]);
241            right--;
242        }
243        
244        //now left is at the beginning and right is at the end of block
245        while(left <= right){
246            ans = max(ans, blocks[left/B]);
247            //?
248            ans = max(ans, blocks_read[left/B]);
249            left += B;
250        }
251        
252        return ans;
253    }
254    
255    void update(int left, int right, int h){
256        while(left % B > 0 && left <= right){
257            heights[left] = max(heights[left], h);
258            blocks_read[left/B] = max(blocks_read[left/B], h);
259            left++;
260        }
261        while(right % B != B-1 && left <= right){
262            heights[right] = max(heights[right], h);
263            blocks_read[right/B] = max(blocks_read[right/B], h);
264            right--;
265        }
266        while(left <= right){
267            blocks[left/B] = max(blocks[left/B], h);
268            left+=B;
269        }
270    }
271    
272    vector<int> fallingSquares(vector<vector<int>>& positions) {
273        set<int> coords;
274        for(vector<int>& pos : positions){
275            coords.insert(pos[0]);
276            coords.insert(pos[0]+pos[1]-1);
277        }
278        
279        vector<int> sortedCoords(coords.begin(), coords.end());
280        sort(sortedCoords.begin(), sortedCoords.end());
281        
282        map<int, int> index;
283        for(int i = 0; i < sortedCoords.size(); i++){
284            index[sortedCoords[i]] = i;
285        }
286        
287        heights = vector<int>(sortedCoords.size());
288        B = (int)sqrt(sortedCoords.size());
289        blocks = vector<int>(B+2, 0);
290        blocks_read = vector<int>(B+2, 0);
291        
292        int best = 0;
293        vector<int> ans;
294        
295        for(vector<int>& pos : positions){
296            int L = index[pos[0]];
297            int R = index[pos[0]+pos[1]-1];
298            int h = query(L, R) + pos[1];
299            update(L, R, h);
300            best = max(best, h);
301            ans.push_back(best);
302        }
303        
304        return ans;
305    }
306};

Cost

Complexity

Time
O(NlogN), space: O(N)
Dominated by the main traversal, recursion, or data-structure operations in the code.
Space
O(n) in the usual case for auxiliary containers or recursion
Auxiliary state plus the answer structure where the problem requires one.