導入
ソート アルゴリズムはすべてのプログラマーのアルゴリズムの基本スキルの 1 つであり、基礎が十分にしっかりしていて初めて、アルゴリズムの分野でより高い成果を上げることができます。ソートアルゴリズムを学ぶときは、それが何であるか、そしてなぜそうなるのかを知る必要があり、そうでないと、対応する問題を実行するときに、デッドフォーミュラを使用して問題を実行することが困難になります。柔軟に使用する方法を学び、ソート アルゴリズムの本質と魂を学びましょう。たとえば、マージ ソートで重要なのは、コード テンプレートではなく、大きなタスクを小さなタスクに分割する分割統治のアイデアです。控えめに言っても、私たちが通常問題を解くとき、他の人がマージとマージというオリジナルのテンプレート問題であなたをテストすることはめったにありません。より大きなスケールで言えば、分割統治の考え方はプログラミング開発においても非常に重要です。例えば、当社のビッグデータ開発でもよく使われています。MapReuce は分割統治の考え方ではないでしょうか? 並行プログラミングもできません。分割統治のアイデアを使用しますか? したがって、これらの古典的な並べ替えの核となる考え方は、注意深く味わう価値があります。
理論的根拠
いわゆるソートは、1 つまたはいくつかのキーワードのサイズに従って昇順または降順に並べられたレコードの文字列を作成する操作です。ソートアルゴリズムは、要件に従ってレコードを配置する方法です。並べ替えアルゴリズムは、多くの分野、特に大量のデータの処理において大きな注目を集めています。優れたアルゴリズムを使用すると、多くのリソースを節約できます。さまざまな分野のデータのさまざまな制限と規範を考慮して、実用的で優れたアルゴリズムを取得するには、多くの推論と分析が必要です。たとえば、クイック ソート、マージ ソート、バブル ソートはコンピュータの世界で広く使用されています。ただし、Likou に関する多くの質問は、ソート アルゴリズムを直接調査するものではなく、クイック ソートの分割操作、逆シーケンス番号の計算、k 番目に大きい要素の検索など、ソート アルゴリズムに組み込まれたアイデアと詳細を調査するものであることに注意してください。、など。基本的なソートアルゴリズムとしては、バブルソート、選択ソート、挿入ソート、ヒルソート、マージソート、クイックソート 、基数ソート、ヒープソート 、 カウンティングソート、バケットソート
の10種類がありますので、紹介していきます。
基本概念の分類
ソートの基本的な考え方は次の 3 点です。
- 時間計算量: つまり、シーケンスの初期状態から、ソート アルゴリズムの変換および最終的なソート結果の状態への移行までにかかる時間の測定値。
- スペースの複雑さ: シーケンスの初期状態から、ソート、シフト、変換のプロセスを経て最終状態に至るまでに費やされるスペースのオーバーヘッドです。
- 安定性: アルゴリズムは、並べ替えプロセス中に要素の位置の相対的な順序を変更しません。逆に、不安定な並べ替えアルゴリズムでは、この順序が変更されることがよくあります。これは、時間の複雑さ以上に、私たちが見たくないものです。そしてスペース 複雑さの方が重要です。
多くの並べ替えアルゴリズムがあり、さまざまなタイプの並べ替えアルゴリズムがさまざまなタイプのシナリオに適しています。スペースを節約する必要があり、それほど時間は必要ない場合もあります。逆に、より多くの時間を考慮したいが、それほど時間は必要ない場合もあります。一般に、すべての人は両方ではなく、一方の側面から選択をしなければなりません。
10 個の並べ替えアルゴリズム
次に、10 個のアルゴリズムを個別に紹介しますが、最初に、次の表に示すように、10 個のソート アルゴリズムの状況を統一的に見ていきます。
バブルソート
バブル ソート (Bubble Sort) も、シンプルで直感的な並べ替えアルゴリズムです。ソート対象の配列を繰り返し調べて、一度に 2 つの要素を比較し、順序が間違っている場合はそれらを交換します。シーケンスを訪問する作業は、交換する必要がなくなるまで、つまりシーケンスがソートされるまで繰り返されます。このアルゴリズムの名前は、小さな要素が交換によって配列の先頭にゆっくりと「浮遊」するという事実に由来しています。
コード
public class BubbleSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
for (int i = 1; i < arr.length; i++) {
// 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
boolean flag = true;
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = false;
}
}
if (flag) {
break;
}
}
return arr;
}
}
選択ソート
選択ソートはシンプルで直観的なソート アルゴリズムであり、どのようなデータが入力されるかに関係なく、時間計算量は O(n²) です。したがって、使用する場合はデータサイズが小さいほど良いです。唯一の利点は、追加のメモリ領域を占有しないことです。
コード
public class SelectionSort {
public int[] sort(int[] sourceArray) throws Exception {
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
// 总共要经过 N-1 轮比较
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
// 每轮需要比较的次数 N-i
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
// 记录目前能找到的最小值元素的下标
min = j;
}
}
// 将找到的最小值和i位置所在的值进行交换
if (i != min) {
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
return arr;
}
}
挿入ソート
挿入ソートのコード実装はバブル ソートや選択ソートほど単純で粗雑ではありませんが、ポーカーをプレイしたことがある人なら誰でも数秒で理解できるはずなので、その原理は最も理解しやすいはずです。挿入ソートは、最もシンプルで直観的なソート アルゴリズムです。順序付けされたシーケンスを構築することで機能します。ソートされていないデータの場合は、ソートされたシーケンスの後ろから前にスキャンし、対応する位置を見つけて挿入します。
コード
public class InsertSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
// 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
for (int i = 1; i < arr.length; i++) {
// 记录要插入的数据
int tmp = arr[i];
// 从已经排序的序列最右边的开始比较,找到比其小的数
int j = i;
while (j > 0 && tmp < arr[j - 1]) {
arr[j] = arr[j - 1];
j--;
}
// 存在比其小的数,插入
if (j != i) {
arr[j] = tmp;
}
}
return arr;
}
}
ヒルソート
ヒル ソートは、デクリメントインクリメント ソート アルゴリズムとも呼ばれ、挿入ソートのより効率的かつ改良されたバージョンです。ただし、ヒル ソートは不安定なソート アルゴリズムです。基本的な考え方は次のとおりです。まず、ソート対象のレコード シーケンス全体を直接挿入ソート用にいくつかのサブシーケンスに分割し、シーケンス全体のレコードが「基本的に正しい順序」になったら、すべてのレコードに対して順番に直接挿入ソートを実行します。
コード
public static void shellSort(int[] arr) {
int length = arr.length;
int temp;
for (int step = length / 2; step >= 1; step /= 2) {
for (int i = step; i < length; i++) {
temp = arr[i];
int j = i - step;
while (j >= 0 && arr[j] > temp) {
arr[j + step] = arr[j];
j -= step;
}
arr[j + step] = temp;
}
}
}
マージソート
マージ ソート (マージ ソート) は、マージ操作に基づく効果的な並べ替えアルゴリズムです。このアルゴリズムは、分割統治の非常に典型的なアプリケーションです。分割統治の典型的なアルゴリズムの適用として、マージ ソートを実装するには 2 つの方法があります。
- トップダウン再帰 (すべての再帰メソッドは反復を使用して書き換えることができるため、2 番目の方法があります)。
- ボトムアップの反復。
コード
public class MergeSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
if (arr.length < 2) {
return arr;
}
int middle = (int) Math.floor(arr.length / 2);
int[] left = Arrays.copyOfRange(arr, 0, middle);
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
return merge(sort(left), sort(right));
}
protected int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int i = 0;
while (left.length > 0 && right.length > 0) {
if (left[0] <= right[0]) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
while (left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
while (right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
}
クイックソート
クイックソートは、Tony Hall によって開発された並べ替えアルゴリズムです。平均して、n 個の項目を並べ替えるには O(nlogn) 個の比較が必要です。最悪の場合、Ο(n2) の比較が必要になりますが、これは一般的ではありません。実際、クイックソートは、その内部ループがほとんどのアーキテクチャで効率的に実装できるため、通常、他の Ο(nlogn) アルゴリズムよりも大幅に高速です。クイック ソートは、ソート アルゴリズムにおける分割統治の考え方のもう 1 つの典型的な応用です。本質的に、クイック ソートはバブル ソートに基づく再帰的な分割統治法と見なされるべきです。クイックソートの名前はシンプルで失礼ですが、名前を聞いたらすぐにその存在の意味がわかり、高速かつ効率的です。これは、大量のデータに対する最速の並べ替えアルゴリズムの 1 つです。
コード
public class QuickSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return quickSort(arr, 0, arr.length - 1);
}
private int[] quickSort(int[] arr, int left, int right) {
if (left < right) {
int partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
private int partition(int[] arr, int left, int right) {
// 设定基准值(pivot)
int pivot = left;
int index = pivot + 1;
for (int i = index; i <= right; i++) {
if (arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);
return index - 1;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
ヒープソート
ヒープソート (Heapsort) は、ヒープのデータ構造を使用して設計されたソート アルゴリズムを指します。スタッキングは完全なバイナリ ツリーに近似する構造であり、同時にスタッキングの性質も満たします。つまり、子ノードのキー値またはインデックスは常にその親ノードよりも小さい (または大きい) ものです。ヒープソートは、ヒープの概念をソートに利用した選択ソートと言えます。2 つの方法に分かれています。
- ラージトップヒープ: 各ノードの値はその子ノードの値以上であり、ヒープソートアルゴリズムで昇順で使用されます。
- スモールトップヒープ: 各ノードの値はその子ノードの値以下であり、ヒープソートアルゴリズムで降順で使用されます。
ヒープソートの平均時間計算量は O(nlogn) です。
コード
public class HeapSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int len = arr.length;
buildMaxHeap(arr, len);
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0, len);
}
return arr;
}
private void buildMaxHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
}
private void heapify(int[] arr, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
カウントソート
カウントソートの核心は、入力データ値をキーに変換し、それを追加の配列スペースに格納することです。一種の線形時間計算量として、カウンティング ソートでは、入力データが特定の範囲の整数である必要があります。
コード
public class CountingSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxValue = getMaxValue(arr);
return countingSort(arr, maxValue);
}
private int[] countingSort(int[] arr, int maxValue) {
int bucketLen = maxValue + 1;
int[] bucket = new int[bucketLen];
for (int value : arr) {
bucket[value]++;
}
int sortedIndex = 0;
for (int j = 0; j < bucketLen; j++) {
while (bucket[j] > 0) {
arr[sortedIndex++] = j;
bucket[j]--;
}
}
return arr;
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
}
バケットソート
バケット ソートは、カウンティング ソートのアップグレード バージョンです。これは関数のマッピング関係を利用するもので、高効率の鍵はこのマッピング関数の決定にあります。バケットソートを効率的にするには、次の 2 つのことを行う必要があります。
- 十分なスペースに余裕がある場合は、バケットの数をできるだけ増やしてください。
- 使用されるマッピング関数は、入力 N データを K 個のバケットに均等に分散できます。
同時に、バケット内の要素を並べ替える場合、比較並べ替えアルゴリズムの選択がパフォーマンスにとって重要です。
コード
public class BucketSort {
private static final InsertSort insertSort = new InsertSort();
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return bucketSort(arr, 5);
}
private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
if (arr.length == 0) {
return arr;
}
int minValue = arr[0];
int maxValue = arr[0];
for (int value : arr) {
if (value < minValue) {
minValue = value;
} else if (value > maxValue) {
maxValue = value;
}
}
int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
int[][] buckets = new int[bucketCount][0];
// 利用映射函数将数据分配到各个桶中
for (int i = 0; i < arr.length; i++) {
int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
for (int[] bucket : buckets) {
if (bucket.length <= 0) {
continue;
}
// 对每个桶进行排序,这里使用了插入排序
bucket = insertSort.sort(bucket);
for (int value : bucket) {
arr[arrIndex++] = value;
}
}
return arr;
}
/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private int[] arrAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
}
基数ソート
基数ソートは非比較整数ソート アルゴリズムであり、その原理は、整数を桁に応じて異なる数値に分割し、各桁を個別に比較することです。整数は特定の形式の文字列 (名前や日付など) や浮動小数点数も表すことができるため、基数ソートは整数に限定されません。
コード
public class RadixSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxDigit = getMaxDigit(arr);
return radixSort(arr, maxDigit);
}
/**
* 获取最高位数
*/
private int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLenght(maxValue);
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
protected int getNumLenght(long num) {
if (num == 0) {
return 1;
}
int lenght = 0;
for (long temp = num; temp != 0; temp /= 10) {
lenght++;
}
return lenght;
}
private int[] radixSort(int[] arr, int maxDigit) {
int mod = 10;
int dev = 1;
for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
int[][] counter = new int[mod * 2][0];
for (int j = 0; j < arr.length; j++) {
int bucket = ((arr[j] % mod) / dev) + mod;
counter[bucket] = arrayAppend(counter[bucket], arr[j]);
}
int pos = 0;
for (int[] bucket : counter) {
for (int value : bucket) {
arr[pos++] = value;
}
}
}
return arr;
}
/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
}
問題解決の経験
- 並べ替えアルゴリズムは非常に基本的で重要です。並べ替えアルゴリズムをよく学ぶことは、アルゴリズムをうまく学ぶための基礎の 1 つです。それが何なのか、そしてその理由を知る必要があります。
- 特定のテンプレートを習得することは非常に重要ですが、アルゴリズムの概念を習得することも同様に重要です。
- 問題を解決するとき、トピックが明確に並べ替えを検討している場合、API を直接使用することはできません。そうしないと、インタビューは冷たくなります。
- 質問の並べ替えには多くのオプションがあり、必要に応じて選択できますが、時間要件が最も重要であるとは言えません。
- 場合によっては、問題解決の要件を満たすために並べ替えアルゴリズム全体を書き出す必要はなく、柔軟性が必要です。
- 多くの場合、トピックの特性をソート アルゴリズムの基本特性と比較すると、どのアルゴリズムを使用すればよいかがわかります。
アルゴリズムのトピック
21. ソートされた 2 つのリンク リストを結合する
トピック分析: マージ・ソートに似たマージ操作。
コードは以下のように表示されます。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
/**
* 归并排序
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode tmp = new ListNode(0);
ListNode res = tmp;
while (l1 != null && l2 != null) {
// 无论大于,还是等于,都选择l2的值
if (l1.val < l2.val) {
tmp.next = l1;
l1 = l1.next;
tmp = tmp.next;
} else {
tmp.next = l2;
l2 = l2.next;
tmp = tmp.next;
}
}
if (l1 != null) {
tmp.next = l1;
}
if (l2 != null) {
tmp.next = l2;
}
return res.next;
}
}
23. K 個の昇順リンク リストを結合する
トピック分析: マージ・ソート・マージと同様に、再帰、分割統治を使用します。
コードは以下のように表示されます。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
/**
* 归并排序
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists, 0, lists.length - 1);
}
// 此方法最为轻便快捷
public ListNode merge(ListNode[] lists, int l, int r) {
// 只有一个列表时返回,开始递归合并
if (l == r) {
return lists[l];
}
if (l > r) {
return null;
}
int mid = l + (r - l) / 2;
return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
}
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode tmp = new ListNode(0);
ListNode res = tmp;
while (l1 != null && l2 != null) {
// 无论大于,还是等于,都选择l2的值
if (l1.val < l2.val) {
tmp.next = l1;
l1 = l1.next;
tmp = tmp.next;
} else {
tmp.next = l2;
l2 = l2.next;
tmp = tmp.next;
}
}
if (l1 != null) {
tmp.next = l1;
}
if (l2 != null) {
tmp.next = l2;
}
return res.next;
}
}
56. 間隔を結合する
トピック分析: 間隔の左端に従って並べ替える場合、並べ替えられたリストでは、マージできる間隔は連続している必要があります。
コードは以下のように表示されます:
/**
* 排序
*/
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> res = new LinkedList<>();
int[] res0 = new int[intervals.length];
int[] res1 = new int[intervals.length];
// 将区间值左右边界分开
for (int i = 0; i < intervals.length; i++) {
res0[i] = intervals[i][0];
res1[i] = intervals[i][1];
}
// 排序
Arrays.sort(res0);
Arrays.sort(res1);
for (int i = 0, j = 0; i < intervals.length; i++) {
if (i == (intervals.length - 1) || res0[i + 1] > res1[i]) {
res.add(new int[]{res0[j], res1[i]});
j = i + 1;
}
}
return res.toArray(new int[res.size()][]);
}
}
147. 挿入ソートリンクリスト
トピック分析: 反復法を使用して挿入ソートを実装します。
コードは以下のように表示されます。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
/**
* 插入排序
*/
class Solution {
public ListNode insertionSortList(ListNode head) {
// 快速排序可能会插入头部,用dummy会让插入头部和其他位置操作一致
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
// 需要维护一个至今位置有序的节点。叫做tail
// 因为需要把当前元素cur插到前面有序链表中,要将最后一个元素与cur后面的元素建立连接
ListNode tail = head;
ListNode cur = head.next;
while (cur != null) {
if (cur.val >= tail.val) {
tail = cur;
} else {
// 一个前驱节点pre来找到需要插入的位置,
// 找到第一个满足 pre.next >= cur.val 的前驱节点
ListNode pre = dummyHead;
while (pre.next.val < cur.val) {
pre = pre.next;
}
tail.next = cur.next;
cur.next = pre.next;
pre.next = cur;
}
cur = tail.next;
}
return dummyHead.next;
}
}
148. リンクされたリストのソート
トピック分析: トピックは O(1) の空間計算量を必要とするため、再帰は使用できません。そのため、ここではマージ ソート アルゴリズムを使用して解決します。
コードは以下のように表示されます。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
/**
* 归并排序
*/
class Solution {
public ListNode sortList(ListNode head) {
return mergeSort(head);
}
// 归并排序
private ListNode mergeSort(ListNode head){
// 如果没有结点,只有一个结点,无需排序,直接返回
if (head == null || head.next == null) return head;
// 快慢指针找出中位点
ListNode slowp = head, fastp = head.next.next, l, r;
while (fastp != null && fastp.next != null){
slowp = slowp.next;
fastp = fastp.next.next;
}
// 对右半部分进行归并排序
r = mergeSort(slowp.next);
// 链表判断结束的标志:末尾节点.next==null
slowp.next = null;
// 对左半部分进行归并排序
l = mergeSort(head);
return mergeList(l, r);
}
// 合并链表
private ListNode mergeList(ListNode l, ListNode r){
// 临时头节点
ListNode tmpHead = new ListNode(-1);
ListNode p = tmpHead;
while (l != null && r != null){
if (l.val < r.val){
p.next = l;
l = l.next;
}else {
p.next = r;
r = r.next;
}
p = p.next;
}
p.next =l == null ? r : l;
return tmpHead.next;
}
}
164. 最大間隔
トピック分析: 最も単純なアイデアの 1 つは、配列をソートしてから最大距離を見つけることですが、従来の比較ベースのソート アルゴリズム (クイック ソート、マージ ソートなど) はすべて O(NlogN) の時間計算量を必要とします。時間計算量を O(N) に削減したい場合は、他の並べ替えアルゴリズムを使用する必要があります。たとえば、基数ソートは O(N) 時間で整数をソートできます。
コードは以下のように表示されます。
/**
* 基数排序
*/
class Solution {
public int maximumGap(int[] nums) {
int n = nums.length;
if (n < 2) {
return 0;
}
long exp = 1;
int[] buf = new int[n];
int maxVal = Arrays.stream(nums).max().getAsInt();
while (maxVal >= exp) {
int[] cnt = new int[10];
for (int i = 0; i < n; i++) {
int digit = (nums[i] / (int) exp) % 10;
cnt[digit]++;
}
for (int i = 1; i < 10; i++) {
cnt[i] += cnt[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
int digit = (nums[i] / (int) exp) % 10;
buf[cnt[digit] - 1] = nums[i];
cnt[digit]--;
}
System.arraycopy(buf, 0, nums, 0, n);
exp *= 10;
}
int ret = 0;
for (int i = 1; i < n; i++) {
ret = Math.max(ret, nums[i] - nums[i - 1]);
}
return ret;
}
}
215. 配列内の K 番目に大きい要素
トピック分析: この問題は主に並べ替えについて検討します。並べ替えが完了したら、len - k + 1 番目の値を取り出すだけです。
コードは以下のように表示されます。
/**
* 快速排序
*/
class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int t = len - k + 1;
return quick_sort(nums, 0, len - 1, t);
}
private int quick_sort(int[] q, int left, int right, int k) {
if (left >= right) return q[left];
int x = q[left + right >> 1];
int i = left - 1;
int j = right + 1;
while (i < j) {
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) {
int smp = q[i];
q[i] = q[j];
q[j] = smp;
}
}
int sl = j - left + 1;
if (sl >= k) {
return quick_sort(q, left, j, k);
} else {
return quick_sort(q, j + 1, right, k - sl);
}
}
}
217. 重複した要素が存在します
トピック分析: 最初にソートし、次に 2 つの隣接する要素が等しいかどうかを判断します。
コードは以下のように表示されます。
/**
* 排序
*/
class Solution {
public boolean containsDuplicate(int[] nums) {
Arrays.sort(nums);
for(int i=0;i<nums.length-1;i++){
if(nums[i]==nums[i+1]){
return true;
}
}
return false;
}
}
274.Hインデックス
トピック分析: まず、初期の H インデックス h を 0 に設定し、次に参照の数をソートし、ソートされた配列を大きいものから小さいものへと走査します。
コードは以下のように表示されます。
/**
* 排序
*/
class Solution {
public int hIndex(int[] citations) {
Arrays.sort(citations);
for (int i = 0; i < citations.length; i++) {
int h = citations.length - i;
if (h <= citations[i]) {
return h;
}
}
return 0;
}
}
275.HインデックスⅡ
トピック分析: 二分法を使用して、アルゴリズムの複雑さを軽減します。
コードは以下のように表示されます。
/**
* 二分法
*/
class Solution {
public int hIndex(int[] citations) {
int n = citations.length;
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (citations[mid] >= n - mid) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return n - left;
}
}
ホームページに戻る
Leetcode 500 以上の質問を磨くことについての感想