【LeetCode題解】排序

1. 排序

排序(sort)是一種常見的算法,把數據根據特定的順序進行排列。經典的排序算法以下:html

  • 冒泡排序(bubble sort)
  • 插入排序(insertion sort)
  • 選擇排序(selection sort)
  • 快速排序(quick sort)
  • 堆排序(heap sort)
  • 歸併排序(merge sort)

冒泡排序依次比較相鄰的兩個元素,若逆序則交換;如此走訪數列重複n次,即再也不發生交換,排序完成。(如下圖片均來自於Wikipedia)java

可是,冒泡排序存在着許多無心義的交換,好比:對於基本有序的數組(最好狀況),冒泡排序仍舊有\(O(n^2)\)次交換。咱們能夠標記須要交換的可能,從而下降交換次數到\(O(n)\)算法

void bubble_sort(int a[], int n) {
  int i, bound;
  int exchange = n - 1;       // 初始化
  while (exchange) {
    bound = exchange; // 記錄上一次的交換位置
    exchange = 0;     // 假定這一次沒發生交換
    for (i = 0; i < bound; i++)
      if (a[i] > a[i + 1]) {
        swap(&a[i], &a[i + 1]);
        exchange = i;
      }
  }
}

插入排序能很好地避免部分無心義的交換,其核心思想:從前日後掃描序列,已掃描的元素構成一個已排序的有序序列,當前掃描的元素做爲待插入元素,從有序序列中找到其適合的位置進行插入。數組

選擇排序是一種直觀的排序算法,其基本思想:從前日後走訪待排序序列,找出最小的元素置於待排序序列的首端;如此往復,直到待排序序列只包含一個元素。app

selection-sort

快速排序是由Hoare提出,採用了分治(divide and conquer)策略:選取一個基準pivot,將序列劃分爲兩個子序列,比pivot小的元素歸爲左子序列,比pivot大(或等於)歸於右子序列;如此遞歸地劃分子序列直到無需劃分(即總體有序)。此劃分操做也被稱爲partition;下圖給出以元素5爲pivot的partition操做:
ide

堆排序是指利用大頂堆(max heap)進行排序的算法,基本思想:依次刪除堆頂元素(待排序序列的最大值),將其置於待排序序列的末端;如此往復,直至堆爲空。
ui

歸併排序也是採用分治策略:將相鄰兩個有序的子序列進行歸併(merge)操做,如此往復,直到歸併成一個完整序列(排序完成)。初始時,子序列對應於每個元素。this

穩定性是衡量排序算法是否改變相等鍵值的次序的指標。典型地,好比快排因pivot的選取可能會改變相等鍵值的次序。各類排序算法的比較以下:spa

排序算法 時間複雜度 空間複雜度 穩定性
冒泡排序 \(O(n^2)\) \(T(1)\) 穩定
插入排序 \(O(n^2)\) \(T(1)\) 穩定
選擇排序 \(O(n^2)\) \(T(1)\) 不穩定
快速排序 \(O(n \log n)\) \(T(1)\) 不穩定
堆排序 \(O(n \log n)\) \(T(1)\) 不穩定
歸併排序 \(O(n \log n)\) \(T(\log n)\) 穩定

2. 題解

LeetCode題目 歸類
75. Sort Colors
349. Intersection of Two Arrays 插入
148. Sort List 歸併
242. Valid Anagram
56. Merge Intervals
57. Insert Interval
274. H-Index
275. H-Index II
179. Largest Number
349. Intersection of Two Arrays
350. Intersection of Two Arrays II

75. Sort Colors
數字0、一、2排序,採起相似選擇排序的思路,數字0放在首端,數字2放在尾端。code

public void sortColors(int[] nums) {
  int low = 0, high = nums.length - 1;
  for (int k = 0; k <= high; k++) {
    if (nums[k] == 0)
      swap(nums, k, low++);
    else if (nums[k] == 2)
      swap(nums, k--, high--);
  }
}

private void swap(int[] A, int a, int b) {
  int temp = A[a];
  A[a] = A[b];
  A[b] = temp;
}

349. Intersection of Two Arrays
鏈表的插入排序。

public ListNode insertionSortList(ListNode head) {
  ListNode nHead = new ListNode(0), p = head, pNext, np, nPre;
  while (p != null) {
    // find the suitable position to insert
    for (np = nHead.next, nPre = nHead; np != null && np.val < p.val; ) {
      np = np.next;
      nPre = nPre.next;
    }
    nPre.next = p;
    pNext = p.next;
    p.next = np;
    p = pNext;
  }
  return nHead.next;
}

148. Sort List
排序鏈表,要求時間複雜度\(O(n \log n)\)、空間複雜度\(T(1)\),因此使用歸併排序。其中,合併兩個有序鏈表複用了問題21. Merge Two Sorted Lists的代碼。

