原地hash和時間複雜度的一點理解

話很少說直接看題目

287尋找重複數:算法

給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。api

示例

輸入: [1,3,4,2,2]
輸出: 2
輸入: [3,1,3,4,2]
輸出: 3
給你一個未排序的整數數組,請你找出其中沒有出現的最小的正整數。數組

先用hash表來解決函數

var fn = function(nums, target) {
    const map = {};
    for(let i=0; i<nums.length; ++i){
        if(map[nums[i]]) return nums[i]
        map[nums[i]] = 1
    }
};

很簡單,完美解決,還有其餘方式嗎
以輸入[1,3,4,2,2]爲例,咱們最終獲得的map是指針

{ 1:1, 2:1, 3:1, 4:1 }

能夠看到這個對象的 keys 是 [1,2,3,4], 就是數組下標+1。也就是說 咱們能夠把數組下標看成對象的key,來構建這個hashmap。從而把空間複雜度從 O(n) 降到 O(1)。code

具體解題思路:循環,若是nums[i]!== i + 1, 把 nums[i] 放到下標爲 nums[i]- 1的位置,而後把nums[i] - 1位置上本來的數 a,放到下標爲a - 1,並以此循環循環,若是nums[i] === nums[nums[i] - 1]則說明這個數出現了兩次(被放置到正確的位置過,或者本來就存在)。文字可能說的不清楚,看代碼對象

var findDuplicate = function(nums) {
  for(let i = 0, len = nums.length; i < len; ++i) {
    // 若是當前位置不是i + 1, 進循環去換
    while(nums[i] !== i + 1) { 
      if(nums[nums[i] - 1] === nums[i]) {
        return nums[i]
      }
      let temp = nums[nums[i] - 1]
      nums[nums[i] - 1] = nums[i]
      nums[i] = temp
    }
  }
};

這樣咱們使用本來的數組nums來代替map,使空間複雜度降成 O(1)。
須要注意的是,時間複雜度並無由於兩層循環增長爲 O(n^2),由於目標語句(交換數值的部分)執行的次數依然是n,因此時間複雜度依然是 O(n)。
再次重申,這道題的題目要求不能修改原數組,這種解法是不符合要求的,不過由於忽略這個條件的話比較合適舉例。下面看一道正統的原地hash題目。排序

第二題

41缺失的第一個整數:leetcode

給你一個未排序的整數數組,請你找出其中沒有出現的最小的正整數。rem

示例

輸入: [1,2,0]
輸出: 3
輸入: [3,4,-1,1]
輸出: 2
輸入: [7,8,9,11,12]
輸出: 1

題目給的提示是你的算法的時間複雜度應爲O(n),而且只能使用常數級別的額外空間。完美符合。
要注意的是,以[7,8,9,11,12]爲例,咱們不能爲了把7放到數組第(7-1)位,而改變數組大小,由於數組length是5那麼缺失的數只有多是1-6,那麼大於數組length或者小於0的數就不須要管了,置爲0就能夠了。代碼以下。

var firstMissingPositive = function(nums) {
  let len = nums.length
  for(let i = 0; i < len; ++i) {
    while(nums[i] !== i + 1 && nums[i] > 0) {
      if(nums[nums[i] - 1] === nums[i] || nums[i] > nums.length) {
        nums[i] = 0
      } else {
        let mid = nums[nums[i] - 1]
        nums[nums[i] - 1] = nums[i]
        nums[i] = mid
      }
    }
  }
  for(let i = 0; i < nums.length; ++i) {
    if(!(nums[i] > 0)) {
      return i + 1
    }
  }
  return (nums[nums.length - 1] || 0) + 1
};

第三題

80刪除排序數組中的重複項 II:

給定一個增序排列數組 nums ,你須要在 原地 刪除重複出現的元素,使得每一個元素最多出現兩次,返回移除後數組的新長度。
不要使用額外的數組空間,你必須在 原地 修改輸入數組 並在使用 O(1) 額外空間的條件下完成。

示例

輸入:nums = [1,1,1,2,2,3]
輸出:5, nums = [1,1,2,2,3]
解釋:函數應返回新長度 length = 5, 而且原數組的前五個元素被修改成 1, 1, 2, 2, 3 。 你不須要考慮數組中超出新長度後面的元素。

輸入:nums = [0,0,1,1,1,1,2,3,3]
輸出:7, nums = [0,0,1,1,2,3,3]
解釋:函數應返回新長度 length = 7, 而且原數組的前五個元素被修改成 0, 0, 1, 1, 2, 3, 3 。 你不須要考慮數組中超出新長度後面的元素。

先說一下我一開始的思路:兩個指針,一個begin指向第一個數,end向後找到第一個與begin不一樣的數,若是兩個指針之間距離大於2, 說明出現了兩次以上的相同數字,使用splice刪除,而後更新begin和end的位置繼續。

var removeDuplicates = function(nums) {
  for(let begin = end = 0; begin < nums.length; ++end) {
    if(nums[begin] !== nums[end]) {
      if(end - begin > 2) {
        nums.splice(begin, end - begin - 2)
        begin = end = begin + 2
      } else {
        begin = end
      }
    }
  }
  return nums.length
};

我覺得複雜度是O(n),可是忽略了調用splice這個api自己的複雜度,若是認爲splice是O(n),那麼最終複雜度應該是O(n^2)。

而後就是原地hash的解法,不過此次不能單純的用數組下標來構建了,須要借用一個指針來更新,因此仍是兩個指針,一個指針begin更新結果,一個指針 i 配合 i-1 向前推動。
若是nums[i] !== nums[i-1]說明出現了新的數字,把double置爲false,更新nums[begin]
若是nums[i] === nums[i-1]double=false說明第一次出現相同的數字,更新nums[begin],把double置爲true,意爲已經有兩個相同的數字了,後面不用理:

var removeDuplicates = function(nums) {
  if(nums.length <= 2) return nums.length;
  var begin = 1,
    doubled = false;
  for(var i = 1; i < nums.length; i++) {
    if(nums[i] !== nums[i-1]) {
      doubled = false;
      nums[begin++] = nums[i];
    } else if(!doubled) {
      doubled = true;
      nums[begin++] = nums[i];
    }
  }
  nums.splice(begin);
  return begin;
};
相關文章
相關標籤/搜索