1. 排序

排序(sort)是一种常见的算法,把数据根据特定的顺序进行排列。经典的排序算法如下:

  • 冒泡排序(bubble sort)
  • 插入排序(insertion sort)
  • 选择排序(selection sort)
  • 快速排序(quick sort)
  • 堆排序(heap sort)
  • 归并排序(merge sort)

冒泡排序依次比较相邻的两个元素,若逆序则交换;如此走访数列重复n次,即不再发生交换,排序完成。(以下图片均来自于Wikipedia)

但是,冒泡排序存在着许多无意义的交换,比如:对于基本有序的数组(最好情况),冒泡排序仍旧有\(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;
}
}
}

插入排序能很好地避免部分无意义的交换,其核心思想:从前往后扫描序列,已扫描的元素构成一个已排序的有序序列,当前扫描的元素作为待插入元素,从有序序列中找到其适合的位置进行插入。

选择排序是一种直观的排序算法,其基本思想:从前往后走访待排序序列,找出最小的元素置于待排序序列的首端;如此往复,直到待排序序列只包含一个元素。

快速排序是由Hoare提出,采用了分治(divide and conquer)策略:选取一个基准pivot,将序列划分为两个子序列,比pivot小的元素归为左子序列,比pivot大(或等于)归于右子序列;如此递归地划分子序列直到无需划分(即整体有序)。此划分操作也被称为partition;下图给出以元素5为pivot的partition操作:

堆排序是指利用大顶堆(max heap)进行排序的算法,基本思想:依次删除堆顶元素(待排序序列的最大值),将其置于待排序序列的末端;如此往复,直至堆为空。

归并排序也是采用分治策略:将相邻两个有序的子序列进行归并(merge)操作,如此往复,直到归并成一个完整序列(排序完成)。初始时,子序列对应于每一个元素。

稳定性是衡量排序算法是否改变相等键值的次序的指标。典型地,比如快排因pivot的选取可能会改变相等键值的次序。各种排序算法的比较如下:

排序算法 时间复杂度 空间复杂度 稳定性
冒泡排序 \(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、1、2排序,采取类似选择排序的思路,数字0放在首端,数字2放在尾端。

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

同上求交集,不需要做去重。

【LeetCode题解】排序的更多相关文章

  1. 【LeetCode题解】347_前K个高频元素(Top-K-Frequent-Elements)

    目录 描述 解法一:排序算法(不满足时间复杂度要求) Java 实现 Python 实现 复杂度分析 解法二:最小堆 思路 Java 实现 Python 实现 复杂度分析 解法三:桶排序(bucket ...

  2. [LeetCode 题解] Combination Sum

    前言   [LeetCode 题解]系列传送门:  http://www.cnblogs.com/double-win/category/573499.html   1.题目描述 Given a se ...

  3. [LeetCode题解]: Sort Colors

    前言   [LeetCode 题解]系列传送门:  http://www.cnblogs.com/double-win/category/573499.html   1.题目描述 Given an a ...

  4. LeetCode:删除排序数组中的重复项||【80】

    LeetCode:删除排序数组中的重复项||[80] 题目描述 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原 ...

  5. 【LeetCode题解】二叉树的遍历

    我准备开始一个新系列[LeetCode题解],用来记录刷LeetCode题,顺便复习一下数据结构与算法. 1. 二叉树 二叉树(binary tree)是一种极为普遍的数据结构,树的每一个节点最多只有 ...

  6. leetcode题解-122买卖股票的最佳时期

    题目 leetcode题解-122.买卖股票的最佳时机:https://www.yanbinghu.com/2019/03/14/30893.html 题目详情 给定一个数组,它的第 i 个元素是一支 ...

  7. 【LeetCode题解】3_无重复字符的最长子串(Longest-Substring-Without-Repeating-Characters)

    目录 描述 解法一:暴力枚举法(Time Limit Exceeded) 思路 Java 实现 Python 实现 复杂度分析 解法二:滑动窗口(双指针) 思路 Java 实现 Python 实现 复 ...

  8. 【LeetCode题解】225_用队列实现栈(Implement-Stack-using-Queues)

    目录 描述 解法一:双队列,入快出慢 思路 入栈(push) 出栈(pop) 查看栈顶元素(peek) 是否为空(empty) Java 实现 Python 实现 解法二:双队列,入慢出快 思路 入栈 ...

  9. 【LeetCode题解】232_用栈实现队列(Implement-Queue-using-Stacks)

    目录 描述 解法一:在一个栈中维持所有元素的出队顺序 思路 入队(push) 出队(pop) 查看队首(peek) 是否为空(empty) Java 实现 Python 实现 解法二:一个栈入,一个栈 ...

  10. 【LeetCode题解】844_比较含退格的字符串(Backspace-String-Compare)

    目录 描述 解法一:字符串比较 思路 Java 实现 Python 实现 复杂度分析 解法二:双指针(推荐) 思路 Java 实现 Python 实现 复杂度分析 更多 LeetCode 题解笔记可以 ...

随机推荐

  1. 不用搭环境的10分钟AngularJS指令简易入门01(含例子)

    不用搭环境的10分钟AngularJS指令简易入门01(含例子) `#不用搭环境系列AngularJS教程01,前端新手也可以轻松入坑~阅读本文大概需要10分钟~` AngularJS的指令是一大特色 ...

  2. Laravel使用Seeder自动填充数据

    要查看代码,可以点击 或者转到链接:https://github.com/laravel/framework Laravel自动填充数据使用的是Seeder类 <?php use Illumin ...

  3. mfc---ActiveX控件

    AvtiveX控件可看做是一个极小的服务器应用程序,不能独立运行,必须嵌入到某个容器程序中 容器应用程序:可以嵌入或链接对象的应用程序 服务器应用程序:创建对象并且当对象被双击时,可以被启动的应用程序 ...

  4. Linux进程管理详解

    何谓进程?进程,就是正在执行的一个程序或命令,每一个进程都是一个运行实体,有自己的地址空间,并占用一定的系统资源.简而言之,进程就是运行中的程序.在Linux中,诸如ls等命令都是进程,只不过某些命令 ...

  5. poj 2229 DP

    Sumsets Time Limit: 2000MS   Memory Limit: 200000K Total Submissions: 15326   Accepted: 6088 Descrip ...

  6. ThinkPhp框架的数据库操作(查询)

    TP框架有一套自己的数据库操作的代码,包括数据库的增.删.改.查.本文主要讲解TP框架的数据库查询操作. 找到入口文件的控制器: 我这里的入口文件是Show文件夹下的控制器. 打开Login控制器. ...

  7. 基于SSH协议的端口转发

    [前言] 最近一直在使用ssh协议的端口转发(隧道)功能,完成对内网空透等.这篇文章将主要讲解3种常用的ssh tunnelling使用方法和基本原理. 在介绍具体内容前,我先奉上端口转发的常用情景: ...

  8. $_FILES详解

    <form enctype="multipart/form-data" action="upload.php" method="post&quo ...

  9. Python--定时给Ta讲笑话

    受到这篇文章的启发http://python.jobbole.com/84796/,我也动手写了个程序玩一玩. 接口请求说明: 接口请求地址http://api.1-blog.com/biz/bizs ...

  10. java日期处理函数

    java中获取本年第一天的日期 public static Timestamp getYearFirstDay() { Calendar calendar = Calendar.getInstance ...