public ListNode sortList(ListNode head) {
  if (head == null || head.next == null) return head;
  ListNode pre = head, slow = head, fast = head;
  // cut the list into two halves
  while (fast != null && fast.next != null) {
    pre = slow;
    slow = slow.next;
    fast = fast.next.next;
  }
  pre.next = null;
  // sort the two halves
  ListNode l1 = sortList(head);
  ListNode l2 = sortList(slow);
  // merge the two sorted halves
  return mergeTwoLists(l1, l2);
}

242. Valid Anagram
判斷兩個字符串是否同構(變位詞)。將values數組排序後,判斷是否相等。

public boolean isAnagram(String s, String t) {
  char[] sValues = s.toCharArray();
  char[] tValues = t.toCharArray();
  Arrays.sort(sValues);
  Arrays.sort(tValues);
  return Arrays.equals(sValues, tValues);
}

56. Merge Intervals
合併重複的區間段。思路:排序區間,而後根據條件進行合併。

public List<Interval> merge(List<Interval> intervals) {
  if (intervals.size() <= 1) return intervals;
  intervals.sort((i1, i2) -> {
    if (i1.start == i2.start) return i1.end - i2.end;
    return i1.start - i2.start;
  });
  List<Interval> result = new ArrayList<>();
  for (int i = 0; i < intervals.size(); ) {
    int j, margin = intervals.get(i).end;
    for (j = i + 1; j < intervals.size(); j++) {
      if (intervals.get(j).start > margin)
        break;
      margin = Math.max(margin, intervals.get(j).end);
    }
    result.add(new Interval(intervals.get(i).start, margin));
    i = j;
  }
  return result;
}

57. Insert Interval
將一個區間插入到有序區間列表中,如有重複區間則須要作合併。

public List<Interval> insert(List<Interval> intervals, Interval newInterval) {
  LinkedList<Interval> result = new LinkedList<>();
  if (intervals == null || intervals.isEmpty()) {
    result.add(newInterval);
    return result;
  }
  int len = intervals.size(), start, end, j;
  Interval interval;
  boolean hasAdded = false;
  for (int i = 0; i < len; ) {
    interval = intervals.get(i);
    if (interval.end < newInterval.start) { // newInterval is right-outside
      result.add(interval);
      i++;
    } else if (interval.start > newInterval.end) { // newInterval is left-outside
      if (!hasAdded) {
        result.add(newInterval);
        hasAdded = true;
      }
      result.add(interval);
      i++;
    } else {
      start = Math.min(interval.start, newInterval.start);
      end = Math.max(interval.end, newInterval.end);
      for (j = i + 1; j < len; j++) {
        interval = intervals.get(j);
        if (interval.start > end) break;
        end = Math.max(end, interval.end);
      }
      result.add(new Interval(start, end));
      hasAdded = true;
      i = j;
    }
  }
  if (!hasAdded) result.add(newInterval);
  return result;
}

274. H-Index
計算做者的h-index。思路:對引用次數數組排序,找出至少有h篇論文的引用次數>=h。

public int hIndex(int[] citations) {
  Arrays.sort(citations);
  int h = 0;
  for (int i = citations.length - 1; i >= 0; i--) {
    if (citations[i] < h + 1) break;
    h++;
  }
  return h;
}

275. H-Index II
與上相似,不一樣在於citations已排序,且是升序。

179. Largest Number
從一串數字中,找出能拼成的最大整數;至關於對整數字符串的排序。

public String largestNumber(int[] nums) {
  int len = nums.length;
  String[] strs = new String[len];
  for (int i = 0; i < len; i++) {
    strs[i] = String.valueOf(nums[i]);
  }
  Arrays.sort(strs, this::compareNum);
  // special case
  if (strs[0].charAt(0) == '0') return "0";
  StringBuilder builder = new StringBuilder();
  for (String str : strs) {
    builder.append(str);
  }
  return builder.toString();
}

// compare two number-strings
private int compareNum(String num1, String num2) {
  String cat1 = num1 + num2;
  String cat2 = num2 + num1;
  return cat2.compareTo(cat1);
}

349. Intersection of Two Arrays
求解兩個數組的交集,並去重。思路:排序兩個數組,而後作依次比較,用pre保存上一次添加元素(用於去重)。

public int[] intersection(int[] nums1, int[] nums2) {
  if (nums1 == null || nums2 == null) return null;
  Arrays.sort(nums1);
  Arrays.sort(nums2);
  int len1 = nums1.length, len2 = nums2.length, pre = 0;
  boolean hasAdded = false;
  ArrayList<Integer> common = new ArrayList<>();
  for (int i = 0, j = 0; i < len1 && j < len2; ) {
    if (nums1[i] < nums2[j])
      i++;
    else if (nums1[i] > nums2[j])
      j++;
    else {
      if (nums1[i] != pre || !hasAdded) {
        common.add(nums1[i]);
        pre = nums1[i];
        hasAdded = true;
      }
      i++;
      j++;
    }
  }
  int[] result = new int[common.size()];
  for (int i = 0; i < common.size(); i++) {
    result[i] = common.get(i);
  }
  return result;
}

350. Intersection of Two Arrays II 同上求交集,不須要作去重。

相關文章
相關標籤/搜索