有序的數組,試試用指針法遍歷

最近在看數據結構和算法,努力總結出道~前端

TL:DR

指針的本質,是記住數組遍歷的進度,從而減小無效遍歷的範圍。算法

當遍歷有序的數組的時候,由於有序,因此更大更小的值已經肯定,能夠用指針法記住數組遍歷的進度,從而減小無效遍歷的範圍數組

數組上,單個指針的話,先將指針指向開始(或末尾),而後指針移動,當移動到數組以外,就表示數組遍歷完畢。markdown

數組上,兩個指針的話,若是出如今兩端,隨着兩邊指針移動,未遍歷中間的元素範圍就慢慢變小,直到指針相鄰或者重合表示,遍歷完畢,也叫對撞指針法數據結構

遇到求和或者比大小問題的時候,若是數組是無序的,能夠嘗試有序以後再使用指針法。app

練習:有序數組合並

merge1

我看到的第一想法是sort大法:數據結構和算法

var merge = function (nums1, m, nums2, n) {
  // num1裏面多餘的元素刪掉,而且將num2裏面的元素插入到num1裏面
  nums1.splice(m,n,...nums2.slice(0,n))
  // 排序
  nums1.sort((a,b)=>a-b)
};
複製代碼

splice方法必定要會,可刪,可增。第一個索引表示要操做的位置(這個位置的元素確定要變),第二個表示刪除後面幾個,以後的都表示插入的元素。oop

但其實,既然已經有序,就要充分利用這個條件,減小無效遍歷。上指針大法!ui

本題,由於是num1夠長,由於後面的空間是空的,因此從後面開始利用,節省空間。url

  • nums一、nums2自己有序,因此裏面的數也是從小到大排列,也就是最大的數始終在末尾
  • 將大的數,倒插進num1就好
  • 一個指針,指向num1的最後一個數的位置。一個指向num2的最後一個數的位置。還用個指針指向填充到num1的哪一個位置了
  • 只要指針還在,就一直比較,前兩個指針的數,大的日後填,且指針移動
  • 指針1小於0的時候,表示num1遍歷完了,那直接將num2裏面,未遍歷的數挨個直接塞到num1裏就好
  • 指針2小於0的時候,表示num2遍歷完了,那就不用管了,數都在num1裏面了
var merge = function (nums1, m, nums2, n) {
  // 指針初始都在末尾
  let pointer1 = m - 1;
  let pointer2 = n - 1;
  let fillPointer = m + n - 1;
  // 一直遍歷,直到有一個數組遍歷完,一個數組遍歷完的表現就是:pointer1 <0 || pointer2 <0
  while (pointer1 >= 0 && pointer2 >= 0) {
    // num1指針的指向的值更大的話,就填充此值,而後移動指針,表示此值以後再也不須要遍歷了
    if (nums1[pointer1] >= nums2[pointer2]) {
      nums1[fillPointer] = nums1[pointer1];
      pointer1--;
      fillPointer--;
    } else {
      // 同理
      nums1[fillPointer] = nums2[pointer2];
      pointer2--;
      fillPointer--;
    }
  }
  // 當nums1先遍歷完的時候,將nums2裏面未遍歷的值,插入到nums1便可
  if (pointer1 < 0) {
    // pointer2 >=0是遍歷的條件,一旦小於0就表示遍歷完了
    while (pointer2 >=0) {
      nums1[newPointer] = nums2[pointer2];
      pointer2--;
      newPointer--;
    }
  }
};
複製代碼

顯然空間O(1),時間O(m+n)

能夠看下官方視頻

練習:三數求和

threeSum

暴力法,我就不說了,三重遍歷,合適的組合丟出來就行,時間複雜度O(n^3)。

顯然,這不是想要的過程。

兩數求和,用Map,以空間換時間。 但三數求和,不固定的數有兩個,是不適合用Map的,這裏數組和求和,聯想下指針。

指針的前提條件是有序的數組,因此這裏先排序,而後使用指針法

  • 數組先排序
  • 固定一個數nums[i],再使用左右指針指向 nums[i]後面的兩端
  • 判斷三數之和,大於0右指針後退,小於0左指針前進,等於0保存索引且左右指針都動
  • nums[i]若是大於0,則中止遍歷,由於以後的確定更大於0
  • 對於重複的問題:左右指針以及i,只要和前面的值一致則跳過。
var threeSum = function (nums) {
  // nums = Array.from(new Set(nums));
  nums.sort((a, b) => a - b);
  let res = [];
  let len = nums.length;
  // i是固定的第一個數的指針
  for (let i = 0; i < len - 2; i++) {
    if (nums[i] > 0) {
      break;
    }
    // 相等就跳過,由於有i-1因此前面必須判斷i>0,否則會報錯
    if (i > 0 && nums[i] === nums[i - 1]) {
      continue;
    }
    // L是左指針
    let L = i + 1;
    // R是右指針
    let R = len - 1;
    // 當L>=R的時候,表示數組遍歷完了
    while (L < R) {
      const sum = nums[L] + nums[R] + nums[i];
      // 大於0,右指針後退
      if (sum > 0) {
        // 這句是去重複的。值相同的話,跳過,這裏注意必須加L<R,否則R--,是有可能小於L的,這不是咱們想要的
        while (L < R && nums[R] === nums[R - 1]) R--;
        R--;
      } else if (sum < 0) {
        while (L < R && nums[L] === nums[L + 1]) L++;
        L++;
      } else {
        res.push([nums[i], nums[L], nums[R]]);
        while (L < R && nums[L] === nums[L + 1]) L++;
        L++;
        while (L < R && nums[R] === nums[R - 1]) R--;
        R--;
      }
    }
  }
  return res;
};
複製代碼

時間複雜度降成O(n^2)。

能夠看下官方視頻

引用

相關文章
相關標籤/搜索