1. 程式人生 > 實用技巧 >leetcode每日一題(2020-07-11):315. 計算右側小於當前元素的個數

leetcode每日一題(2020-07-11):315. 計算右側小於當前元素的個數

題目描述:
給定一個整數陣列 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;
}