leetcode每日一題(2020-07-11):315. 計算右側小於當前元素的個數
阿新 • • 發佈:2020-07-11
題目描述:
給定一個整數陣列 nums,按要求返回一個新陣列 counts。陣列 counts 有該性質: counts[i] 的值是 nums[i] 右側小於 nums[i] 的元素的數量。
今日學習:
1.二分法的靈活應用:sorted每次加入一個元素看它應該在哪個位置
2.線段樹:這個有點太難了Orz...參考:部落格1,部落格2
3.複習歸併排序
題解:
1.暴力法
2.二分法
3.歸併1
4.歸併2
5.線段樹
6.巧妙的方法
/** * @param {number[]} nums * @return {number[]} */ //暴力法 var countSmaller = function(nums) { let len = nums.length // let res = new Array() for(let i = 0; i < len; i++) { let cur = nums[i] let count = 0 for(let j = i + 1; j < len; j++) { if(cur > nums[j]) { count++ } } // res.push(count) nums[i] = count } // return res return nums }; //二分查詢 var countSmaller = function(nums) { let len = nums.length if(len == 0) return nums // let counts = new Array(len) let sorted = [] for(let i = len - 1; i >= 0; i--) { let index = findIndex(sorted, nums[i]) sorted.splice(index, 0, nums[i]) // counts[i] = index nums[i] = index } // return counts return nums } var findIndex = function(arr, target) { let low = 0 let high = arr.length - 1 while(low < high) { let mid = (low + high) >> 1 if(target > arr[mid]) { low = mid + 1 }else { high = mid } } if(arr[low] < target) return low + 1 else return low } //歸併排序1 function countSmaller(nums) { if (!nums.length) return [] let objArr = [] //物件和index let resArr = [] //存放次數 for (let i = 0; i < nums.length; i++) { const ele = nums[i] let obj = { num: ele, index: i } resArr.push(0) objArr.push(obj) } haha(objArr) return resArr function haha(nums) { if (nums.length === 1) return nums let mid = nums.length >> 1 let left = nums.slice(0, mid) let right = nums.slice(mid, nums.length) return merge(haha(left), haha(right)) } function merge(left, right) { let res = [] // 定義“後有序陣列”中一個指標 let j = 0 while (left.length && right.length) { if (left[0].num > right[0].num) { res.push(right[0]) right.shift() j++ } else { // “前有序陣列” 的元素出列的時候,數一數 “後有序陣列” 已經出列了多少元素 resArr[left[0].index] += j res.push(left[0]) left.shift() } } while (left.length) { // 同理 resArr[left[0].index] += j res.push(left[0]) left.shift() } while (right.length) { res.push(right[0]) right.shift() j++ } return res } } //歸併排序2 var countSmaller = function(nums) { let len = nums.length; let arr_index = new Array(len); nums.forEach((_,key)=>{ arr_index[key] = key; }) let ans = new Array(len).fill(0); const merge = (left, right)=>{ let llen = left.length; let rlen = right.length; let len = llen+rlen; let res = new Array(len); for(let i = 0, j= 0, k= 0 ; k <len;++k){ if(i == llen){ res[k] = right[j++] }else if(j == rlen){ res[k] = left[i++]; }else if(nums[left[i]]>nums[right[j]]){ ans[left[i]]+=(rlen-j); res[k] = left[i++]; }else{ res[k] = right[j++]; } } return res; } const mergeSort = (arr)=>{ if(arr.length<2){ return arr; } let len = arr.length; let mid = len>>1; let left = arr.slice(0,mid); let right = arr.slice(mid); return merge(mergeSort(left), mergeSort(right)); } mergeSort(arr_index); return ans; }; //線段樹 var countSmaller = function(nums) { if (nums.length === 0) return []; let min = Infinity, max = -Infinity, nlen = nums.length, segmentTree = [], c = 0, ans = []; // 求最大值和最小值 for (let i = 0; i < nlen; i++) { if (nums[i] < min) { min = nums[i]; } if (nums[i] > max) { max = nums[i]; } } // 使用陣列構建線段樹 - 遞迴 function createSegmentTree(start, end, p) { segmentTree[p] = { start: start, end: end, count: 0 }; if (start === end) return ; let mid = Math.floor((end - start) / 2) + start; createSegmentTree(start, mid, 2 * p + 1); createSegmentTree(mid + 1, end, 2 * p + 2); } createSegmentTree(min, max, 0); // 將當前元素放入線段樹中 function setElement(val, p) { if (segmentTree[p] !== undefined && val >= segmentTree[p].start && val <= segmentTree[p].end ) { segmentTree[p].count += 1; } // 終止條件 if (segmentTree[p] === undefined) { return ; } if (segmentTree[p].start === val && segmentTree.end === val) { return ; } // 繼續遞迴左右子線段樹 if (segmentTree[2 * p + 1] !== undefined && val >= segmentTree[2 * p + 1].start && val <= segmentTree[2 * p + 1].end) { setElement(val, 2 * p + 1); } if (segmentTree[2 * p + 2] !== undefined && val >= segmentTree[2 * p + 2].start && val <= segmentTree[2 * p + 2].end) { setElement(val, 2 * p + 2); } } // 線上段樹中查詢比當前元素小的元素的個數,因為是倒序遍歷 nums 陣列,所以,當前線段樹中比當前元素小的元素一定是它右側的元素 function searchRightBigCount(el, p) { if (segmentTree[p] === undefined) { return ; } // 如果當前線段樹的區間完全包含在 el 的線段中 if (segmentTree[p].start >= el.start && segmentTree[p].end <= el.end) { c += segmentTree[p].count; return ; } // 如果當前線段樹與 el 的線段無交叉,終止遞迴 if (el.end < segmentTree[p].start || el.start > segmentTree[p].end) { return ; } // 如果當前線段樹和 el 的線段有交叉,那麼把 el 與 當前線段樹交集部分的線段賦值給 el, // 繼續遍歷左右子線段樹 if (el.start <= segmentTree[p].start && el.end >= segmentTree[p].start) { el.start = segmentTree[p].start; searchRightBigCount(el, 2 * p + 1); searchRightBigCount(el, 2 * p + 2); } else if (el.start <= segmentTree[p].end && el.end >= segmentTree[p].end) { el.end = segmentTree[p].end; searchRightBigCount(el, 2 * p + 1); searchRightBigCount(el, 2 * p + 2); } } // 倒序遍歷陣列 nums,一邊將元素填入線段樹,一遍計算它右側元素的個數 for (let i = nlen - 1; i >= 0; i--) { setElement( nums[i], 0 ); if (min > nums[i] - 1) { ans[i] = 0; } else { searchRightBigCount({ start: min, end: nums[i] - 1 }, 0); ans[i] = c; } c = 0; } return ans; } //巧妙 /* 參考作者 NoBey 1.把 nums 正序排序,放到一個新的陣列中 2.遍歷 nums ,去這個新陣列中找到他的索引位置就可以了,索引值就是比他小的個數 3.注意,每次找完一個元素,需要把他在新陣列中刪掉,因為他如果比下一個要刪查詢的元素小的話,回導致下一個元素的索引 錯誤,因為他是下一個元素左邊的元素,題目要求是:求他右邊比他小的元素; 同時還有一個好處就是:防止因為元素重複導致錯誤 4.優化的一點是 forEach 直接在原陣列操作,沒有使用新的記憶體空間 */ var countSmaller = function(nums) { let newArr = [...nums].sort((a, b) => a - b); nums.forEach((v, k) => { let index = newArr.indexOf( v ); nums[k] = index; newArr.splice(index, 1); }); return nums; }