Jan 15, 2020
   
   
    
    
    
    
     Edit me
    
   #. Problem
Minimum Operations to Reduce X to Zero
1. My approach
BFS
I was quite sure that it would be DP, but I couldn’t think of any ways so I just used BFS just to see if it works. And obviously it didn’t work. Time complexity would be O(2^N).
/**
 * @param {number[]} nums
 * @param {number} x
 * @return {number}
 */
var minOperations = function(nums, x) {
    const q = [{
        curX: x,
        curArr: nums,
        curCnt: 0
    }];
    let ans = 0;
    
    while(q.length !== 0){
        let temp = q.shift();
        let curX = temp.curX;
        let curCnt = temp.curCnt;
        let curArr = temp.curArr;
        if(curX === 0){
            return temp.curCnt;
        }
        
        if(curX - curArr[0] >= 0){
            q.push({
                curX: curX - curArr[0],
                curArr: curArr.slice(1),
                curCnt: curCnt+1
            })
        }
        
        if(curArr.length > 1 && curX - curArr[curArr.length-1] >= 0){
            q.push({
                curX: curX - curArr[curArr.length-1],
                curArr: curArr.slice(0, curArr.length-1),
                curCnt: curCnt+1
            })
        }
        // console.log(q);
    }
    
    return -1;
};
| Time(O(2^N)) | Space(O(?)) | 
|---|---|
| ? ms | ? MB | 
super long DP
and I came up with this DP which was quite cleaver at the time but it was too big, making ‘heap out of memory’ error.
- TC - O(N)
- SC - O(N)
- Where N = 1e9 :P
/**
 * @param {number[]} nums
 * @param {number} x
 * @return {number}
 */
var minOperations = function(nums, x) {
    const arr = Array.from({length:x+1}, ()=>{
        return {
            curCnt: 100000,
            curLeft: 0,
            curRight: 0
        }
    });
    let i = 0, j = nums.length-1;
    arr[0].curCnt = 0;
    arr[0].curLeft = 0;
    arr[0].curRight = nums.length-1;
    
    for(let i = 0; i <= x; i++){
        let curCnt = arr[i].curCnt;
        let curLeft = arr[i].curLeft;
        let curRight = arr[i].curRight;
        
        if(i + nums[curLeft] <= x && curCnt + 1 < arr[i + nums[curLeft]].curCnt){
            arr[i + nums[curLeft]].curCnt = curCnt + 1;
            arr[i + nums[curLeft]].curLeft = curLeft + 1;
            arr[i + nums[curLeft]].curRight = curRight;
        }
        
        if(curLeft < curRight && i + nums[curRight] <= x && curCnt + 1 < arr[i + nums[curRight]].curCnt){
            arr[i + nums[curRight]].curCnt = curCnt + 1;
            arr[i + nums[curRight]].curLeft = curLeft;
            arr[i + nums[curRight]].curRight = curRight - 1;
        }
    }
    
    // console.log(arr);
    return arr[x].curCnt === 100000? -1:arr[x].curCnt;
    
};
2. Solution
It’s called ‘Sliding window’, using two pointers, left and right. Starting from 0 and moving towards the end. Very cleaver, and efficient.
The time complexity here would be O(N) where N = 1e5, and space complexity would be, O(1). This time complexity might be confusing, but here’s why it’s O(N):
- O((right in 0 to N) + (left in 0 to k)) = O(N + k) = O(N)
Sliding window
/**
 * @param {number[]} nums
 * @param {number} x
 * @return {number}
 */
var minOperations = function(nums, x) {
    
    const sum = nums.reduce((acc, cur) => acc + cur, 0);
    const target = sum - x;
    let curSum = 0;
    
    if(target < 0) return -1;
    if(!target) return nums.length;
    
    let l = 0, r = 0;
    let maxLen = -Infinity;
    
    while(r <= nums.length){
        curSum += nums[r];
        while(curSum > target) curSum -= nums[l++];
        if(curSum === target) maxLen = Math.max(r-l+1, maxLen);
        r++;
    }
    
    return maxLen === -Infinity? -1:nums.length-maxLen;
};
| Time(O(N)) | Space(O(1)) | 
|---|---|
| 108 ms | 50.6 MB | 
3. Epilogue
What I’ve learned from this exercise:
- DP is difficult.
- JavaScript has ‘Infinity’ as a value of number type.
- Mostly, we approach from how we solve the problem in our head, but sometimes it’s important to approach it differently since it might be better solution.